From 16f5220adcf7a432e7180e04a0aeba547aee9291 Mon Sep 17 00:00:00 2001 From: Haris Rozajac <58232930+harisrozajac@users.noreply.github.com> Date: Thu, 15 Feb 2024 09:40:58 -0700 Subject: [PATCH] DashboardScene: Empty dashboard state (#82338) * Organize * Refactor * Fix where settings were not showing up --- .../dashboard-scene/scene/DashboardScene.tsx | 32 ++++++++++++++++ .../scene/DashboardSceneRenderer.tsx | 38 +++++++++++++------ .../features/dashboard-scene/utils/utils.ts | 29 +++++++++++++- .../dashboard/dashgrid/DashboardEmpty.tsx | 32 +++++++++++----- 4 files changed, 109 insertions(+), 22 deletions(-) diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.tsx index 11402d317ae..701cdbfc709 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.tsx @@ -414,6 +414,24 @@ export class DashboardScene extends SceneObjectBase { return this._initialState; } + public addPanel(vizPanel: VizPanel): void { + // TODO: need logic for adding a panel when other panels exist + // This is the logic when dashboard is empty + this.setState({ + body: new SceneGridLayout({ + children: [ + new SceneGridItem({ + height: 10, + width: 10, + x: 0.2, + y: 0, + body: vizPanel, + }), + ], + }), + }); + } + public duplicatePanel(vizPanel: VizPanel) { if (!vizPanel.parent) { return; @@ -504,6 +522,20 @@ 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 919e1502dde..84950c6c999 100644 --- a/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx @@ -7,6 +7,7 @@ import { SceneComponentProps, SceneDebugger } from '@grafana/scenes'; import { CustomScrollbar, useStyles2 } from '@grafana/ui'; import { Page } from 'app/core/components/Page/Page'; import { getNavModel } from 'app/core/selectors/navModel'; +import DashboardEmpty from 'app/features/dashboard/dashgrid/DashboardEmpty'; import { useSelector } from 'app/types'; import { DashboardScene } from './DashboardScene'; @@ -31,6 +32,29 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps +
{showDebugger && }
+ + + ); + + const withPanels = ( + <> + {controls && ( +
+ {controls.map((control) => ( + + ))} + {showDebugger && } +
+ )} +
+ +
+ + ); + return ( {editPanel && } @@ -38,18 +62,7 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps
- - {controls && ( -
- {controls.map((control) => ( - - ))} - {showDebugger && } -
- )} -
- -
+ {model.isEmpty() ? emptyState : withPanels}
)} @@ -86,6 +99,7 @@ function getStyles(theme: GrafanaTheme2) { background: theme.colors.background.canvas, zIndex: theme.zIndex.activePanel, padding: theme.spacing(2, 0), + marginLeft: 'auto', }), }; } diff --git a/public/app/features/dashboard-scene/utils/utils.ts b/public/app/features/dashboard-scene/utils/utils.ts index 5e1f3074693..d964c15ca16 100644 --- a/public/app/features/dashboard-scene/utils/utils.ts +++ b/public/app/features/dashboard-scene/utils/utils.ts @@ -1,4 +1,5 @@ -import { IntervalVariableModel } from '@grafana/data'; +import { getDataSourceRef, IntervalVariableModel } from '@grafana/data'; +import { getDataSourceSrv } from '@grafana/runtime'; import { MultiValueVariable, SceneDataTransformer, @@ -6,10 +7,13 @@ import { SceneObject, SceneQueryRunner, VizPanel, + VizPanelMenu, } from '@grafana/scenes'; import { initialIntervalVariableModelState } from 'app/features/variables/interval/reducer'; import { DashboardScene } from '../scene/DashboardScene'; +import { VizPanelLinks, VizPanelLinksMenu } from '../scene/PanelLinks'; +import { panelMenuBehavior } from '../scene/PanelMenuBehavior'; export function getVizPanelKeyForPanelId(panelId: number) { return `panel-${panelId}`; @@ -187,3 +191,26 @@ export function getClosestVizPanel(sceneObject: SceneObject): VizPanel | null { export function isPanelClone(key: string) { return key.includes('clone'); } + +export function onCreateNewPanel(dashboard: DashboardScene): number { + const vizPanel = new VizPanel({ + title: 'Panel Title', + key: 'panel-1', // the first panel should always be panel-1 + pluginId: 'timeseries', + titleItems: [new VizPanelLinks({ menu: new VizPanelLinksMenu({}) })], + menu: new VizPanelMenu({ + $behaviors: [panelMenuBehavior], + }), + $data: new SceneDataTransformer({ + $data: new SceneQueryRunner({ + queries: [{ refId: 'A' }], + datasource: getDataSourceRef(getDataSourceSrv().getInstanceSettings(null)!), + }), + transformations: [], + }), + }); + dashboard.addPanel(vizPanel); + const id = getPanelIdForVizPanel(vizPanel); + + return id; +} diff --git a/public/app/features/dashboard/dashgrid/DashboardEmpty.tsx b/public/app/features/dashboard/dashgrid/DashboardEmpty.tsx index 341ef3c447f..3ac12389b66 100644 --- a/public/app/features/dashboard/dashgrid/DashboardEmpty.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardEmpty.tsx @@ -8,13 +8,15 @@ import { Button, useStyles2, Text, Box, Stack } from '@grafana/ui'; import { Trans } from 'app/core/internationalization'; import { DashboardModel } from 'app/features/dashboard/state'; import { onAddLibraryPanel, onCreateNewPanel, onImportDashboard } from 'app/features/dashboard/utils/dashboard'; +import { DashboardScene } from 'app/features/dashboard-scene/scene/DashboardScene'; import { DashboardInteractions } from 'app/features/dashboard-scene/utils/interactions'; +import { onCreateNewPanel as onCreateNewPanelScene } from 'app/features/dashboard-scene/utils/utils'; import { useDispatch, useSelector } from 'app/types'; import { setInitialDatasource } from '../state/reducers'; export interface Props { - dashboard: DashboardModel; + dashboard: DashboardModel | DashboardScene; canCreate: boolean; } @@ -22,6 +24,20 @@ const DashboardEmpty = ({ dashboard, canCreate }: Props) => { const styles = useStyles2(getStyles); const dispatch = useDispatch(); const initialDatasource = useSelector((state) => state.dashboard.initialDatasource); + const isDashboardScene = dashboard instanceof DashboardScene; + + const onAddVisualization = () => { + let id; + if (isDashboardScene) { + id = onCreateNewPanelScene(dashboard); + } else { + id = onCreateNewPanel(dashboard, initialDatasource); + dispatch(setInitialDatasource(undefined)); + } + + locationService.partial({ editPanel: id, firstPanel: true }); + DashboardInteractions.emptyDashboardButtonClicked({ item: 'add_visualization' }); + }; return ( @@ -46,13 +62,7 @@ const DashboardEmpty = ({ dashboard, canCreate }: Props) => { size="lg" icon="plus" data-testid={selectors.pages.AddDashboard.itemButton('Create new panel button')} - onClick={() => { - const id = onCreateNewPanel(dashboard, initialDatasource); - DashboardInteractions.emptyDashboardButtonClicked({ item: 'add_visualization' }); - - locationService.partial({ editPanel: id, firstPanel: true }); - dispatch(setInitialDatasource(undefined)); - }} + onClick={onAddVisualization} disabled={!canCreate} > Add visualization @@ -104,7 +114,11 @@ const DashboardEmpty = ({ dashboard, canCreate }: Props) => { data-testid={selectors.pages.AddDashboard.itemButton('Add a panel from the panel library button')} onClick={() => { DashboardInteractions.emptyDashboardButtonClicked({ item: 'import_from_library' }); - onAddLibraryPanel(dashboard); + if (isDashboardScene) { + // TODO: dashboard scene logic for adding a library panel + } else { + onAddLibraryPanel(dashboard); + } }} disabled={!canCreate} >