diff --git a/public/app/features/trails/ActionTabs/BreakdownScene.tsx b/public/app/features/trails/ActionTabs/BreakdownScene.tsx index b6652baaec5..d01c35c0262 100644 --- a/public/app/features/trails/ActionTabs/BreakdownScene.tsx +++ b/public/app/features/trails/ActionTabs/BreakdownScene.tsx @@ -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>, 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, }), ], }); diff --git a/public/app/features/trails/ActionTabs/ByFrameRepeater.tsx b/public/app/features/trails/ActionTabs/ByFrameRepeater.tsx index b11013fded4..34b1f13b02c 100644 --- a/public/app/features/trails/ActionTabs/ByFrameRepeater.tsx +++ b/public/app/features/trails/ActionTabs/ByFrameRepeater.tsx @@ -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 { 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 { 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) => { + public static Component = ({ model }: SceneComponentProps) => { const { body } = model.useState(); - return ; + 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 ( + + + + ); }; } diff --git a/public/app/features/trails/StatusWrapper.tsx b/public/app/features/trails/StatusWrapper.tsx index 372a558bed1..d9d3edc0804 100644 --- a/public/app/features/trails/StatusWrapper.tsx +++ b/public/app/features/trails/StatusWrapper.tsx @@ -34,6 +34,7 @@ function getStyles(theme: GrafanaTheme2) { fontStyle: 'italic', marginTop: theme.spacing(7), textAlign: 'center', + width: '100%', }), }; }