2022-04-22 08:33:13 -05:00
|
|
|
import { css } from '@emotion/css';
|
2021-11-25 02:41:03 -06:00
|
|
|
import React from 'react';
|
2022-04-22 08:33:13 -05:00
|
|
|
|
2023-04-27 22:10:02 -05:00
|
|
|
import {
|
|
|
|
CoreApp,
|
|
|
|
GrafanaTheme2,
|
|
|
|
PanelDataSummary,
|
|
|
|
VisualizationSuggestionsBuilder,
|
|
|
|
VisualizationSuggestion,
|
|
|
|
} from '@grafana/data';
|
|
|
|
import { PanelDataErrorViewProps, locationService } from '@grafana/runtime';
|
2022-04-22 08:33:13 -05:00
|
|
|
import { usePanelContext, useStyles2 } from '@grafana/ui';
|
2021-11-25 02:41:03 -06:00
|
|
|
import { CardButton } from 'app/core/components/CardButton';
|
|
|
|
import { LS_VISUALIZATION_SELECT_TAB_KEY } from 'app/core/constants';
|
2022-04-22 08:33:13 -05:00
|
|
|
import store from 'app/core/store';
|
|
|
|
import { toggleVizPicker } from 'app/features/dashboard/components/PanelEditor/state/reducers';
|
2021-11-25 02:41:03 -06:00
|
|
|
import { VisualizationSelectPaneTab } from 'app/features/dashboard/components/PanelEditor/types';
|
2022-04-22 08:33:13 -05:00
|
|
|
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
2022-09-19 04:49:35 -05:00
|
|
|
import { useDispatch } from 'app/types';
|
2022-04-22 08:33:13 -05:00
|
|
|
|
|
|
|
import { changePanelPlugin } from '../state/actions';
|
2021-11-25 02:41:03 -06:00
|
|
|
|
|
|
|
export function PanelDataErrorView(props: PanelDataErrorViewProps) {
|
|
|
|
const styles = useStyles2(getStyles);
|
|
|
|
const context = usePanelContext();
|
|
|
|
const builder = new VisualizationSuggestionsBuilder(props.data);
|
|
|
|
const { dataSummary } = builder;
|
|
|
|
const message = getMessageFor(props, dataSummary);
|
|
|
|
const dispatch = useDispatch();
|
2023-04-27 22:10:02 -05:00
|
|
|
const panel = getDashboardSrv().getCurrent()?.getPanelById(props.panelId);
|
2021-11-25 02:41:03 -06:00
|
|
|
|
|
|
|
const openVizPicker = () => {
|
|
|
|
store.setObject(LS_VISUALIZATION_SELECT_TAB_KEY, VisualizationSelectPaneTab.Suggestions);
|
|
|
|
dispatch(toggleVizPicker(true));
|
|
|
|
};
|
|
|
|
|
|
|
|
const switchToTable = () => {
|
|
|
|
if (!panel) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
dispatch(
|
|
|
|
changePanelPlugin({
|
|
|
|
panel,
|
|
|
|
pluginId: 'table',
|
|
|
|
})
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2023-04-27 22:10:02 -05:00
|
|
|
const loadSuggestion = (s: VisualizationSuggestion) => {
|
|
|
|
if (!panel) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
dispatch(
|
|
|
|
changePanelPlugin({
|
|
|
|
...s, // includes panelId, config, etc
|
|
|
|
panel,
|
|
|
|
})
|
|
|
|
);
|
|
|
|
if (s.transformations) {
|
|
|
|
setTimeout(() => {
|
|
|
|
locationService.partial({ tab: 'transform' });
|
|
|
|
}, 100);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-11-25 02:41:03 -06:00
|
|
|
return (
|
|
|
|
<div className={styles.wrapper}>
|
|
|
|
<div className={styles.message}>{message}</div>
|
2023-04-27 22:10:02 -05:00
|
|
|
{context.app === CoreApp.PanelEditor && dataSummary.hasData && panel && (
|
2021-11-25 02:41:03 -06:00
|
|
|
<div className={styles.actions}>
|
2023-04-27 22:10:02 -05:00
|
|
|
{props.suggestions && (
|
|
|
|
<>
|
|
|
|
{props.suggestions.map((v) => (
|
|
|
|
<CardButton key={v.name} icon="process" onClick={() => loadSuggestion(v)}>
|
|
|
|
{v.name}
|
|
|
|
</CardButton>
|
|
|
|
))}
|
|
|
|
</>
|
|
|
|
)}
|
2021-11-25 02:41:03 -06:00
|
|
|
<CardButton icon="table" onClick={switchToTable}>
|
|
|
|
Switch to table
|
|
|
|
</CardButton>
|
|
|
|
<CardButton icon="chart-line" onClick={openVizPicker}>
|
|
|
|
Open visualization suggestions
|
|
|
|
</CardButton>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function getMessageFor(
|
2022-04-14 03:10:03 -05:00
|
|
|
{ data, fieldConfig, message, needsNumberField, needsTimeField, needsStringField }: PanelDataErrorViewProps,
|
2021-11-25 02:41:03 -06:00
|
|
|
dataSummary: PanelDataSummary
|
|
|
|
): string {
|
|
|
|
if (message) {
|
|
|
|
return message;
|
|
|
|
}
|
|
|
|
|
2023-03-28 17:55:13 -05:00
|
|
|
if (!data.series || data.series.length === 0 || data.series.every((frame) => frame.length === 0)) {
|
2022-04-14 03:10:03 -05:00
|
|
|
return fieldConfig?.defaults.noValue ?? 'No data';
|
2021-11-25 02:41:03 -06:00
|
|
|
}
|
|
|
|
|
2021-12-08 04:45:56 -06:00
|
|
|
if (needsStringField && !dataSummary.hasStringField) {
|
|
|
|
return 'Data is missing a string field';
|
|
|
|
}
|
|
|
|
|
2021-11-25 02:41:03 -06:00
|
|
|
if (needsNumberField && !dataSummary.hasNumberField) {
|
|
|
|
return 'Data is missing a number field';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (needsTimeField && !dataSummary.hasTimeField) {
|
|
|
|
return 'Data is missing a time field';
|
|
|
|
}
|
|
|
|
|
|
|
|
return 'Cannot visualize data';
|
|
|
|
}
|
|
|
|
|
|
|
|
const getStyles = (theme: GrafanaTheme2) => {
|
|
|
|
return {
|
|
|
|
wrapper: css({
|
|
|
|
display: 'flex',
|
|
|
|
flexDirection: 'column',
|
|
|
|
justifyContent: 'center',
|
|
|
|
alignItems: 'center',
|
|
|
|
height: '100%',
|
|
|
|
width: '100%',
|
|
|
|
}),
|
|
|
|
message: css({
|
|
|
|
textAlign: 'center',
|
|
|
|
color: theme.colors.text.secondary,
|
|
|
|
fontSize: theme.typography.size.lg,
|
|
|
|
width: '100%',
|
|
|
|
}),
|
|
|
|
actions: css({
|
|
|
|
marginTop: theme.spacing(2),
|
|
|
|
display: 'flex',
|
|
|
|
height: '50%',
|
|
|
|
maxHeight: '150px',
|
|
|
|
columnGap: theme.spacing(1),
|
|
|
|
rowGap: theme.spacing(1),
|
|
|
|
width: '100%',
|
|
|
|
maxWidth: '600px',
|
|
|
|
}),
|
|
|
|
};
|
|
|
|
};
|