mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
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.
|
* Describes the properties that can be passed to the PanelDataErrorView.
|
||||||
@@ -10,6 +10,7 @@ export interface PanelDataErrorViewProps {
|
|||||||
message?: string;
|
message?: string;
|
||||||
panelId: number;
|
panelId: number;
|
||||||
data: PanelData;
|
data: PanelData;
|
||||||
|
fieldConfig?: FieldConfigSource;
|
||||||
needsTimeField?: boolean;
|
needsTimeField?: boolean;
|
||||||
needsNumberField?: boolean;
|
needsNumberField?: boolean;
|
||||||
needsStringField?: boolean;
|
needsStringField?: boolean;
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ export const getStandardFieldConfigs = () => {
|
|||||||
const noValue: FieldConfigPropertyItem<any, string, StringFieldConfigSettings> = {
|
const noValue: FieldConfigPropertyItem<any, string, StringFieldConfigSettings> = {
|
||||||
id: 'noValue',
|
id: 'noValue',
|
||||||
path: 'noValue',
|
path: 'noValue',
|
||||||
name: 'No Value',
|
name: 'No value',
|
||||||
description: 'What to show when there is no value',
|
description: 'What to show when there is no value',
|
||||||
|
|
||||||
editor: standardEditorsRegistry.get('text').editor as any,
|
editor: standardEditorsRegistry.get('text').editor as any,
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
}
|
||||||
@@ -57,7 +57,7 @@ export function PanelDataErrorView(props: PanelDataErrorViewProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getMessageFor(
|
function getMessageFor(
|
||||||
{ data, message, needsNumberField, needsTimeField, needsStringField }: PanelDataErrorViewProps,
|
{ data, fieldConfig, message, needsNumberField, needsTimeField, needsStringField }: PanelDataErrorViewProps,
|
||||||
dataSummary: PanelDataSummary
|
dataSummary: PanelDataSummary
|
||||||
): string {
|
): string {
|
||||||
if (message) {
|
if (message) {
|
||||||
@@ -66,7 +66,7 @@ function getMessageFor(
|
|||||||
|
|
||||||
// In some cases there is a data frame but with no fields
|
// 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)) {
|
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) {
|
if (needsStringField && !dataSummary.hasStringField) {
|
||||||
|
|||||||
@@ -63,7 +63,15 @@ const propsToDiff: Array<string | PropDiffFn> = [
|
|||||||
|
|
||||||
interface Props extends PanelProps<PanelOptions> {}
|
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 theme = useTheme2();
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
const { eventBus } = usePanelContext();
|
const { eventBus } = usePanelContext();
|
||||||
@@ -133,7 +141,15 @@ export const BarChartPanel: React.FunctionComponent<Props> = ({ data, options, w
|
|||||||
}, [height, options.xTickLabelRotation, options.xTickLabelMaxLength]);
|
}, [height, options.xTickLabelRotation, options.xTickLabelMaxLength]);
|
||||||
|
|
||||||
if (!info.viz[0]?.fields.length) {
|
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) => {
|
const renderTooltip = (alignedFrame: DataFrame, seriesIdx: number | null, datapointIdx: number | null) => {
|
||||||
|
|||||||
@@ -209,7 +209,15 @@ export const CandlestickPanel: React.FC<CandlestickPanelProps> = ({
|
|||||||
}, [options, data.structureRev]);
|
}, [options, data.structureRev]);
|
||||||
|
|
||||||
if (!info) {
|
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());
|
const enableAnnotationCreation = Boolean(canAddAnnotations && canAddAnnotations());
|
||||||
|
|||||||
@@ -119,7 +119,15 @@ export const HeatmapPanel: React.FC<HeatmapPanelProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (info.warning || !info.heatmap) {
|
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 (
|
return (
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ interface LogsPanelProps extends PanelProps<Options> {}
|
|||||||
export const LogsPanel: React.FunctionComponent<LogsPanelProps> = ({
|
export const LogsPanel: React.FunctionComponent<LogsPanelProps> = ({
|
||||||
data,
|
data,
|
||||||
timeZone,
|
timeZone,
|
||||||
|
fieldConfig,
|
||||||
options: {
|
options: {
|
||||||
showLabels,
|
showLabels,
|
||||||
showTime,
|
showTime,
|
||||||
@@ -83,7 +84,7 @@ export const LogsPanel: React.FunctionComponent<LogsPanelProps> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!data || logRows.length === 0) {
|
if (!data || logRows.length === 0) {
|
||||||
return <PanelDataErrorView panelId={id} data={data} needsStringField />;
|
return <PanelDataErrorView fieldConfig={fieldConfig} panelId={id} data={data} needsStringField />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderCommonLabels = () => (
|
const renderCommonLabels = () => (
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
VizLegendItem,
|
VizLegendItem,
|
||||||
} from '@grafana/ui';
|
} from '@grafana/ui';
|
||||||
import { filterDisplayItems, sumDisplayItemsReducer } from './utils';
|
import { filterDisplayItems, sumDisplayItemsReducer } from './utils';
|
||||||
|
import { PanelDataErrorView } from '@grafana/runtime';
|
||||||
|
|
||||||
const defaultLegendOptions: PieChartLegendOptions = {
|
const defaultLegendOptions: PieChartLegendOptions = {
|
||||||
displayMode: LegendDisplayMode.List,
|
displayMode: LegendDisplayMode.List,
|
||||||
@@ -37,7 +38,7 @@ interface Props extends PanelProps<PieChartOptions> {}
|
|||||||
* @beta
|
* @beta
|
||||||
*/
|
*/
|
||||||
export function PieChartPanel(props: Props) {
|
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 theme = useTheme2();
|
||||||
const highlightedTitle = useSliceHighlightState();
|
const highlightedTitle = useSliceHighlightState();
|
||||||
@@ -51,11 +52,7 @@ export function PieChartPanel(props: Props) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!hasFrames(fieldDisplayValues)) {
|
if (!hasFrames(fieldDisplayValues)) {
|
||||||
return (
|
return <PanelDataErrorView panelId={id} fieldConfig={fieldConfig} data={data} />;
|
||||||
<div className="panel-empty">
|
|
||||||
<p>No data</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { applyFilterFromTable } from '../../../features/variables/adhoc/actions'
|
|||||||
import { getDashboardSrv } from '../../../features/dashboard/services/DashboardSrv';
|
import { getDashboardSrv } from '../../../features/dashboard/services/DashboardSrv';
|
||||||
import { getFooterCells } from './footer';
|
import { getFooterCells } from './footer';
|
||||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||||
|
import { PanelDataErrorView } from '@grafana/runtime';
|
||||||
|
|
||||||
interface Props extends PanelProps<PanelOptions> {}
|
interface Props extends PanelProps<PanelOptions> {}
|
||||||
|
|
||||||
@@ -118,14 +119,14 @@ export class TablePanel extends Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { data, height, width, options } = this.props;
|
const { data, height, width, options, fieldConfig, id } = this.props;
|
||||||
|
|
||||||
const frames = data.series;
|
const frames = data.series;
|
||||||
const count = frames?.length;
|
const count = frames?.length;
|
||||||
const hasFields = frames[0]?.fields.length;
|
const hasFields = frames[0]?.fields.length;
|
||||||
|
|
||||||
if (!count || !hasFields) {
|
if (!count || !hasFields) {
|
||||||
return <div className={tableStyles.noData}>No data</div>;
|
return <PanelDataErrorView panelId={id} fieldConfig={fieldConfig} data={data} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count > 1) {
|
if (count > 1) {
|
||||||
|
|||||||
@@ -36,7 +36,15 @@ export const TimeSeriesPanel: React.FC<TimeSeriesPanelProps> = ({
|
|||||||
const frames = useMemo(() => prepareGraphableFields(data.series, config.theme2), [data]);
|
const frames = useMemo(() => prepareGraphableFields(data.series, config.theme2), [data]);
|
||||||
|
|
||||||
if (!frames) {
|
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());
|
const enableAnnotationCreation = Boolean(canAddAnnotations && canAddAnnotations());
|
||||||
|
|||||||
Reference in New Issue
Block a user