mirror of
https://github.com/grafana/grafana.git
synced 2025-07-31 18:42:27 +08:00
Errors: support errors with frame data from backend responses (#24176)
This commit is contained in:
@ -1,51 +1,11 @@
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import { resultsToDataFrames, grafanaDataFrameToArrowTable, arrowTableToDataFrame } from './ArrowDataFrame';
|
import { grafanaDataFrameToArrowTable, arrowTableToDataFrame } from './ArrowDataFrame';
|
||||||
import { toDataFrameDTO, toDataFrame } from './processDataFrame';
|
import { toDataFrameDTO, toDataFrame } from './processDataFrame';
|
||||||
import { FieldType } from '../types';
|
import { FieldType } from '../types';
|
||||||
import { Table } from 'apache-arrow';
|
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', () => {
|
describe('Read/Write arrow Table to DataFrame', () => {
|
||||||
test('should parse output with dataframe', () => {
|
test('should parse output with dataframe', () => {
|
||||||
const frame = toDataFrame({
|
const frame = toDataFrame({
|
||||||
|
@ -161,20 +161,3 @@ export function grafanaDataFrameToArrowTable(data: DataFrame): Table {
|
|||||||
}
|
}
|
||||||
return 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;
|
|
||||||
}
|
|
||||||
|
@ -1,108 +1,5 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// 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`] = `
|
exports[`Read/Write arrow Table to DataFrame should read all types 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"fields": Array [
|
"fields": Array [
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
"@rollup/plugin-node-resolve": "7.1.1",
|
"@rollup/plugin-node-resolve": "7.1.1",
|
||||||
"@types/rollup-plugin-visualizer": "2.6.0",
|
"@types/rollup-plugin-visualizer": "2.6.0",
|
||||||
"@types/systemjs": "^0.20.6",
|
"@types/systemjs": "^0.20.6",
|
||||||
|
"@types/jest": "23.3.14",
|
||||||
"lodash": "4.17.15",
|
"lodash": "4.17.15",
|
||||||
"pretty-format": "25.1.0",
|
"pretty-format": "25.1.0",
|
||||||
"rollup": "2.0.6",
|
"rollup": "2.0.6",
|
||||||
|
@ -9,3 +9,4 @@ export * from './types';
|
|||||||
export { loadPluginCss, SystemJS, PluginCssOptions } from './utils/plugin';
|
export { loadPluginCss, SystemJS, PluginCssOptions } from './utils/plugin';
|
||||||
export { reportMetaAnalytics } from './utils/analytics';
|
export { reportMetaAnalytics } from './utils/analytics';
|
||||||
export { DataSourceWithBackend, HealthCheckResult, HealthStatus } from './utils/DataSourceWithBackend';
|
export { DataSourceWithBackend, HealthCheckResult, HealthStatus } from './utils/DataSourceWithBackend';
|
||||||
|
export { toDataQueryError, toDataQueryResponse } from './utils/queryResponse';
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
import { Observable, from } from 'rxjs';
|
import { Observable, from } from 'rxjs';
|
||||||
import { config } from '..';
|
import { config } from '..';
|
||||||
import { getBackendSrv } from '../services';
|
import { getBackendSrv } from '../services';
|
||||||
|
import { toDataQueryResponse } from './queryResponse';
|
||||||
|
|
||||||
const ExpressionDatasourceID = '__expr__';
|
const ExpressionDatasourceID = '__expr__';
|
||||||
|
|
||||||
@ -94,7 +95,11 @@ export class DataSourceWithBackend<
|
|||||||
requestId,
|
requestId,
|
||||||
})
|
})
|
||||||
.then((rsp: any) => {
|
.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);
|
return from(req);
|
||||||
@ -109,16 +114,6 @@ export class DataSourceWithBackend<
|
|||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This makes the arrow library loading async.
|
|
||||||
*/
|
|
||||||
async toDataQueryResponse(rsp: any): Promise<DataQueryResponse> {
|
|
||||||
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
|
* Make a GET request to the datasource resource path
|
||||||
*/
|
*/
|
||||||
|
165
packages/grafana-runtime/src/utils/queryResponse.test.ts
Normal file
165
packages/grafana-runtime/src/utils/queryResponse.test.ts
Normal file
@ -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",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
91
packages/grafana-runtime/src/utils/queryResponse.ts
Normal file
91
packages/grafana-runtime/src/utils/queryResponse.ts
Normal file
@ -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;
|
||||||
|
}
|
@ -18,6 +18,7 @@ import {
|
|||||||
DataFrame,
|
DataFrame,
|
||||||
guessFieldTypes,
|
guessFieldTypes,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
|
import { toDataQueryError } from '@grafana/runtime';
|
||||||
import { emitDataRequestEvent } from './analyticsProcessor';
|
import { emitDataRequestEvent } from './analyticsProcessor';
|
||||||
import { ExpressionDatasourceID, expressionDatasource } from 'app/features/expressions/ExpressionDatasource';
|
import { ExpressionDatasourceID, expressionDatasource } from 'app/features/expressions/ExpressionDatasource';
|
||||||
|
|
||||||
@ -117,7 +118,7 @@ export function runRequest(datasource: DataSourceApi, request: DataQueryRequest)
|
|||||||
of({
|
of({
|
||||||
...state.panelData,
|
...state.panelData,
|
||||||
state: LoadingState.Error,
|
state: LoadingState.Error,
|
||||||
error: processQueryError(err),
|
error: toDataQueryError(err),
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
tap(emitDataRequestEvent(datasource)),
|
tap(emitDataRequestEvent(datasource)),
|
||||||
@ -153,30 +154,6 @@ export function callQueryMethod(datasource: DataSourceApi, request: DataQueryReq
|
|||||||
return from(returnVal);
|
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
|
* All panels will be passed tables that have our best guess at colum type set
|
||||||
*
|
*
|
||||||
|
@ -14,7 +14,6 @@ import {
|
|||||||
ScopedVars,
|
ScopedVars,
|
||||||
TimeRange,
|
TimeRange,
|
||||||
DataFrame,
|
DataFrame,
|
||||||
resultsToDataFrames,
|
|
||||||
DataQueryResponse,
|
DataQueryResponse,
|
||||||
LoadingState,
|
LoadingState,
|
||||||
toDataFrame,
|
toDataFrame,
|
||||||
@ -22,7 +21,7 @@ import {
|
|||||||
FieldType,
|
FieldType,
|
||||||
LogRowModel,
|
LogRowModel,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { getBackendSrv } from '@grafana/runtime';
|
import { getBackendSrv, toDataQueryResponse } from '@grafana/runtime';
|
||||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||||
import { ThrottlingErrorMessage } from './components/ThrottlingErrorMessage';
|
import { ThrottlingErrorMessage } from './components/ThrottlingErrorMessage';
|
||||||
@ -496,6 +495,12 @@ export class CloudWatchDatasource extends DataSourceApi<CloudWatchQuery, CloudWa
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resultsToDataFrames = (val: any): DataFrame[] => {
|
||||||
|
// 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(
|
return from(this.awsRequest(TSDB_QUERY_ENDPOINT, requestParams)).pipe(
|
||||||
map(response => resultsToDataFrames(response)),
|
map(response => resultsToDataFrames(response)),
|
||||||
catchError(err => {
|
catchError(err => {
|
||||||
|
@ -14,13 +14,12 @@ import {
|
|||||||
DataFrame,
|
DataFrame,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { Scenario, TestDataQuery } from './types';
|
import { Scenario, TestDataQuery } from './types';
|
||||||
import { getBackendSrv } from '@grafana/runtime';
|
import { getBackendSrv, toDataQueryError } from '@grafana/runtime';
|
||||||
import { queryMetricTree } from './metricTree';
|
import { queryMetricTree } from './metricTree';
|
||||||
import { from, merge, Observable, of } from 'rxjs';
|
import { from, merge, Observable, of } from 'rxjs';
|
||||||
import { runStream } from './runStreams';
|
import { runStream } from './runStreams';
|
||||||
import templateSrv from 'app/features/templating/template_srv';
|
import templateSrv from 'app/features/templating/template_srv';
|
||||||
import { getSearchFilterScopedVar } from 'app/features/templating/utils';
|
import { getSearchFilterScopedVar } from 'app/features/templating/utils';
|
||||||
import { processQueryError } from 'app/features/dashboard/state/runRequest';
|
|
||||||
|
|
||||||
type TestData = TimeSeries | TableData;
|
type TestData = TimeSeries | TableData;
|
||||||
|
|
||||||
@ -164,7 +163,7 @@ function runArrowFile(target: TestDataQuery, req: DataQueryRequest<TestDataQuery
|
|||||||
data = [arrowTableToDataFrame(table)];
|
data = [arrowTableToDataFrame(table)];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('Error reading saved arrow', e);
|
console.warn('Error reading saved arrow', e);
|
||||||
const error = processQueryError(e);
|
const error = toDataQueryError(e);
|
||||||
error.refId = target.refId;
|
error.refId = target.refId;
|
||||||
return of({ state: LoadingState.Error, error, data });
|
return of({ state: LoadingState.Error, error, data });
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user