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:
parent
aceedb3a32
commit
e3590e1a9b
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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(
|
||||
{ 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) {
|
||||
|
@ -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) => {
|
||||
|
@ -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());
|
||||
|
@ -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 (
|
||||
|
@ -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 = () => (
|
||||
|
@ -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 (
|
||||
|
@ -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) {
|
||||
|
@ -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());
|
||||
|
Loading…
Reference in New Issue
Block a user