Datatrails: hide empty breakdown panels, improve loading status (#85028)

feat: hide empty breakdown panels, improve loading status
This commit is contained in:
Darren Janeczek
2024-03-25 18:56:03 -04:00
committed by GitHub
parent d4e6b325bf
commit 7c03cc83c6
3 changed files with 75 additions and 57 deletions
@@ -4,7 +4,6 @@ import React from 'react';
import { DataFrame, FieldType, GrafanaTheme2, PanelData, SelectableValue } from '@grafana/data';
import {
FieldConfigBuilders,
PanelBuilders,
QueryVariable,
SceneComponentProps,
@@ -299,7 +298,6 @@ export function buildAllLayout(options: Array<SelectableValue<string>>, queryDef
})
);
}
return new LayoutSwitcher({
options: [
{ value: 'grid', label: 'Grid' },
@@ -328,6 +326,32 @@ const GRID_TEMPLATE_COLUMNS = 'repeat(auto-fit, minmax(400px, 1fr))';
function buildNormalLayout(queryDef: AutoQueryDef) {
const unit = queryDef.unit;
function getLayoutChild(data: PanelData, frame: DataFrame, frameIndex: number): SceneFlexItem {
const vizPanel: VizPanel = queryDef
.vizBuilder()
.setTitle(getLabelValue(frame))
.setData(new SceneDataNode({ data: { ...data, series: [frame] } }))
.setColor({ mode: 'fixed', fixedColor: getColorByIndex(frameIndex) })
.setHeaderActions(new AddToFiltersGraphAction({ frame }))
.setUnit(unit)
.build();
// Find a frame that has at more than one point.
const isHidden = frame.length <= 1;
const item: SceneCSSGridItem = new SceneCSSGridItem({
$behaviors: [yAxisSyncBehavior],
body: vizPanel,
isHidden,
});
vizPanel.addActivationHandler(() => {
vizPanel.onOptionsChange(breakdownPanelOptions);
});
return item;
}
return new LayoutSwitcher({
$data: new SceneQueryRunner({
datasource: trailDS,
@@ -355,29 +379,7 @@ function buildNormalLayout(queryDef: AutoQueryDef) {
autoRows: '200px',
children: [],
}),
getLayoutChild: (data, frame, frameIndex) => {
const vizPanel = queryDef
.vizBuilder()
.setTitle(getLabelValue(frame))
.setData(new SceneDataNode({ data: { ...data, series: [frame] } }))
.setColor({ mode: 'fixed', fixedColor: getColorByIndex(frameIndex) })
.setHeaderActions(new AddToFiltersGraphAction({ frame }))
.setUnit(unit)
.build();
if (vizPanel.isActive) {
vizPanel.onOptionsChange(breakdownPanelOptions);
} else {
vizPanel.addActivationHandler(() => {
vizPanel.onOptionsChange(breakdownPanelOptions);
});
}
return new SceneCSSGridItem({
$behaviors: [yAxisSyncBehavior],
body: vizPanel,
});
},
getLayoutChild,
}),
new ByFrameRepeater({
body: new SceneCSSGridLayout({
@@ -385,31 +387,7 @@ function buildNormalLayout(queryDef: AutoQueryDef) {
autoRows: '200px',
children: [],
}),
getLayoutChild: (data, frame, frameIndex) => {
const vizPanel: VizPanel = queryDef
.vizBuilder()
.setTitle(getLabelValue(frame))
.setData(new SceneDataNode({ data: { ...data, series: [frame] } }))
.setColor({ mode: 'fixed', fixedColor: getColorByIndex(frameIndex) })
.setHeaderActions(new AddToFiltersGraphAction({ frame }))
.setUnit(unit)
.build();
if (vizPanel.isActive) {
vizPanel.onOptionsChange(breakdownPanelOptions);
} else {
vizPanel.addActivationHandler(() => {
vizPanel.onOptionsChange(breakdownPanelOptions);
});
}
FieldConfigBuilders.timeseries().build();
return new SceneCSSGridItem({
$behaviors: [yAxisSyncBehavior],
body: vizPanel,
});
},
getLayoutChild,
}),
],
});
@@ -7,10 +7,14 @@ import {
SceneObjectBase,
sceneGraph,
SceneComponentProps,
SceneByFrameRepeater,
SceneLayout,
SceneDataNode,
} from '@grafana/scenes';
import { StatusWrapper } from '../StatusWrapper';
import { findSceneObjectsByType } from './utils';
interface ByFrameRepeaterState extends SceneObjectState {
body: SceneLayout;
getLayoutChild(data: PanelData, frame: DataFrame, frameIndex: number): SceneFlexItem;
@@ -24,9 +28,20 @@ export class ByFrameRepeater extends SceneObjectBase<ByFrameRepeaterState> {
const data = sceneGraph.getData(this);
this._subs.add(
data.subscribeToState((data) => {
if (data.data?.state === LoadingState.Done) {
this.performRepeat(data.data);
data.subscribeToState((newState, oldState) => {
if (newState.data === undefined) {
return;
}
const newData = newState.data;
if (newState.data !== undefined && newState.data?.state !== oldState.data?.state) {
findSceneObjectsByType(this, SceneDataNode).forEach((dataNode) => {
dataNode.setState({ data: { ...dataNode.state.data, state: newData.state } });
});
}
if (newData.state === LoadingState.Done) {
this.performRepeat(newData);
}
})
);
@@ -41,15 +56,39 @@ export class ByFrameRepeater extends SceneObjectBase<ByFrameRepeaterState> {
const newChildren: SceneFlexItem[] = [];
for (let seriesIndex = 0; seriesIndex < data.series.length; seriesIndex++) {
const layoutChild = this.state.getLayoutChild(data, data.series[seriesIndex], seriesIndex);
const frame = data.series[seriesIndex];
if (frame.length <= 1) {
// If the data doesn't have at least two points, we skip it.
continue;
}
const layoutChild = this.state.getLayoutChild(data, frame, seriesIndex);
newChildren.push(layoutChild);
}
this.state.body.setState({ children: newChildren });
this.setState({ body: this.state.body });
}
public static Component = ({ model }: SceneComponentProps<SceneByFrameRepeater>) => {
public static Component = ({ model }: SceneComponentProps<ByFrameRepeater>) => {
const { body } = model.useState();
return <body.Component model={body} />;
const { children } = body.useState();
const data = sceneGraph.getData(model);
const sceneDataState = data.useState();
const panelData = sceneDataState?.data;
const isLoading = panelData?.state === 'Loading' && children.length === 0;
const error = panelData?.state === LoadingState.Error ? 'Failed to load data.' : undefined;
const blockingMessage =
!isLoading && children.length === 0 && !error
? 'There is no data available. Try adjusting your filters, time range, or selecting a different label.'
: undefined;
return (
<StatusWrapper {...{ blockingMessage, error, isLoading }}>
<body.Component model={body} />
</StatusWrapper>
);
};
}
@@ -34,6 +34,7 @@ function getStyles(theme: GrafanaTheme2) {
fontStyle: 'italic',
marginTop: theme.spacing(7),
textAlign: 'center',
width: '100%',
}),
};
}