diff --git a/packages/grafana-runtime/src/components/PanelDataErrorView.tsx b/packages/grafana-runtime/src/components/PanelDataErrorView.tsx index 25e577d6712..2e3907c9264 100644 --- a/packages/grafana-runtime/src/components/PanelDataErrorView.tsx +++ b/packages/grafana-runtime/src/components/PanelDataErrorView.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { PanelData } from '@grafana/data'; +import { FieldConfigSource, PanelData } from '@grafana/data'; /** * Describes the properties that can be passed to the PanelDataErrorView. @@ -10,6 +10,7 @@ export interface PanelDataErrorViewProps { message?: string; panelId: number; data: PanelData; + fieldConfig?: FieldConfigSource; needsTimeField?: boolean; needsNumberField?: boolean; needsStringField?: boolean; diff --git a/packages/grafana-ui/src/utils/standardEditors.tsx b/packages/grafana-ui/src/utils/standardEditors.tsx index 8ddbb3cf528..065c1812113 100644 --- a/packages/grafana-ui/src/utils/standardEditors.tsx +++ b/packages/grafana-ui/src/utils/standardEditors.tsx @@ -134,7 +134,7 @@ export const getStandardFieldConfigs = () => { const noValue: FieldConfigPropertyItem = { id: 'noValue', path: 'noValue', - name: 'No Value', + name: 'No value', description: 'What to show when there is no value', editor: standardEditorsRegistry.get('text').editor as any, diff --git a/public/app/features/panel/components/PanelDataErrorView.test.tsx b/public/app/features/panel/components/PanelDataErrorView.test.tsx new file mode 100644 index 00000000000..10525bf0751 --- /dev/null +++ b/public/app/features/panel/components/PanelDataErrorView.test.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { defaultsDeep } from 'lodash'; +import { PanelDataErrorView } from './PanelDataErrorView'; +import { PanelDataErrorViewProps } from '@grafana/runtime'; +import { getDefaultTimeRange, LoadingState } from '@grafana/data'; +import { Provider } from 'react-redux'; +import { configureStore } from 'app/store/configureStore'; + +describe('PanelDataErrorView', () => { + it('show No data when there is no data', () => { + renderWithProps(); + + expect(screen.getByText('No data')).toBeInTheDocument(); + }); + + it('show no value field config when there is no data', () => { + renderWithProps({ + fieldConfig: { + overrides: [], + defaults: { + noValue: 'Query returned nothing', + }, + }, + }); + + expect(screen.getByText('Query returned nothing')).toBeInTheDocument(); + }); +}); + +function renderWithProps(overrides?: Partial) { + const defaults: PanelDataErrorViewProps = { + panelId: 1, + data: { + state: LoadingState.Done, + series: [], + timeRange: getDefaultTimeRange(), + }, + }; + + const props = defaultsDeep(overrides ?? {}, defaults); + const store = configureStore(); + + const stuff = render( + + + + ); + return { ...stuff }; +} diff --git a/public/app/features/panel/components/PanelDataErrorView.tsx b/public/app/features/panel/components/PanelDataErrorView.tsx index 35baee77e1c..55bb7aa50ca 100644 --- a/public/app/features/panel/components/PanelDataErrorView.tsx +++ b/public/app/features/panel/components/PanelDataErrorView.tsx @@ -57,7 +57,7 @@ export function PanelDataErrorView(props: PanelDataErrorViewProps) { } function getMessageFor( - { data, message, needsNumberField, needsTimeField, needsStringField }: PanelDataErrorViewProps, + { data, fieldConfig, message, needsNumberField, needsTimeField, needsStringField }: PanelDataErrorViewProps, dataSummary: PanelDataSummary ): string { if (message) { @@ -66,7 +66,7 @@ function getMessageFor( // In some cases there is a data frame but with no fields if (!data.series || data.series.length === 0 || (data.series.length === 1 && data.series[0].fields.length === 0)) { - return 'No data'; + return fieldConfig?.defaults.noValue ?? 'No data'; } if (needsStringField && !dataSummary.hasStringField) { diff --git a/public/app/plugins/panel/barchart/BarChartPanel.tsx b/public/app/plugins/panel/barchart/BarChartPanel.tsx index 7a4e7dc5f2c..61515d561b7 100755 --- a/public/app/plugins/panel/barchart/BarChartPanel.tsx +++ b/public/app/plugins/panel/barchart/BarChartPanel.tsx @@ -63,7 +63,15 @@ const propsToDiff: Array = [ interface Props extends PanelProps {} -export const BarChartPanel: React.FunctionComponent = ({ data, options, width, height, timeZone, id }) => { +export const BarChartPanel: React.FunctionComponent = ({ + data, + options, + fieldConfig, + width, + height, + timeZone, + id, +}) => { const theme = useTheme2(); const styles = useStyles2(getStyles); const { eventBus } = usePanelContext(); @@ -133,7 +141,15 @@ export const BarChartPanel: React.FunctionComponent = ({ data, options, w }, [height, options.xTickLabelRotation, options.xTickLabelMaxLength]); if (!info.viz[0]?.fields.length) { - return ; + return ( + + ); } const renderTooltip = (alignedFrame: DataFrame, seriesIdx: number | null, datapointIdx: number | null) => { diff --git a/public/app/plugins/panel/candlestick/CandlestickPanel.tsx b/public/app/plugins/panel/candlestick/CandlestickPanel.tsx index ddd24370c6b..fd691d5b84f 100644 --- a/public/app/plugins/panel/candlestick/CandlestickPanel.tsx +++ b/public/app/plugins/panel/candlestick/CandlestickPanel.tsx @@ -209,7 +209,15 @@ export const CandlestickPanel: React.FC = ({ }, [options, data.structureRev]); if (!info) { - return ; + return ( + + ); } const enableAnnotationCreation = Boolean(canAddAnnotations && canAddAnnotations()); diff --git a/public/app/plugins/panel/heatmap-new/HeatmapPanel.tsx b/public/app/plugins/panel/heatmap-new/HeatmapPanel.tsx index 12c0e7a2816..a76ece81a90 100644 --- a/public/app/plugins/panel/heatmap-new/HeatmapPanel.tsx +++ b/public/app/plugins/panel/heatmap-new/HeatmapPanel.tsx @@ -119,7 +119,15 @@ export const HeatmapPanel: React.FC = ({ }; if (info.warning || !info.heatmap) { - return ; + return ( + + ); } return ( diff --git a/public/app/plugins/panel/logs/LogsPanel.tsx b/public/app/plugins/panel/logs/LogsPanel.tsx index d5cc9973979..72f742ab929 100644 --- a/public/app/plugins/panel/logs/LogsPanel.tsx +++ b/public/app/plugins/panel/logs/LogsPanel.tsx @@ -22,6 +22,7 @@ interface LogsPanelProps extends PanelProps {} export const LogsPanel: React.FunctionComponent = ({ data, timeZone, + fieldConfig, options: { showLabels, showTime, @@ -83,7 +84,7 @@ export const LogsPanel: React.FunctionComponent = ({ ); if (!data || logRows.length === 0) { - return ; + return ; } const renderCommonLabels = () => ( diff --git a/public/app/plugins/panel/piechart/PieChartPanel.tsx b/public/app/plugins/panel/piechart/PieChartPanel.tsx index a1ba2a08a3a..d8eeeb59efd 100644 --- a/public/app/plugins/panel/piechart/PieChartPanel.tsx +++ b/public/app/plugins/panel/piechart/PieChartPanel.tsx @@ -23,6 +23,7 @@ import { VizLegendItem, } from '@grafana/ui'; import { filterDisplayItems, sumDisplayItemsReducer } from './utils'; +import { PanelDataErrorView } from '@grafana/runtime'; const defaultLegendOptions: PieChartLegendOptions = { displayMode: LegendDisplayMode.List, @@ -37,7 +38,7 @@ interface Props extends PanelProps {} * @beta */ export function PieChartPanel(props: Props) { - const { data, timeZone, fieldConfig, replaceVariables, width, height, options } = props; + const { data, timeZone, fieldConfig, replaceVariables, width, height, options, id } = props; const theme = useTheme2(); const highlightedTitle = useSliceHighlightState(); @@ -51,11 +52,7 @@ export function PieChartPanel(props: Props) { }); if (!hasFrames(fieldDisplayValues)) { - return ( -
-

No data

-
- ); + return ; } return ( diff --git a/public/app/plugins/panel/table/TablePanel.tsx b/public/app/plugins/panel/table/TablePanel.tsx index 852b0bbaeeb..a55e7e21e2e 100644 --- a/public/app/plugins/panel/table/TablePanel.tsx +++ b/public/app/plugins/panel/table/TablePanel.tsx @@ -17,6 +17,7 @@ import { applyFilterFromTable } from '../../../features/variables/adhoc/actions' import { getDashboardSrv } from '../../../features/dashboard/services/DashboardSrv'; import { getFooterCells } from './footer'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; +import { PanelDataErrorView } from '@grafana/runtime'; interface Props extends PanelProps {} @@ -118,14 +119,14 @@ export class TablePanel extends Component { } render() { - const { data, height, width, options } = this.props; + const { data, height, width, options, fieldConfig, id } = this.props; const frames = data.series; const count = frames?.length; const hasFields = frames[0]?.fields.length; if (!count || !hasFields) { - return
No data
; + return ; } if (count > 1) { diff --git a/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx b/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx index 64c4b6ea7df..5eb7e489b41 100644 --- a/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx +++ b/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx @@ -36,7 +36,15 @@ export const TimeSeriesPanel: React.FC = ({ const frames = useMemo(() => prepareGraphableFields(data.series, config.theme2), [data]); if (!frames) { - return ; + return ( + + ); } const enableAnnotationCreation = Boolean(canAddAnnotations && canAddAnnotations());