diff --git a/packages/grafana-data/src/dataframe/ArrowDataFrame.test.ts b/packages/grafana-data/src/dataframe/ArrowDataFrame.test.ts index cf3da3d4fc4..415b5b9c1ca 100644 --- a/packages/grafana-data/src/dataframe/ArrowDataFrame.test.ts +++ b/packages/grafana-data/src/dataframe/ArrowDataFrame.test.ts @@ -1,51 +1,11 @@ import fs from 'fs'; import path from 'path'; -import { resultsToDataFrames, grafanaDataFrameToArrowTable, arrowTableToDataFrame } from './ArrowDataFrame'; +import { grafanaDataFrameToArrowTable, arrowTableToDataFrame } from './ArrowDataFrame'; import { toDataFrameDTO, toDataFrame } from './processDataFrame'; import { FieldType } from '../types'; import { Table } from 'apache-arrow'; -/* eslint-disable */ -const resp = { - results: { - '': { - refId: '', - dataframes: [ - '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', - ], - series: [] as any[], - tables: null as any, - frames: null as any, - }, - }, -}; -/* eslint-enable */ - -describe('GEL Utils', () => { - test('should parse output with dataframe', () => { - const frames = resultsToDataFrames(resp); - for (const frame of frames) { - console.log('Frame', frame.refId); - for (const field of frame.fields) { - console.log(' > ', field.name, field.labels); - console.log(' (values)= ', field.values.toArray()); - } - } - - const norm = frames.map(f => toDataFrameDTO(f)); - expect(norm).toMatchSnapshot(); - }); - - test('processEmptyResults', () => { - const frames = resultsToDataFrames({ - results: { '': { refId: '', meta: null, series: null, tables: null, dataframes: null } }, - }); - expect(frames.length).toEqual(0); - }); -}); - describe('Read/Write arrow Table to DataFrame', () => { test('should parse output with dataframe', () => { const frame = toDataFrame({ diff --git a/packages/grafana-data/src/dataframe/ArrowDataFrame.ts b/packages/grafana-data/src/dataframe/ArrowDataFrame.ts index cce08dc5ada..20d5045a528 100644 --- a/packages/grafana-data/src/dataframe/ArrowDataFrame.ts +++ b/packages/grafana-data/src/dataframe/ArrowDataFrame.ts @@ -161,20 +161,3 @@ export function grafanaDataFrameToArrowTable(data: DataFrame): Table { } return table; } - -export function resultsToDataFrames(rsp: any): DataFrame[] { - if (rsp === undefined || rsp.results === undefined) { - return []; - } - - const results = rsp.results as Array<{ dataframes: string[] }>; - const frames: DataFrame[] = Object.values(results).flatMap(res => { - if (!res.dataframes) { - return []; - } - - return res.dataframes.map((b: string) => arrowTableToDataFrame(base64StringToArrowTable(b))); - }); - - return frames; -} diff --git a/packages/grafana-data/src/dataframe/__snapshots__/ArrowDataFrame.test.ts.snap b/packages/grafana-data/src/dataframe/__snapshots__/ArrowDataFrame.test.ts.snap index 358c55e3907..29d44ce7138 100644 --- a/packages/grafana-data/src/dataframe/__snapshots__/ArrowDataFrame.test.ts.snap +++ b/packages/grafana-data/src/dataframe/__snapshots__/ArrowDataFrame.test.ts.snap @@ -1,108 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`GEL Utils should parse output with dataframe 1`] = ` -Array [ - Object { - "fields": Array [ - Object { - "config": Object {}, - "labels": undefined, - "name": "Time", - "type": "time", - "values": Array [ - 1569334575000, - 1569334580000, - 1569334585000, - 1569334590000, - 1569334595000, - 1569334600000, - 1569334605000, - 1569334610000, - 1569334615000, - 1569334620000, - 1569334625000, - 1569334630000, - 1569334635000, - ], - }, - Object { - "config": Object {}, - "labels": undefined, - "name": "", - "type": "number", - "values": Array [ - 3, - 3, - 3, - 5, - 5, - 5, - 3, - 3, - 3, - 5, - 5, - 5, - 3, - ], - }, - ], - "meta": undefined, - "name": undefined, - "refId": "GC", - }, - Object { - "fields": Array [ - Object { - "config": Object {}, - "labels": undefined, - "name": "Time", - "type": "time", - "values": Array [ - 1569334575000, - 1569334580000, - 1569334585000, - 1569334590000, - 1569334595000, - 1569334600000, - 1569334605000, - 1569334610000, - 1569334615000, - 1569334620000, - 1569334625000, - 1569334630000, - 1569334635000, - ], - }, - Object { - "config": Object {}, - "labels": undefined, - "name": "GB-series", - "type": "number", - "values": Array [ - 0, - 0, - 0, - 2, - 2, - 2, - 0, - 0, - 0, - 2, - 2, - 2, - 0, - ], - }, - ], - "meta": undefined, - "name": undefined, - "refId": "GB", - }, -] -`; - exports[`Read/Write arrow Table to DataFrame should read all types 1`] = ` Object { "fields": Array [ diff --git a/packages/grafana-runtime/package.json b/packages/grafana-runtime/package.json index 28a6dea230f..f0419570de7 100644 --- a/packages/grafana-runtime/package.json +++ b/packages/grafana-runtime/package.json @@ -34,6 +34,7 @@ "@rollup/plugin-node-resolve": "7.1.1", "@types/rollup-plugin-visualizer": "2.6.0", "@types/systemjs": "^0.20.6", + "@types/jest": "23.3.14", "lodash": "4.17.15", "pretty-format": "25.1.0", "rollup": "2.0.6", diff --git a/packages/grafana-runtime/src/index.ts b/packages/grafana-runtime/src/index.ts index 2b0463417aa..386dc95a9ae 100644 --- a/packages/grafana-runtime/src/index.ts +++ b/packages/grafana-runtime/src/index.ts @@ -9,3 +9,4 @@ export * from './types'; export { loadPluginCss, SystemJS, PluginCssOptions } from './utils/plugin'; export { reportMetaAnalytics } from './utils/analytics'; export { DataSourceWithBackend, HealthCheckResult, HealthStatus } from './utils/DataSourceWithBackend'; +export { toDataQueryError, toDataQueryResponse } from './utils/queryResponse'; diff --git a/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts b/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts index 535cb20b265..25e45caddc3 100644 --- a/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts +++ b/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts @@ -9,6 +9,7 @@ import { import { Observable, from } from 'rxjs'; import { config } from '..'; import { getBackendSrv } from '../services'; +import { toDataQueryResponse } from './queryResponse'; const ExpressionDatasourceID = '__expr__'; @@ -94,7 +95,11 @@ export class DataSourceWithBackend< requestId, }) .then((rsp: any) => { - return this.toDataQueryResponse(rsp?.data); + return toDataQueryResponse(rsp); + }) + .catch(err => { + err.isHandled = true; // Avoid extra popup warning + return toDataQueryResponse(err); }); return from(req); @@ -109,16 +114,6 @@ export class DataSourceWithBackend< return query; } - /** - * This makes the arrow library loading async. - */ - async toDataQueryResponse(rsp: any): Promise { - const { resultsToDataFrames } = await import( - /* webpackChunkName: "apache-arrow-util" */ '@grafana/data/src/dataframe/ArrowDataFrame' - ); - return { data: resultsToDataFrames(rsp) }; - } - /** * Make a GET request to the datasource resource path */ diff --git a/packages/grafana-runtime/src/utils/queryResponse.test.ts b/packages/grafana-runtime/src/utils/queryResponse.test.ts new file mode 100644 index 00000000000..25169669a0a --- /dev/null +++ b/packages/grafana-runtime/src/utils/queryResponse.test.ts @@ -0,0 +1,165 @@ +import { toDataFrameDTO } from '@grafana/data'; + +import { toDataQueryResponse } from './queryResponse'; + +/* eslint-disable */ +const resp = { + data: { + results: { + GC: { + dataframes: [ + 'QVJST1cxAACsAQAAEAAAAAAACgAOAAwACwAEAAoAAAAUAAAAAAAAAQMACgAMAAAACAAEAAoAAAAIAAAAUAAAAAIAAAAoAAAABAAAAOD+//8IAAAADAAAAAIAAABHQwAABQAAAHJlZklkAAAAAP///wgAAAAMAAAAAAAAAAAAAAAEAAAAbmFtZQAAAAACAAAAlAAAAAQAAACG////FAAAAGAAAABgAAAAAAADAWAAAAACAAAALAAAAAQAAABQ////CAAAABAAAAAGAAAAbnVtYmVyAAAEAAAAdHlwZQAAAAB0////CAAAAAwAAAAAAAAAAAAAAAQAAABuYW1lAAAAAAAAAABm////AAACAAAAAAAAABIAGAAUABMAEgAMAAAACAAEABIAAAAUAAAAbAAAAHQAAAAAAAoBdAAAAAIAAAA0AAAABAAAANz///8IAAAAEAAAAAQAAAB0aW1lAAAAAAQAAAB0eXBlAAAAAAgADAAIAAQACAAAAAgAAAAQAAAABAAAAFRpbWUAAAAABAAAAG5hbWUAAAAAAAAAAAAABgAIAAYABgAAAAAAAwAEAAAAVGltZQAAAAC8AAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAA0AAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAWAAAAA0AAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABoAAAAAAAAAGgAAAAAAAAAAAAAAAAAAABoAAAAAAAAAGgAAAAAAAAAAAAAAAIAAAANAAAAAAAAAAAAAAAAAAAADQAAAAAAAAAAAAAAAAAAAAAAAAAAFp00e2XHFQAIo158ZccVAPqoiH1lxxUA7K6yfmXHFQDetNx/ZccVANC6BoFlxxUAwsAwgmXHFQC0xlqDZccVAKbMhIRlxxUAmNKuhWXHFQCK2NiGZccVAHzeAohlxxUAbuQsiWXHFQAAAAAAAAhAAAAAAAAACEAAAAAAAAAIQAAAAAAAABRAAAAAAAAAFEAAAAAAAAAUQAAAAAAAAAhAAAAAAAAACEAAAAAAAAAIQAAAAAAAABRAAAAAAAAAFEAAAAAAAAAUQAAAAAAAAAhAEAAAAAwAFAASAAwACAAEAAwAAAAQAAAALAAAADgAAAAAAAMAAQAAALgBAAAAAAAAwAAAAAAAAADQAAAAAAAAAAAAAAAAAAAAAAAKAAwAAAAIAAQACgAAAAgAAABQAAAAAgAAACgAAAAEAAAA4P7//wgAAAAMAAAAAgAAAEdDAAAFAAAAcmVmSWQAAAAA////CAAAAAwAAAAAAAAAAAAAAAQAAABuYW1lAAAAAAIAAACUAAAABAAAAIb///8UAAAAYAAAAGAAAAAAAAMBYAAAAAIAAAAsAAAABAAAAFD///8IAAAAEAAAAAYAAABudW1iZXIAAAQAAAB0eXBlAAAAAHT///8IAAAADAAAAAAAAAAAAAAABAAAAG5hbWUAAAAAAAAAAGb///8AAAIAAAAAAAAAEgAYABQAEwASAAwAAAAIAAQAEgAAABQAAABsAAAAdAAAAAAACgF0AAAAAgAAADQAAAAEAAAA3P///wgAAAAQAAAABAAAAHRpbWUAAAAABAAAAHR5cGUAAAAACAAMAAgABAAIAAAACAAAABAAAAAEAAAAVGltZQAAAAAEAAAAbmFtZQAAAAAAAAAAAAAGAAgABgAGAAAAAAADAAQAAABUaW1lAAAAANgBAABBUlJPVzE=', + ], + frames: null as any, + }, + }, + }, +}; + +const resWithError = { + data: { + results: { + A: { + error: 'Hello Error', + series: null, + tables: null, + dataframes: [ + 'QVJST1cxAAD/////WAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAJwAAAADAAAATAAAACgAAAAEAAAAPP///wgAAAAMAAAAAAAAAAAAAAAFAAAAcmVmSWQAAABc////CAAAAAwAAAAAAAAAAAAAAAQAAABuYW1lAAAAAHz///8IAAAANAAAACoAAAB7Im5vdGljZXMiOlt7InNldmVyaXR5IjoyLCJ0ZXh0IjoiVGV4dCJ9XX0AAAQAAABtZXRhAAAAAAEAAAAYAAAAAAASABgAFAAAABMADAAAAAgABAASAAAAFAAAAEQAAABMAAAAAAAAA0wAAAABAAAADAAAAAgADAAIAAQACAAAAAgAAAAQAAAABwAAAG51bWJlcnMABAAAAG5hbWUAAAAAAAAAAAAABgAIAAYABgAAAAAAAgAHAAAAbnVtYmVycwAAAAAA/////4gAAAAUAAAAAAAAAAwAFgAUABMADAAEAAwAAAAQAAAAAAAAABQAAAAAAAADAwAKABgADAAIAAQACgAAABQAAAA4AAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAEAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAA8D8AAAAAAAAIQBAAAAAMABQAEgAMAAgABAAMAAAAEAAAACwAAAA4AAAAAAADAAEAAABoAQAAAAAAAJAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAACgAMAAAACAAEAAoAAAAIAAAAnAAAAAMAAABMAAAAKAAAAAQAAAA8////CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAAFz///8IAAAADAAAAAAAAAAAAAAABAAAAG5hbWUAAAAAfP///wgAAAA0AAAAKgAAAHsibm90aWNlcyI6W3sic2V2ZXJpdHkiOjIsInRleHQiOiJUZXh0In1dfQAABAAAAG1ldGEAAAAAAQAAABgAAAAAABIAGAAUAAAAEwAMAAAACAAEABIAAAAUAAAARAAAAEwAAAAAAAADTAAAAAEAAAAMAAAACAAMAAgABAAIAAAACAAAABAAAAAHAAAAbnVtYmVycwAEAAAAbmFtZQAAAAAAAAAAAAAGAAgABgAGAAAAAAACAAcAAABudW1iZXJzAIABAABBUlJPVzE=', + ], + }, + }, + }, +}; + +const emptyResults = { + data: { '': { refId: '', meta: null, series: null, tables: null, dataframes: null } }, +}; + +/* eslint-enable */ + +describe('GEL Utils', () => { + test('should parse output with dataframe', () => { + const res = toDataQueryResponse(resp); + const frames = res.data; + for (const frame of frames) { + expect(frame.refId).toEqual('GC'); + } + + const norm = frames.map(f => toDataFrameDTO(f)); + expect(norm).toMatchInlineSnapshot(` + Array [ + Object { + "fields": Array [ + Object { + "config": Object {}, + "labels": undefined, + "name": "Time", + "type": "time", + "values": Array [ + 1569334575000, + 1569334580000, + 1569334585000, + 1569334590000, + 1569334595000, + 1569334600000, + 1569334605000, + 1569334610000, + 1569334615000, + 1569334620000, + 1569334625000, + 1569334630000, + 1569334635000, + ], + }, + Object { + "config": Object {}, + "labels": undefined, + "name": "", + "type": "number", + "values": Array [ + 3, + 3, + 3, + 5, + 5, + 5, + 3, + 3, + 3, + 5, + 5, + 5, + 3, + ], + }, + ], + "meta": undefined, + "name": undefined, + "refId": "GC", + }, + ] + `); + }); + + test('processEmptyResults', () => { + const frames = toDataQueryResponse(emptyResults).data; + expect(frames.length).toEqual(0); + }); + + test('resultWithError', () => { + // Generated from: + // qdr.Responses[q.GetRefID()] = backend.DataResponse{ + // Error: fmt.Errorf("an Error: %w", fmt.Errorf("another error")), + // Frames: data.Frames{ + // { + // Fields: data.Fields{data.NewField("numbers", nil, []float64{1, 3})}, + // Meta: &data.FrameMeta{ + // Notices: []data.Notice{ + // { + // Severity: data.NoticeSeverityError, + // Text: "Text", + // }, + // }, + // }, + // }, + // }, + // } + const res = toDataQueryResponse(resWithError); + expect(res.error).toMatchInlineSnapshot(` + Object { + "message": "Hello Error", + "refId": "A", + } + `); + + const norm = res.data.map(f => toDataFrameDTO(f)); + expect(norm).toMatchInlineSnapshot(` + Array [ + Object { + "fields": Array [ + Object { + "config": Object {}, + "labels": undefined, + "name": "numbers", + "type": "number", + "values": Array [ + 1, + 3, + ], + }, + ], + "meta": Object { + "notices": Array [ + Object { + "severity": 2, + "text": "Text", + }, + ], + }, + "name": undefined, + "refId": "A", + }, + ] + `); + }); +}); diff --git a/packages/grafana-runtime/src/utils/queryResponse.ts b/packages/grafana-runtime/src/utils/queryResponse.ts new file mode 100644 index 00000000000..cea33b5addd --- /dev/null +++ b/packages/grafana-runtime/src/utils/queryResponse.ts @@ -0,0 +1,91 @@ +import { + DataQueryResponse, + arrowTableToDataFrame, + base64StringToArrowTable, + KeyValue, + LoadingState, + DataQueryError, +} from '@grafana/data'; + +interface DataResponse { + error?: string; + refId?: string; + dataframes?: string[]; + // series: null, + // tables: null, +} + +/** + * Parse the results from `/api/ds/query + */ +export function toDataQueryResponse(res: any): DataQueryResponse { + const rsp: DataQueryResponse = { data: [], state: LoadingState.Done }; + if (res.data?.results) { + const results: KeyValue = res.data.results; + for (const refId of Object.keys(results)) { + const dr = results[refId] as DataResponse; + if (dr) { + if (dr.error) { + if (!rsp.error) { + rsp.error = { + refId, + message: dr.error, + }; + rsp.state = LoadingState.Error; + } + } + + if (dr.dataframes) { + for (const b64 of dr.dataframes) { + const t = base64StringToArrowTable(b64); + const f = arrowTableToDataFrame(t); + if (!f.refId) { + f.refId = refId; + } + rsp.data.push(f); + } + } + } + } + } + + // When it is not an OK response, make sure the error gets added + if (res.status && res.status !== 200) { + if (rsp.state !== LoadingState.Error) { + rsp.state = LoadingState.Error; + } + if (!rsp.error) { + rsp.error = toDataQueryError(res); + } + } + + return rsp; +} + +/** + * Convert an object into a DataQueryError -- if this is an HTTP response, + * it will put the correct values in the error filds + */ +export function toDataQueryError(err: any): DataQueryError { + const error = (err || {}) as DataQueryError; + + if (!error.message) { + if (typeof err === 'string' || err instanceof String) { + return { message: err } as DataQueryError; + } + + let message = 'Query error'; + if (error.message) { + message = error.message; + } else if (error.data && error.data.message) { + message = error.data.message; + } else if (error.data && error.data.error) { + message = error.data.error; + } else if (error.status) { + message = `Query error: ${error.status} ${error.statusText}`; + } + error.message = message; + } + + return error; +} diff --git a/public/app/features/dashboard/state/runRequest.ts b/public/app/features/dashboard/state/runRequest.ts index 57d02406860..9a6810f705f 100644 --- a/public/app/features/dashboard/state/runRequest.ts +++ b/public/app/features/dashboard/state/runRequest.ts @@ -18,6 +18,7 @@ import { DataFrame, guessFieldTypes, } from '@grafana/data'; +import { toDataQueryError } from '@grafana/runtime'; import { emitDataRequestEvent } from './analyticsProcessor'; import { ExpressionDatasourceID, expressionDatasource } from 'app/features/expressions/ExpressionDatasource'; @@ -117,7 +118,7 @@ export function runRequest(datasource: DataSourceApi, request: DataQueryRequest) of({ ...state.panelData, state: LoadingState.Error, - error: processQueryError(err), + error: toDataQueryError(err), }) ), tap(emitDataRequestEvent(datasource)), @@ -153,30 +154,6 @@ export function callQueryMethod(datasource: DataSourceApi, request: DataQueryReq return from(returnVal); } -export function processQueryError(err: any): DataQueryError { - const error = (err || {}) as DataQueryError; - - if (!error.message) { - if (typeof err === 'string' || err instanceof String) { - return { message: err } as DataQueryError; - } - - let message = 'Query error'; - if (error.message) { - message = error.message; - } else if (error.data && error.data.message) { - message = error.data.message; - } else if (error.data && error.data.error) { - message = error.data.error; - } else if (error.status) { - message = `Query error: ${error.status} ${error.statusText}`; - } - error.message = message; - } - - return error; -} - /** * All panels will be passed tables that have our best guess at colum type set * diff --git a/public/app/plugins/datasource/cloudwatch/datasource.ts b/public/app/plugins/datasource/cloudwatch/datasource.ts index a6b0d2aadd3..c0e010f9627 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.ts +++ b/public/app/plugins/datasource/cloudwatch/datasource.ts @@ -14,7 +14,6 @@ import { ScopedVars, TimeRange, DataFrame, - resultsToDataFrames, DataQueryResponse, LoadingState, toDataFrame, @@ -22,7 +21,7 @@ import { FieldType, LogRowModel, } from '@grafana/data'; -import { getBackendSrv } from '@grafana/runtime'; +import { getBackendSrv, toDataQueryResponse } from '@grafana/runtime'; import { TemplateSrv } from 'app/features/templating/template_srv'; import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { ThrottlingErrorMessage } from './components/ThrottlingErrorMessage'; @@ -496,6 +495,12 @@ export class CloudWatchDatasource extends DataSourceApi { + // NOTE: this function currently only processes binary results from: + // /api/ds/query -- it will retrun empty results most of the time + return toDataQueryResponse(val).data || []; + }; + return from(this.awsRequest(TSDB_QUERY_ENDPOINT, requestParams)).pipe( map(response => resultsToDataFrames(response)), catchError(err => { diff --git a/public/app/plugins/datasource/testdata/datasource.ts b/public/app/plugins/datasource/testdata/datasource.ts index 7f69be4660c..a86fd271190 100644 --- a/public/app/plugins/datasource/testdata/datasource.ts +++ b/public/app/plugins/datasource/testdata/datasource.ts @@ -14,13 +14,12 @@ import { DataFrame, } from '@grafana/data'; import { Scenario, TestDataQuery } from './types'; -import { getBackendSrv } from '@grafana/runtime'; +import { getBackendSrv, toDataQueryError } from '@grafana/runtime'; import { queryMetricTree } from './metricTree'; import { from, merge, Observable, of } from 'rxjs'; import { runStream } from './runStreams'; import templateSrv from 'app/features/templating/template_srv'; import { getSearchFilterScopedVar } from 'app/features/templating/utils'; -import { processQueryError } from 'app/features/dashboard/state/runRequest'; type TestData = TimeSeries | TableData; @@ -164,7 +163,7 @@ function runArrowFile(target: TestDataQuery, req: DataQueryRequest