Panels: Use the No value option when showing the no data message (#47675)

* Performance: Create separate div for portal root to limit reflow -> recalc style

* refactoring

* NoData: Use field config No value option as text when display no data message

* Use PanelDataErrorView in TabelPanel

* Add PanelDataErrorView to PieChart

* Updated
This commit is contained in:
Torkel Ödegaard 2022-04-14 10:10:03 +02:00 committed by GitHub
parent aceedb3a32
commit e3590e1a9b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 108 additions and 18 deletions

View File

@ -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;

View File

@ -134,7 +134,7 @@ export const getStandardFieldConfigs = () => {
const noValue: FieldConfigPropertyItem<any, string, StringFieldConfigSettings> = {
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,

View File

@ -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<PanelDataErrorViewProps>) {
const defaults: PanelDataErrorViewProps = {
panelId: 1,
data: {
state: LoadingState.Done,
series: [],
timeRange: getDefaultTimeRange(),
},
};
const props = defaultsDeep(overrides ?? {}, defaults);
const store = configureStore();
const stuff = render(
<Provider store={store}>
<PanelDataErrorView {...props} />
</Provider>
);
return { ...stuff };
}

View File

@ -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) {

View File

@ -63,7 +63,15 @@ const propsToDiff: Array<string | PropDiffFn> = [
interface Props extends PanelProps<PanelOptions> {}
export const BarChartPanel: React.FunctionComponent<Props> = ({ data, options, width, height, timeZone, id }) => {
export const BarChartPanel: React.FunctionComponent<Props> = ({
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<Props> = ({ data, options, w
}, [height, options.xTickLabelRotation, options.xTickLabelMaxLength]);
if (!info.viz[0]?.fields.length) {
return <PanelDataErrorView panelId={id} data={data} message={info.warn} needsNumberField={true} />;
return (
<PanelDataErrorView
panelId={id}
fieldConfig={fieldConfig}
data={data}
message={info.warn}
needsNumberField={true}
/>
);
}
const renderTooltip = (alignedFrame: DataFrame, seriesIdx: number | null, datapointIdx: number | null) => {

View File

@ -209,7 +209,15 @@ export const CandlestickPanel: React.FC<CandlestickPanelProps> = ({
}, [options, data.structureRev]);
if (!info) {
return <PanelDataErrorView panelId={id} data={data} needsTimeField={true} needsNumberField={true} />;
return (
<PanelDataErrorView
panelId={id}
fieldConfig={fieldConfig}
data={data}
needsTimeField={true}
needsNumberField={true}
/>
);
}
const enableAnnotationCreation = Boolean(canAddAnnotations && canAddAnnotations());

View File

@ -119,7 +119,15 @@ export const HeatmapPanel: React.FC<HeatmapPanelProps> = ({
};
if (info.warning || !info.heatmap) {
return <PanelDataErrorView panelId={id} data={data} needsNumberField={true} message={info.warning} />;
return (
<PanelDataErrorView
panelId={id}
fieldConfig={fieldConfig}
data={data}
needsNumberField={true}
message={info.warning}
/>
);
}
return (

View File

@ -22,6 +22,7 @@ interface LogsPanelProps extends PanelProps<Options> {}
export const LogsPanel: React.FunctionComponent<LogsPanelProps> = ({
data,
timeZone,
fieldConfig,
options: {
showLabels,
showTime,
@ -83,7 +84,7 @@ export const LogsPanel: React.FunctionComponent<LogsPanelProps> = ({
);
if (!data || logRows.length === 0) {
return <PanelDataErrorView panelId={id} data={data} needsStringField />;
return <PanelDataErrorView fieldConfig={fieldConfig} panelId={id} data={data} needsStringField />;
}
const renderCommonLabels = () => (

View File

@ -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<PieChartOptions> {}
* @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 (
<div className="panel-empty">
<p>No data</p>
</div>
);
return <PanelDataErrorView panelId={id} fieldConfig={fieldConfig} data={data} />;
}
return (

View File

@ -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<PanelOptions> {}
@ -118,14 +119,14 @@ export class TablePanel extends Component<Props> {
}
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 <div className={tableStyles.noData}>No data</div>;
return <PanelDataErrorView panelId={id} fieldConfig={fieldConfig} data={data} />;
}
if (count > 1) {

View File

@ -36,7 +36,15 @@ export const TimeSeriesPanel: React.FC<TimeSeriesPanelProps> = ({
const frames = useMemo(() => prepareGraphableFields(data.series, config.theme2), [data]);
if (!frames) {
return <PanelDataErrorView panelId={id} data={data} needsTimeField={true} needsNumberField={true} />;
return (
<PanelDataErrorView
panelId={id}
fieldConfig={fieldConfig}
data={data}
needsTimeField={true}
needsNumberField={true}
/>
);
}
const enableAnnotationCreation = Boolean(canAddAnnotations && canAddAnnotations());