DashboardScene: Empty dashboard state (#82338)

* Organize

* Refactor

* Fix where settings were not showing up
This commit is contained in:
Haris Rozajac 2024-02-15 09:40:58 -07:00 committed by GitHub
parent f4d81a8480
commit 16f5220adc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 109 additions and 22 deletions

View File

@ -414,6 +414,24 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
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<DashboardSceneState> {
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
*/

View File

@ -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<DashboardS
);
}
const emptyState = (
<>
<div className={styles.controls}>{showDebugger && <SceneDebugger scene={model} key={'scene-debugger'} />}</div>
<DashboardEmpty dashboard={model} canCreate={!!model.state.meta.canEdit} />
</>
);
const withPanels = (
<>
{controls && (
<div className={styles.controls}>
{controls.map((control) => (
<control.Component key={control.state.key} model={control} />
))}
{showDebugger && <SceneDebugger scene={model} key={'scene-debugger'} />}
</div>
)}
<div className={cx(styles.body)}>
<bodyToRender.Component model={bodyToRender} />
</div>
</>
);
return (
<Page navModel={navModel} pageNav={pageNav} layout={PageLayoutType.Custom}>
{editPanel && <editPanel.Component model={editPanel} />}
@ -38,18 +62,7 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps<DashboardS
<CustomScrollbar autoHeightMin={'100%'}>
<div className={styles.canvasContent}>
<NavToolbarActions dashboard={model} />
{controls && (
<div className={styles.controls}>
{controls.map((control) => (
<control.Component key={control.state.key} model={control} />
))}
{showDebugger && <SceneDebugger scene={model} key={'scene-debugger'} />}
</div>
)}
<div className={cx(styles.body)}>
<bodyToRender.Component model={bodyToRender} />
</div>
{model.isEmpty() ? emptyState : withPanels}
</div>
</CustomScrollbar>
)}
@ -86,6 +99,7 @@ function getStyles(theme: GrafanaTheme2) {
background: theme.colors.background.canvas,
zIndex: theme.zIndex.activePanel,
padding: theme.spacing(2, 0),
marginLeft: 'auto',
}),
};
}

View File

@ -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;
}

View File

@ -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 (
<Stack alignItems="center" justifyContent="center">
@ -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}
>
<Trans i18nKey="dashboard.empty.add-visualization-button">Add visualization</Trans>
@ -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}
>