From d85d547ab3a257e33fa82474e12486bf9a0374bd Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 18 Nov 2020 09:35:28 -0800 Subject: [PATCH] BackendSrv: support binary responseType like $http did (#29004) --- .../src/services/backendSrv.ts | 9 ++++ public/app/core/services/backend_srv.ts | 11 +---- public/app/core/utils/fetch.test.ts | 46 +++++++++++++++++++ public/app/core/utils/fetch.ts | 27 +++++++++++ 4 files changed, 84 insertions(+), 9 deletions(-) diff --git a/packages/grafana-runtime/src/services/backendSrv.ts b/packages/grafana-runtime/src/services/backendSrv.ts index 3e0cd540842..734980a6876 100644 --- a/packages/grafana-runtime/src/services/backendSrv.ts +++ b/packages/grafana-runtime/src/services/backendSrv.ts @@ -61,6 +61,15 @@ export type BackendSrvRequest = { */ params?: Record; + /** + * Define how the response object should be parsed. See: + * + * https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Sending_and_Receiving_Binary_Data + * + * By default values are json parsed from text + */ + responseType?: 'json' | 'text' | 'arraybuffer' | 'blob'; + /** * The credentials read-only property of the Request interface indicates whether the user agent should send cookies from the other domain in the case of cross-origin requests. */ diff --git a/public/app/core/services/backend_srv.ts b/public/app/core/services/backend_srv.ts index 62060b2fa4d..4d35db085a8 100644 --- a/public/app/core/services/backend_srv.ts +++ b/public/app/core/services/backend_srv.ts @@ -11,7 +11,7 @@ import { DashboardSearchHit } from 'app/features/search/types'; import { FolderDTO } from 'app/types'; import { coreModule } from 'app/core/core_module'; import { ContextSrv, contextSrv } from './context_srv'; -import { parseInitFromOptions, parseUrlFromOptions } from '../utils/fetch'; +import { parseInitFromOptions, parseResponseBody, parseUrlFromOptions } from '../utils/fetch'; import { isDataQuery, isLocalUrl } from '../utils/query'; import { FetchQueue } from './FetchQueue'; import { ResponseQueue } from './ResponseQueue'; @@ -175,15 +175,8 @@ export class BackendSrv implements BackendService { return this.dependencies.fromFetch(url, init).pipe( mergeMap(async response => { const { status, statusText, ok, headers, url, type, redirected } = response; - const textData = await response.text(); // this could be just a string, prometheus requests for instance - let data: T; - - try { - data = JSON.parse(textData); // majority of the requests this will be something that can be parsed - } catch { - data = textData as any; - } + const data = await parseResponseBody(response, options.responseType); const fetchResponse: FetchResponse = { status, statusText, diff --git a/public/app/core/utils/fetch.test.ts b/public/app/core/utils/fetch.test.ts index 31f0048bccd..ed4ed197011 100644 --- a/public/app/core/utils/fetch.test.ts +++ b/public/app/core/utils/fetch.test.ts @@ -5,6 +5,7 @@ import { parseCredentials, parseHeaders, parseInitFromOptions, + parseResponseBody, parseUrlFromOptions, } from './fetch'; @@ -127,3 +128,48 @@ describe('parseCredentials', () => { } ); }); + +describe('parseResponseBody', () => { + const rsp = ({} as unknown) as Response; + it('parses json', async () => { + const value = { hello: 'world' }; + const body = await parseResponseBody( + { + ...rsp, + json: jest.fn().mockImplementationOnce(() => value), + }, + 'json' + ); + expect(body).toEqual(value); + }); + + it('parses text', async () => { + const value = 'RAW TEXT'; + const body = await parseResponseBody( + { + ...rsp, + text: jest.fn().mockImplementationOnce(() => value), + }, + 'text' + ); + expect(body).toEqual(value); + }); + + it('undefined text', async () => { + const value = 'RAW TEXT'; + const body = await parseResponseBody({ + ...rsp, + text: jest.fn().mockImplementationOnce(() => value), + }); + expect(body).toEqual(value); + }); + + it('undefined as parsed json', async () => { + const value = { hello: 'world' }; + const body = await parseResponseBody({ + ...rsp, + text: jest.fn().mockImplementationOnce(() => JSON.stringify(value)), + }); + expect(body).toEqual(value); + }); +}); diff --git a/public/app/core/utils/fetch.ts b/public/app/core/utils/fetch.ts index ccf051ca572..e8744e1490e 100644 --- a/public/app/core/utils/fetch.ts +++ b/public/app/core/utils/fetch.ts @@ -91,6 +91,33 @@ export const parseBody = (options: BackendSrvRequest, isAppJson: boolean) => { return isAppJson ? JSON.stringify(options.data) : new URLSearchParams(options.data); }; +export async function parseResponseBody( + response: Response, + responseType?: 'json' | 'text' | 'arraybuffer' | 'blob' +): Promise { + if (responseType) { + switch (responseType) { + case 'arraybuffer': + return response.arrayBuffer() as any; + + case 'blob': + return response.blob() as any; + + case 'json': + return response.json(); + + case 'text': + return response.text() as any; + } + } + + const textData = await response.text(); // this could be just a string, prometheus requests for instance + try { + return JSON.parse(textData); // majority of the requests this will be something that can be parsed + } catch {} + return textData as any; +} + export function serializeParams(data: Record): string { return Object.keys(data) .map(key => {