mirror of
https://github.com/grafana/grafana.git
synced 2026-08-18 09:05:04 -05:00
DashboardScene: Fix issues with dashboard empty state (#85406)
Fix Tests Make sure edit mode is on when adding panel/library panel Co-authored-by: kay delaney <kay@grafana.com>
This commit is contained in:
co-authored by
kay delaney
parent
368fec9b97
commit
fa9e139123
@@ -8,10 +8,13 @@ import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
|
|||||||
import { PanelProps } from '@grafana/data';
|
import { PanelProps } from '@grafana/data';
|
||||||
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
|
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
|
||||||
import { config, getPluginLinkExtensions, locationService, setPluginImportUtils } from '@grafana/runtime';
|
import { config, getPluginLinkExtensions, locationService, setPluginImportUtils } from '@grafana/runtime';
|
||||||
|
import { VizPanel } from '@grafana/scenes';
|
||||||
import { Dashboard } from '@grafana/schema';
|
import { Dashboard } from '@grafana/schema';
|
||||||
import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps';
|
import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps';
|
||||||
import { DashboardLoaderSrv, setDashboardLoaderSrv } from 'app/features/dashboard/services/DashboardLoaderSrv';
|
import { DashboardLoaderSrv, setDashboardLoaderSrv } from 'app/features/dashboard/services/DashboardLoaderSrv';
|
||||||
|
|
||||||
|
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
|
||||||
|
|
||||||
import { DashboardScenePage, Props } from './DashboardScenePage';
|
import { DashboardScenePage, Props } from './DashboardScenePage';
|
||||||
import { getDashboardScenePageStateManager } from './DashboardScenePageStateManager';
|
import { getDashboardScenePageStateManager } from './DashboardScenePageStateManager';
|
||||||
|
|
||||||
@@ -195,12 +198,44 @@ describe('DashboardScenePage', () => {
|
|||||||
expect(await screen.findByTitle('Panel B')).toBeInTheDocument();
|
expect(await screen.findByTitle('Panel B')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('empty state', () => {
|
||||||
it('Shows empty state when dashboard is empty', async () => {
|
it('Shows empty state when dashboard is empty', async () => {
|
||||||
loadDashboardMock.mockResolvedValue({ dashboard: { panels: [] }, meta: {} });
|
loadDashboardMock.mockResolvedValue({ dashboard: { panels: [] }, meta: {} });
|
||||||
setup();
|
setup();
|
||||||
|
|
||||||
expect(await screen.findByText('Start your new dashboard by adding a visualization')).toBeInTheDocument();
|
expect(await screen.findByText('Start your new dashboard by adding a visualization')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('shows and hides empty state when panels are added and removed', async () => {
|
||||||
|
setup();
|
||||||
|
|
||||||
|
await waitForDashbordToRender();
|
||||||
|
|
||||||
|
expect(await screen.queryByText('Start your new dashboard by adding a visualization')).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
// Hacking a bit, accessing private cache property to get access to the underlying DashboardScene object
|
||||||
|
const dashboardScenesCache = getDashboardScenePageStateManager()['cache'];
|
||||||
|
const dashboard = dashboardScenesCache['my-dash-uid'];
|
||||||
|
const panels = dashboardSceneGraph.getVizPanels(dashboard);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
dashboard.removePanel(panels[0]);
|
||||||
|
});
|
||||||
|
expect(await screen.queryByText('Start your new dashboard by adding a visualization')).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
dashboard.removePanel(panels[1]);
|
||||||
|
});
|
||||||
|
expect(await screen.findByText('Start your new dashboard by adding a visualization')).toBeInTheDocument();
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
dashboard.addPanel(new VizPanel({ title: 'Panel Added', key: 'panel-4', pluginId: 'timeseries' }));
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(await screen.findByTitle('Panel Added')).toBeInTheDocument();
|
||||||
|
expect(await screen.queryByText('Start your new dashboard by adding a visualization')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
interface VizOptions {
|
interface VizOptions {
|
||||||
|
|||||||
@@ -283,11 +283,15 @@ describe('DashboardScene', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Should create and add a new panel to the dashboard', () => {
|
it('Should create and add a new panel to the dashboard', () => {
|
||||||
|
scene.exitEditMode({ skipConfirm: true });
|
||||||
|
expect(scene.state.isEditing).toBe(false);
|
||||||
|
|
||||||
scene.onCreateNewPanel();
|
scene.onCreateNewPanel();
|
||||||
|
|
||||||
const body = scene.state.body as SceneGridLayout;
|
const body = scene.state.body as SceneGridLayout;
|
||||||
const gridItem = body.state.children[0] as DashboardGridItem;
|
const gridItem = body.state.children[0] as DashboardGridItem;
|
||||||
|
|
||||||
|
expect(scene.state.isEditing).toBe(true);
|
||||||
expect(body.state.children.length).toBe(6);
|
expect(body.state.children.length).toBe(6);
|
||||||
expect(gridItem.state.body!.state.key).toBe('panel-7');
|
expect(gridItem.state.body!.state.key).toBe('panel-7');
|
||||||
});
|
});
|
||||||
@@ -298,6 +302,7 @@ describe('DashboardScene', () => {
|
|||||||
const body = scene.state.body as SceneGridLayout;
|
const body = scene.state.body as SceneGridLayout;
|
||||||
const gridRow = body.state.children[0] as SceneGridRow;
|
const gridRow = body.state.children[0] as SceneGridRow;
|
||||||
|
|
||||||
|
expect(scene.state.isEditing).toBe(true);
|
||||||
expect(body.state.children.length).toBe(4);
|
expect(body.state.children.length).toBe(4);
|
||||||
expect(gridRow.state.key).toBe('panel-7');
|
expect(gridRow.state.key).toBe('panel-7');
|
||||||
expect(gridRow.state.children[0].state.key).toBe('griditem-1');
|
expect(gridRow.state.children[0].state.key).toBe('griditem-1');
|
||||||
@@ -509,11 +514,15 @@ describe('DashboardScene', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Should create a new add library panel widget', () => {
|
it('Should create a new add library panel widget', () => {
|
||||||
|
scene.exitEditMode({ skipConfirm: true });
|
||||||
|
expect(scene.state.isEditing).toBe(false);
|
||||||
|
|
||||||
scene.onCreateLibPanelWidget();
|
scene.onCreateLibPanelWidget();
|
||||||
|
|
||||||
const body = scene.state.body as SceneGridLayout;
|
const body = scene.state.body as SceneGridLayout;
|
||||||
const gridItem = body.state.children[0] as DashboardGridItem;
|
const gridItem = body.state.children[0] as DashboardGridItem;
|
||||||
|
|
||||||
|
expect(scene.state.isEditing).toBe(true);
|
||||||
expect(body.state.children.length).toBe(6);
|
expect(body.state.children.length).toBe(6);
|
||||||
expect(gridItem.state.body!.state.key).toBe('panel-7');
|
expect(gridItem.state.body!.state.key).toBe('panel-7');
|
||||||
expect(gridItem.state.y).toBe(0);
|
expect(gridItem.state.y).toBe(0);
|
||||||
|
|||||||
@@ -740,6 +740,10 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
|||||||
throw new Error('Trying to add a panel in a layout that is not SceneGridLayout');
|
throw new Error('Trying to add a panel in a layout that is not SceneGridLayout');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.state.isEditing) {
|
||||||
|
this.onEnterEditMode();
|
||||||
|
}
|
||||||
|
|
||||||
const sceneGridLayout = this.state.body;
|
const sceneGridLayout = this.state.body;
|
||||||
|
|
||||||
const panelId = dashboardSceneGraph.getNextPanelId(this);
|
const panelId = dashboardSceneGraph.getNextPanelId(this);
|
||||||
@@ -767,6 +771,10 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onCreateNewPanel(): number {
|
public onCreateNewPanel(): number {
|
||||||
|
if (!this.state.isEditing) {
|
||||||
|
this.onEnterEditMode();
|
||||||
|
}
|
||||||
|
|
||||||
const vizPanel = getDefaultVizPanel(this);
|
const vizPanel = getDefaultVizPanel(this);
|
||||||
|
|
||||||
this.addPanel(vizPanel);
|
this.addPanel(vizPanel);
|
||||||
|
|||||||
@@ -62,7 +62,10 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps<DashboardS
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<CustomScrollbar autoHeightMin={'100%'} className={styles.scrollbarContainer}>
|
<CustomScrollbar autoHeightMin={'100%'} className={styles.scrollbarContainer}>
|
||||||
<div className={styles.canvasContent}>{isEmpty ? emptyState : withPanels}</div>
|
<div className={styles.canvasContent}>
|
||||||
|
<>{isEmpty && emptyState}</>
|
||||||
|
{withPanels}
|
||||||
|
</div>
|
||||||
</CustomScrollbar>
|
</CustomScrollbar>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import {
|
|||||||
UserActionEvent,
|
UserActionEvent,
|
||||||
GroupByVariable,
|
GroupByVariable,
|
||||||
AdHocFiltersVariable,
|
AdHocFiltersVariable,
|
||||||
SceneFlexLayout,
|
|
||||||
} from '@grafana/scenes';
|
} from '@grafana/scenes';
|
||||||
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
||||||
import { trackDashboardLoaded } from 'app/features/dashboard/utils/tracking';
|
import { trackDashboardLoaded } from 'app/features/dashboard/utils/tracking';
|
||||||
@@ -52,6 +51,7 @@ import { createPanelDataProvider } from '../utils/createPanelDataProvider';
|
|||||||
import { DashboardInteractions } from '../utils/interactions';
|
import { DashboardInteractions } from '../utils/interactions';
|
||||||
import {
|
import {
|
||||||
getCurrentValueForOldIntervalModel,
|
getCurrentValueForOldIntervalModel,
|
||||||
|
getDashboardSceneFor,
|
||||||
getIntervalsFromQueryString,
|
getIntervalsFromQueryString,
|
||||||
getVizPanelKeyForPanelId,
|
getVizPanelKeyForPanelId,
|
||||||
} from '../utils/utils';
|
} from '../utils/utils';
|
||||||
@@ -285,6 +285,7 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel)
|
|||||||
body: new SceneGridLayout({
|
body: new SceneGridLayout({
|
||||||
isLazy: true,
|
isLazy: true,
|
||||||
children: createSceneObjectsForPanels(oldModel.panels),
|
children: createSceneObjectsForPanels(oldModel.panels),
|
||||||
|
$behaviors: [trackIfEmpty],
|
||||||
}),
|
}),
|
||||||
$timeRange: new SceneTimeRange({
|
$timeRange: new SceneTimeRange({
|
||||||
from: oldModel.time.from,
|
from: oldModel.time.from,
|
||||||
@@ -303,7 +304,6 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel)
|
|||||||
registerDashboardMacro,
|
registerDashboardMacro,
|
||||||
registerDashboardSceneTracking(oldModel),
|
registerDashboardSceneTracking(oldModel),
|
||||||
registerPanelInteractionsReporter,
|
registerPanelInteractionsReporter,
|
||||||
trackIfIsEmpty,
|
|
||||||
new behaviors.LiveNowTimer(oldModel.liveNow),
|
new behaviors.LiveNowTimer(oldModel.liveNow),
|
||||||
],
|
],
|
||||||
$data: new DashboardDataLayerSet({ annotationLayers, alertStatesLayer }),
|
$data: new DashboardDataLayerSet({ annotationLayers, alertStatesLayer }),
|
||||||
@@ -570,21 +570,6 @@ 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[] => {
|
const convertSnapshotData = (snapshotData: DataFrameDTO[]): DataFrameJSON[] => {
|
||||||
return snapshotData.map((data) => {
|
return snapshotData.map((data) => {
|
||||||
return {
|
return {
|
||||||
@@ -619,3 +604,17 @@ export const convertOldSnapshotToScenesSnapshot = (panel: PanelModel) => {
|
|||||||
panel.snapshotData = [];
|
panel.snapshotData = [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function trackIfEmpty(grid: SceneGridLayout) {
|
||||||
|
getDashboardSceneFor(grid).setState({ isEmpty: grid.state.children.length === 0 });
|
||||||
|
|
||||||
|
const sub = grid.subscribeToState((n, p) => {
|
||||||
|
if (n.children.length !== p.children.length || n.children !== p.children) {
|
||||||
|
getDashboardSceneFor(grid).setState({ isEmpty: n.children.length === 0 });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
sub.unsubscribe();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,7 +7,11 @@ import { config, locationService } from '@grafana/runtime';
|
|||||||
import { Button, useStyles2, Text, Box, Stack } from '@grafana/ui';
|
import { Button, useStyles2, Text, Box, Stack } from '@grafana/ui';
|
||||||
import { Trans } from 'app/core/internationalization';
|
import { Trans } from 'app/core/internationalization';
|
||||||
import { DashboardModel } from 'app/features/dashboard/state';
|
import { DashboardModel } from 'app/features/dashboard/state';
|
||||||
import { onAddLibraryPanel, onCreateNewPanel, onImportDashboard } from 'app/features/dashboard/utils/dashboard';
|
import {
|
||||||
|
onAddLibraryPanel as onAddLibraryPanelImpl,
|
||||||
|
onCreateNewPanel,
|
||||||
|
onImportDashboard,
|
||||||
|
} from 'app/features/dashboard/utils/dashboard';
|
||||||
import { DashboardScene } from 'app/features/dashboard-scene/scene/DashboardScene';
|
import { DashboardScene } from 'app/features/dashboard-scene/scene/DashboardScene';
|
||||||
import { DashboardInteractions } from 'app/features/dashboard-scene/utils/interactions';
|
import { DashboardInteractions } from 'app/features/dashboard-scene/utils/interactions';
|
||||||
import { useDispatch, useSelector } from 'app/types';
|
import { useDispatch, useSelector } from 'app/types';
|
||||||
@@ -37,6 +41,15 @@ const DashboardEmpty = ({ dashboard, canCreate }: Props) => {
|
|||||||
DashboardInteractions.emptyDashboardButtonClicked({ item: 'add_visualization' });
|
DashboardInteractions.emptyDashboardButtonClicked({ item: 'add_visualization' });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onAddLibraryPanel = () => {
|
||||||
|
DashboardInteractions.emptyDashboardButtonClicked({ item: 'import_from_library' });
|
||||||
|
if (dashboard instanceof DashboardScene) {
|
||||||
|
dashboard.onCreateLibPanelWidget();
|
||||||
|
} else {
|
||||||
|
onAddLibraryPanelImpl(dashboard);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack alignItems="center" justifyContent="center">
|
<Stack alignItems="center" justifyContent="center">
|
||||||
<div className={styles.wrapper}>
|
<div className={styles.wrapper}>
|
||||||
@@ -110,14 +123,7 @@ const DashboardEmpty = ({ dashboard, canCreate }: Props) => {
|
|||||||
icon="plus"
|
icon="plus"
|
||||||
fill="outline"
|
fill="outline"
|
||||||
data-testid={selectors.pages.AddDashboard.itemButton('Add a panel from the panel library button')}
|
data-testid={selectors.pages.AddDashboard.itemButton('Add a panel from the panel library button')}
|
||||||
onClick={() => {
|
onClick={onAddLibraryPanel}
|
||||||
DashboardInteractions.emptyDashboardButtonClicked({ item: 'import_from_library' });
|
|
||||||
if (dashboard instanceof DashboardScene) {
|
|
||||||
dashboard.onCreateLibPanelWidget();
|
|
||||||
} else {
|
|
||||||
onAddLibraryPanel(dashboard);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
disabled={!canCreate}
|
disabled={!canCreate}
|
||||||
>
|
>
|
||||||
<Trans i18nKey="dashboard.empty.add-library-panel-button">Add library panel</Trans>
|
<Trans i18nKey="dashboard.empty.add-library-panel-button">Add library panel</Trans>
|
||||||
|
|||||||
Reference in New Issue
Block a user