BarChart/StateTimeline: Use noValue setting for error message when data is empty (#107147)

* fix: use fieldConfig.defaults.noValue in barchart and statetimeline

* change name of test

* remove todo comment

* lean on PanelDataErrorView in the case where no fields are present

* update StateTimeline and StatusHistory to use PanelDataErrorView

* fix test

* self-review cleanup
pull/107326/head
Paul Marbach 3 weeks ago committed by GitHub
parent f7eab21f9f
commit ee6cbd0efc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 12
      public/app/core/components/TimelineChart/utils.test.ts
  2. 8
      public/app/core/components/TimelineChart/utils.ts
  3. 12
      public/app/features/panel/components/PanelDataErrorView.tsx
  4. 14
      public/app/plugins/panel/barchart/utils.test.ts
  5. 12
      public/app/plugins/panel/barchart/utils.ts
  6. 11
      public/app/plugins/panel/state-timeline/StateTimelinePanel.tsx
  7. 11
      public/app/plugins/panel/status-history/StatusHistoryPanel.tsx
  8. 21
      public/locales/en-US/grafana.json

@ -62,6 +62,18 @@ describe('prepare timeline graph', () => {
expect(info.warn).toEqual('No graphable fields');
});
it('errors with no frame', () => {
const info = prepareTimelineFields(undefined, true, timeRange, theme);
expect(info.frames).toBeUndefined();
expect(info.warn).toBe('');
});
it('errors with empty frame', () => {
const info = prepareTimelineFields([], true, timeRange, theme);
expect(info.frames).toBeUndefined();
expect(info.warn).toBe('');
});
it('will merge duplicate values', () => {
const frames = [
toDataFrame({

@ -23,6 +23,7 @@ import {
SpecialValueMatch,
} from '@grafana/data';
import { maybeSortFrame, NULL_RETAIN } from '@grafana/data/internal';
import { t } from '@grafana/i18n';
import {
VizLegendOptions,
AxisPlacement,
@ -309,8 +310,9 @@ export function prepareTimelineFields(
timeRange: TimeRange,
theme: GrafanaTheme2
): { frames?: DataFrame[]; warn?: string } {
// this allows PanelDataErrorView to show the default noValue message
if (!series?.length) {
return { warn: 'No data in response' };
return { warn: '' };
}
cacheFieldDisplayNames(series);
@ -436,10 +438,10 @@ export function prepareTimelineFields(
}
if (!hasTimeseries) {
return { warn: 'Data does not have a time field' };
return { warn: t('timeline.missing-field.time', 'Data does not have a time field') };
}
if (!frames.length) {
return { warn: 'No graphable fields' };
return { warn: t('timeline.missing-field.all', 'No graphable fields') };
}
return { frames };

@ -8,7 +8,7 @@ import {
VisualizationSuggestion,
} from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { Trans } from '@grafana/i18n';
import { t, Trans } from '@grafana/i18n';
import { PanelDataErrorViewProps, locationService } from '@grafana/runtime';
import { VizPanel } from '@grafana/scenes';
import { usePanelContext, useStyles2 } from '@grafana/ui';
@ -132,22 +132,22 @@ function getMessageFor(
}
if (!data.series || data.series.length === 0 || data.series.every((frame) => frame.length === 0)) {
return fieldConfig?.defaults.noValue ?? 'No data';
return fieldConfig?.defaults.noValue ?? t('panel.panel-data-error-view.no-value.default', 'No data');
}
if (needsStringField && !dataSummary.hasStringField) {
return 'Data is missing a string field';
return t('panel.panel-data-error-view.missing-value.string', 'Data is missing a string field');
}
if (needsNumberField && !dataSummary.hasNumberField) {
return 'Data is missing a number field';
return t('panel.panel-data-error-view.missing-value.number', 'Data is missing a number field');
}
if (needsTimeField && !dataSummary.hasTimeField) {
return 'Data is missing a time field';
return t('panel.panel-data-error-view.missing-value.time', 'Data is missing a time field');
}
return 'Cannot visualize data';
return t('panel.panel-data-error-view.missing-value.unknown', 'Cannot visualize data');
}
const getStyles = (theme: GrafanaTheme2) => {

@ -166,14 +166,14 @@ describe('BarChart utils', () => {
});
describe('prepareGraphableFrames', () => {
it('will warn when there is no frames in the response', () => {
it('will return empty string when there are no frames in the response', () => {
const info = prepSeries([], fieldConfig, StackingMode.None, createTheme());
const warning = assertIsDefined('warn' in info ? info : null);
expect(warning.warn).toEqual('No data in response');
expect(info.warn).toBe('');
expect(info.series).toHaveLength(0);
});
it('will warn when there is no data in the response', () => {
it('will return empty string when there is no data in the response', () => {
const info = prepSeries(
[
{
@ -185,9 +185,9 @@ describe('BarChart utils', () => {
StackingMode.None,
createTheme()
);
const warning = assertIsDefined('warn' in info ? info : null);
expect(warning.warn).toEqual('No data in response');
expect(info.warn).toBe('');
expect(info.series).toHaveLength(0);
});
it('will warn when there is no string or time field', () => {
@ -201,7 +201,7 @@ describe('BarChart utils', () => {
const info = prepSeries([df], fieldConfig, StackingMode.None, createTheme());
const warning = assertIsDefined('warn' in info ? info : null);
expect(warning.warn).toEqual('Bar charts requires a string or time field');
expect(warning.warn).toEqual('Bar charts require a string or time field');
});
it('will warn when there are no numeric fields in the response', () => {

@ -14,6 +14,7 @@ import {
outerJoinDataFrames,
} from '@grafana/data';
import { decoupleHideFromState } from '@grafana/data/internal';
import { t } from '@grafana/i18n';
import {
AxisColorMode,
AxisPlacement,
@ -56,8 +57,13 @@ export function prepSeries(
xFieldName?: string,
colorFieldName?: string
): BarSeries {
// this allows PanelDataErrorView to show the default noValue message
if (frames.length === 0 || frames.every((fr) => fr.length === 0)) {
return { series: [], _rest: [], warn: 'No data in response' };
return {
warn: '',
series: [],
_rest: [],
};
}
cacheFieldDisplayNames(frames);
@ -120,7 +126,7 @@ export function prepSeries(
let warn: string | null = null;
if (fields.length === 1) {
warn = 'No numeric fields found';
warn = t('bar-chart.warn.missing-numeric', 'No numeric fields found');
}
frame.fields = fields;
@ -141,7 +147,7 @@ export function prepSeries(
series: [],
_rest: [],
color: null,
warn: 'Bar charts requires a string or time field',
warn: t('bar-chart.warn.missing-series', 'Bar charts require a string or time field'),
};
}

@ -1,6 +1,7 @@
import { useMemo, useState } from 'react';
import { DashboardCursorSync, PanelProps } from '@grafana/data';
import { PanelDataErrorView } from '@grafana/runtime';
import {
AxisPlacement,
EventBusPlugin,
@ -37,8 +38,10 @@ export const StateTimelinePanel = ({
options,
width,
height,
fieldConfig,
replaceVariables,
onChangeTimeRange,
id: panelId,
}: TimelinePanelProps) => {
const theme = useTheme2();
@ -64,12 +67,8 @@ export const StateTimelinePanel = ({
const timezones = useMemo(() => getTimezones(options.timezone, timeZone), [options.timezone, timeZone]);
if (!paginatedFrames || warn) {
return (
<div className="panel-empty">
<p>{warn ?? 'No data found in response'}</p>
</div>
);
if (!paginatedFrames || typeof warn === 'string') {
return <PanelDataErrorView panelId={panelId} fieldConfig={fieldConfig} data={data} message={warn} needsTimeField />;
}
const enableAnnotationCreation = Boolean(canAddAnnotations && canAddAnnotations());

@ -2,6 +2,7 @@ import { useMemo, useState } from 'react';
import { DashboardCursorSync, PanelProps } from '@grafana/data';
import { Trans } from '@grafana/i18n';
import { PanelDataErrorView } from '@grafana/runtime';
import {
AxisPlacement,
EventBusPlugin,
@ -38,8 +39,10 @@ export const StatusHistoryPanel = ({
options,
width,
height,
fieldConfig,
replaceVariables,
onChangeTimeRange,
id: panelId,
}: TimelinePanelProps) => {
const theme = useTheme2();
@ -67,12 +70,8 @@ export const StatusHistoryPanel = ({
const timezones = useMemo(() => getTimezones(options.timezone, timeZone), [options.timezone, timeZone]);
if (!paginatedFrames || warn) {
return (
<div className="panel-empty">
<p>{warn ?? 'No data found in response'}</p>
</div>
);
if (!paginatedFrames || typeof warn === 'string') {
return <PanelDataErrorView panelId={panelId} fieldConfig={fieldConfig} data={data} message={warn} needsTimeField />;
}
// Status grid requires some space between values

@ -3257,6 +3257,12 @@
"auth-config-auth-config-page-unconnected": {
"subtitle": "Manage your auth settings and configure single sign-on. Find out more in our <2>documentation</2>."
},
"bar-chart": {
"warn": {
"missing-numeric": "No numeric fields found",
"missing-series": "Bar charts require a string or time field"
}
},
"barchart": {
"config": {
"category-thresholds": "Thresholds",
@ -9503,6 +9509,15 @@
"view": "View"
},
"panel-data-error-view": {
"missing-value": {
"number": "Data is missing a number field",
"string": "Data is missing a string field",
"time": "Data is missing a time field",
"unknown": "Cannot visualize data"
},
"no-value": {
"default": "No data"
},
"open-visualization-suggestions": "Open visualization suggestions",
"switch-to-table": "Switch to table"
},
@ -11603,6 +11618,12 @@
"select-search-input": "Type to search (country, city, abbreviation)"
}
},
"timeline": {
"missing-field": {
"all": "No graphable fields",
"time": "Data does not have a time field"
}
},
"timeseries": {
"annotation-editor2": {
"add-annotation": "Add annotation",

Loading…
Cancel
Save