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
This commit is contained in:
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user