diff --git a/packages/grafana-data/src/types/datasource.ts b/packages/grafana-data/src/types/datasource.ts index 81cf18bbcbb..d32d8f980e6 100644 --- a/packages/grafana-data/src/types/datasource.ts +++ b/packages/grafana-data/src/types/datasource.ts @@ -451,9 +451,15 @@ export interface DataQueryResponse { /** * Optionally include error info along with the response data + * @deprecated use errors instead -- will be removed in Grafana 10+ */ error?: DataQueryError; + /** + * Optionally include multiple errors for different targets + */ + errors?: DataQueryError[]; + /** * Use this to control which state the response should have * Defaults to LoadingState.Done if state is not defined diff --git a/packages/grafana-data/src/types/panel.ts b/packages/grafana-data/src/types/panel.ts index 6ff01c3fcee..7e72b7680a7 100644 --- a/packages/grafana-data/src/types/panel.ts +++ b/packages/grafana-data/src/types/panel.ts @@ -57,6 +57,11 @@ export interface PanelData { timings?: DataQueryTimings; /** Any query errors */ + errors?: DataQueryError[]; + /** + * Single error for legacy reasons + * @deprecated use errors instead -- will be removed in Grafana 10+ + */ error?: DataQueryError; /** Contains the range from the request or a shifted time range if a request uses relative time */ diff --git a/packages/grafana-runtime/src/utils/queryResponse.test.ts b/packages/grafana-runtime/src/utils/queryResponse.test.ts index a36a58521af..3cd421ac4be 100644 --- a/packages/grafana-runtime/src/utils/queryResponse.test.ts +++ b/packages/grafana-runtime/src/utils/queryResponse.test.ts @@ -356,6 +356,12 @@ describe('Query Response parser', () => { "refId": "A", } `); + expect(res.errors).toEqual([ + { + message: 'Hello Error', + refId: 'A', + }, + ]); const norm = res.data.map((f) => toDataFrameDTO(f)); expect(norm).toMatchInlineSnapshot(` diff --git a/packages/grafana-runtime/src/utils/queryResponse.ts b/packages/grafana-runtime/src/utils/queryResponse.ts index 908e17e7e01..902c9b28da7 100644 --- a/packages/grafana-runtime/src/utils/queryResponse.ts +++ b/packages/grafana-runtime/src/utils/queryResponse.ts @@ -87,8 +87,13 @@ export function toDataQueryResponse( refId: dr.refId, message: dr.error, }; - rsp.state = LoadingState.Error; } + if (rsp.errors) { + rsp.errors.push({ refId: dr.refId, message: dr.error }); + } else { + rsp.errors = [{ refId: dr.refId, message: dr.error }]; + } + rsp.state = LoadingState.Error; } if (dr.frames?.length) { diff --git a/public/app/features/query/components/QueryEditorRow.test.ts b/public/app/features/query/components/QueryEditorRow.test.ts index 338461e7436..1c7eb61f393 100644 --- a/public/app/features/query/components/QueryEditorRow.test.ts +++ b/public/app/features/query/components/QueryEditorRow.test.ts @@ -21,10 +21,12 @@ describe('filterPanelDataToQuery', () => { toDataFrame({ refId: 'B', fields: [{ name: 'B333' }], meta: {} }), toDataFrame({ refId: 'C', fields: [{ name: 'CCCC' }], meta: { requestId: 'sub3' } }), ], - error: { - refId: 'B', - message: 'Error!!', - }, + errors: [ + { + refId: 'B', + message: 'Error!!', + }, + ], request: makePretendRequest('111', [ makePretendRequest('sub1'), makePretendRequest('sub2'), @@ -38,6 +40,7 @@ describe('filterPanelDataToQuery', () => { expect(panelData?.series.length).toBe(1); expect(panelData?.series[0].refId).toBe('A'); expect(panelData?.error).toBeUndefined(); + expect(panelData?.errors).toBeUndefined(); }); it('should match the error to the query', () => { @@ -45,6 +48,7 @@ describe('filterPanelDataToQuery', () => { expect(panelData?.series.length).toBe(3); expect(panelData?.series[0].refId).toBe('B'); expect(panelData?.error!.refId).toBe('B'); + expect(panelData?.errors![0].refId).toBe('B'); }); it('should include errors when missing data', () => { @@ -53,12 +57,14 @@ describe('filterPanelDataToQuery', () => { error: { message: 'Error!!', }, + errors: [{ message: 'Error!!' }], } as unknown as PanelData; const panelData = filterPanelDataToQuery(withError, 'B'); expect(panelData).toBeDefined(); expect(panelData?.state).toBe(LoadingState.Error); expect(panelData?.error).toBe(withError.error); + expect(panelData?.errors).toEqual(withError.errors); }); it('should set the state to done if the frame has no errors', () => { @@ -91,6 +97,7 @@ describe('filterPanelDataToQuery', () => { const panelDataB = filterPanelDataToQuery(withError, 'Q'); expect(panelDataB?.series.length).toBe(0); expect(panelDataB?.error?.refId).toBe('Q'); + expect(panelDataB?.errors![0].refId).toBe('Q'); }); it('should not set the state to done if the frame is loading and has no errors', () => { diff --git a/public/app/features/query/components/QueryEditorRow.tsx b/public/app/features/query/components/QueryEditorRow.tsx index ea1605dd3f5..84bee65a618 100644 --- a/public/app/features/query/components/QueryEditorRow.tsx +++ b/public/app/features/query/components/QueryEditorRow.tsx @@ -558,7 +558,7 @@ export function filterPanelDataToQuery(data: PanelData, refId: string): PanelDat const series = data.series.filter((series) => series.refId === refId); // If there was an error with no data and the panel is not in a loading state, pass it to the QueryEditors - if (data.state !== LoadingState.Loading && data.error && !data.series.length) { + if (data.state !== LoadingState.Loading && (data.error || data.errors?.length) && !data.series.length) { return { ...data, state: LoadingState.Error, @@ -567,7 +567,11 @@ export function filterPanelDataToQuery(data: PanelData, refId: string): PanelDat // Only say this is an error if the error links to the query let state = data.state; - const error = data.error && data.error.refId === refId ? data.error : undefined; + let error = data.errors?.find((e) => e.refId === refId); + if (!error && data.error) { + error = data.error.refId === refId ? data.error : undefined; + } + if (state !== LoadingState.Loading) { if (error) { state = LoadingState.Error; @@ -583,6 +587,7 @@ export function filterPanelDataToQuery(data: PanelData, refId: string): PanelDat state, series, error, + errors: error ? [error] : undefined, timeRange, }; } diff --git a/public/app/features/query/state/runRequest.ts b/public/app/features/query/state/runRequest.ts index b3ccc3435f5..4c4006274ad 100644 --- a/public/app/features/query/state/runRequest.ts +++ b/public/app/features/query/state/runRequest.ts @@ -53,6 +53,7 @@ export function processResponsePacket(packet: DataQueryResponse, state: RunningQ let loadingState = packet.state || LoadingState.Done; let error: DataQueryError | undefined = undefined; + let errors: DataQueryError[] | undefined = undefined; const series: DataQueryResponseData[] = []; const annotations: DataQueryResponseData[] = []; @@ -60,9 +61,10 @@ export function processResponsePacket(packet: DataQueryResponse, state: RunningQ for (const key in packets) { const packet = packets[key]; - if (packet.error) { + if (packet.error || packet.errors?.length) { loadingState = LoadingState.Error; error = packet.error; + errors = packet.errors; } if (packet.data && packet.data.length) { @@ -79,11 +81,12 @@ export function processResponsePacket(packet: DataQueryResponse, state: RunningQ const timeRange = getRequestTimeRange(request, loadingState); - const panelData = { + const panelData: PanelData = { state: loadingState, series, annotations, error, + errors, request, timeRange, };