From e4276a4ede829cb2e30e6dd32a9dc4ca09845054 Mon Sep 17 00:00:00 2001 From: Haris Rozajac <58232930+harisrozajac@users.noreply.github.com> Date: Fri, 23 Feb 2024 15:03:35 -0700 Subject: [PATCH] Dashboard-Scene: Show empty state after removing last panel (#83114) * Show empty state after removing last panel * betterer * Refactor isEmpty state update in DashboardScene.tsx * don't need viewPanelScene check * track isEmpty through a behavior * Fix test * Add test for empty state * minor fix * Refactor isEmpty check * Don't use const * clean up --- .../pages/DashboardScenePage.test.tsx | 7 +++++++ .../dashboard-scene/scene/DashboardScene.tsx | 15 +-------------- .../scene/DashboardSceneRenderer.tsx | 14 ++++++-------- .../dashboard-scene/scene/PanelMenuBehavior.tsx | 4 +--- .../transformSaveModelToScene.test.ts | 2 +- .../serialization/transformSaveModelToScene.ts | 17 +++++++++++++++++ 6 files changed, 33 insertions(+), 26 deletions(-) diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePage.test.tsx b/public/app/features/dashboard-scene/pages/DashboardScenePage.test.tsx index 72107998d74..3033bd6390c 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePage.test.tsx +++ b/public/app/features/dashboard-scene/pages/DashboardScenePage.test.tsx @@ -194,6 +194,13 @@ describe('DashboardScenePage', () => { expect(screen.queryByTitle('Panel A')).not.toBeInTheDocument(); expect(await screen.findByTitle('Panel B')).toBeInTheDocument(); }); + + it('Shows empty state when dashboard is empty', async () => { + loadDashboardMock.mockResolvedValue({ dashboard: { panels: [] }, meta: {} }); + setup(); + + expect(await screen.findByText('Start your new dashboard by adding a visualization')).toBeInTheDocument(); + }); }); interface VizOptions { diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.tsx index 412448b1f58..a8b64713295 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.tsx @@ -94,6 +94,7 @@ export interface DashboardSceneState extends SceneObjectState { editPanel?: PanelEditor; /** Scene object that handles the current drawer or modal */ overlay?: SceneObject; + isEmpty?: boolean; } export class DashboardScene extends SceneObjectBase { @@ -522,20 +523,6 @@ export class DashboardScene extends SceneObjectBase { locationService.partial({ editview: 'settings' }); }; - public isEmpty = (): boolean => { - const { body, viewPanelScene } = this.state; - - if (!!viewPanelScene) { - return !!viewPanelScene.state.body; - } - - if (body instanceof SceneFlexLayout || body instanceof SceneGridLayout) { - return body.state.children.length === 0; - } - - throw new Error('Invalid body type'); - }; - /** * Called by the SceneQueryRunner to privide contextural parameters (tracking) props for the request */ diff --git a/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx b/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx index cd58d311fdd..17915583be9 100644 --- a/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx @@ -14,7 +14,7 @@ import { DashboardScene } from './DashboardScene'; import { NavToolbarActions } from './NavToolbarActions'; export function DashboardSceneRenderer({ model }: SceneComponentProps) { - const { controls, overlay, editview, editPanel } = model.useState(); + const { controls, overlay, editview, editPanel, isEmpty } = model.useState(); const styles = useStyles2(getStyles); const location = useLocation(); const navIndex = useSelector((state) => state.navIndex); @@ -34,12 +34,9 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps; const withPanels = ( - <> - {controls && } -
- -
- +
+ +
); return ( @@ -49,7 +46,8 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps
- {model.isEmpty() ? emptyState : withPanels} + {controls && } + {isEmpty ? emptyState : withPanels}
)} diff --git a/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx b/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx index c3b105fa186..f5b823d5e9e 100644 --- a/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx +++ b/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx @@ -417,9 +417,7 @@ export function removePanel(dashboard: DashboardScene, panel: VizPanel, ask: boo const layout = dashboard.state.body; if (layout instanceof SceneGridLayout || SceneFlexLayout) { - layout.setState({ - children: panels, - }); + layout.setState({ children: panels }); } } diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts index c43026dd293..3992895e622 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts @@ -140,7 +140,7 @@ describe('transformSaveModelToScene', () => { const scene = createDashboardSceneFromDashboardModel(oldModel); - expect(scene.state.$behaviors).toHaveLength(5); + expect(scene.state.$behaviors).toHaveLength(6); expect(scene.state.$behaviors![0]).toBeInstanceOf(behaviors.CursorSync); expect((scene.state.$behaviors![0] as behaviors.CursorSync).state.sync).toEqual(DashboardCursorSync.Crosshair); }); diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts index 611375cc10a..47d89943714 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts @@ -28,6 +28,7 @@ import { UserActionEvent, GroupByVariable, AdHocFiltersVariable, + SceneFlexLayout, } from '@grafana/scenes'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { trackDashboardLoaded } from 'app/features/dashboard/utils/tracking'; @@ -266,6 +267,7 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel) registerDashboardMacro, registerDashboardSceneTracking(oldModel), registerPanelInteractionsReporter, + trackIfIsEmpty, ], $data: layers.length > 0 @@ -535,6 +537,21 @@ function registerPanelInteractionsReporter(scene: DashboardScene) { }); } +export function trackIfIsEmpty(parent: DashboardScene) { + updateIsEmpty(parent); + + parent.state.body.subscribeToState(() => { + updateIsEmpty(parent); + }); +} + +function updateIsEmpty(parent: DashboardScene) { + const { body } = parent.state; + if (body instanceof SceneFlexLayout || body instanceof SceneGridLayout) { + parent.setState({ isEmpty: body.state.children.length === 0 }); + } +} + const convertSnapshotData = (snapshotData: DataFrameDTO[]): DataFrameJSON[] => { return snapshotData.map((data) => { return {