From 3ab410de0bcb948fec2a7c0690fbdb52f5126117 Mon Sep 17 00:00:00 2001 From: Gilles De Mey Date: Tue, 28 Jun 2022 13:38:31 +0200 Subject: [PATCH] QueryEditorRow: Render frame warnings in QueryEditorRow (#50116) --- .../query/components/QueryEditorRow.test.ts | 78 ++++++++++++++++++- .../query/components/QueryEditorRow.tsx | 49 +++++++++++- 2 files changed, 123 insertions(+), 4 deletions(-) diff --git a/public/app/features/query/components/QueryEditorRow.test.ts b/public/app/features/query/components/QueryEditorRow.test.ts index e5199482b05..cc03790c24f 100644 --- a/public/app/features/query/components/QueryEditorRow.test.ts +++ b/public/app/features/query/components/QueryEditorRow.test.ts @@ -1,6 +1,8 @@ +import { render, screen } from '@testing-library/react'; + import { DataQueryRequest, dateTime, LoadingState, PanelData, toDataFrame } from '@grafana/data'; -import { filterPanelDataToQuery } from './QueryEditorRow'; +import { filterPanelDataToQuery, QueryEditorRow } from './QueryEditorRow'; function makePretendRequest(requestId: string, subRequests?: DataQueryRequest[]): DataQueryRequest { return { @@ -108,3 +110,77 @@ describe('filterPanelDataToQuery', () => { expect(panelDataA?.state).toBe(LoadingState.Loading); }); }); + +describe('frame results with warnings', () => { + const meta = { + notices: [ + { + severity: 'warning', + text: 'Reduce operation is not needed. Input query or expression A is already reduced data.', + }, + ], + }; + + const dataWithWarnings: PanelData = { + state: LoadingState.Done, + series: [ + toDataFrame({ + refId: 'B', + fields: [{ name: 'B1' }], + meta, + }), + toDataFrame({ + refId: 'B', + fields: [{ name: 'B2' }], + meta, + }), + ], + timeRange: { from: dateTime(), to: dateTime(), raw: { from: 'now-1d', to: 'now' } }, + }; + + const dataWithoutWarnings: PanelData = { + state: LoadingState.Done, + series: [ + toDataFrame({ + refId: 'B', + fields: [{ name: 'B1' }], + meta: {}, + }), + toDataFrame({ + refId: 'B', + fields: [{ name: 'B2' }], + meta: {}, + }), + ], + timeRange: { from: dateTime(), to: dateTime(), raw: { from: 'now-1d', to: 'now' } }, + }; + + it('should show a warning badge and de-duplicate warning messages', () => { + // @ts-ignore: there are _way_ too many props to inject here :( + const editorRow = new QueryEditorRow({ + data: dataWithWarnings, + query: { + refId: 'B', + }, + }); + + const warningsComponent = editorRow.renderWarnings(); + expect(warningsComponent).not.toBe(null); + + render(warningsComponent!); + expect(screen.getByText('1 warning')).toBeInTheDocument(); + }); + + it('should not show a warning badge when there are no warnings', () => { + // @ts-ignore: there are _way_ too many props to inject here :( + const editorRow = new QueryEditorRow({ + data: dataWithoutWarnings, + query: { + refId: 'B', + }, + }); + + const warningsComponent = editorRow.renderWarnings(); + expect(warningsComponent).toBe(null); + }); +}); diff --git a/public/app/features/query/components/QueryEditorRow.tsx b/public/app/features/query/components/QueryEditorRow.tsx index f545f04c29b..3229f5de665 100644 --- a/public/app/features/query/components/QueryEditorRow.tsx +++ b/public/app/features/query/components/QueryEditorRow.tsx @@ -1,6 +1,7 @@ // Libraries import classNames from 'classnames'; -import { cloneDeep, has } from 'lodash'; +import { cloneDeep, filter, has, uniqBy } from 'lodash'; +import pluralize from 'pluralize'; import React, { PureComponent, ReactNode } from 'react'; // Utils & Services @@ -15,12 +16,13 @@ import { LoadingState, PanelData, PanelEvents, + QueryResultMetaNotice, TimeRange, toLegacyResponseData, } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { AngularComponent, getAngularLoader } from '@grafana/runtime'; -import { ErrorBoundaryAlert, HorizontalGroup } from '@grafana/ui'; +import { Badge, ErrorBoundaryAlert, HorizontalGroup } from '@grafana/ui'; import { OperationRowHelp } from 'app/core/components/QueryOperationRow/OperationRowHelp'; import { QueryOperationAction } from 'app/core/components/QueryOperationRow/QueryOperationAction'; import { @@ -307,9 +309,46 @@ export class QueryEditorRow extends PureComponent { + const { data, query } = this.props; + const dataFilteredByRefId = filterPanelDataToQuery(data, query.refId)?.series ?? []; + + const allWarnings = dataFilteredByRefId.reduce((acc: QueryResultMetaNotice[], serie) => { + if (!serie.meta?.notices) { + return acc; + } + + const warnings = filter(serie.meta.notices, { severity: 'warning' }) ?? []; + return acc.concat(warnings); + }, []); + + const uniqueWarnings = uniqBy(allWarnings, 'text'); + + const hasWarnings = uniqueWarnings.length > 0; + if (!hasWarnings) { + return null; + } + + const serializedWarnings = uniqueWarnings.map((warning) => warning.text).join('\n'); + + return ( + + {uniqueWarnings.length} {pluralize('warning', uniqueWarnings.length)} + + } + tooltip={serializedWarnings} + /> + ); + }; + renderExtraActions = () => { const { query, queries, data, onAddQuery, dataSource } = this.props; - return RowActionComponents.getAllExtraRenderAction() + + const extraActions = RowActionComponents.getAllExtraRenderAction() .map((action, index) => action({ query, @@ -321,6 +360,10 @@ export class QueryEditorRow extends PureComponent {