diff --git a/.betterer.results b/.betterer.results
index 3ec225ceb44..a6c0daa79b8 100644
--- a/.betterer.results
+++ b/.betterer.results
@@ -2785,6 +2785,10 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings. Wrap text with ", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "1"]
],
+ "public/app/features/dashboard-scene/scene/types.ts:5381": [
+ [0, 0, 0, "Unexpected any. Specify a different type.", "0"],
+ [0, 0, 0, "Unexpected any. Specify a different type.", "1"]
+ ],
"public/app/features/dashboard-scene/serialization/angularMigration.test.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
diff --git a/public/app/features/dashboard-scene/inspect/HelpWizard/HelpWizard.test.tsx b/public/app/features/dashboard-scene/inspect/HelpWizard/HelpWizard.test.tsx
index e217fbd6d35..da8dd9998f7 100644
--- a/public/app/features/dashboard-scene/inspect/HelpWizard/HelpWizard.test.tsx
+++ b/public/app/features/dashboard-scene/inspect/HelpWizard/HelpWizard.test.tsx
@@ -2,12 +2,12 @@ import { render, screen } from '@testing-library/react';
import { FieldType, getDefaultTimeRange, LoadingState, toDataFrame } from '@grafana/data';
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
-import { SceneGridLayout, SceneQueryRunner, SceneTimeRange, VizPanel, VizPanelMenu } from '@grafana/scenes';
+import { SceneQueryRunner, SceneTimeRange, VizPanel, VizPanelMenu } from '@grafana/scenes';
-import { DashboardGridItem } from '../../scene/DashboardGridItem';
import { DashboardScene } from '../../scene/DashboardScene';
import { VizPanelLinks, VizPanelLinksMenu } from '../../scene/PanelLinks';
import { panelMenuBehavior } from '../../scene/PanelMenuBehavior';
+import { DefaultGridLayoutManager } from '../../scene/layout-default/DefaultGridLayoutManager';
import { HelpWizard } from './HelpWizard';
@@ -67,18 +67,7 @@ async function buildTestScene() {
canEdit: true,
isEmbedded: false,
},
- body: new SceneGridLayout({
- children: [
- new DashboardGridItem({
- key: 'griditem-1',
- x: 0,
- y: 0,
- width: 10,
- height: 12,
- body: panel,
- }),
- ],
- }),
+ body: DefaultGridLayoutManager.fromVizPanels([panel]),
});
await new Promise((r) => setTimeout(r, 1));
diff --git a/public/app/features/dashboard-scene/inspect/HelpWizard/SupportSnapshotService.test.ts b/public/app/features/dashboard-scene/inspect/HelpWizard/SupportSnapshotService.test.ts
index 4d1fc427cbd..4ecf7826ae4 100644
--- a/public/app/features/dashboard-scene/inspect/HelpWizard/SupportSnapshotService.test.ts
+++ b/public/app/features/dashboard-scene/inspect/HelpWizard/SupportSnapshotService.test.ts
@@ -1,10 +1,10 @@
import { FieldType, getDefaultTimeRange, LoadingState, toDataFrame } from '@grafana/data';
-import { SceneGridLayout, SceneQueryRunner, SceneTimeRange, VizPanel, VizPanelMenu } from '@grafana/scenes';
+import { SceneQueryRunner, SceneTimeRange, VizPanel, VizPanelMenu } from '@grafana/scenes';
-import { DashboardGridItem } from '../../scene/DashboardGridItem';
import { DashboardScene } from '../../scene/DashboardScene';
import { VizPanelLinks, VizPanelLinksMenu } from '../../scene/PanelLinks';
import { panelMenuBehavior } from '../../scene/PanelMenuBehavior';
+import { DefaultGridLayoutManager } from '../../scene/layout-default/DefaultGridLayoutManager';
import { SnapshotTab, SupportSnapshotService } from './SupportSnapshotService';
@@ -114,18 +114,7 @@ async function buildTestScene() {
canEdit: true,
isEmbedded: false,
},
- body: new SceneGridLayout({
- children: [
- new DashboardGridItem({
- key: 'griditem-1',
- x: 0,
- y: 0,
- width: 10,
- height: 12,
- body: panel,
- }),
- ],
- }),
+ body: DefaultGridLayoutManager.fromVizPanels([panel]),
});
await new Promise((r) => setTimeout(r, 1));
diff --git a/public/app/features/dashboard-scene/inspect/InspectJsonTab.test.tsx b/public/app/features/dashboard-scene/inspect/InspectJsonTab.test.tsx
index 84e7df9aa7c..ed28bd2c630 100644
--- a/public/app/features/dashboard-scene/inspect/InspectJsonTab.test.tsx
+++ b/public/app/features/dashboard-scene/inspect/InspectJsonTab.test.tsx
@@ -12,7 +12,7 @@ import {
} from '@grafana/data';
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
import { setPluginImportUtils, setRunRequest } from '@grafana/runtime';
-import { SceneCanvasText, SceneDataTransformer, SceneGridLayout, SceneQueryRunner, VizPanel } from '@grafana/scenes';
+import { SceneCanvasText, SceneDataTransformer, SceneQueryRunner, VizPanel } from '@grafana/scenes';
import * as libpanels from 'app/features/library-panels/state/api';
import { getStandardTransformers } from 'app/features/transformers/standardTransformers';
@@ -20,6 +20,7 @@ import { DashboardGridItem } from '../scene/DashboardGridItem';
import { DashboardScene } from '../scene/DashboardScene';
import { LibraryPanelBehavior } from '../scene/LibraryPanelBehavior';
import { VizPanelLinks, VizPanelLinksMenu } from '../scene/PanelLinks';
+import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLayoutManager';
import { vizPanelToPanel } from '../serialization/transformSceneToSaveModel';
import { activateFullSceneTree } from '../utils/test-utils';
import { findVizPanelByKey } from '../utils/utils';
@@ -106,7 +107,7 @@ describe('InspectJsonTab', () => {
const { tab } = await buildTestScene();
const obj = JSON.parse(tab.state.jsonText);
- expect(obj.gridPos).toEqual({ x: 0, y: 0, w: 10, h: 12 });
+ expect(obj.gridPos).toEqual({ x: 0, y: 0, w: 8, h: 10 });
expect(tab.isEditable()).toBe(true);
});
@@ -114,7 +115,7 @@ describe('InspectJsonTab', () => {
const { tab } = await buildTestSceneWithLibraryPanel();
const obj = JSON.parse(tab.state.jsonText);
- expect(obj.gridPos).toEqual({ x: 0, y: 0, w: 10, h: 12 });
+ expect(obj.gridPos).toEqual({ x: 0, y: 0, w: 8, h: 10 });
expect(obj.type).toEqual('table');
expect(tab.isEditable()).toBe(false);
});
@@ -202,18 +203,7 @@ async function buildTestScene() {
meta: {
canEdit: true,
},
- body: new SceneGridLayout({
- children: [
- new DashboardGridItem({
- key: 'griditem-1',
- x: 0,
- y: 0,
- width: 10,
- height: 12,
- body: panel,
- }),
- ],
- }),
+ body: DefaultGridLayoutManager.fromVizPanels([panel]),
});
activateFullSceneTree(scene);
@@ -265,24 +255,13 @@ async function buildTestSceneWithLibraryPanel() {
jest.spyOn(libpanels, 'getLibraryPanel').mockResolvedValue({ ...libraryPanelState, ...panel });
- const gridItem = new DashboardGridItem({
- key: 'griditem-1',
- x: 0,
- y: 0,
- width: 10,
- height: 12,
- body: libraryPanel,
- });
-
const scene = new DashboardScene({
title: 'hello',
uid: 'dash-1',
meta: {
canEdit: true,
},
- body: new SceneGridLayout({
- children: [gridItem],
- }),
+ body: DefaultGridLayoutManager.fromVizPanels([libraryPanel]),
});
activateFullSceneTree(scene);
diff --git a/public/app/features/dashboard-scene/panel-edit/PanelEditor.test.ts b/public/app/features/dashboard-scene/panel-edit/PanelEditor.test.ts
index 8117146a4fc..78370296e0a 100644
--- a/public/app/features/dashboard-scene/panel-edit/PanelEditor.test.ts
+++ b/public/app/features/dashboard-scene/panel-edit/PanelEditor.test.ts
@@ -21,6 +21,7 @@ import * as libAPI from 'app/features/library-panels/state/api';
import { DashboardGridItem } from '../scene/DashboardGridItem';
import { DashboardScene } from '../scene/DashboardScene';
import { LibraryPanelBehavior } from '../scene/LibraryPanelBehavior';
+import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLayoutManager';
import { vizPanelToPanel } from '../serialization/transformSceneToSaveModel';
import { activateFullSceneTree } from '../utils/test-utils';
import { findVizPanelByKey, getQueryRunnerFor } from '../utils/utils';
@@ -124,7 +125,8 @@ describe('PanelEditor', () => {
const { panelEditor, dashboard } = await setup({ isNewPanel: true });
panelEditor.onDiscard();
- expect((dashboard.state.body as SceneGridLayout).state.children.length).toBe(0);
+ const panels = dashboard.state.body.getVizPanels();
+ expect(panels.length).toBe(0);
});
it('should discard query runner changes', async () => {
@@ -212,8 +214,10 @@ describe('PanelEditor', () => {
editPanel: editScene,
$timeRange: new SceneTimeRange({ from: 'now-6h', to: 'now' }),
isEditing: true,
- body: new SceneGridLayout({
- children: [gridItem],
+ body: new DefaultGridLayoutManager({
+ grid: new SceneGridLayout({
+ children: [gridItem],
+ }),
}),
});
@@ -332,8 +336,10 @@ async function setup(options: SetupOptions = {}) {
}),
],
}),
- body: new SceneGridLayout({
- children: [gridItem],
+ body: new DefaultGridLayoutManager({
+ grid: new SceneGridLayout({
+ children: [gridItem],
+ }),
}),
});
diff --git a/public/app/features/dashboard-scene/saving/DashboardPrompt.test.tsx b/public/app/features/dashboard-scene/saving/DashboardPrompt.test.tsx
index 76dc822344f..ac2014b6fd5 100644
--- a/public/app/features/dashboard-scene/saving/DashboardPrompt.test.tsx
+++ b/public/app/features/dashboard-scene/saving/DashboardPrompt.test.tsx
@@ -1,9 +1,9 @@
-import { SceneGridLayout, SceneQueryRunner, SceneTimeRange, VizPanel, behaviors } from '@grafana/scenes';
+import { SceneQueryRunner, SceneTimeRange, VizPanel, behaviors } from '@grafana/scenes';
import { ContextSrv, setContextSrv } from 'app/core/services/context_srv';
import { DashboardControls } from '../scene/DashboardControls';
-import { DashboardGridItem } from '../scene/DashboardGridItem';
import { DashboardScene, DashboardSceneState } from '../scene/DashboardScene';
+import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLayoutManager';
import { transformSceneToSaveModel } from '../serialization/transformSceneToSaveModel';
import { ignoreChanges } from './DashboardPrompt';
@@ -136,20 +136,14 @@ function buildTestScene(overrides?: Partial) {
}),
controls: new DashboardControls({}),
$behaviors: [new behaviors.CursorSync({})],
- body: new SceneGridLayout({
- children: [
- new DashboardGridItem({
- key: 'griditem-1',
- x: 0,
- body: new VizPanel({
- title: 'Panel A',
- key: 'panel-1',
- pluginId: 'table',
- $data: new SceneQueryRunner({ key: 'data-query-runner', queries: [{ refId: 'A' }] }),
- }),
- }),
- ],
- }),
+ body: DefaultGridLayoutManager.fromVizPanels([
+ new VizPanel({
+ title: 'Panel A',
+ key: 'panel-1',
+ pluginId: 'table',
+ $data: new SceneQueryRunner({ key: 'data-query-runner', queries: [{ refId: 'A' }] }),
+ }),
+ ]),
...overrides,
});
diff --git a/public/app/features/dashboard-scene/scene/AddLibraryPanelDrawer.test.tsx b/public/app/features/dashboard-scene/scene/AddLibraryPanelDrawer.test.tsx
index 90116dec207..f298e7b19f9 100644
--- a/public/app/features/dashboard-scene/scene/AddLibraryPanelDrawer.test.tsx
+++ b/public/app/features/dashboard-scene/scene/AddLibraryPanelDrawer.test.tsx
@@ -1,12 +1,12 @@
-import { SceneGridLayout, SceneGridRow, SceneTimeRange, VizPanel } from '@grafana/scenes';
+import { SceneTimeRange, VizPanel } from '@grafana/scenes';
import { LibraryPanel } from '@grafana/schema/dist/esm/index.gen';
import { activateFullSceneTree } from '../utils/test-utils';
import { AddLibraryPanelDrawer } from './AddLibraryPanelDrawer';
-import { DashboardGridItem } from './DashboardGridItem';
import { DashboardScene } from './DashboardScene';
import { LibraryPanelBehavior } from './LibraryPanelBehavior';
+import { DefaultGridLayoutManager } from './layout-default/DefaultGridLayoutManager';
jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'),
@@ -41,13 +41,12 @@ describe('AddLibraryPanelWidget', () => {
addLibPanelDrawer.onAddLibraryPanel(panelInfo);
- const layout = dashboard.state.body as SceneGridLayout;
- const gridItem = layout.state.children[0] as DashboardGridItem;
+ const panels = dashboard.state.body.getVizPanels();
+ const panel = panels[0];
- expect(layout.state.children.length).toBe(1);
- expect(gridItem.state.body!).toBeInstanceOf(VizPanel);
- expect(gridItem.state.body.state.$behaviors![0]).toBeInstanceOf(LibraryPanelBehavior);
- expect(gridItem.state.body.state.key).toBe('panel-1');
+ expect(panels.length).toBe(1);
+ expect(panel.state.$behaviors![0]).toBeInstanceOf(LibraryPanelBehavior);
+ expect(panel.state.key).toBe('panel-1');
});
it('should add library panel from menu and enter edit mode in a dashboard that is not already in edit mode', async () => {
@@ -60,9 +59,6 @@ describe('AddLibraryPanelWidget', () => {
meta: {
canEdit: true,
},
- body: new SceneGridLayout({
- children: [],
- }),
overlay: drawer,
});
@@ -86,24 +82,15 @@ describe('AddLibraryPanelWidget', () => {
drawer.onAddLibraryPanel(panelInfo);
- const layout = dashboard.state.body as SceneGridLayout;
- const gridItem = layout.state.children[0] as DashboardGridItem;
+ const panels = dashboard.state.body.getVizPanels();
+ const panel = panels[0];
- expect(layout.state.children.length).toBe(1);
- expect(gridItem.state.body!).toBeInstanceOf(VizPanel);
- expect(gridItem.state.body.state.$behaviors![0]).toBeInstanceOf(LibraryPanelBehavior);
- expect(gridItem.state.body.state.key).toBe('panel-1');
+ expect(panels.length).toBe(1);
+ expect(panel.state.$behaviors![0]).toBeInstanceOf(LibraryPanelBehavior);
+ expect(panel.state.key).toBe('panel-1');
expect(dashboard.state.isEditing).toBe(true);
});
- it('should throw error if adding lib panel in a layout that is not SceneGridLayout', () => {
- dashboard.setState({ body: undefined });
-
- expect(() => addLibPanelDrawer.onAddLibraryPanel({} as LibraryPanel)).toThrow(
- 'Trying to add a library panel in a layout that is not SceneGridLayout'
- );
- });
-
it('should replace grid item when grid item state is passed', async () => {
const libPanel = new VizPanel({
title: 'Panel Title',
@@ -112,10 +99,6 @@ describe('AddLibraryPanelWidget', () => {
$behaviors: [new LibraryPanelBehavior({ title: 'LibraryPanel A title', name: 'LibraryPanel A', uid: 'uid' })],
});
- let gridItem = new DashboardGridItem({
- body: libPanel,
- key: 'grid-item-1',
- });
addLibPanelDrawer = new AddLibraryPanelDrawer({ panelToReplaceRef: libPanel.getRef() });
dashboard = new DashboardScene({
$timeRange: new SceneTimeRange({}),
@@ -125,9 +108,7 @@ describe('AddLibraryPanelWidget', () => {
meta: {
canEdit: true,
},
- body: new SceneGridLayout({
- children: [gridItem],
- }),
+ body: DefaultGridLayoutManager.fromVizPanels([libPanel]),
overlay: addLibPanelDrawer,
});
@@ -143,71 +124,12 @@ describe('AddLibraryPanelWidget', () => {
addLibPanelDrawer.onAddLibraryPanel(panelInfo);
- const layout = dashboard.state.body as SceneGridLayout;
- gridItem = layout.state.children[0] as DashboardGridItem;
- const behavior = gridItem.state.body!.state.$behaviors![0] as LibraryPanelBehavior;
+ const panels = dashboard.state.body.getVizPanels();
+ expect(panels.length).toBe(1);
+
+ const behavior = panels[0].state.$behaviors![0] as LibraryPanelBehavior;
- expect(layout.state.children.length).toBe(1);
- expect(gridItem.state.body!).toBeInstanceOf(VizPanel);
expect(behavior).toBeInstanceOf(LibraryPanelBehavior);
- expect(gridItem.state.key).toBe('grid-item-1');
- expect(behavior.state.uid).toBe('new_uid');
- expect(behavior.state.name).toBe('new_name');
- });
-
- it('should replace grid item in row when grid item state is passed', async () => {
- const libPanel = new VizPanel({
- title: 'Panel Title',
- pluginId: 'table',
- key: 'panel-1',
- $behaviors: [new LibraryPanelBehavior({ title: 'LibraryPanel A title', name: 'LibraryPanel A', uid: 'uid' })],
- });
-
- let gridItem = new DashboardGridItem({
- body: libPanel,
- key: 'grid-item-1',
- });
- addLibPanelDrawer = new AddLibraryPanelDrawer({ panelToReplaceRef: libPanel.getRef() });
- dashboard = new DashboardScene({
- $timeRange: new SceneTimeRange({}),
- title: 'hello',
- uid: 'dash-1',
- version: 4,
- meta: {
- canEdit: true,
- },
- body: new SceneGridLayout({
- children: [
- new SceneGridRow({
- children: [gridItem],
- }),
- ],
- }),
- overlay: addLibPanelDrawer,
- });
-
- const panelInfo: LibraryPanel = {
- uid: 'new_uid',
- model: {
- type: 'timeseries',
- },
- name: 'new_name',
- version: 1,
- type: 'timeseries',
- };
-
- addLibPanelDrawer.onAddLibraryPanel(panelInfo);
-
- const layout = dashboard.state.body as SceneGridLayout;
- const gridRow = layout.state.children[0] as SceneGridRow;
- gridItem = gridRow.state.children[0] as DashboardGridItem;
- const behavior = gridItem.state.body!.state.$behaviors![0] as LibraryPanelBehavior;
-
- expect(layout.state.children.length).toBe(1);
- expect(gridRow.state.children.length).toBe(1);
- expect(gridItem.state.body!).toBeInstanceOf(VizPanel);
- expect(behavior).toBeInstanceOf(LibraryPanelBehavior);
- expect(gridItem.state.key).toBe('grid-item-1');
expect(behavior.state.uid).toBe('new_uid');
expect(behavior.state.name).toBe('new_name');
});
@@ -223,9 +145,6 @@ async function buildTestScene() {
meta: {
canEdit: true,
},
- body: new SceneGridLayout({
- children: [],
- }),
overlay: drawer,
});
diff --git a/public/app/features/dashboard-scene/scene/AddLibraryPanelDrawer.tsx b/public/app/features/dashboard-scene/scene/AddLibraryPanelDrawer.tsx
index 54f7679c87a..a06154459ff 100644
--- a/public/app/features/dashboard-scene/scene/AddLibraryPanelDrawer.tsx
+++ b/public/app/features/dashboard-scene/scene/AddLibraryPanelDrawer.tsx
@@ -1,11 +1,4 @@
-import {
- SceneComponentProps,
- SceneGridLayout,
- SceneObjectBase,
- SceneObjectRef,
- SceneObjectState,
- VizPanel,
-} from '@grafana/scenes';
+import { SceneComponentProps, SceneObjectBase, SceneObjectRef, SceneObjectState, VizPanel } from '@grafana/scenes';
import { LibraryPanel } from '@grafana/schema';
import { Drawer } from '@grafana/ui';
import { t } from 'app/core/internationalization';
@@ -14,8 +7,7 @@ import {
LibraryPanelsSearchVariant,
} from 'app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch';
-import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
-import { NEW_PANEL_HEIGHT, NEW_PANEL_WIDTH, getDashboardSceneFor, getDefaultVizPanel } from '../utils/utils';
+import { getDashboardSceneFor, getDefaultVizPanel } from '../utils/utils';
import { DashboardGridItem } from './DashboardGridItem';
import { LibraryPanelBehavior } from './LibraryPanelBehavior';
@@ -31,16 +23,10 @@ export class AddLibraryPanelDrawer extends SceneObjectBase {
const dashboard = getDashboardSceneFor(this);
- const layout = dashboard.state.body;
- if (!(layout instanceof SceneGridLayout)) {
- throw new Error('Trying to add a library panel in a layout that is not SceneGridLayout');
- }
+ const newPanel = getDefaultVizPanel(dashboard);
- const panelId = dashboardSceneGraph.getNextPanelId(dashboard);
-
- const body = getDefaultVizPanel(dashboard);
- body.setState({
+ newPanel.setState({
$behaviors: [new LibraryPanelBehavior({ uid: panelInfo.uid, name: panelInfo.name })],
});
@@ -53,22 +39,9 @@ export class AddLibraryPanelDrawer extends SceneObjectBase {
meta: {
canEdit: true,
},
- body: new SceneGridLayout({
- children: [
- new DashboardGridItem({
- key: 'griditem-1',
- x: 0,
- y: 0,
- width: 10,
- height: 12,
- body: sourcePanel,
- }),
- ],
- }),
+ body: DefaultGridLayoutManager.fromVizPanels([sourcePanel]),
});
activateFullSceneTree(scene);
@@ -183,22 +172,10 @@ describe('DashboardDatasourceBehaviour', () => {
const spy = jest.spyOn(dashboardDSPanel.state.$data as SceneQueryRunner, 'runQueries');
- const layout = scene.state.body as SceneGridLayout;
+ //const layout = scene.state.body as DefaultGridLayoutManager;
// we add the new panel, it should run it's query as usual
- layout.setState({
- children: [
- ...layout.state.children,
- new DashboardGridItem({
- key: 'griditem-2',
- x: 0,
- y: 0,
- width: 10,
- height: 12,
- body: dashboardDSPanel,
- }),
- ],
- });
+ scene.addPanel(dashboardDSPanel);
dashboardDSPanel.activate();
@@ -237,26 +214,7 @@ describe('DashboardDatasourceBehaviour', () => {
meta: {
canEdit: true,
},
- body: new SceneGridLayout({
- children: [
- new DashboardGridItem({
- key: 'griditem-1',
- x: 0,
- y: 0,
- width: 10,
- height: 12,
- body: sourcePanel,
- }),
- new DashboardGridItem({
- key: 'griditem-2',
- x: 0,
- y: 0,
- width: 10,
- height: 12,
- body: dashboardDSPanel,
- }),
- ],
- }),
+ body: DefaultGridLayoutManager.fromVizPanels([sourcePanel, dashboardDSPanel]),
});
const spy = jest.spyOn(dashboardDSPanel.state.$data as SceneQueryRunner, 'runQueries');
@@ -313,26 +271,7 @@ describe('DashboardDatasourceBehaviour', () => {
meta: {
canEdit: true,
},
- body: new SceneGridLayout({
- children: [
- new DashboardGridItem({
- key: 'griditem-1',
- x: 0,
- y: 0,
- width: 10,
- height: 12,
- body: sourcePanel,
- }),
- new DashboardGridItem({
- key: 'griditem-2',
- x: 0,
- y: 0,
- width: 10,
- height: 12,
- body: dashboardDSPanel,
- }),
- ],
- }),
+ body: DefaultGridLayoutManager.fromVizPanels([sourcePanel, dashboardDSPanel]),
});
const sceneDeactivate = activateFullSceneTree(scene);
@@ -382,26 +321,7 @@ describe('DashboardDatasourceBehaviour', () => {
meta: {
canEdit: true,
},
- body: new SceneGridLayout({
- children: [
- new DashboardGridItem({
- key: 'griditem-1',
- x: 0,
- y: 0,
- width: 10,
- height: 12,
- body: sourcePanel,
- }),
- new DashboardGridItem({
- key: 'griditem-2',
- x: 0,
- y: 0,
- width: 10,
- height: 12,
- body: anotherPanel,
- }),
- ],
- }),
+ body: DefaultGridLayoutManager.fromVizPanels([sourcePanel, anotherPanel]),
});
const sceneDeactivate = activateFullSceneTree(scene);
@@ -457,26 +377,7 @@ describe('DashboardDatasourceBehaviour', () => {
meta: {
canEdit: true,
},
- body: new SceneGridLayout({
- children: [
- new DashboardGridItem({
- key: 'griditem-1',
- x: 0,
- y: 0,
- width: 10,
- height: 12,
- body: sourcePanel,
- }),
- new DashboardGridItem({
- key: 'griditem-2',
- x: 0,
- y: 0,
- width: 10,
- height: 12,
- body: dashboardDSPanel,
- }),
- ],
- }),
+ body: DefaultGridLayoutManager.fromVizPanels([sourcePanel, dashboardDSPanel]),
});
try {
@@ -526,26 +427,7 @@ describe('DashboardDatasourceBehaviour', () => {
meta: {
canEdit: true,
},
- body: new SceneGridLayout({
- children: [
- new DashboardGridItem({
- key: 'griditem-1',
- x: 0,
- y: 0,
- width: 10,
- height: 12,
- body: sourcePanel,
- }),
- new DashboardGridItem({
- key: 'griditem-2',
- x: 0,
- y: 0,
- width: 10,
- height: 12,
- body: dashboardDSPanel,
- }),
- ],
- }),
+ body: DefaultGridLayoutManager.fromVizPanels([sourcePanel, dashboardDSPanel]),
});
const sceneDeactivate = activateFullSceneTree(scene);
@@ -605,26 +487,7 @@ describe('DashboardDatasourceBehaviour', () => {
meta: {
canEdit: true,
},
- body: new SceneGridLayout({
- children: [
- new DashboardGridItem({
- key: 'griditem-1',
- x: 0,
- y: 0,
- width: 10,
- height: 12,
- body: sourcePanel,
- }),
- new DashboardGridItem({
- key: 'griditem-2',
- x: 0,
- y: 0,
- width: 10,
- height: 12,
- body: dashboardDSPanel,
- }),
- ],
- }),
+ body: DefaultGridLayoutManager.fromVizPanels([sourcePanel, dashboardDSPanel]),
});
activateFullSceneTree(scene);
@@ -684,26 +547,7 @@ async function buildTestScene() {
meta: {
canEdit: true,
},
- body: new SceneGridLayout({
- children: [
- new DashboardGridItem({
- key: 'griditem-1',
- x: 0,
- y: 0,
- width: 10,
- height: 12,
- body: sourcePanel,
- }),
- new DashboardGridItem({
- key: 'griditem-2',
- x: 0,
- y: 0,
- width: 10,
- height: 12,
- body: dashboardDSPanel,
- }),
- ],
- }),
+ body: DefaultGridLayoutManager.fromVizPanels([sourcePanel, dashboardDSPanel]),
});
const sceneDeactivate = activateFullSceneTree(scene);
diff --git a/public/app/features/dashboard-scene/scene/DashboardGridItem.test.tsx b/public/app/features/dashboard-scene/scene/DashboardGridItem.test.tsx
index de74e9934a7..cb695ff747d 100644
--- a/public/app/features/dashboard-scene/scene/DashboardGridItem.test.tsx
+++ b/public/app/features/dashboard-scene/scene/DashboardGridItem.test.tsx
@@ -8,6 +8,7 @@ import { activateFullSceneTree, buildPanelRepeaterScene } from '../utils/test-ut
import { DashboardGridItem, DashboardGridItemState } from './DashboardGridItem';
import { DashboardScene } from './DashboardScene';
+import { DefaultGridLayoutManager } from './layout-default/DefaultGridLayoutManager';
jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'),
@@ -122,8 +123,8 @@ describe('PanelRepeaterGridItem', () => {
$variables: new SceneVariableSet({
variables: [variable],
}),
- body: new SceneGridLayout({
- children: [panel],
+ body: new DefaultGridLayoutManager({
+ grid: new SceneGridLayout({ children: [panel] }),
}),
});
@@ -193,8 +194,10 @@ describe('PanelRepeaterGridItem', () => {
$variables: new SceneVariableSet({
variables: [variable],
}),
- body: new SceneGridLayout({
- children: [panel, panel2],
+ body: new DefaultGridLayoutManager({
+ grid: new SceneGridLayout({
+ children: [panel, panel2],
+ }),
}),
});
@@ -269,7 +272,8 @@ describe('PanelRepeaterGridItem', () => {
const { scene, repeater } = buildPanelRepeaterScene({ variableQueryTime: 0, maxPerRow: 2, itemHeight: 10 });
const layoutForceRender = jest.fn();
- (scene.state.body as SceneGridLayout).forceRender = layoutForceRender;
+ const layout = scene.state.body as DefaultGridLayoutManager;
+ layout.state.grid.forceRender = layoutForceRender;
activateFullSceneTree(scene);
diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx
index 3df3a8f73e6..6dd0aedbfdf 100644
--- a/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx
+++ b/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx
@@ -25,13 +25,14 @@ import { DecoratedRevisionModel } from '../settings/VersionsEditView';
import { historySrv } from '../settings/version-history/HistorySrv';
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
import { djb2Hash } from '../utils/djb2Hash';
-import { findVizPanelByKey } from '../utils/utils';
+import { findVizPanelByKey, getLibraryPanelBehavior, isLibraryPanel } from '../utils/utils';
import { DashboardControls } from './DashboardControls';
import { DashboardGridItem } from './DashboardGridItem';
import { DashboardScene, DashboardSceneState } from './DashboardScene';
import { LibraryPanelBehavior } from './LibraryPanelBehavior';
import { PanelTimeRange } from './PanelTimeRange';
+import { DefaultGridLayoutManager } from './layout-default/DefaultGridLayoutManager';
import { RowActions } from './row-actions/RowActions';
jest.mock('../settings/version-history/HistorySrv');
@@ -343,9 +344,8 @@ describe('DashboardScene', () => {
});
it('A change to any library panel name should set isDirty true', () => {
- const libraryVizPanel = ((scene.state.body as SceneGridLayout).state.children[4] as DashboardGridItem).state
- .body;
- const behavior = libraryVizPanel.state.$behaviors![0] as LibraryPanelBehavior;
+ const panel = findVizPanelByKey(scene, 'panel-5')!;
+ const behavior = getLibraryPanelBehavior(panel)!;
const prevValue = behavior.state.name;
behavior.setState({ name: 'new name' });
@@ -353,9 +353,9 @@ describe('DashboardScene', () => {
expect(scene.state.isDirty).toBe(true);
scene.exitEditMode({ skipConfirm: true });
- const restoredLibraryVizPanel = ((scene.state.body as SceneGridLayout).state.children[4] as DashboardGridItem)
- .state.body;
- const restoredBehavior = restoredLibraryVizPanel.state.$behaviors![0] as LibraryPanelBehavior;
+
+ const restoredPanel = findVizPanelByKey(scene, 'panel-5')!;
+ const restoredBehavior = getLibraryPanelBehavior(restoredPanel)!;
expect(restoredBehavior.state.name).toBe(prevValue);
});
@@ -451,160 +451,15 @@ describe('DashboardScene', () => {
expect(scene.state.isDirty).toBeFalsy();
});
- it('Should throw an error when adding a panel to a layout that is not SceneGridLayout', () => {
- const scene = buildTestScene({ body: undefined });
-
- expect(() => {
- scene.addPanel(new VizPanel({ title: 'Panel Title', key: 'panel-4', pluginId: 'timeseries' }));
- }).toThrow('Trying to add a panel in a layout that is not SceneGridLayout');
- });
-
- it('Should add a new panel to the dashboard', () => {
- const vizPanel = new VizPanel({
- title: 'Panel Title',
- key: 'panel-5',
- pluginId: 'timeseries',
- $data: new SceneQueryRunner({ key: 'data-query-runner', queries: [{ refId: 'A' }] }),
- });
-
- scene.addPanel(vizPanel);
-
- const body = scene.state.body as SceneGridLayout;
- const gridItem = body.state.children[0] as DashboardGridItem;
-
- expect(body.state.children.length).toBe(6);
- expect(gridItem.state.body!.state.key).toBe('panel-5');
- expect(gridItem.state.y).toBe(0);
- });
-
it('Should create and add a new panel to the dashboard', () => {
scene.exitEditMode({ skipConfirm: true });
expect(scene.state.isEditing).toBe(false);
- scene.onCreateNewPanel();
-
- const body = scene.state.body as SceneGridLayout;
- const gridItem = body.state.children[0] as DashboardGridItem;
+ const panel = scene.onCreateNewPanel();
expect(scene.state.isEditing).toBe(true);
- expect(body.state.children.length).toBe(6);
- expect(gridItem.state.body!.state.key).toBe('panel-7');
- });
-
- it('Should create and add a new row to the dashboard', () => {
- scene.onCreateNewRow();
-
- const body = scene.state.body as SceneGridLayout;
- const gridRow = body.state.children[0] as SceneGridRow;
-
- expect(scene.state.isEditing).toBe(true);
- expect(body.state.children.length).toBe(4);
- expect(gridRow.state.key).toBe('panel-7');
- expect(gridRow.state.children[0].state.key).toBe('griditem-1');
- expect(gridRow.state.children[1].state.key).toBe('griditem-2');
- });
-
- it('Should create a row and add all panels in the dashboard under it', () => {
- const scene = buildTestScene({
- body: new SceneGridLayout({
- children: [
- new DashboardGridItem({
- key: 'griditem-1',
- x: 0,
- body: new VizPanel({
- title: 'Panel A',
- key: 'panel-1',
- pluginId: 'table',
- $data: new SceneQueryRunner({ key: 'data-query-runner', queries: [{ refId: 'A' }] }),
- }),
- }),
- new DashboardGridItem({
- key: 'griditem-2',
- body: new VizPanel({
- title: 'Panel B',
- key: 'panel-2',
- pluginId: 'table',
- }),
- }),
- ],
- }),
- });
-
- scene.onCreateNewRow();
-
- const body = scene.state.body as SceneGridLayout;
- const gridRow = body.state.children[0] as SceneGridRow;
-
- expect(body.state.children.length).toBe(1);
- expect(gridRow.state.children.length).toBe(2);
- });
-
- it('Should create and add two new rows, but the second has no children', () => {
- scene.onCreateNewRow();
- scene.onCreateNewRow();
-
- const body = scene.state.body as SceneGridLayout;
- const gridRow = body.state.children[0] as SceneGridRow;
-
- expect(body.state.children.length).toBe(5);
- expect(gridRow.state.children.length).toBe(0);
- });
-
- it('Should create an empty row when nothing else in dashboard', () => {
- const scene = buildTestScene({
- body: new SceneGridLayout({
- children: [],
- }),
- });
-
- scene.onCreateNewRow();
-
- const body = scene.state.body as SceneGridLayout;
- const gridRow = body.state.children[0] as SceneGridRow;
-
- expect(body.state.children.length).toBe(1);
- expect(gridRow.state.children.length).toBe(0);
- });
-
- it('Should remove a row and move its children to the grid layout', () => {
- const body = scene.state.body as SceneGridLayout;
- const row = body.state.children[2] as SceneGridRow;
-
- scene.removeRow(row);
-
- const vizPanel = (body.state.children[2] as DashboardGridItem).state.body as VizPanel;
-
- expect(body.state.children.length).toBe(6);
- expect(vizPanel.state.key).toBe('panel-4');
- });
-
- it('Should remove a row and its children', () => {
- const body = scene.state.body as SceneGridLayout;
- const row = body.state.children[2] as SceneGridRow;
-
- scene.removeRow(row, true);
-
- expect(body.state.children.length).toBe(4);
- });
-
- it('Should remove an empty row from the layout', () => {
- const row = new SceneGridRow({
- key: 'panel-1',
- });
-
- const scene = buildTestScene({
- body: new SceneGridLayout({
- children: [row],
- }),
- });
-
- const body = scene.state.body as SceneGridLayout;
-
- expect(body.state.children.length).toBe(1);
-
- scene.removeRow(row);
-
- expect(body.state.children.length).toBe(0);
+ expect(scene.state.body.getVizPanels().length).toBe(7);
+ expect(panel.state.key).toBe('panel-7');
});
it('Should fail to copy a panel if it does not have a grid item parent', () => {
@@ -633,16 +488,16 @@ describe('DashboardScene', () => {
});
it('Should copy a panel', () => {
- const vizPanel = ((scene.state.body as SceneGridLayout).state.children[0] as DashboardGridItem).state.body;
+ const vizPanel = findVizPanelByKey(scene, 'panel-1')!;
scene.copyPanel(vizPanel as VizPanel);
expect(store.exists(LS_PANEL_COPY_KEY)).toBe(true);
});
it('Should copy a library viz panel', () => {
- const libVizPanel = ((scene.state.body as SceneGridLayout).state.children[4] as DashboardGridItem).state.body;
+ const libVizPanel = findVizPanelByKey(scene, 'panel-6')!;
- expect(libVizPanel.state.$behaviors![0]).toBeInstanceOf(LibraryPanelBehavior);
+ expect(isLibraryPanel(libVizPanel)).toBe(true);
scene.copyPanel(libVizPanel);
@@ -651,7 +506,6 @@ describe('DashboardScene', () => {
it('Should paste a panel', () => {
store.set(LS_PANEL_COPY_KEY, JSON.stringify({ key: 'panel-7' }));
- jest.spyOn(JSON, 'parse').mockReturnThis();
jest.mocked(buildGridItemForPanel).mockReturnValue(
new DashboardGridItem({
key: 'griditem-9',
@@ -665,19 +519,15 @@ describe('DashboardScene', () => {
scene.pastePanel();
- const body = scene.state.body as SceneGridLayout;
- const gridItem = body.state.children[0] as DashboardGridItem;
-
expect(buildGridItemForPanel).toHaveBeenCalledTimes(1);
- expect(body.state.children.length).toBe(6);
- expect(gridItem.state.body!.state.key).toBe('panel-7');
- expect(gridItem.state.y).toBe(0);
+
+ const addedPanel = findVizPanelByKey(scene, 'panel-7')!;
+ expect(addedPanel).toBeDefined();
expect(store.exists(LS_PANEL_COPY_KEY)).toBe(false);
});
it('Should paste a library viz panel', () => {
store.set(LS_PANEL_COPY_KEY, JSON.stringify({ key: 'panel-7' }));
- jest.spyOn(JSON, 'parse').mockReturnValue({ libraryPanel: { uid: 'uid', name: 'libraryPanel' } });
jest.mocked(buildGridItemForPanel).mockReturnValue(
new DashboardGridItem({
body: new VizPanel({
@@ -691,200 +541,14 @@ describe('DashboardScene', () => {
scene.pastePanel();
- const body = scene.state.body as SceneGridLayout;
- const gridItem = body.state.children[0] as DashboardGridItem;
-
- const libVizPanel = gridItem.state.body;
-
expect(buildGridItemForPanel).toHaveBeenCalledTimes(1);
- expect(body.state.children.length).toBe(6);
- expect(libVizPanel.state.key).toBe('panel-7');
- expect(gridItem.state.y).toBe(0);
+
+ const addedPanel = findVizPanelByKey(scene, 'panel-7')!;
+ expect(addedPanel).toBeDefined();
+ expect(addedPanel.state.key).toBe('panel-7');
expect(store.exists(LS_PANEL_COPY_KEY)).toBe(false);
});
- it('Should remove a panel', () => {
- const vizPanel = ((scene.state.body as SceneGridLayout).state.children[0] as DashboardGridItem).state.body;
- scene.removePanel(vizPanel as VizPanel);
-
- const body = scene.state.body as SceneGridLayout;
- expect(body.state.children.length).toBe(4);
- });
-
- it('Should remove a panel within a row', () => {
- const vizPanel = (
- ((scene.state.body as SceneGridLayout).state.children[2] as SceneGridRow).state
- .children[0] as DashboardGridItem
- ).state.body;
- scene.removePanel(vizPanel as VizPanel);
-
- const body = scene.state.body as SceneGridLayout;
- const gridRow = body.state.children[2] as SceneGridRow;
-
- expect(gridRow.state.children.length).toBe(1);
- });
-
- it('Should remove a library panel', () => {
- const libraryPanel = ((scene.state.body as SceneGridLayout).state.children[4] as DashboardGridItem).state.body;
- scene.removePanel(libraryPanel);
-
- const body = scene.state.body as SceneGridLayout;
- expect(body.state.children.length).toBe(4);
- });
-
- it('Should remove a library panel within a row', () => {
- const libraryPanel = (
- ((scene.state.body as SceneGridLayout).state.children[2] as SceneGridRow).state
- .children[1] as DashboardGridItem
- ).state.body;
-
- scene.removePanel(libraryPanel);
-
- const body = scene.state.body as SceneGridLayout;
- const gridRow = body.state.children[2] as SceneGridRow;
- expect(gridRow.state.children.length).toBe(1);
- });
-
- it('Should duplicate a panel', () => {
- const vizPanel = ((scene.state.body as SceneGridLayout).state.children[0] as DashboardGridItem).state.body;
- scene.duplicatePanel(vizPanel as VizPanel);
-
- const body = scene.state.body as SceneGridLayout;
- const gridItem = body.state.children[5] as DashboardGridItem;
-
- expect(body.state.children.length).toBe(6);
- expect(gridItem.state.body!.state.key).toBe('panel-7');
- });
-
- it('Should maintain size of duplicated panel', () => {
- const gItem = (scene.state.body as SceneGridLayout).state.children[0] as DashboardGridItem;
- gItem.setState({ height: 1 });
- const vizPanel = gItem.state.body;
- scene.duplicatePanel(vizPanel as VizPanel);
-
- const body = scene.state.body as SceneGridLayout;
- const newGridItem = body.state.children[5] as DashboardGridItem;
-
- expect(body.state.children.length).toBe(6);
- expect(newGridItem.state.body!.state.key).toBe('panel-7');
- expect(newGridItem.state.height).toBe(1);
- });
-
- it('Should duplicate a library panel', () => {
- const libraryPanel = ((scene.state.body as SceneGridLayout).state.children[4] as DashboardGridItem).state.body;
- scene.duplicatePanel(libraryPanel);
-
- const body = scene.state.body as SceneGridLayout;
- const gridItem = body.state.children[5] as DashboardGridItem;
-
- const libVizPanel = gridItem.state.body;
-
- expect(body.state.children.length).toBe(6);
- expect(libVizPanel.state.key).toBe('panel-7');
- });
-
- it('Should deep clone data provider when duplicating a panel', () => {
- const vizPanel = ((scene.state.body as SceneGridLayout).state.children[0] as DashboardGridItem).state.body;
- scene.duplicatePanel(vizPanel as VizPanel);
-
- const panelQueries = (
- ((scene.state.body as SceneGridLayout).state.children[0] as DashboardGridItem).state.body.state.$data?.state
- .$data as SceneQueryRunner
- ).state.queries;
- const duplicatedPanelQueries = (
- ((scene.state.body as SceneGridLayout).state.children[5] as DashboardGridItem).state.body.state.$data?.state
- .$data as SceneQueryRunner
- ).state.queries;
-
- expect(panelQueries[0]).not.toBe(duplicatedPanelQueries[0]);
- });
-
- it('Should duplicate a repeated panel', () => {
- const scene = buildTestScene({
- body: new SceneGridLayout({
- children: [
- new DashboardGridItem({
- key: `grid-item-1`,
- width: 24,
- height: 8,
- repeatedPanels: [
- new VizPanel({
- title: 'Library Panel',
- key: 'panel-1',
- pluginId: 'table',
- }),
- ],
- body: new VizPanel({
- title: 'Library Panel',
- key: 'panel-1',
- pluginId: 'table',
- }),
- variableName: 'custom',
- }),
- ],
- }),
- });
-
- const vizPanel = ((scene.state.body as SceneGridLayout).state.children[0] as DashboardGridItem).state
- .repeatedPanels![0];
-
- scene.duplicatePanel(vizPanel as VizPanel);
-
- const body = scene.state.body as SceneGridLayout;
- const gridItem = body.state.children[1] as DashboardGridItem;
-
- expect(body.state.children.length).toBe(2);
- expect(gridItem.state.body!.state.key).toBe('panel-2');
- });
-
- it('Should duplicate a panel in a row', () => {
- const vizPanel = (
- ((scene.state.body as SceneGridLayout).state.children[2] as SceneGridRow).state
- .children[0] as DashboardGridItem
- ).state.body;
- scene.duplicatePanel(vizPanel as VizPanel);
-
- const body = scene.state.body as SceneGridLayout;
- const gridRow = body.state.children[2] as SceneGridRow;
- const gridItem = gridRow.state.children[2] as DashboardGridItem;
-
- expect(gridRow.state.children.length).toBe(3);
- expect(gridItem.state.body!.state.key).toBe('panel-7');
- });
-
- it('Should duplicate a library panel in a row', () => {
- const libraryPanel = (
- ((scene.state.body as SceneGridLayout).state.children[2] as SceneGridRow).state
- .children[1] as DashboardGridItem
- ).state.body;
-
- scene.duplicatePanel(libraryPanel);
-
- const body = scene.state.body as SceneGridLayout;
- const gridRow = body.state.children[2] as SceneGridRow;
- const gridItem = gridRow.state.children[2] as DashboardGridItem;
-
- const libVizPanel = gridItem.state.body;
-
- expect(gridRow.state.children.length).toBe(3);
- expect(libVizPanel.state.key).toBe('panel-7');
- });
-
- it('Should fail to duplicate a panel if it does not have a grid item parent', () => {
- const vizPanel = new VizPanel({
- title: 'Panel Title',
- key: 'panel-5',
- pluginId: 'timeseries',
- });
-
- scene.duplicatePanel(vizPanel);
-
- const body = scene.state.body as SceneGridLayout;
-
- // length remains unchanged
- expect(body.state.children.length).toBe(5);
- });
-
it('Should unlink a library panel', () => {
const libPanel = new VizPanel({
title: 'Panel B',
@@ -893,24 +557,14 @@ describe('DashboardScene', () => {
});
const scene = buildTestScene({
- body: new SceneGridLayout({
- children: [
- new DashboardGridItem({
- key: 'griditem-2',
- body: libPanel,
- }),
- ],
- }),
+ body: DefaultGridLayoutManager.fromVizPanels([libPanel]),
});
+ expect(isLibraryPanel(libPanel)).toBe(true);
+
scene.unlinkLibraryPanel(libPanel);
- const body = scene.state.body as SceneGridLayout;
- const gridItem = body.state.children[0] as DashboardGridItem;
-
- expect(body.state.children.length).toBe(1);
- expect(gridItem.state.body).toBeInstanceOf(VizPanel);
- expect(gridItem.state.$behaviors).toBeUndefined();
+ expect(isLibraryPanel(libPanel)).toBe(false);
});
it('Should create a library panel', () => {
@@ -925,10 +579,9 @@ describe('DashboardScene', () => {
body: vizPanel,
});
+ const grid = new SceneGridLayout({ children: [gridItem] });
const scene = buildTestScene({
- body: new SceneGridLayout({
- children: [gridItem],
- }),
+ body: new DefaultGridLayoutManager({ grid }),
});
const libPanel = {
@@ -938,52 +591,10 @@ describe('DashboardScene', () => {
scene.createLibraryPanel(vizPanel, libPanel as LibraryPanel);
- const layout = scene.state.body as SceneGridLayout;
- const newGridItem = layout.state.children[0] as DashboardGridItem;
+ const newGridItem = grid.state.children[0] as DashboardGridItem;
const behavior = newGridItem.state.body.state.$behaviors![0] as LibraryPanelBehavior;
- expect(layout.state.children.length).toBe(1);
- expect(newGridItem.state.body).toBeInstanceOf(VizPanel);
- expect(behavior.state.uid).toBe('uid');
- expect(behavior.state.name).toBe('name');
- });
-
- it('Should create a library panel under a row', () => {
- const vizPanel = new VizPanel({
- title: 'Panel A',
- key: 'panel-1',
- pluginId: 'table',
- });
-
- const gridItem = new DashboardGridItem({
- key: 'griditem-1',
- body: vizPanel,
- });
-
- const scene = buildTestScene({
- body: new SceneGridLayout({
- children: [
- new SceneGridRow({
- key: 'row-1',
- children: [gridItem],
- }),
- ],
- }),
- });
-
- const libPanel = {
- uid: 'uid',
- name: 'name',
- };
-
- scene.createLibraryPanel(vizPanel, libPanel as LibraryPanel);
-
- const layout = scene.state.body as SceneGridLayout;
- const newGridItem = (layout.state.children[0] as SceneGridRow).state.children[0] as DashboardGridItem;
- const behavior = newGridItem.state.body.state.$behaviors![0] as LibraryPanelBehavior;
-
- expect(layout.state.children.length).toBe(1);
- expect((layout.state.children[0] as SceneGridRow).state.children.length).toBe(1);
+ expect(grid.state.children.length).toBe(1);
expect(newGridItem.state.body).toBeInstanceOf(VizPanel);
expect(behavior.state.uid).toBe('uid');
expect(behavior.state.name).toBe('name');
@@ -1166,6 +777,19 @@ describe('DashboardScene', () => {
describe('When coming from explore', () => {
// When coming from Explore the first panel in a dashboard is a temporary panel
it('should remove first panel from the grid when discarding changes', () => {
+ const layout = DefaultGridLayoutManager.fromVizPanels([
+ new VizPanel({
+ title: 'Panel A',
+ key: 'panel-1',
+ pluginId: 'table',
+ $data: new SceneQueryRunner({ key: 'data-query-runner', queries: [{ refId: 'A' }] }),
+ }),
+ new VizPanel({
+ title: 'Panel B',
+ key: 'panel-2',
+ pluginId: 'table',
+ }),
+ ]);
const scene = new DashboardScene({
title: 'hello',
uid: 'dash-1',
@@ -1176,37 +800,18 @@ describe('DashboardScene', () => {
}),
controls: new DashboardControls({}),
$behaviors: [new behaviors.CursorSync({})],
- body: new SceneGridLayout({
- children: [
- new DashboardGridItem({
- key: 'griditem-1',
- x: 0,
- body: new VizPanel({
- title: 'Panel A',
- key: 'panel-1',
- pluginId: 'table',
- $data: new SceneQueryRunner({ key: 'data-query-runner', queries: [{ refId: 'A' }] }),
- }),
- }),
- new DashboardGridItem({
- key: 'griditem-2',
- body: new VizPanel({
- title: 'Panel B',
- key: 'panel-2',
- pluginId: 'table',
- }),
- }),
- ],
- }),
+ body: layout,
});
scene.onEnterEditMode(true);
expect(scene.state.isEditing).toBe(true);
- expect((scene.state.body as SceneGridLayout).state.children.length).toBe(2);
+ expect(layout.state.grid.state.children.length).toBe(2);
scene.exitEditMode({ skipConfirm: true });
+
+ const restoredGrid = scene.state.body as DefaultGridLayoutManager;
expect(scene.state.isEditing).toBe(false);
- expect((scene.state.body as SceneGridLayout).state.children.length).toBe(1);
+ expect(restoredGrid.state.grid.state.children.length).toBe(1);
});
});
});
@@ -1223,75 +828,74 @@ function buildTestScene(overrides?: Partial) {
}),
controls: new DashboardControls({}),
$behaviors: [new behaviors.CursorSync({}), new behaviors.LiveNowTimer({})],
- body: new SceneGridLayout({
- children: [
- new DashboardGridItem({
- key: 'griditem-1',
- x: 0,
- body: new VizPanel({
- title: 'Panel A',
- key: 'panel-1',
- pluginId: 'table',
- $timeRange: new PanelTimeRange({
- from: 'now-12h',
- to: 'now',
- timeZone: 'browser',
- }),
- $data: new SceneDataTransformer({
- transformations: [],
- $data: new SceneQueryRunner({
- key: 'data-query-runner',
- queries: [{ refId: 'A', target: 'aliasByMetric(carbon.**)' }],
+ body: new DefaultGridLayoutManager({
+ grid: new SceneGridLayout({
+ children: [
+ new DashboardGridItem({
+ key: 'griditem-1',
+ x: 0,
+ body: new VizPanel({
+ title: 'Panel A',
+ key: 'panel-1',
+ pluginId: 'table',
+ $timeRange: new PanelTimeRange({
+ from: 'now-12h',
+ to: 'now',
+ timeZone: 'browser',
+ }),
+ $data: new SceneDataTransformer({
+ transformations: [],
+ $data: new SceneQueryRunner({ key: 'data-query-runner', queries: [{ refId: 'A' }] }),
}),
}),
}),
- }),
- new DashboardGridItem({
- key: 'griditem-2',
- body: new VizPanel({
- title: 'Panel B',
- key: 'panel-2',
- pluginId: 'table',
- }),
- }),
- new SceneGridRow({
- key: 'panel-3',
- actions: new RowActions({}),
- children: [
- new DashboardGridItem({
- body: new VizPanel({
- title: 'Panel C',
- key: 'panel-4',
- pluginId: 'table',
- }),
+ new DashboardGridItem({
+ key: 'griditem-2',
+ body: new VizPanel({
+ title: 'Panel B',
+ key: 'panel-2',
+ pluginId: 'table',
}),
- new DashboardGridItem({
- body: new VizPanel({
- title: 'Library Panel',
- pluginId: 'table',
- key: 'panel-5',
- $behaviors: [new LibraryPanelBehavior({ title: 'Library Panel', name: 'libraryPanel', uid: 'uid' })],
+ }),
+ new SceneGridRow({
+ key: 'panel-3',
+ actions: new RowActions({}),
+ children: [
+ new DashboardGridItem({
+ body: new VizPanel({
+ title: 'Panel C',
+ key: 'panel-4',
+ pluginId: 'table',
+ }),
}),
+ new DashboardGridItem({
+ body: new VizPanel({
+ title: 'Library Panel',
+ pluginId: 'table',
+ key: 'panel-5',
+ $behaviors: [new LibraryPanelBehavior({ title: 'Library Panel', name: 'libraryPanel', uid: 'uid' })],
+ }),
+ }),
+ ],
+ }),
+ new DashboardGridItem({
+ body: new VizPanel({
+ title: 'Panel B',
+ key: 'panel-2-clone-1',
+ pluginId: 'table',
+ $data: new SceneQueryRunner({ key: 'data-query-runner2', queries: [{ refId: 'A' }] }),
}),
- ],
- }),
- new DashboardGridItem({
- body: new VizPanel({
- title: 'Panel B',
- key: 'panel-2-clone-1',
- pluginId: 'table',
- $data: new SceneQueryRunner({ key: 'data-query-runner2', queries: [{ refId: 'A' }] }),
}),
- }),
- new DashboardGridItem({
- body: new VizPanel({
- title: 'Library Panel',
- pluginId: 'table',
- key: 'panel-6',
- $behaviors: [new LibraryPanelBehavior({ title: 'Library Panel', name: 'libraryPanel', uid: 'uid' })],
+ new DashboardGridItem({
+ body: new VizPanel({
+ title: 'Library Panel',
+ pluginId: 'table',
+ key: 'panel-6',
+ $behaviors: [new LibraryPanelBehavior({ title: 'Library Panel', name: 'libraryPanel', uid: 'uid' })],
+ }),
}),
- }),
- ],
+ ],
+ }),
}),
...overrides,
});
diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.tsx
index 6c1bffe482a..72c7025a25f 100644
--- a/public/app/features/dashboard-scene/scene/DashboardScene.tsx
+++ b/public/app/features/dashboard-scene/scene/DashboardScene.tsx
@@ -12,14 +12,12 @@ import {
} from '@grafana/data';
import { config, locationService } from '@grafana/runtime';
import {
- SceneFlexLayout,
- sceneGraph,
- SceneGridLayout,
SceneGridRow,
SceneObject,
SceneObjectBase,
SceneObjectRef,
SceneObjectState,
+ SceneTimeRange,
sceneUtils,
SceneVariable,
SceneVariableDependencyConfigLike,
@@ -52,15 +50,10 @@ import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
import { djb2Hash } from '../utils/djb2Hash';
import { getDashboardUrl, getViewPanelUrl } from '../utils/urlBuilders';
import {
- NEW_PANEL_HEIGHT,
- NEW_PANEL_WIDTH,
- forceRenderChildren,
getClosestVizPanel,
getDashboardSceneFor,
- getDefaultRow,
getDefaultVizPanel,
getPanelIdForVizPanel,
- getQueryRunnerFor,
getVizPanelKeyForPanelId,
isPanelClone,
} from '../utils/utils';
@@ -74,6 +67,8 @@ import { LibraryPanelBehavior } from './LibraryPanelBehavior';
import { RowRepeaterBehavior } from './RowRepeaterBehavior';
import { ViewPanelScene } from './ViewPanelScene';
import { setupKeyboardShortcuts } from './keyboardShortcuts';
+import { DefaultGridLayoutManager } from './layout-default/DefaultGridLayoutManager';
+import { DashboardLayoutManager } from './types';
export const PERSISTED_PROPS = ['title', 'description', 'tags', 'editable', 'graphTooltip', 'links', 'meta', 'preload'];
@@ -95,7 +90,7 @@ export interface DashboardSceneState extends SceneObjectState {
/** @deprecated */
id?: number | null;
/** Layout of panels */
- body: SceneObject;
+ body: DashboardLayoutManager;
/** NavToolbar actions */
actions?: SceneObject[];
/** Fixed row at the top of the canvas with for example variables and time range controls */
@@ -175,7 +170,8 @@ export class DashboardScene extends SceneObjectBase {
title: 'Dashboard',
meta: {},
editable: true,
- body: state.body ?? new SceneFlexLayout({ children: [] }),
+ $timeRange: state.$timeRange ?? new SceneTimeRange({}),
+ body: state.body ?? DefaultGridLayoutManager.fromVizPanels(),
links: state.links ?? [],
...state,
});
@@ -235,7 +231,7 @@ export class DashboardScene extends SceneObjectBase {
this.setState({ isEditing: true });
// Propagate change edit mode change to children
- this.propagateEditModeChange();
+ this.state.body.editModeChanged(true);
// Propagate edit mode to scopes
this._scopesFacade?.enterReadOnly();
@@ -272,13 +268,6 @@ export class DashboardScene extends SceneObjectBase {
this._changeTracker.startTrackingChanges();
}
- private propagateEditModeChange() {
- if (this.state.body instanceof SceneGridLayout) {
- this.state.body.setState({ isDraggable: this.state.isEditing, isResizable: this.state.isEditing });
- forceRenderChildren(this.state.body, true);
- }
- }
-
public exitEditMode({ skipConfirm, restoreInitialState }: { skipConfirm: boolean; restoreInitialState?: boolean }) {
if (!this.canDiscard()) {
console.error('Trying to discard back to a state that does not exist, initialState undefined');
@@ -342,7 +331,7 @@ export class DashboardScene extends SceneObjectBase {
}
// Disable grid dragging
- this.propagateEditModeChange();
+ this.state.body.editModeChanged(false);
}
private cleanupStateFromExplore() {
@@ -352,10 +341,8 @@ export class DashboardScene extends SceneObjectBase {
this._initialSaveModel.panels = this._initialSaveModel.panels.slice(1);
}
- if (this._initialState && this._initialState.body instanceof SceneGridLayout) {
- this._initialState.body.setState({
- children: this._initialState.body.state.children.slice(1),
- });
+ if (this._initialState) {
+ this._initialState.body.cleanUpStateFromExplore?.();
}
}
@@ -467,79 +454,15 @@ export class DashboardScene extends SceneObjectBase {
return this._initialState;
}
- public addRow(row: SceneGridRow) {
- if (!(this.state.body instanceof SceneGridLayout)) {
- throw new Error('Trying to add a panel in a layout that is not SceneGridLayout');
- }
-
- const sceneGridLayout = this.state.body;
-
- // find all panels until the first row and put them into the newly created row. If there are no other rows,
- // add all panels to the row. If there are no panels just create an empty row
- const indexTillNextRow = sceneGridLayout.state.children.findIndex((child) => child instanceof SceneGridRow);
- const rowChildren = sceneGridLayout.state.children
- .splice(0, indexTillNextRow === -1 ? sceneGridLayout.state.children.length : indexTillNextRow)
- .map((child) => child.clone());
-
- if (rowChildren) {
- row.setState({
- children: rowChildren,
- });
- }
-
- sceneGridLayout.setState({
- children: [row, ...sceneGridLayout.state.children],
- });
- }
-
- public removeRow(row: SceneGridRow, removePanels = false) {
- if (!(this.state.body instanceof SceneGridLayout)) {
- throw new Error('Trying to add a panel in a layout that is not SceneGridLayout');
- }
-
- const sceneGridLayout = this.state.body;
-
- const children = sceneGridLayout.state.children.filter((child) => child.state.key !== row.state.key);
-
- if (!removePanels) {
- const rowChildren = row.state.children.map((child) => child.clone());
- const indexOfRow = sceneGridLayout.state.children.findIndex((child) => child.state.key === row.state.key);
-
- children.splice(indexOfRow, 0, ...rowChildren);
- }
-
- sceneGridLayout.setState({ children });
- }
-
public addPanel(vizPanel: VizPanel): void {
- if (!(this.state.body instanceof 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 panelId = getPanelIdForVizPanel(vizPanel);
- const newGridItem = new DashboardGridItem({
- height: NEW_PANEL_HEIGHT,
- width: NEW_PANEL_WIDTH,
- x: 0,
- y: 0,
- body: vizPanel,
- key: `grid-item-${panelId}`,
- });
-
- sceneGridLayout.setState({
- children: [newGridItem, ...sceneGridLayout.state.children],
- });
+ this.state.body.addPanel(vizPanel);
}
public createLibraryPanel(panelToReplace: VizPanel, libPanel: LibraryPanel) {
- const layout = this.state.body;
-
- if (!(layout instanceof SceneGridLayout)) {
- throw new Error('Trying to add a panel in a layout that is not SceneGridLayout');
- }
-
const body = panelToReplace.clone({
$behaviors: [new LibraryPanelBehavior({ uid: libPanel.uid, name: libPanel.name })],
});
@@ -554,74 +477,7 @@ export class DashboardScene extends SceneObjectBase {
}
public duplicatePanel(vizPanel: VizPanel) {
- if (!vizPanel.parent) {
- return;
- }
-
- const gridItem = vizPanel.parent;
-
- if (!(gridItem instanceof DashboardGridItem)) {
- console.error('Trying to duplicate a panel in a layout that is not DashboardGridItem');
- return;
- }
-
- let panelState;
- let panelData;
- let newGridItem;
- const newPanelId = dashboardSceneGraph.getNextPanelId(this);
-
- if (gridItem instanceof DashboardGridItem) {
- panelState = sceneUtils.cloneSceneObjectState(gridItem.state.body.state);
-
- let queryRunner = getQueryRunnerFor(gridItem.state.body);
- const queries = queryRunner?.state.queries.map((q) => ({ ...q }));
- queryRunner = queryRunner?.clone({ queries });
- panelData = sceneGraph.getData(gridItem.state.body).clone({ $data: queryRunner });
- } else {
- panelState = sceneUtils.cloneSceneObjectState(vizPanel.state);
-
- let queryRunner = getQueryRunnerFor(vizPanel);
- const queries = queryRunner?.state.queries.map((q) => ({ ...q }));
- queryRunner = queryRunner?.clone({ queries });
- panelData = sceneGraph.getData(vizPanel).clone({ $data: queryRunner });
- }
-
- // when we duplicate a panel we don't want to clone the alert state
- delete panelData.state.data?.alertState;
-
- newGridItem = new DashboardGridItem({
- x: gridItem.state.x,
- y: gridItem.state.y,
- height: gridItem.state.height,
- width: gridItem.state.width,
- variableName: gridItem.state.variableName,
- repeatDirection: gridItem.state.repeatDirection,
- maxPerRow: gridItem.state.maxPerRow,
- body: new VizPanel({ ...panelState, $data: panelData, key: getVizPanelKeyForPanelId(newPanelId) }),
- });
-
- if (!(this.state.body instanceof SceneGridLayout)) {
- console.error('Trying to duplicate a panel in a layout that is not SceneGridLayout ');
- return;
- }
-
- const sceneGridLayout = this.state.body;
-
- if (gridItem.parent instanceof SceneGridRow) {
- const row = gridItem.parent;
-
- row.setState({
- children: [...row.state.children, newGridItem],
- });
-
- sceneGridLayout.forceRender();
-
- return;
- }
-
- sceneGridLayout.setState({
- children: [...sceneGridLayout.state.children, newGridItem],
- });
+ this.state.body.duplicatePanel(vizPanel);
}
public copyPanel(vizPanel: VizPanel) {
@@ -643,83 +499,23 @@ export class DashboardScene extends SceneObjectBase {
}
public pastePanel() {
- if (!(this.state.body instanceof SceneGridLayout)) {
- throw new Error('Trying to add a panel in a layout that is not SceneGridLayout');
- }
-
const jsonData = store.get(LS_PANEL_COPY_KEY);
const jsonObj = JSON.parse(jsonData);
const panelModel = new PanelModel(jsonObj);
const gridItem = buildGridItemForPanel(panelModel);
-
- const sceneGridLayout = this.state.body;
-
- if (!(gridItem instanceof DashboardGridItem)) {
- throw new Error('Cannot paste invalid grid item');
- }
-
const panelId = dashboardSceneGraph.getNextPanelId(this);
+ const panel = gridItem.state.body;
- gridItem.state.body.setState({
- key: getVizPanelKeyForPanelId(panelId),
- });
+ panel.setState({ key: getVizPanelKeyForPanelId(panelId) });
+ panel.clearParent();
- gridItem.setState({
- height: NEW_PANEL_HEIGHT,
- width: NEW_PANEL_WIDTH,
- x: 0,
- y: 0,
- key: `grid-item-${panelId}`,
- });
-
- sceneGridLayout.setState({
- children: [gridItem, ...sceneGridLayout.state.children],
- });
+ this.state.body.addPanel(panel);
store.delete(LS_PANEL_COPY_KEY);
}
public removePanel(panel: VizPanel) {
- const panels: SceneObject[] = [];
- const key = panel.parent?.state.key;
-
- if (!key) {
- return;
- }
-
- let row: SceneGridRow | undefined;
-
- try {
- row = sceneGraph.getAncestor(panel, SceneGridRow);
- } catch {
- row = undefined;
- }
-
- if (row) {
- row.state.children.forEach((child: SceneObject) => {
- if (child.state.key !== key) {
- panels.push(child);
- }
- });
-
- row.setState({ children: panels });
-
- this.state.body.forceRender();
-
- return;
- }
-
- this.state.body.forEachChild((child: SceneObject) => {
- if (child.state.key !== key) {
- panels.push(child);
- }
- });
-
- const layout = this.state.body;
-
- if (layout instanceof SceneGridLayout || layout instanceof SceneFlexLayout) {
- layout.setState({ children: panels });
- }
+ this.state.body.removePanel(panel);
}
public unlinkLibraryPanel(panel: VizPanel) {
@@ -775,18 +571,10 @@ export class DashboardScene extends SceneObjectBase {
}
public onCreateNewRow() {
- const row = getDefaultRow(this);
-
- this.addRow(row);
-
- return getPanelIdForVizPanel(row);
+ this.state.body.addNewRow();
}
public onCreateNewPanel(): VizPanel {
- if (!this.state.isEditing) {
- this.onEnterEditMode();
- }
-
const vizPanel = getDefaultVizPanel(this);
this.addPanel(vizPanel);
@@ -852,40 +640,6 @@ export class DashboardScene extends SceneObjectBase {
locationService.replace('/');
}
- public collapseAllRows() {
- if (!(this.state.body instanceof SceneGridLayout)) {
- throw new Error('Dashboard scene layout is not SceneGridLayout');
- }
-
- const sceneGridLayout = this.state.body;
-
- sceneGridLayout.state.children.forEach((child) => {
- if (!(child instanceof SceneGridRow)) {
- return;
- }
- if (!child.state.isCollapsed) {
- sceneGridLayout.toggleRow(child);
- }
- });
- }
-
- public expandAllRows() {
- if (!(this.state.body instanceof SceneGridLayout)) {
- throw new Error('Dashboard scene layout is not SceneGridLayout');
- }
-
- const sceneGridLayout = this.state.body;
-
- sceneGridLayout.state.children.forEach((child) => {
- if (!(child instanceof SceneGridRow)) {
- return;
- }
- if (child.state.isCollapsed) {
- sceneGridLayout.toggleRow(child);
- }
- });
- }
-
public onSetScrollRef = (scrollElement: ScrollRefElement): void => {
this._scrollRef = scrollElement;
};
@@ -925,11 +679,11 @@ export class DashboardVariableDependency implements SceneVariableDependencyConfi
* The first repeated row has the row repeater behavior but it also has a local SceneVariableSet with a local variable value
*/
const layout = this._dashboard.state.body;
- if (!(layout instanceof SceneGridLayout)) {
+ if (!(layout instanceof DefaultGridLayoutManager)) {
return;
}
- for (const child of layout.state.children) {
+ for (const child of layout.state.grid.state.children) {
if (!(child instanceof SceneGridRow) || !child.state.$behaviors) {
continue;
}
diff --git a/public/app/features/dashboard-scene/scene/DashboardSceneUrlSync.test.ts b/public/app/features/dashboard-scene/scene/DashboardSceneUrlSync.test.ts
index c1a7172044f..e676a90930e 100644
--- a/public/app/features/dashboard-scene/scene/DashboardSceneUrlSync.test.ts
+++ b/public/app/features/dashboard-scene/scene/DashboardSceneUrlSync.test.ts
@@ -1,10 +1,11 @@
import { AppEvents } from '@grafana/data';
-import { SceneGridLayout, SceneQueryRunner, VizPanel } from '@grafana/scenes';
+import { SceneQueryRunner, VizPanel } from '@grafana/scenes';
import appEvents from 'app/core/app_events';
import { KioskMode } from 'app/types';
import { DashboardGridItem } from './DashboardGridItem';
import { DashboardScene } from './DashboardScene';
+import { DefaultGridLayoutManager } from './layout-default/DefaultGridLayoutManager';
import { DashboardRepeatsProcessedEvent } from './types';
describe('DashboardSceneUrlSync', () => {
@@ -30,14 +31,17 @@ describe('DashboardSceneUrlSync', () => {
it('Should set UNSAFE_fitPanels when url has autofitpanels', () => {
const scene = buildTestScene();
scene.urlSync?.updateFromUrl({ autofitpanels: '' });
- expect((scene.state.body as SceneGridLayout).state.UNSAFE_fitPanels).toBe(true);
+ const layout = scene.state.body as DefaultGridLayoutManager;
+
+ expect(layout.state.grid.state.UNSAFE_fitPanels).toBe(true);
});
it('Should get the autofitpanels from the scene state', () => {
const scene = buildTestScene();
expect(scene.urlSync?.getUrlState().autofitpanels).toBeUndefined();
- (scene.state.body as SceneGridLayout).setState({ UNSAFE_fitPanels: true });
+ const layout = scene.state.body as DefaultGridLayoutManager;
+ layout.state.grid.setState({ UNSAFE_fitPanels: true });
expect(scene.urlSync?.getUrlState().autofitpanels).toBe('true');
});
@@ -89,8 +93,9 @@ describe('DashboardSceneUrlSync', () => {
expect(errorNotice).toBe(0);
// fake adding clone panel
- const layout = scene.state.body as SceneGridLayout;
- layout.setState({
+ const layout = scene.state.body as DefaultGridLayoutManager;
+
+ layout.state.grid.setState({
children: [
new DashboardGridItem({
key: 'griditem-1',
@@ -114,27 +119,20 @@ function buildTestScene() {
const scene = new DashboardScene({
title: 'hello',
uid: 'dash-1',
- body: new SceneGridLayout({
- children: [
- new DashboardGridItem({
- key: 'griditem-1',
- x: 0,
- body: new VizPanel({
- title: 'Panel A',
- key: 'panel-1',
- pluginId: 'table',
- $data: new SceneQueryRunner({ key: 'data-query-runner', queries: [{ refId: 'A' }] }),
- }),
- }),
- new DashboardGridItem({
- body: new VizPanel({
- title: 'Panel B',
- key: 'panel-2',
- pluginId: 'table',
- }),
- }),
- ],
- }),
+ body: DefaultGridLayoutManager.fromVizPanels([
+ new VizPanel({
+ title: 'Panel A',
+ key: 'panel-1',
+ pluginId: 'table',
+ $data: new SceneQueryRunner({ key: 'data-query-runner', queries: [{ refId: 'A' }] }),
+ }),
+
+ new VizPanel({
+ title: 'Panel B',
+ key: 'panel-2',
+ pluginId: 'table',
+ }),
+ ]),
});
return scene;
diff --git a/public/app/features/dashboard-scene/scene/DashboardSceneUrlSync.ts b/public/app/features/dashboard-scene/scene/DashboardSceneUrlSync.ts
index 21335a1904d..2892c555d07 100644
--- a/public/app/features/dashboard-scene/scene/DashboardSceneUrlSync.ts
+++ b/public/app/features/dashboard-scene/scene/DashboardSceneUrlSync.ts
@@ -2,7 +2,7 @@ import { Unsubscribable } from 'rxjs';
import { AppEvents } from '@grafana/data';
import { config, locationService } from '@grafana/runtime';
-import { SceneGridLayout, SceneObjectUrlSyncHandler, SceneObjectUrlValues, VizPanel } from '@grafana/scenes';
+import { SceneObjectUrlSyncHandler, SceneObjectUrlValues, VizPanel } from '@grafana/scenes';
import appEvents from 'app/core/app_events';
import { KioskMode } from 'app/types';
@@ -16,6 +16,7 @@ import { findVizPanelByKey, getLibraryPanelBehavior, isPanelClone } from '../uti
import { DashboardScene, DashboardSceneState } from './DashboardScene';
import { LibraryPanelBehavior } from './LibraryPanelBehavior';
import { ViewPanelScene } from './ViewPanelScene';
+import { DefaultGridLayoutManager } from './layout-default/DefaultGridLayoutManager';
import { DashboardRepeatsProcessedEvent } from './types';
export class DashboardSceneUrlSync implements SceneObjectUrlSyncHandler {
@@ -29,9 +30,10 @@ export class DashboardSceneUrlSync implements SceneObjectUrlSyncHandler {
getUrlState(): SceneObjectUrlValues {
const state = this._scene.state;
+
return {
inspect: state.inspectPanelKey,
- autofitpanels: state.body instanceof SceneGridLayout && !!state.body.state.UNSAFE_fitPanels ? 'true' : undefined,
+ autofitpanels: this.getAutoFitPanels(),
viewPanel: state.viewPanelScene?.getUrlKey(),
editview: state.editview?.getUrlKey(),
editPanel: state.editPanel?.getUrlKey() || undefined,
@@ -40,6 +42,14 @@ export class DashboardSceneUrlSync implements SceneObjectUrlSyncHandler {
};
}
+ private getAutoFitPanels(): string | undefined {
+ if (this._scene.state.body instanceof DefaultGridLayoutManager) {
+ return this._scene.state.body.state.grid.state.UNSAFE_fitPanels ? 'true' : undefined;
+ }
+
+ return undefined;
+ }
+
updateFromUrl(values: SceneObjectUrlValues): void {
const { inspectPanelKey, viewPanelScene, isEditing, editPanel, shareView } = this._scene.state;
const update: Partial = {};
@@ -142,11 +152,12 @@ export class DashboardSceneUrlSync implements SceneObjectUrlSyncHandler {
update.shareView = undefined;
}
- if (this._scene.state.body instanceof SceneGridLayout) {
+ const layout = this._scene.state.body;
+ if (layout instanceof DefaultGridLayoutManager) {
const UNSAFE_fitPanels = typeof values.autofitpanels === 'string';
- if (!!this._scene.state.body.state.UNSAFE_fitPanels !== UNSAFE_fitPanels) {
- this._scene.state.body.setState({ UNSAFE_fitPanels });
+ if (!!layout.state.grid.state.UNSAFE_fitPanels !== UNSAFE_fitPanels) {
+ layout.state.grid.setState({ UNSAFE_fitPanels });
}
}
diff --git a/public/app/features/dashboard-scene/scene/LibraryPanelBehavior.test.tsx b/public/app/features/dashboard-scene/scene/LibraryPanelBehavior.test.tsx
index 7a558d8c5f5..78378863e89 100644
--- a/public/app/features/dashboard-scene/scene/LibraryPanelBehavior.test.tsx
+++ b/public/app/features/dashboard-scene/scene/LibraryPanelBehavior.test.tsx
@@ -13,6 +13,7 @@ import { activateFullSceneTree } from '../utils/test-utils';
import { DashboardGridItem } from './DashboardGridItem';
import { DashboardScene } from './DashboardScene';
import { LibraryPanelBehavior } from './LibraryPanelBehavior';
+import { DefaultGridLayoutManager } from './layout-default/DefaultGridLayoutManager';
setPluginImportUtils({
importPanelPlugin: (id: string) => Promise.resolve(getPanelPlugin({})),
@@ -170,8 +171,10 @@ async function buildTestSceneWithLibraryPanel() {
meta: {
canEdit: true,
},
- body: new SceneGridLayout({
- children: [gridItem],
+ body: new DefaultGridLayoutManager({
+ grid: new SceneGridLayout({
+ children: [gridItem],
+ }),
}),
});
diff --git a/public/app/features/dashboard-scene/scene/NavToolbarActions.test.tsx b/public/app/features/dashboard-scene/scene/NavToolbarActions.test.tsx
index 39048ed5724..468bcc5adf9 100644
--- a/public/app/features/dashboard-scene/scene/NavToolbarActions.test.tsx
+++ b/public/app/features/dashboard-scene/scene/NavToolbarActions.test.tsx
@@ -5,15 +5,15 @@ import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
import { selectors } from '@grafana/e2e-selectors';
import { LocationServiceProvider, config, locationService } from '@grafana/runtime';
-import { SceneGridLayout, SceneQueryRunner, SceneTimeRange, UrlSyncContextProvider, VizPanel } from '@grafana/scenes';
+import { SceneQueryRunner, SceneTimeRange, UrlSyncContextProvider, VizPanel } from '@grafana/scenes';
import { playlistSrv } from 'app/features/playlist/PlaylistSrv';
import { DashboardMeta } from 'app/types';
import { buildPanelEditScene } from '../panel-edit/PanelEditor';
-import { DashboardGridItem } from './DashboardGridItem';
import { DashboardScene } from './DashboardScene';
import { ToolbarActions } from './NavToolbarActions';
+import { DefaultGridLayoutManager } from './layout-default/DefaultGridLayoutManager';
jest.mock('app/features/playlist/PlaylistSrv', () => ({
playlistSrv: {
@@ -120,9 +120,8 @@ describe('NavToolbarActions', () => {
await act(() => {
dashboard.onEnterEditMode();
- const editingPanel = ((dashboard.state.body as SceneGridLayout).state.children[0] as DashboardGridItem).state
- .body as VizPanel;
- dashboard.setState({ editPanel: buildPanelEditScene(editingPanel, true) });
+ const panel = dashboard.state.body.getVizPanels()[0];
+ dashboard.setState({ editPanel: buildPanelEditScene(panel, true) });
});
expect(await screen.findByText('Save dashboard')).toBeInTheDocument();
@@ -135,9 +134,8 @@ describe('NavToolbarActions', () => {
await act(() => {
dashboard.onEnterEditMode();
- const editingPanel = ((dashboard.state.body as SceneGridLayout).state.children[0] as DashboardGridItem).state
- .body as VizPanel;
- dashboard.setState({ editPanel: buildPanelEditScene(editingPanel) });
+ const panel = dashboard.state.body.getVizPanels()[0];
+ dashboard.setState({ editPanel: buildPanelEditScene(panel) });
});
expect(await screen.findByText('Save dashboard')).toBeInTheDocument();
@@ -207,27 +205,19 @@ function setup(meta?: DashboardMeta) {
},
title: 'hello',
uid: 'dash-1',
- body: new SceneGridLayout({
- children: [
- new DashboardGridItem({
- key: 'griditem-1',
- x: 0,
- body: new VizPanel({
- title: 'Panel A',
- key: 'panel-1',
- pluginId: 'table',
- $data: new SceneQueryRunner({ key: 'data-query-runner', queries: [{ refId: 'A' }] }),
- }),
- }),
- new DashboardGridItem({
- body: new VizPanel({
- title: 'Panel B',
- key: 'panel-2',
- pluginId: 'table',
- }),
- }),
- ],
- }),
+ body: DefaultGridLayoutManager.fromVizPanels([
+ new VizPanel({
+ title: 'Panel A',
+ key: 'panel-1',
+ pluginId: 'table',
+ $data: new SceneQueryRunner({ key: 'data-query-runner', queries: [{ refId: 'A' }] }),
+ }),
+ new VizPanel({
+ title: 'Panel B',
+ key: 'panel-2',
+ pluginId: 'table',
+ }),
+ ]),
});
const context = getGrafanaContextMock();
diff --git a/public/app/features/dashboard-scene/scene/PanelMenuBehavior.test.tsx b/public/app/features/dashboard-scene/scene/PanelMenuBehavior.test.tsx
index 5863a58fe47..5b0fa84910a 100644
--- a/public/app/features/dashboard-scene/scene/PanelMenuBehavior.test.tsx
+++ b/public/app/features/dashboard-scene/scene/PanelMenuBehavior.test.tsx
@@ -11,7 +11,6 @@ import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
import { getPluginLinkExtensions, locationService } from '@grafana/runtime';
import {
LocalValueVariable,
- SceneGridLayout,
SceneQueryRunner,
SceneTimeRange,
SceneVariableSet,
@@ -23,10 +22,10 @@ import { GetExploreUrlArguments } from 'app/core/utils/explore';
import { buildPanelEditScene } from '../panel-edit/PanelEditor';
-import { DashboardGridItem } from './DashboardGridItem';
import { DashboardScene } from './DashboardScene';
import { VizPanelLinks, VizPanelLinksMenu } from './PanelLinks';
import { panelMenuBehavior } from './PanelMenuBehavior';
+import { DefaultGridLayoutManager } from './layout-default/DefaultGridLayoutManager';
const mocks = {
contextSrv: jest.mocked(contextSrv),
@@ -588,18 +587,7 @@ async function buildTestScene(options: SceneOptions) {
canEdit: true,
isEmbedded: options.isEmbedded ?? false,
},
- body: new SceneGridLayout({
- children: [
- new DashboardGridItem({
- key: 'griditem-1',
- x: 0,
- y: 0,
- width: 10,
- height: 12,
- body: panel,
- }),
- ],
- }),
+ body: DefaultGridLayoutManager.fromVizPanels([panel]),
});
await new Promise((r) => setTimeout(r, 1));
diff --git a/public/app/features/dashboard-scene/scene/RowRepeaterBehavior.test.tsx b/public/app/features/dashboard-scene/scene/RowRepeaterBehavior.test.tsx
index f9cd350a9a7..a5b3c0a0b81 100644
--- a/public/app/features/dashboard-scene/scene/RowRepeaterBehavior.test.tsx
+++ b/public/app/features/dashboard-scene/scene/RowRepeaterBehavior.test.tsx
@@ -21,6 +21,7 @@ import { DashboardGridItem, RepeatDirection } from './DashboardGridItem';
import { DashboardScene } from './DashboardScene';
import { panelMenuBehavior, repeatPanelMenuBehavior } from './PanelMenuBehavior';
import { RowRepeaterBehavior } from './RowRepeaterBehavior';
+import { DefaultGridLayoutManager } from './layout-default/DefaultGridLayoutManager';
import { RowActions } from './row-actions/RowActions';
jest.mock('@grafana/runtime', () => ({
@@ -350,7 +351,7 @@ function buildScene(
}),
],
}),
- body: grid,
+ body: new DefaultGridLayoutManager({ grid }),
});
const rowToRepeat = repeatBehavior.parent as SceneGridRow;
diff --git a/public/app/features/dashboard-scene/scene/ViewPanelScene.test.tsx b/public/app/features/dashboard-scene/scene/ViewPanelScene.test.tsx
index 5919e5c432c..47f6a3307d0 100644
--- a/public/app/features/dashboard-scene/scene/ViewPanelScene.test.tsx
+++ b/public/app/features/dashboard-scene/scene/ViewPanelScene.test.tsx
@@ -3,6 +3,7 @@ import { LocalValueVariable, SceneGridLayout, SceneGridRow, SceneVariableSet, Vi
import { DashboardGridItem } from './DashboardGridItem';
import { DashboardScene } from './DashboardScene';
import { ViewPanelScene } from './ViewPanelScene';
+import { DefaultGridLayoutManager } from './layout-default/DefaultGridLayoutManager';
jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'),
@@ -32,23 +33,25 @@ function buildScene(options?: SceneOptions) {
});
const dashboard = new DashboardScene({
- body: new SceneGridLayout({
- children: [
- new SceneGridRow({
- x: 0,
- y: 10,
- width: 24,
- $variables: new SceneVariableSet({
- variables: [new LocalValueVariable({ value: 'row-var-value' })],
- }),
- height: 1,
- children: [
- new DashboardGridItem({
- body: panel,
+ body: new DefaultGridLayoutManager({
+ grid: new SceneGridLayout({
+ children: [
+ new SceneGridRow({
+ x: 0,
+ y: 10,
+ width: 24,
+ $variables: new SceneVariableSet({
+ variables: [new LocalValueVariable({ value: 'row-var-value' })],
}),
- ],
- }),
- ],
+ height: 1,
+ children: [
+ new DashboardGridItem({
+ body: panel,
+ }),
+ ],
+ }),
+ ],
+ }),
}),
});
diff --git a/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts b/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts
index 4e025631e01..10fc2feac74 100644
--- a/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts
+++ b/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts
@@ -14,6 +14,7 @@ import { getPanelIdForVizPanel } from '../utils/utils';
import { DashboardScene } from './DashboardScene';
import { onRemovePanel, toggleVizPanelLegend } from './PanelMenuBehavior';
+import { DefaultGridLayoutManager } from './layout-default/DefaultGridLayoutManager';
export function setupKeyboardShortcuts(scene: DashboardScene) {
const keybindings = new KeybindingSet();
@@ -214,7 +215,9 @@ export function setupKeyboardShortcuts(scene: DashboardScene) {
keybindings.addBinding({
key: 'd shift+c',
onTrigger: () => {
- scene.collapseAllRows();
+ if (scene.state.body instanceof DefaultGridLayoutManager) {
+ scene.state.body.collapseAllRows();
+ }
},
});
@@ -222,7 +225,9 @@ export function setupKeyboardShortcuts(scene: DashboardScene) {
keybindings.addBinding({
key: 'd shift+e',
onTrigger: () => {
- scene.expandAllRows();
+ if (scene.state.body instanceof DefaultGridLayoutManager) {
+ scene.state.body.expandAllRows();
+ }
},
});
}
diff --git a/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.test.tsx b/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.test.tsx
new file mode 100644
index 00000000000..37f25cc54d0
--- /dev/null
+++ b/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.test.tsx
@@ -0,0 +1,281 @@
+import { SceneGridItemLike, SceneGridLayout, SceneGridRow, SceneQueryRunner, VizPanel } from '@grafana/scenes';
+
+import { findVizPanelByKey } from '../../utils/utils';
+import { DashboardGridItem } from '../DashboardGridItem';
+
+import { DefaultGridLayoutManager } from './DefaultGridLayoutManager';
+
+describe('DefaultGridLayoutManager', () => {
+ describe('getVizPanels', () => {
+ it('Should return all panels', () => {
+ const { manager } = setup();
+ const vizPanels = manager.getVizPanels();
+
+ expect(vizPanels.length).toBe(4);
+ expect(vizPanels[0].state.title).toBe('Panel A');
+ expect(vizPanels[1].state.title).toBe('Panel B');
+ expect(vizPanels[2].state.title).toBe('Panel C');
+ expect(vizPanels[3].state.title).toBe('Panel D');
+ });
+
+ it('Should return an empty array when scene has no panels', () => {
+ const { manager } = setup({ gridItems: [] });
+ const vizPanels = manager.getVizPanels();
+ expect(vizPanels.length).toBe(0);
+ });
+ });
+
+ describe('getNextPanelId', () => {
+ it('should get next panel id in a simple 3 panel layout', () => {
+ const { manager } = setup();
+ const id = manager.getNextPanelId();
+
+ expect(id).toBe(4);
+ });
+
+ it('should return 1 if no panels are found', () => {
+ const { manager } = setup({ gridItems: [] });
+ const id = manager.getNextPanelId();
+
+ expect(id).toBe(1);
+ });
+ });
+
+ describe('addPanel', () => {
+ it('Should add a new panel', () => {
+ const { manager } = setup();
+
+ const vizPanel = new VizPanel({
+ title: 'Panel Title',
+ key: 'panel-55',
+ pluginId: 'timeseries',
+ $data: new SceneQueryRunner({ key: 'data-query-runner', queries: [{ refId: 'A' }] }),
+ });
+
+ manager.addPanel(vizPanel);
+
+ const panel = findVizPanelByKey(manager, 'panel-55')!;
+ const gridItem = panel.parent as DashboardGridItem;
+
+ expect(panel).toBeDefined();
+ expect(gridItem.state.y).toBe(0);
+ });
+ });
+
+ describe('addNewRow', () => {
+ it('Should create and add a new row to the dashboard', () => {
+ const { manager, grid } = setup();
+ const row = manager.addNewRow();
+
+ expect(grid.state.children.length).toBe(2);
+ expect(row.state.key).toBe('panel-4');
+ expect(row.state.children[0].state.key).toBe('griditem-1');
+ expect(row.state.children[1].state.key).toBe('griditem-2');
+ });
+
+ it('Should create a row and add all panels in the dashboard under it', () => {
+ const { manager, grid } = setup({
+ gridItems: [
+ new DashboardGridItem({
+ key: 'griditem-1',
+ x: 0,
+ body: new VizPanel({
+ title: 'Panel A',
+ key: 'panel-1',
+ pluginId: 'table',
+ $data: new SceneQueryRunner({ key: 'data-query-runner', queries: [{ refId: 'A' }] }),
+ }),
+ }),
+ new DashboardGridItem({
+ key: 'griditem-2',
+ body: new VizPanel({
+ title: 'Panel B',
+ key: 'panel-2',
+ pluginId: 'table',
+ }),
+ }),
+ ],
+ });
+
+ const row = manager.addNewRow();
+
+ expect(grid.state.children.length).toBe(1);
+ expect(row.state.children.length).toBe(2);
+ });
+
+ it('Should create and add two new rows, but the second has no children', () => {
+ const { manager, grid } = setup();
+ const row1 = manager.addNewRow();
+ const row2 = manager.addNewRow();
+
+ expect(grid.state.children.length).toBe(3);
+ expect(row1.state.children.length).toBe(2);
+ expect(row2.state.children.length).toBe(0);
+ });
+
+ it('Should create an empty row when nothing else in dashboard', () => {
+ const { manager, grid } = setup({ gridItems: [] });
+ const row = manager.addNewRow();
+
+ expect(grid.state.children.length).toBe(1);
+ expect(row.state.children.length).toBe(0);
+ });
+ });
+
+ describe('Remove row', () => {
+ it('Should remove a row and move its children to the grid layout', () => {
+ const { manager, grid } = setup();
+ const row = grid.state.children[2] as SceneGridRow;
+
+ manager.removeRow(row);
+
+ expect(grid.state.children.length).toBe(4);
+ });
+
+ it('Should remove a row and its children', () => {
+ const { manager, grid } = setup();
+ const row = grid.state.children[2] as SceneGridRow;
+
+ manager.removeRow(row, true);
+
+ expect(grid.state.children.length).toBe(2);
+ });
+
+ it('Should remove an empty row from the layout', () => {
+ const row = new SceneGridRow({ key: 'panel-1' });
+ const { manager, grid } = setup({ gridItems: [row] });
+
+ manager.removeRow(row);
+
+ expect(grid.state.children.length).toBe(0);
+ });
+ });
+
+ describe('removePanel', () => {
+ it('Should remove grid item', () => {
+ const { manager } = setup();
+ const panel = findVizPanelByKey(manager, 'panel-1')!;
+ manager.removePanel(panel);
+
+ expect(findVizPanelByKey(manager, 'panel-1')).toBeNull();
+ });
+
+ it('Should remove a grid item within a row', () => {
+ const { manager, grid } = setup();
+ const vizPanel = findVizPanelByKey(manager, 'panel-within-row1')!;
+
+ manager.removePanel(vizPanel);
+
+ const gridRow = grid.state.children[2] as SceneGridRow;
+ expect(gridRow.state.children.length).toBe(1);
+ });
+ });
+
+ describe('duplicatePanel', () => {
+ it('Should duplicate a panel', () => {
+ const { manager, grid } = setup();
+ const vizPanel = findVizPanelByKey(manager, 'panel-1')!;
+
+ expect(grid.state.children.length).toBe(3);
+
+ manager.duplicatePanel(vizPanel);
+
+ const newGridItem = grid.state.children[3];
+
+ expect(grid.state.children.length).toBe(4);
+ expect(newGridItem.state.key).toBe('grid-item-4');
+ });
+
+ it('Should maintain size of duplicated panel', () => {
+ const { manager, grid } = setup();
+
+ const gItem = grid.state.children[0] as DashboardGridItem;
+ gItem.setState({ height: 1 });
+
+ const vizPanel = gItem.state.body;
+ manager.duplicatePanel(vizPanel);
+
+ const newGridItem = grid.state.children[grid.state.children.length - 1] as DashboardGridItem;
+
+ expect(newGridItem.state.height).toBe(1);
+ });
+
+ it('Should duplicate a repeated panel', () => {
+ const { manager, grid } = setup();
+ const gItem = grid.state.children[0] as DashboardGridItem;
+ gItem.setState({ variableName: 'server', repeatDirection: 'v', maxPerRow: 100 });
+ const vizPanel = gItem.state.body;
+ manager.duplicatePanel(vizPanel as VizPanel);
+
+ const newGridItem = grid.state.children[grid.state.children.length - 1] as DashboardGridItem;
+
+ expect(newGridItem.state.variableName).toBe('server');
+ expect(newGridItem.state.repeatDirection).toBe('v');
+ expect(newGridItem.state.maxPerRow).toBe(100);
+ });
+
+ it('Should duplicate a panel in a row', () => {
+ const { manager } = setup();
+ const vizPanel = findVizPanelByKey(manager, 'panel-within-row1')!;
+ const gridRow = vizPanel.parent?.parent as SceneGridRow;
+
+ expect(gridRow.state.children.length).toBe(2);
+
+ manager.duplicatePanel(vizPanel);
+
+ expect(gridRow.state.children.length).toBe(3);
+ });
+ });
+});
+
+interface TestOptions {
+ gridItems: SceneGridItemLike[];
+}
+
+function setup(options?: TestOptions) {
+ const gridItems = options?.gridItems ?? [
+ new DashboardGridItem({
+ key: 'griditem-1',
+ x: 0,
+ body: new VizPanel({
+ title: 'Panel A',
+ key: 'panel-1',
+ pluginId: 'table',
+ $data: new SceneQueryRunner({ key: 'data-query-runner', queries: [{ refId: 'A' }] }),
+ }),
+ }),
+ new DashboardGridItem({
+ key: 'griditem-2',
+ body: new VizPanel({
+ title: 'Panel B',
+ key: 'panel-2',
+ pluginId: 'table',
+ }),
+ }),
+ new SceneGridRow({
+ key: 'panel-3',
+ title: 'row',
+ children: [
+ new DashboardGridItem({
+ body: new VizPanel({
+ title: 'Panel C',
+ key: 'panel-within-row1',
+ pluginId: 'table',
+ }),
+ }),
+ new DashboardGridItem({
+ body: new VizPanel({
+ title: 'Panel D',
+ key: 'panel-within-row2',
+ pluginId: 'table',
+ }),
+ }),
+ ],
+ }),
+ ];
+
+ const grid = new SceneGridLayout({ children: gridItems });
+ const manager = new DefaultGridLayoutManager({ grid: grid });
+
+ return { manager, grid };
+}
diff --git a/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx
new file mode 100644
index 00000000000..6728a02e669
--- /dev/null
+++ b/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx
@@ -0,0 +1,355 @@
+import {
+ SceneObjectState,
+ SceneGridLayout,
+ SceneObjectBase,
+ SceneGridRow,
+ VizPanel,
+ sceneGraph,
+ sceneUtils,
+ SceneComponentProps,
+} from '@grafana/scenes';
+import { GRID_COLUMN_COUNT } from 'app/core/constants';
+
+import {
+ forceRenderChildren,
+ getPanelIdForVizPanel,
+ NEW_PANEL_HEIGHT,
+ NEW_PANEL_WIDTH,
+ getVizPanelKeyForPanelId,
+} from '../../utils/utils';
+import { DashboardGridItem } from '../DashboardGridItem';
+import { RowRepeaterBehavior } from '../RowRepeaterBehavior';
+import { RowActions } from '../row-actions/RowActions';
+import { DashboardLayoutManager } from '../types';
+
+interface DefaultGridLayoutManagerState extends SceneObjectState {
+ grid: SceneGridLayout;
+}
+
+/**
+ * State manager for the default grid layout
+ */
+export class DefaultGridLayoutManager
+ extends SceneObjectBase
+ implements DashboardLayoutManager
+{
+ public editModeChanged(isEditing: boolean): void {
+ this.state.grid.setState({ isDraggable: isEditing, isResizable: isEditing });
+ forceRenderChildren(this.state.grid, true);
+ }
+
+ /**
+ * Removes the first panel
+ */
+ public cleanUpStateFromExplore(): void {
+ this.state.grid.setState({
+ children: this.state.grid.state.children.slice(1),
+ });
+ }
+
+ public addPanel(vizPanel: VizPanel): void {
+ const panelId = getPanelIdForVizPanel(vizPanel);
+ const newGridItem = new DashboardGridItem({
+ height: NEW_PANEL_HEIGHT,
+ width: NEW_PANEL_WIDTH,
+ x: 0,
+ y: 0,
+ body: vizPanel,
+ key: `grid-item-${panelId}`,
+ });
+
+ this.state.grid.setState({
+ children: [newGridItem, ...this.state.grid.state.children],
+ });
+ }
+
+ /**
+ * Adds a new emtpy row
+ */
+ public addNewRow(): SceneGridRow {
+ const id = this.getNextPanelId();
+ const row = new SceneGridRow({
+ key: getVizPanelKeyForPanelId(id),
+ title: 'Row title',
+ actions: new RowActions({}),
+ y: 0,
+ });
+
+ const sceneGridLayout = this.state.grid;
+
+ // find all panels until the first row and put them into the newly created row. If there are no other rows,
+ // add all panels to the row. If there are no panels just create an empty row
+ const indexTillNextRow = sceneGridLayout.state.children.findIndex((child) => child instanceof SceneGridRow);
+ const rowChildren = sceneGridLayout.state.children
+ .splice(0, indexTillNextRow === -1 ? sceneGridLayout.state.children.length : indexTillNextRow)
+ .map((child) => child.clone());
+
+ if (rowChildren) {
+ row.setState({ children: rowChildren });
+ }
+
+ sceneGridLayout.setState({ children: [row, ...sceneGridLayout.state.children] });
+
+ return row;
+ }
+
+ /**
+ * Removes a row
+ * @param row
+ * @param removePanels
+ */
+ public removeRow(row: SceneGridRow, removePanels = false) {
+ const sceneGridLayout = this.state.grid;
+
+ const children = sceneGridLayout.state.children.filter((child) => child.state.key !== row.state.key);
+
+ if (!removePanels) {
+ const rowChildren = row.state.children.map((child) => child.clone());
+ const indexOfRow = sceneGridLayout.state.children.findIndex((child) => child.state.key === row.state.key);
+
+ children.splice(indexOfRow, 0, ...rowChildren);
+ }
+
+ sceneGridLayout.setState({ children });
+ }
+
+ /**
+ * Removes a panel
+ */
+ public removePanel(panel: VizPanel) {
+ const gridItem = panel.parent!;
+
+ if (!(gridItem instanceof DashboardGridItem)) {
+ throw new Error('Trying to remove panel that is not inside a DashboardGridItem');
+ }
+
+ const layout = this.state.grid;
+
+ let row: SceneGridRow | undefined;
+
+ try {
+ row = sceneGraph.getAncestor(gridItem, SceneGridRow);
+ } catch {
+ row = undefined;
+ }
+
+ if (row) {
+ row.setState({ children: row.state.children.filter((child) => child !== gridItem) });
+ layout.forceRender();
+ return;
+ }
+
+ this.state.grid.setState({
+ children: layout.state.children.filter((child) => child !== gridItem),
+ });
+ }
+
+ public duplicatePanel(vizPanel: VizPanel): void {
+ const gridItem = vizPanel.parent;
+ if (!(gridItem instanceof DashboardGridItem)) {
+ console.error('Trying to duplicate a panel that is not inside a DashboardGridItem');
+ return;
+ }
+
+ let panelState;
+ let panelData;
+ let newGridItem;
+
+ const newPanelId = this.getNextPanelId();
+ const grid = this.state.grid;
+
+ if (gridItem instanceof DashboardGridItem) {
+ panelState = sceneUtils.cloneSceneObjectState(gridItem.state.body.state);
+ panelData = sceneGraph.getData(gridItem.state.body).clone();
+ } else {
+ panelState = sceneUtils.cloneSceneObjectState(vizPanel.state);
+ panelData = sceneGraph.getData(vizPanel).clone();
+ }
+
+ // when we duplicate a panel we don't want to clone the alert state
+ delete panelData.state.data?.alertState;
+
+ newGridItem = new DashboardGridItem({
+ x: gridItem.state.x,
+ y: gridItem.state.y,
+ height: gridItem.state.height,
+ width: gridItem.state.width,
+ variableName: gridItem.state.variableName,
+ repeatDirection: gridItem.state.repeatDirection,
+ maxPerRow: gridItem.state.maxPerRow,
+ key: `grid-item-${newPanelId}`,
+ body: new VizPanel({ ...panelState, $data: panelData, key: getVizPanelKeyForPanelId(newPanelId) }),
+ });
+
+ if (gridItem.parent instanceof SceneGridRow) {
+ const row = gridItem.parent;
+
+ row.setState({ children: [...row.state.children, newGridItem] });
+
+ grid.forceRender();
+ return;
+ }
+
+ grid.setState({ children: [...grid.state.children, newGridItem] });
+ }
+
+ public getVizPanels(): VizPanel[] {
+ const panels: VizPanel[] = [];
+
+ this.state.grid.forEachChild((child) => {
+ if (!(child instanceof DashboardGridItem) && !(child instanceof SceneGridRow)) {
+ throw new Error('Child is not a DashboardGridItem or SceneGridRow, invalid scene');
+ }
+
+ if (child instanceof DashboardGridItem) {
+ if (child.state.body instanceof VizPanel) {
+ panels.push(child.state.body);
+ }
+ } else if (child instanceof SceneGridRow) {
+ child.forEachChild((child) => {
+ if (child instanceof DashboardGridItem) {
+ if (child.state.body instanceof VizPanel) {
+ panels.push(child.state.body);
+ }
+ }
+ });
+ }
+ });
+
+ return panels;
+ }
+
+ public getNextPanelId(): number {
+ let max = 0;
+
+ for (const child of this.state.grid.state.children) {
+ if (child instanceof DashboardGridItem) {
+ const vizPanel = child.state.body;
+
+ if (vizPanel) {
+ const panelId = getPanelIdForVizPanel(vizPanel);
+
+ if (panelId > max) {
+ max = panelId;
+ }
+ }
+ }
+
+ if (child instanceof SceneGridRow) {
+ //rows follow the same key pattern --- e.g.: `panel-6`
+ const panelId = getPanelIdForVizPanel(child);
+
+ if (panelId > max) {
+ max = panelId;
+ }
+
+ for (const rowChild of child.state.children) {
+ if (rowChild instanceof DashboardGridItem) {
+ const vizPanel = rowChild.state.body;
+
+ if (vizPanel) {
+ const panelId = getPanelIdForVizPanel(vizPanel);
+
+ if (panelId > max) {
+ max = panelId;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return max + 1;
+ }
+
+ public collapseAllRows() {
+ this.state.grid.state.children.forEach((child) => {
+ if (!(child instanceof SceneGridRow)) {
+ return;
+ }
+ if (!child.state.isCollapsed) {
+ this.state.grid.toggleRow(child);
+ }
+ });
+ }
+
+ public expandAllRows() {
+ this.state.grid.state.children.forEach((child) => {
+ if (!(child instanceof SceneGridRow)) {
+ return;
+ }
+ if (child.state.isCollapsed) {
+ this.state.grid.toggleRow(child);
+ }
+ });
+ }
+
+ activateRepeaters(): void {
+ this.state.grid.forEachChild((child) => {
+ if (child instanceof DashboardGridItem && !child.isActive) {
+ child.activate();
+ return;
+ }
+
+ if (child instanceof SceneGridRow && child.state.$behaviors) {
+ for (const behavior of child.state.$behaviors) {
+ if (behavior instanceof RowRepeaterBehavior && !child.isActive) {
+ child.activate();
+ break;
+ }
+ }
+
+ child.state.children.forEach((child) => {
+ if (child instanceof DashboardGridItem && !child.isActive) {
+ child.activate();
+ return;
+ }
+ });
+ }
+ });
+ }
+
+ /**
+ * For simple test grids
+ * @param panels
+ */
+ public static fromVizPanels(panels: VizPanel[] = []): DefaultGridLayoutManager {
+ const children: DashboardGridItem[] = [];
+ const panelHeight = 10;
+ const panelWidth = GRID_COLUMN_COUNT / 3;
+ let currentY = 0;
+ let currentX = 0;
+
+ for (let panel of panels) {
+ children.push(
+ new DashboardGridItem({
+ key: `griditem-${getPanelIdForVizPanel(panel)}`,
+ x: currentX,
+ y: currentY,
+ width: panelWidth,
+ height: panelHeight,
+ body: panel,
+ })
+ );
+
+ currentX += panelWidth;
+
+ if (currentX + panelWidth >= GRID_COLUMN_COUNT) {
+ currentX = 0;
+ currentY += panelHeight;
+ }
+ }
+
+ return new DefaultGridLayoutManager({
+ grid: new SceneGridLayout({
+ children: children,
+ isDraggable: false,
+ isResizable: false,
+ }),
+ });
+ }
+
+ public static Component = ({ model }: SceneComponentProps) => {
+ return ;
+ };
+}
diff --git a/public/app/features/dashboard-scene/scene/row-actions/RowActions.tsx b/public/app/features/dashboard-scene/scene/row-actions/RowActions.tsx
index 9dadfd0cbad..a1cc833602b 100644
--- a/public/app/features/dashboard-scene/scene/row-actions/RowActions.tsx
+++ b/public/app/features/dashboard-scene/scene/row-actions/RowActions.tsx
@@ -1,7 +1,14 @@
import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
-import { SceneComponentProps, SceneGridRow, SceneObjectBase, SceneObjectState, VizPanel } from '@grafana/scenes';
+import {
+ SceneComponentProps,
+ sceneGraph,
+ SceneGridRow,
+ SceneObjectBase,
+ SceneObjectState,
+ VizPanel,
+} from '@grafana/scenes';
import { Icon, TextLink, useStyles2 } from '@grafana/ui';
import appEvents from 'app/core/app_events';
import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard';
@@ -11,6 +18,7 @@ import { getDashboardSceneFor, getQueryRunnerFor } from '../../utils/utils';
import { DashboardGridItem } from '../DashboardGridItem';
import { DashboardScene } from '../DashboardScene';
import { RowRepeaterBehavior } from '../RowRepeaterBehavior';
+import { DefaultGridLayoutManager } from '../layout-default/DefaultGridLayoutManager';
import { RowOptionsButton } from './RowOptionsButton';
@@ -29,6 +37,11 @@ export class RowActions extends SceneObjectBase {
return getDashboardSceneFor(this);
}
+ public removeRow(removePanels?: boolean) {
+ const manager = sceneGraph.getAncestor(this, DefaultGridLayoutManager);
+ manager.removeRow(this.getParent(), removePanels);
+ }
+
public onUpdate = (title: string, repeat?: string | null): void => {
const row = this.getParent();
let repeatBehavior: RowRepeaterBehavior | undefined;
@@ -60,12 +73,8 @@ export class RowActions extends SceneObjectBase {
text: 'Are you sure you want to remove this row and all its panels?',
altActionText: 'Delete row only',
icon: 'trash-alt',
- onConfirm: () => {
- this.getDashboard().removeRow(this.getParent(), true);
- },
- onAltAction: () => {
- this.getDashboard().removeRow(this.getParent());
- },
+ onConfirm: () => this.removeRow(true),
+ onAltAction: () => this.removeRow(),
})
);
};
diff --git a/public/app/features/dashboard-scene/scene/types.ts b/public/app/features/dashboard-scene/scene/types.ts
index 9bf602cc171..37a932ceb20 100644
--- a/public/app/features/dashboard-scene/scene/types.ts
+++ b/public/app/features/dashboard-scene/scene/types.ts
@@ -1,5 +1,89 @@
-import { BusEventWithPayload } from '@grafana/data';
-import { SceneObject } from '@grafana/scenes';
+import { BusEventWithPayload, RegistryItem } from '@grafana/data';
+import { SceneObject, VizPanel } from '@grafana/scenes';
+
+/**
+ * A scene object that usually wraps an underlying layout
+ * Dealing with all the state management and editing of the layout
+ */
+export interface DashboardLayoutManager extends SceneObject {
+ /**
+ * Notify the layout manager that the edit mode has changed
+ * @param isEditing
+ */
+ editModeChanged(isEditing: boolean): void;
+ /**
+ * We should be able to figure out how to add the explore panel in a way that leaves the
+ * initialSaveModel clean from it so we can leverage the default discard changes logic.
+ * Then we can get rid of this.
+ */
+ cleanUpStateFromExplore?(): void;
+ /**
+ * Not sure we will need this in the long run, we should be able to handle this inside internally
+ */
+ getNextPanelId(): number;
+ /**
+ * Remove an elemenet / panel
+ * @param element
+ */
+ removePanel(panel: VizPanel): void;
+ /**
+ * Creates a copy of an existing element and adds it to the layout
+ * @param element
+ */
+ duplicatePanel(panel: VizPanel): void;
+ /**
+ * Adds a new panel to the layout
+ */
+ addPanel(panel: VizPanel): void;
+ /**
+ * Add row
+ */
+ addNewRow(): void;
+ /**
+ * getVizPanels
+ */
+ getVizPanels(): VizPanel[];
+ /**
+ * Turn into a save model
+ * @param saveModel
+ */
+ toSaveModel?(): any;
+ /**
+ * For dynamic panels that need to be viewed in isolation (SoloRoute)
+ */
+ activateRepeaters?(): void;
+}
+
+/**
+ * The layout descriptor used when selecting / switching layouts
+ */
+export interface LayoutRegistryItem extends RegistryItem {
+ /**
+ * When switching between layouts
+ * @param currentLayout
+ */
+ createFromLayout(currentLayout: DashboardLayoutManager): DashboardLayoutManager;
+ /**
+ * Create from persisted state
+ * @param saveModel
+ */
+ createFromSaveModel?(saveModel: any): void;
+}
+
+export interface LayoutEditorProps {
+ layoutManager: T;
+}
+
+/**
+ * This interface is needed to support layouts existing on different levels of the scene (DashboardScene and inside the TabsLayoutManager)
+ */
+export interface LayoutParent extends SceneObject {
+ switchLayout(newLayout: DashboardLayoutManager): void;
+}
+
+export function isLayoutParent(obj: SceneObject): obj is LayoutParent {
+ return 'switchLayout' in obj;
+}
export interface DashboardRepeatsProcessedEventPayload {
source: SceneObject;
diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts
index 868dfd24768..37badeff929 100644
--- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts
+++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts
@@ -31,6 +31,7 @@ import { DashboardGridItem } from '../scene/DashboardGridItem';
import { LibraryPanelBehavior } from '../scene/LibraryPanelBehavior';
import { PanelTimeRange } from '../scene/PanelTimeRange';
import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior';
+import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLayoutManager';
import { NEW_LINK } from '../settings/links/utils';
import { getQueryRunnerFor } from '../utils/utils';
@@ -323,7 +324,8 @@ describe('transformSaveModelToScene', () => {
const oldModel = new DashboardModel(dashboard);
const scene = createDashboardSceneFromDashboardModel(oldModel, dashboard);
- const body = scene.state.body as SceneGridLayout;
+ const layout = scene.state.body as DefaultGridLayoutManager;
+ const body = layout.state.grid;
expect(body.state.children).toHaveLength(3);
const rowScene1 = body.state.children[0] as SceneGridRow;
@@ -375,7 +377,8 @@ describe('transformSaveModelToScene', () => {
const oldModel = new DashboardModel(dashboard);
const scene = createDashboardSceneFromDashboardModel(oldModel, dashboard);
- const body = scene.state.body as SceneGridLayout;
+ const layout = scene.state.body as DefaultGridLayoutManager;
+ const body = layout.state.grid;
expect(body.state.children).toHaveLength(1);
const rowScene = body.state.children[0] as SceneGridRow;
@@ -471,7 +474,8 @@ describe('transformSaveModelToScene', () => {
const oldModel = new DashboardModel(dashboard);
const scene = createDashboardSceneFromDashboardModel(oldModel, dashboard);
- const body = scene.state.body as SceneGridLayout;
+ const layout = scene.state.body as DefaultGridLayoutManager;
+ const body = layout.state.grid;
expect(body.state.children).toHaveLength(4);
expect(body).toBeInstanceOf(SceneGridLayout);
@@ -750,7 +754,9 @@ describe('transformSaveModelToScene', () => {
dashboard: repeatingRowsAndPanelsDashboardJson as DashboardDataDTO,
meta: {},
});
- const body = scene.state.body as SceneGridLayout;
+
+ const layout = scene.state.body as DefaultGridLayoutManager;
+ const body = layout.state.grid;
const row2 = body.state.children[1] as SceneGridRow;
expect(row2.state.$behaviors?.[0]).toBeInstanceOf(RowRepeaterBehavior);
diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts
index 0195625f24b..b5237a83683 100644
--- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts
+++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts
@@ -37,6 +37,7 @@ import { panelLinksBehavior, panelMenuBehavior } from '../scene/PanelMenuBehavio
import { PanelNotices } from '../scene/PanelNotices';
import { PanelTimeRange } from '../scene/PanelTimeRange';
import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior';
+import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLayoutManager';
import { RowActions } from '../scene/row-actions/RowActions';
import { setDashboardPanelContext } from '../scene/setDashboardPanelContext';
import { createPanelDataProvider } from '../utils/createPanelDataProvider';
@@ -221,10 +222,12 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel,
title: oldModel.title,
uid: oldModel.uid,
version: oldModel.version,
- body: new SceneGridLayout({
- isLazy: dto.preload ? false : true,
- children: createSceneObjectsForPanels(oldModel.panels),
- $behaviors: [trackIfEmpty],
+ body: new DefaultGridLayoutManager({
+ grid: new SceneGridLayout({
+ isLazy: dto.preload ? false : true,
+ children: createSceneObjectsForPanels(oldModel.panels),
+ $behaviors: [trackIfEmpty],
+ }),
}),
$timeRange: new SceneTimeRange({
from: oldModel.time.from,
diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts
index eef3e336c16..bbc6d3dbb7a 100644
--- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts
+++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts
@@ -15,7 +15,7 @@ import {
} from '@grafana/data';
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
import { getPluginLinkExtensions, setPluginImportUtils } from '@grafana/runtime';
-import { MultiValueVariable, sceneGraph, SceneGridLayout, SceneGridRow, VizPanel } from '@grafana/scenes';
+import { MultiValueVariable, sceneGraph, SceneGridRow, VizPanel } from '@grafana/scenes';
import { Dashboard, LoadingState, Panel, RowPanel, VariableRefresh } from '@grafana/schema';
import { PanelModel } from 'app/features/dashboard/state';
import { getTimeRange } from 'app/features/dashboard/utils/timeRange';
@@ -27,6 +27,7 @@ import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet';
import { DashboardGridItem } from '../scene/DashboardGridItem';
import { LibraryPanelBehavior } from '../scene/LibraryPanelBehavior';
import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior';
+import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLayoutManager';
import { NEW_LINK } from '../settings/links/utils';
import { activateFullSceneTree, buildPanelRepeaterScene } from '../utils/test-utils';
import { getVizPanelKeyForPanelId } from '../utils/utils';
@@ -225,7 +226,8 @@ describe('transformSceneToSaveModel', () => {
const variable = scene.state.$variables?.state.variables[0] as MultiValueVariable;
variable.changeValueTo(['a', 'b', 'c']);
- const grid = scene.state.body as SceneGridLayout;
+ const layout = scene.state.body as DefaultGridLayoutManager;
+ const grid = layout.state.grid;
const rowWithRepeat = grid.state.children[1] as SceneGridRow;
const rowRepeater = rowWithRepeat.state.$behaviors![0] as RowRepeaterBehavior;
diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts
index 01605ed28ab..86aebd5ee65 100644
--- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts
+++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts
@@ -4,7 +4,6 @@ import { isEmptyObject, ScopedVars, TimeRange } from '@grafana/data';
import {
behaviors,
SceneGridItemLike,
- SceneGridLayout,
SceneGridRow,
VizPanel,
SceneDataTransformer,
@@ -36,6 +35,7 @@ import { DashboardGridItem } from '../scene/DashboardGridItem';
import { DashboardScene } from '../scene/DashboardScene';
import { PanelTimeRange } from '../scene/PanelTimeRange';
import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior';
+import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLayoutManager';
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
import { getLibraryPanelBehavior, getPanelIdForVizPanel, getQueryRunnerFor, isLibraryPanel } from '../utils/utils';
@@ -53,8 +53,8 @@ export function transformSceneToSaveModel(scene: DashboardScene, isSnapshot = fa
let panels: Panel[] = [];
let variables: VariableModel[] = [];
- if (body instanceof SceneGridLayout) {
- for (const child of body.state.children) {
+ if (body instanceof DefaultGridLayoutManager) {
+ for (const child of body.state.grid.state.children) {
if (child instanceof DashboardGridItem) {
// handle panel repeater scenario
if (child.state.variableName) {
diff --git a/public/app/features/dashboard-scene/settings/AnnotationsEditView.test.tsx b/public/app/features/dashboard-scene/settings/AnnotationsEditView.test.tsx
index d464023a07a..547e4d8e41b 100644
--- a/public/app/features/dashboard-scene/settings/AnnotationsEditView.test.tsx
+++ b/public/app/features/dashboard-scene/settings/AnnotationsEditView.test.tsx
@@ -11,7 +11,7 @@ import {
LoadingState,
PanelData,
} from '@grafana/data';
-import { SceneGridLayout, SceneTimeRange, dataLayers } from '@grafana/scenes';
+import { SceneTimeRange, dataLayers } from '@grafana/scenes';
import { DataSourceRef } from '@grafana/schema';
import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer';
@@ -221,9 +221,6 @@ async function buildTestScene() {
}),
],
}),
- body: new SceneGridLayout({
- children: [],
- }),
editview: annotationsView,
});
diff --git a/public/app/features/dashboard-scene/settings/DashboardLinksEditView.test.tsx b/public/app/features/dashboard-scene/settings/DashboardLinksEditView.test.tsx
index 7e25117a5e8..5cbd37a99fb 100644
--- a/public/app/features/dashboard-scene/settings/DashboardLinksEditView.test.tsx
+++ b/public/app/features/dashboard-scene/settings/DashboardLinksEditView.test.tsx
@@ -2,7 +2,7 @@ import { render as RTLRender } from '@testing-library/react';
import * as React from 'react';
import { TestProvider } from 'test/helpers/TestProvider';
-import { SceneGridLayout, SceneTimeRange, behaviors } from '@grafana/scenes';
+import { SceneTimeRange, behaviors } from '@grafana/scenes';
import { DashboardCursorSync } from '@grafana/schema';
import { DashboardControls } from '../scene/DashboardControls';
@@ -212,9 +212,6 @@ async function buildTestScene() {
meta: {
canEdit: true,
},
- body: new SceneGridLayout({
- children: [],
- }),
editview: settings,
});
diff --git a/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.test.tsx b/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.test.tsx
index e2f343c383c..a7dcd617186 100644
--- a/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.test.tsx
+++ b/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.test.tsx
@@ -1,4 +1,4 @@
-import { behaviors, SceneGridLayout, SceneTimeRange } from '@grafana/scenes';
+import { behaviors, SceneTimeRange } from '@grafana/scenes';
import { DashboardCursorSync } from '@grafana/schema';
import * as utils from '../pages/utils';
@@ -125,9 +125,6 @@ async function buildTestScene() {
meta: {
canEdit: true,
},
- body: new SceneGridLayout({
- children: [],
- }),
editview: settings,
});
diff --git a/public/app/features/dashboard-scene/settings/PermissionsEditView.test.tsx b/public/app/features/dashboard-scene/settings/PermissionsEditView.test.tsx
index 4bb9868502f..dd6e97e6594 100644
--- a/public/app/features/dashboard-scene/settings/PermissionsEditView.test.tsx
+++ b/public/app/features/dashboard-scene/settings/PermissionsEditView.test.tsx
@@ -1,4 +1,4 @@
-import { SceneGridLayout, SceneTimeRange } from '@grafana/scenes';
+import { SceneTimeRange } from '@grafana/scenes';
import { DashboardScene } from '../scene/DashboardScene';
import { activateFullSceneTree } from '../utils/test-utils';
@@ -36,9 +36,6 @@ async function buildTestScene() {
meta: {
canEdit: true,
},
- body: new SceneGridLayout({
- children: [],
- }),
editview: permissionsView,
});
diff --git a/public/app/features/dashboard-scene/settings/VariablesEditView.test.tsx b/public/app/features/dashboard-scene/settings/VariablesEditView.test.tsx
index a547fa10d4f..f3d93596d03 100644
--- a/public/app/features/dashboard-scene/settings/VariablesEditView.test.tsx
+++ b/public/app/features/dashboard-scene/settings/VariablesEditView.test.tsx
@@ -13,7 +13,6 @@ import { setPluginImportUtils, setRunRequest } from '@grafana/runtime';
import {
SceneVariableSet,
CustomVariable,
- SceneGridLayout,
VizPanel,
AdHocFiltersVariable,
SceneVariableState,
@@ -22,8 +21,8 @@ import {
import { mockDataSource } from 'app/features/alerting/unified/mocks';
import { LegacyVariableQueryEditor } from 'app/features/variables/editor/LegacyVariableQueryEditor';
-import { DashboardGridItem } from '../scene/DashboardGridItem';
import { DashboardScene } from '../scene/DashboardScene';
+import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLayoutManager';
import { activateFullSceneTree } from '../utils/test-utils';
import { VariablesEditView } from './VariablesEditView';
@@ -288,8 +287,7 @@ describe('VariablesEditView', () => {
it('should keep dependencies with panels when the type is changed so the variable is replaced', async () => {
// Uses function to avoid store reference to previous existing variables
const getSourceVariable = () => variableView.getVariables()[0] as CustomVariable;
- const getDependantPanel = () =>
- ((dashboard.state.body as SceneGridLayout).state.children[0] as DashboardGridItem).state.body as VizPanel;
+ const getDependantPanel = () => dashboard.state.body.getVizPanels()[0];
expect(getSourceVariable().getValue()).toBe('test');
// Using description to get the interpolated value
@@ -342,20 +340,14 @@ async function buildTestScene() {
}),
],
}),
- body: new SceneGridLayout({
- children: [
- new DashboardGridItem({
- key: 'griditem-1',
- x: 0,
- body: new VizPanel({
- title: 'Panel A',
- description: 'Panel A depends on customVar with current value $customVar',
- key: 'panel-1',
- pluginId: 'table',
- }),
- }),
- ],
- }),
+ body: DefaultGridLayoutManager.fromVizPanels([
+ new VizPanel({
+ title: 'Panel A',
+ description: 'Panel A depends on customVar with current value $customVar',
+ key: 'panel-1',
+ pluginId: 'table',
+ }),
+ ]),
editview: variableView,
});
diff --git a/public/app/features/dashboard-scene/settings/VersionsEditView.test.tsx b/public/app/features/dashboard-scene/settings/VersionsEditView.test.tsx
index 9e69917dfee..6d2b37219f6 100644
--- a/public/app/features/dashboard-scene/settings/VersionsEditView.test.tsx
+++ b/public/app/features/dashboard-scene/settings/VersionsEditView.test.tsx
@@ -1,4 +1,4 @@
-import { SceneGridLayout, SceneTimeRange } from '@grafana/scenes';
+import { SceneTimeRange } from '@grafana/scenes';
import { DashboardScene } from '../scene/DashboardScene';
import { activateFullSceneTree } from '../utils/test-utils';
@@ -162,9 +162,6 @@ async function buildTestScene() {
meta: {
canEdit: true,
},
- body: new SceneGridLayout({
- children: [],
- }),
editview: versionsView,
});
diff --git a/public/app/features/dashboard-scene/sharing/ExportButton/ExportButton.test.tsx b/public/app/features/dashboard-scene/sharing/ExportButton/ExportButton.test.tsx
index 4bc553b457c..22c5d54e16f 100644
--- a/public/app/features/dashboard-scene/sharing/ExportButton/ExportButton.test.tsx
+++ b/public/app/features/dashboard-scene/sharing/ExportButton/ExportButton.test.tsx
@@ -2,10 +2,10 @@ import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
-import { SceneGridLayout, SceneTimeRange, VizPanel } from '@grafana/scenes';
+import { SceneTimeRange, VizPanel } from '@grafana/scenes';
-import { DashboardGridItem } from '../../scene/DashboardGridItem';
import { DashboardScene } from '../../scene/DashboardScene';
+import { DefaultGridLayoutManager } from '../../scene/layout-default/DefaultGridLayoutManager';
import ExportButton from './ExportButton';
@@ -38,18 +38,7 @@ function setup() {
title: 'hello',
uid: 'dash-1',
$timeRange: new SceneTimeRange({}),
- body: new SceneGridLayout({
- children: [
- new DashboardGridItem({
- key: 'griditem-1',
- x: 0,
- y: 0,
- width: 10,
- height: 12,
- body: panel,
- }),
- ],
- }),
+ body: DefaultGridLayoutManager.fromVizPanels([panel]),
});
render();
diff --git a/public/app/features/dashboard-scene/sharing/ExportButton/ExportMenu.test.tsx b/public/app/features/dashboard-scene/sharing/ExportButton/ExportMenu.test.tsx
index 38045ba05b0..530dd8ae765 100644
--- a/public/app/features/dashboard-scene/sharing/ExportButton/ExportMenu.test.tsx
+++ b/public/app/features/dashboard-scene/sharing/ExportButton/ExportMenu.test.tsx
@@ -1,10 +1,10 @@
import { render, screen } from '@testing-library/react';
import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
-import { SceneGridLayout, SceneTimeRange, VizPanel } from '@grafana/scenes';
+import { SceneTimeRange, VizPanel } from '@grafana/scenes';
-import { DashboardGridItem } from '../../scene/DashboardGridItem';
import { DashboardScene } from '../../scene/DashboardScene';
+import { DefaultGridLayoutManager } from '../../scene/layout-default/DefaultGridLayoutManager';
import ExportMenu from './ExportMenu';
@@ -27,18 +27,7 @@ function setup() {
title: 'hello',
uid: 'dash-1',
$timeRange: new SceneTimeRange({}),
- body: new SceneGridLayout({
- children: [
- new DashboardGridItem({
- key: 'griditem-1',
- x: 0,
- y: 0,
- width: 10,
- height: 12,
- body: panel,
- }),
- ],
- }),
+ body: DefaultGridLayoutManager.fromVizPanels([panel]),
});
render();
}
diff --git a/public/app/features/dashboard-scene/sharing/ShareButton/ShareButton.test.tsx b/public/app/features/dashboard-scene/sharing/ShareButton/ShareButton.test.tsx
index 842b257470a..f939ea8820f 100644
--- a/public/app/features/dashboard-scene/sharing/ShareButton/ShareButton.test.tsx
+++ b/public/app/features/dashboard-scene/sharing/ShareButton/ShareButton.test.tsx
@@ -2,10 +2,10 @@ import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
-import { SceneGridLayout, SceneTimeRange, VizPanel } from '@grafana/scenes';
+import { SceneTimeRange, VizPanel } from '@grafana/scenes';
-import { DashboardGridItem } from '../../scene/DashboardGridItem';
import { DashboardScene } from '../../scene/DashboardScene';
+import { DefaultGridLayoutManager } from '../../scene/layout-default/DefaultGridLayoutManager';
import ShareButton from './ShareButton';
@@ -44,18 +44,7 @@ function setup() {
title: 'hello',
uid: 'dash-1',
$timeRange: new SceneTimeRange({}),
- body: new SceneGridLayout({
- children: [
- new DashboardGridItem({
- key: 'griditem-1',
- x: 0,
- y: 0,
- width: 10,
- height: 12,
- body: panel,
- }),
- ],
- }),
+ body: DefaultGridLayoutManager.fromVizPanels([panel]),
});
render();
diff --git a/public/app/features/dashboard-scene/sharing/ShareButton/ShareMenu.test.tsx b/public/app/features/dashboard-scene/sharing/ShareButton/ShareMenu.test.tsx
index 525ece5f205..0e7940e34d1 100644
--- a/public/app/features/dashboard-scene/sharing/ShareButton/ShareMenu.test.tsx
+++ b/public/app/features/dashboard-scene/sharing/ShareButton/ShareMenu.test.tsx
@@ -1,12 +1,12 @@
import { render, screen } from '@testing-library/react';
import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
-import { SceneGridLayout, SceneTimeRange, VizPanel } from '@grafana/scenes';
+import { SceneTimeRange, VizPanel } from '@grafana/scenes';
import { contextSrv } from 'app/core/services/context_srv';
import { config } from '../../../../core/config';
-import { DashboardGridItem } from '../../scene/DashboardGridItem';
import { DashboardScene, DashboardSceneState } from '../../scene/DashboardScene';
+import { DefaultGridLayoutManager } from '../../scene/layout-default/DefaultGridLayoutManager';
import ShareMenu from './ShareMenu';
@@ -87,18 +87,7 @@ function setup(overrides?: Partial) {
title: 'hello',
uid: 'dash-1',
$timeRange: new SceneTimeRange({}),
- body: new SceneGridLayout({
- children: [
- new DashboardGridItem({
- key: 'griditem-1',
- x: 0,
- y: 0,
- width: 10,
- height: 12,
- body: panel,
- }),
- ],
- }),
+ body: DefaultGridLayoutManager.fromVizPanels([panel]),
...overrides,
});
diff --git a/public/app/features/dashboard-scene/sharing/ShareButton/share-externally/ShareAlerts.test.tsx b/public/app/features/dashboard-scene/sharing/ShareButton/share-externally/ShareAlerts.test.tsx
index 205bf6bc12c..2dab2d48804 100644
--- a/public/app/features/dashboard-scene/sharing/ShareButton/share-externally/ShareAlerts.test.tsx
+++ b/public/app/features/dashboard-scene/sharing/ShareButton/share-externally/ShareAlerts.test.tsx
@@ -4,7 +4,6 @@ import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
import {
CustomVariable,
SceneDataTransformer,
- SceneGridLayout,
SceneQueryRunner,
SceneTimeRange,
SceneVariableSet,
@@ -12,8 +11,8 @@ import {
VizPanelState,
} from '@grafana/scenes';
import { contextSrv } from 'app/core/core';
-import { DashboardGridItem } from 'app/features/dashboard-scene/scene/DashboardGridItem';
import { DashboardScene, DashboardSceneState } from 'app/features/dashboard-scene/scene/DashboardScene';
+import { DefaultGridLayoutManager } from 'app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager';
import { ShareDrawerContext } from '../../ShareDrawer/ShareDrawerContext';
@@ -91,18 +90,7 @@ async function setup(panelState?: Partial, dashboardState?: Parti
title: 'hello',
uid: 'dash-1',
$timeRange: new SceneTimeRange({}),
- body: new SceneGridLayout({
- children: [
- new DashboardGridItem({
- key: 'griditem-1',
- x: 0,
- y: 0,
- width: 10,
- height: 12,
- body: panel,
- }),
- ],
- }),
+ body: DefaultGridLayoutManager.fromVizPanels([panel]),
...dashboardState,
});
diff --git a/public/app/features/dashboard-scene/sharing/ShareButton/share-externally/ShareExternally.test.tsx b/public/app/features/dashboard-scene/sharing/ShareButton/share-externally/ShareExternally.test.tsx
index 7c25a0910cf..07fdec6b6a1 100644
--- a/public/app/features/dashboard-scene/sharing/ShareButton/share-externally/ShareExternally.test.tsx
+++ b/public/app/features/dashboard-scene/sharing/ShareButton/share-externally/ShareExternally.test.tsx
@@ -7,18 +7,17 @@ import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
import { config, setPluginImportUtils } from '@grafana/runtime';
import {
CustomVariable,
- SceneGridLayout,
SceneQueryRunner,
SceneTimeRange,
SceneVariableSet,
VizPanel,
VizPanelState,
} from '@grafana/scenes';
+import { shareDashboardType } from 'app/features/dashboard/components/ShareModal/utils';
+import { DefaultGridLayoutManager } from 'app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager';
import { contextSrv } from '../../../../../core/services/context_srv';
import * as sharePublicDashboardUtils from '../../../../dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboardUtils';
-import { shareDashboardType } from '../../../../dashboard/components/ShareModal/utils';
-import { DashboardGridItem } from '../../../scene/DashboardGridItem';
import { DashboardScene, DashboardSceneState } from '../../../scene/DashboardScene';
import { activateFullSceneTree } from '../../../utils/test-utils';
import { ShareDrawer } from '../../ShareDrawer/ShareDrawer';
@@ -67,6 +66,7 @@ describe('Alerts', () => {
});
expect(screen.queryByTestId(selectors.TemplateVariablesWarningAlert)).toBeInTheDocument();
});
+
it('when dashboard has unsupported datasources, warning is shown', async () => {
await buildAndRenderScenario({
panelOverrides: {
@@ -101,23 +101,14 @@ async function buildAndRenderScenario({
canEdit: true,
},
$timeRange: new SceneTimeRange({}),
- body: new SceneGridLayout({
- children: [
- new DashboardGridItem({
- key: 'griditem-1',
- x: 0,
- y: 0,
- width: 10,
- height: 12,
- body: new VizPanel({
- title: 'Panel A',
- pluginId: 'table',
- key: 'panel-12',
- ...panelOverrides,
- }),
- }),
- ],
- }),
+ body: DefaultGridLayoutManager.fromVizPanels([
+ new VizPanel({
+ title: 'Panel A',
+ pluginId: 'table',
+ key: 'panel-12',
+ ...panelOverrides,
+ }),
+ ]),
overlay: drawer,
...overrides,
});
diff --git a/public/app/features/dashboard-scene/sharing/ShareDrawer/ShareDrawer.test.tsx b/public/app/features/dashboard-scene/sharing/ShareDrawer/ShareDrawer.test.tsx
index ea2bd4f3588..b2772d5f856 100644
--- a/public/app/features/dashboard-scene/sharing/ShareDrawer/ShareDrawer.test.tsx
+++ b/public/app/features/dashboard-scene/sharing/ShareDrawer/ShareDrawer.test.tsx
@@ -4,7 +4,7 @@ import userEvent from '@testing-library/user-event';
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
import { selectors } from '@grafana/e2e-selectors';
import { locationService, setPluginImportUtils } from '@grafana/runtime';
-import { SceneGridLayout, SceneTimeRange, UrlSyncContextProvider } from '@grafana/scenes';
+import { SceneTimeRange, UrlSyncContextProvider } from '@grafana/scenes';
import { render } from '../../../../../test/test-utils';
import { shareDashboardType } from '../../../dashboard/components/ShareModal/utils';
@@ -63,9 +63,6 @@ async function buildAndRenderScenario() {
canEdit: true,
},
$timeRange: new SceneTimeRange({}),
- body: new SceneGridLayout({
- children: [],
- }),
overlay: drawer,
});
diff --git a/public/app/features/dashboard-scene/sharing/ShareLinkTab.test.tsx b/public/app/features/dashboard-scene/sharing/ShareLinkTab.test.tsx
index 7ae9f501f0c..cded7ef3abe 100644
--- a/public/app/features/dashboard-scene/sharing/ShareLinkTab.test.tsx
+++ b/public/app/features/dashboard-scene/sharing/ShareLinkTab.test.tsx
@@ -6,10 +6,10 @@ import { dateTime } from '@grafana/data';
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
import { selectors } from '@grafana/e2e-selectors';
import { config, locationService, setPluginImportUtils } from '@grafana/runtime';
-import { SceneGridLayout, SceneTimeRange, VizPanel } from '@grafana/scenes';
+import { SceneTimeRange, VizPanel } from '@grafana/scenes';
-import { DashboardGridItem } from '../scene/DashboardGridItem';
import { DashboardScene } from '../scene/DashboardScene';
+import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLayoutManager';
import { activateFullSceneTree } from '../utils/test-utils';
import { ShareLinkTab } from './ShareLinkTab';
@@ -111,18 +111,7 @@ function buildAndRenderScenario(options: ScenarioOptions) {
canEdit: true,
},
$timeRange: new SceneTimeRange({}),
- body: new SceneGridLayout({
- children: [
- new DashboardGridItem({
- key: 'griditem-1',
- x: 0,
- y: 0,
- width: 10,
- height: 12,
- body: panel,
- }),
- ],
- }),
+ body: DefaultGridLayoutManager.fromVizPanels([panel]),
overlay: tab,
});
diff --git a/public/app/features/dashboard-scene/sharing/public-dashboards/utils.ts b/public/app/features/dashboard-scene/sharing/public-dashboards/utils.ts
index 37b49dc9d29..35e0ddf223b 100644
--- a/public/app/features/dashboard-scene/sharing/public-dashboards/utils.ts
+++ b/public/app/features/dashboard-scene/sharing/public-dashboards/utils.ts
@@ -1,17 +1,10 @@
import { DataSourceWithBackend } from '@grafana/runtime';
-import {
- SceneGridItemLike,
- VizPanel,
- SceneQueryRunner,
- SceneDataTransformer,
- SceneGridLayout,
- SceneGridRow,
-} from '@grafana/scenes';
+import { VizPanel } from '@grafana/scenes';
import { supportedDatasources } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/SupportedPubdashDatasources';
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
-import { DashboardGridItem } from '../../scene/DashboardGridItem';
import { DashboardScene } from '../../scene/DashboardScene';
+import { getQueryRunnerFor } from '../../utils/utils';
export const getUnsupportedDashboardDatasources = async (types: string[]): Promise => {
let unsupportedDS = new Set();
@@ -33,63 +26,25 @@ export const getUnsupportedDashboardDatasources = async (types: string[]): Promi
export function getPanelDatasourceTypes(scene: DashboardScene): string[] {
const types = new Set();
- const body = scene.state.body;
- if (!(body instanceof SceneGridLayout)) {
- return [];
- }
+ const panels = scene.state.body.getVizPanels();
- for (const child of body.state.children) {
- if (child instanceof DashboardGridItem) {
- const ts = panelDatasourceTypes(child);
- for (const t of ts) {
- types.add(t);
- }
- }
-
- if (child instanceof SceneGridRow) {
- const ts = rowTypes(child);
- for (const t of ts) {
- types.add(t);
- }
+ for (const child of panels) {
+ const ts = panelDatasourceTypes(child);
+ for (const t of ts) {
+ types.add(t);
}
}
return Array.from(types).sort();
}
-function rowTypes(gridRow: SceneGridRow) {
- const types = new Set(gridRow.state.children.map((c) => panelDatasourceTypes(c)).flat());
- return types;
-}
-
-function panelDatasourceTypes(gridItem: SceneGridItemLike) {
- let vizPanel: VizPanel | undefined;
-
- if (gridItem instanceof DashboardGridItem) {
- if (gridItem.state.body instanceof VizPanel) {
- vizPanel = gridItem.state.body;
- } else {
- throw new Error('DashboardGridItem body expected to be VizPanel');
- }
- }
-
- if (!vizPanel) {
- throw new Error('Unsupported grid item type');
- }
- const dataProvider = vizPanel.state.$data;
+function panelDatasourceTypes(vizPanel: VizPanel) {
const types = new Set();
- if (dataProvider instanceof SceneQueryRunner) {
- for (const q of dataProvider.state.queries) {
- types.add(q.datasource?.type ?? '');
- }
- }
- if (dataProvider instanceof SceneDataTransformer) {
- const panelData = dataProvider.state.$data;
- if (panelData instanceof SceneQueryRunner) {
- for (const q of panelData.state.queries) {
- types.add(q.datasource?.type ?? '');
- }
+ const queryRunner = getQueryRunnerFor(vizPanel);
+ if (queryRunner) {
+ for (const q of queryRunner.state.queries) {
+ types.add(q.datasource?.type ?? '');
}
}
diff --git a/public/app/features/dashboard-scene/solo/useSoloPanel.ts b/public/app/features/dashboard-scene/solo/useSoloPanel.ts
index 28d8abf22d9..8905a79ce32 100644
--- a/public/app/features/dashboard-scene/solo/useSoloPanel.ts
+++ b/public/app/features/dashboard-scene/solo/useSoloPanel.ts
@@ -1,10 +1,8 @@
import { useState, useEffect } from 'react';
-import { VizPanel, SceneObject, SceneGridRow, UrlSyncManager } from '@grafana/scenes';
+import { VizPanel, UrlSyncManager } from '@grafana/scenes';
-import { DashboardGridItem } from '../scene/DashboardGridItem';
import { DashboardScene } from '../scene/DashboardScene';
-import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior';
import { DashboardRepeatsProcessedEvent } from '../scene/types';
import { findVizPanelByKey, isPanelClone } from '../utils/utils';
@@ -63,31 +61,10 @@ function findRepeatClone(dashboard: DashboardScene, panelId: string): Promise {
- if (child instanceof DashboardGridItem && !child.isActive) {
- child.activate();
- return;
- }
-
- if (child instanceof SceneGridRow && child.state.$behaviors) {
- for (const behavior of child.state.$behaviors) {
- if (behavior instanceof RowRepeaterBehavior && !child.isActive) {
- child.activate();
- break;
- }
- }
-
- // Activate any panel DashboardGridItem inside the row
- activateAllRepeaters(child);
- }
+ dashboard.state.body.activateRepeaters?.();
});
}
diff --git a/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.test.ts b/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.test.ts
index a6b43edad2e..d8a9b152b03 100644
--- a/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.test.ts
+++ b/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.test.ts
@@ -1,7 +1,6 @@
import { TimeRangeUpdatedEvent } from '@grafana/runtime';
import {
behaviors,
- SceneGridLayout,
SceneQueryRunner,
SceneTimeRange,
VizPanel,
@@ -14,8 +13,8 @@ import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard';
import { AlertStatesDataLayer } from '../scene/AlertStatesDataLayer';
import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer';
import { DashboardControls } from '../scene/DashboardControls';
-import { DashboardGridItem } from '../scene/DashboardGridItem';
import { DashboardScene } from '../scene/DashboardScene';
+import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLayoutManager';
import { NEW_LINK } from '../settings/links/utils';
import { DashboardModelCompatibilityWrapper } from './DashboardModelCompatibilityWrapper';
@@ -97,13 +96,13 @@ describe('DashboardModelCompatibilityWrapper', () => {
});
it('Can remove panel', () => {
- const { wrapper, scene } = setup();
+ const { wrapper } = setup();
- expect((scene.state.body as SceneGridLayout).state.children.length).toBe(5);
+ expect(wrapper.panels.length).toBe(5);
wrapper.removePanel(wrapper.getPanelById(1)!);
- expect((scene.state.body as SceneGridLayout).state.children.length).toBe(4);
+ expect(wrapper.panels.length).toBe(4);
});
it('Checks if annotations are editable', () => {
@@ -173,75 +172,59 @@ function setup() {
controls: new DashboardControls({
hideTimeControls: true,
}),
- body: new SceneGridLayout({
- children: [
- new DashboardGridItem({
- key: 'griditem-1',
- x: 0,
- body: new VizPanel({
- title: 'Panel with a regular data source query',
- key: 'panel-1',
- pluginId: 'table',
- $data: new SceneQueryRunner({
- key: 'data-query-runner',
- queries: [{ refId: 'A' }],
- datasource: { uid: 'gdev-testdata', type: 'grafana-testdata-datasource' },
- }),
- }),
+ body: DefaultGridLayoutManager.fromVizPanels([
+ new VizPanel({
+ title: 'Panel with a regular data source query',
+ key: 'panel-1',
+ pluginId: 'table',
+ $data: new SceneQueryRunner({
+ key: 'data-query-runner',
+ queries: [{ refId: 'A' }],
+ datasource: { uid: 'gdev-testdata', type: 'grafana-testdata-datasource' },
}),
- new DashboardGridItem({
- body: new VizPanel({
- title: 'Panel with no queries',
- key: 'panel-2',
- pluginId: 'table',
- }),
+ }),
+ new VizPanel({
+ title: 'Panel with no queries',
+ key: 'panel-2',
+ pluginId: 'table',
+ }),
+ new VizPanel({
+ title: 'Panel with a shared query',
+ key: 'panel-3',
+ pluginId: 'table',
+ $data: new SceneQueryRunner({
+ key: 'data-query-runner',
+ queries: [{ refId: 'A', panelId: 1 }],
+ datasource: { uid: SHARED_DASHBOARD_QUERY, type: 'datasource' },
}),
-
- new DashboardGridItem({
- body: new VizPanel({
- title: 'Panel with a shared query',
- key: 'panel-3',
- pluginId: 'table',
- $data: new SceneQueryRunner({
- key: 'data-query-runner',
- queries: [{ refId: 'A', panelId: 1 }],
- datasource: { uid: SHARED_DASHBOARD_QUERY, type: 'datasource' },
- }),
+ }),
+ new VizPanel({
+ title: 'Panel with a regular data source query and transformations',
+ key: 'panel-4',
+ pluginId: 'table',
+ $data: new SceneDataTransformer({
+ $data: new SceneQueryRunner({
+ key: 'data-query-runner',
+ queries: [{ refId: 'A' }],
+ datasource: { uid: 'gdev-testdata', type: 'grafana-testdata-datasource' },
}),
+ transformations: [],
}),
-
- new DashboardGridItem({
- body: new VizPanel({
- title: 'Panel with a regular data source query and transformations',
- key: 'panel-4',
- pluginId: 'table',
- $data: new SceneDataTransformer({
- $data: new SceneQueryRunner({
- key: 'data-query-runner',
- queries: [{ refId: 'A' }],
- datasource: { uid: 'gdev-testdata', type: 'grafana-testdata-datasource' },
- }),
- transformations: [],
- }),
+ }),
+ new VizPanel({
+ title: 'Panel with a shared query and transformations',
+ key: 'panel-4',
+ pluginId: 'table',
+ $data: new SceneDataTransformer({
+ $data: new SceneQueryRunner({
+ key: 'data-query-runner',
+ queries: [{ refId: 'A', panelId: 1 }],
+ datasource: { uid: SHARED_DASHBOARD_QUERY, type: 'datasource' },
}),
+ transformations: [],
}),
- new DashboardGridItem({
- body: new VizPanel({
- title: 'Panel with a shared query and transformations',
- key: 'panel-4',
- pluginId: 'table',
- $data: new SceneDataTransformer({
- $data: new SceneQueryRunner({
- key: 'data-query-runner',
- queries: [{ refId: 'A', panelId: 1 }],
- datasource: { uid: SHARED_DASHBOARD_QUERY, type: 'datasource' },
- }),
- transformations: [],
- }),
- }),
- }),
- ],
- }),
+ }),
+ ]),
});
const wrapper = new DashboardModelCompatibilityWrapper(scene);
diff --git a/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts b/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts
index a5e25a93968..9140b9ce6ae 100644
--- a/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts
+++ b/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts
@@ -2,17 +2,8 @@ import { Subscription } from 'rxjs';
import { AnnotationQuery, DashboardCursorSync, dateTimeFormat, DateTimeInput, EventBusSrv } from '@grafana/data';
import { TimeRangeUpdatedEvent } from '@grafana/runtime';
-import {
- behaviors,
- SceneDataLayerSet,
- sceneGraph,
- SceneGridLayout,
- SceneGridRow,
- SceneObject,
- VizPanel,
-} from '@grafana/scenes';
+import { behaviors, SceneDataLayerSet, sceneGraph, SceneObject, VizPanel } from '@grafana/scenes';
-import { DashboardGridItem } from '../scene/DashboardGridItem';
import { DashboardScene } from '../scene/DashboardScene';
import { dataLayersToAnnotations } from '../serialization/dataLayersToAnnotations';
@@ -175,39 +166,7 @@ export class DashboardModelCompatibilityWrapper {
return;
}
- const gridItem = vizPanel.parent;
- if (!(gridItem instanceof DashboardGridItem)) {
- console.error('Trying to remove a panel that is not wrapped in DashboardGridItem');
- return;
- }
-
- const layout = sceneGraph.getLayout(vizPanel);
- if (!(layout instanceof SceneGridLayout)) {
- console.error('Trying to remove a panel in a layout that is not SceneGridLayout ');
- return;
- }
-
- // if grid item is directly in the layout just remove it
- if (layout === gridItem.parent) {
- layout.setState({
- children: layout.state.children.filter((child) => child !== gridItem),
- });
- }
-
- // Removing from a row is a bit more complicated
- if (gridItem.parent instanceof SceneGridRow) {
- // Clone the row and remove the grid item
- const newRow = layout.clone({
- children: layout.state.children.filter((child) => child !== gridItem),
- });
-
- // Now update the grid layout and replace the row with the updated one
- if (layout.parent instanceof SceneGridLayout) {
- layout.parent.setState({
- children: layout.parent.state.children.map((child) => (child === layout ? newRow : child)),
- });
- }
- }
+ this._scene.removePanel(vizPanel);
}
public canEditAnnotations(dashboardUID?: string) {
diff --git a/public/app/features/dashboard-scene/utils/dashboardSceneGraph.test.ts b/public/app/features/dashboard-scene/utils/dashboardSceneGraph.test.ts
index 7e97d9530dd..28dc6622f12 100644
--- a/public/app/features/dashboard-scene/utils/dashboardSceneGraph.test.ts
+++ b/public/app/features/dashboard-scene/utils/dashboardSceneGraph.test.ts
@@ -1,4 +1,4 @@
-import { SceneGridLayout, SceneGridRow, SceneQueryRunner, SceneTimeRange, VizPanel, behaviors } from '@grafana/scenes';
+import { SceneGridLayout, SceneQueryRunner, SceneTimeRange, VizPanel, behaviors } from '@grafana/scenes';
import { DashboardCursorSync } from '@grafana/schema';
import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer';
@@ -6,10 +6,10 @@ import { DashboardControls } from '../scene/DashboardControls';
import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet';
import { DashboardGridItem } from '../scene/DashboardGridItem';
import { DashboardScene, DashboardSceneState } from '../scene/DashboardScene';
-import { LibraryPanelBehavior } from '../scene/LibraryPanelBehavior';
import { VizPanelLinks, VizPanelLinksMenu } from '../scene/PanelLinks';
+import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLayoutManager';
-import { dashboardSceneGraph, getNextPanelId } from './dashboardSceneGraph';
+import { dashboardSceneGraph } from './dashboardSceneGraph';
import { findVizPanelByKey } from './utils';
describe('dashboardSceneGraph', () => {
@@ -27,36 +27,6 @@ describe('dashboardSceneGraph', () => {
});
});
- describe('getVizPanels', () => {
- let scene: DashboardScene;
-
- beforeEach(async () => {
- scene = buildTestScene();
- });
-
- it('Should return all panels', () => {
- const vizPanels = dashboardSceneGraph.getVizPanels(scene);
-
- expect(vizPanels.length).toBe(6);
- expect(vizPanels[0].state.title).toBe('Panel A');
- expect(vizPanels[1].state.title).toBe('Panel B');
- expect(vizPanels[2].state.title).toBe('Panel C');
- expect(vizPanels[3].state.title).toBe('Panel D');
- expect(vizPanels[4].state.title).toBe('Panel E');
- expect(vizPanels[5].state.title).toBe('Panel F');
- });
-
- it('Should return an empty array when scene has no panels', () => {
- scene.setState({
- body: new SceneGridLayout({ children: [] }),
- });
-
- const vizPanels = dashboardSceneGraph.getVizPanels(scene);
-
- expect(vizPanels.length).toBe(0);
- });
- });
-
describe('getDataLayers', () => {
let scene: DashboardScene;
@@ -80,141 +50,6 @@ describe('dashboardSceneGraph', () => {
});
});
- describe('getNextPanelId', () => {
- it('should get next panel id in a simple 3 panel layout', () => {
- const scene = buildTestScene({
- body: new SceneGridLayout({
- children: [
- new DashboardGridItem({
- body: new VizPanel({
- title: 'Panel A',
- key: 'panel-1',
- pluginId: 'table',
- }),
- }),
- new DashboardGridItem({
- body: new VizPanel({
- title: 'Panel B',
- key: 'panel-2',
- pluginId: 'table',
- }),
- }),
- new DashboardGridItem({
- body: new VizPanel({
- title: 'Panel C',
- key: 'panel-3',
- pluginId: 'table',
- }),
- }),
- ],
- }),
- });
-
- const id = getNextPanelId(scene);
-
- expect(id).toBe(4);
- });
-
- it('should take library panels, panels in rows and panel repeaters into account', () => {
- const scene = buildTestScene({
- body: new SceneGridLayout({
- children: [
- new DashboardGridItem({
- key: 'griditem-1',
- x: 0,
- body: new VizPanel({
- title: 'Panel A',
- key: 'panel-1',
- pluginId: 'table',
- }),
- }),
- new DashboardGridItem({
- body: new VizPanel({
- title: 'Library Panel 1',
- key: 'panel-2',
- $behaviors: [
- new LibraryPanelBehavior({
- uid: 'uid',
- name: 'LibPanel',
- title: 'Library Panel 1',
- }),
- ],
- }),
- }),
- new DashboardGridItem({
- body: new VizPanel({
- title: 'Panel C',
- key: 'panel-2-clone-1',
- pluginId: 'table',
- }),
- }),
- new DashboardGridItem({
- body: new VizPanel({
- title: 'Panel C',
- key: 'panel-4',
- pluginId: 'table',
- }),
- variableName: 'repeat',
- repeatedPanels: [],
- repeatDirection: 'h',
- maxPerRow: 1,
- }),
- new SceneGridRow({
- key: 'key',
- title: 'row',
- children: [
- new DashboardGridItem({
- body: new VizPanel({
- title: 'Panel E',
- key: 'panel-2-clone-2',
- pluginId: 'table',
- }),
- }),
- new DashboardGridItem({
- body: new VizPanel({
- title: 'Library Panel 2',
- key: 'panel-3',
- $behaviors: [
- new LibraryPanelBehavior({
- uid: 'uid',
- name: 'LibPanel',
- title: 'Library Panel 2',
- }),
- ],
- }),
- }),
- ],
- }),
- ],
- }),
- });
-
- const id = getNextPanelId(scene);
-
- expect(id).toBe(5);
- });
-
- it('should get next panel id in a layout with rows', () => {
- const scene = buildTestScene();
- const id = getNextPanelId(scene);
-
- expect(id).toBe(3);
- });
-
- it('should return 1 if no panels are found', () => {
- const scene = buildTestScene({ body: new SceneGridLayout({ children: [] }) });
- const id = getNextPanelId(scene);
-
- expect(id).toBe(1);
- });
-
- it('should throw an error if body is not SceneGridLayout', () => {
- const scene = buildTestScene({ body: undefined });
-
- expect(() => getNextPanelId(scene)).toThrow('Dashboard body is not a SceneGridLayout');
- });
- });
-
describe('getCursorSync', () => {
it('should return cursor sync behavior', () => {
const scene = buildTestScene();
@@ -224,7 +59,8 @@ describe('dashboardSceneGraph', () => {
});
it('should return undefined if no cursor sync behavior', () => {
- const scene = buildTestScene({ $behaviors: [] });
+ const scene = buildTestScene();
+ scene.setState({ $behaviors: [] });
const cursorSync = dashboardSceneGraph.getCursorSync(scene);
expect(cursorSync).toBeUndefined();
@@ -259,63 +95,30 @@ function buildTestScene(overrides?: Partial) {
}),
],
}),
- body: new SceneGridLayout({
- children: [
- new DashboardGridItem({
- key: 'griditem-1',
- x: 0,
- body: new VizPanel({
- title: 'Panel A',
- key: 'panel-1',
- pluginId: 'table',
- $data: new SceneQueryRunner({ key: 'data-query-runner', queries: [{ refId: 'A' }] }),
- }),
- }),
- new DashboardGridItem({
- body: new VizPanel({
- title: 'Panel B',
- key: 'panel-2',
- pluginId: 'table',
- }),
- }),
- new DashboardGridItem({
- body: new VizPanel({
- title: 'Panel C',
- key: 'panel-2-clone-1',
- pluginId: 'table',
- $data: new SceneQueryRunner({ key: 'data-query-runner2', queries: [{ refId: 'A' }] }),
- }),
- }),
- new DashboardGridItem({
- body: new VizPanel({
- title: 'Panel D',
- key: 'panel-with-links',
- pluginId: 'table',
- $data: new SceneQueryRunner({ key: 'data-query-runner3', queries: [{ refId: 'A' }] }),
- titleItems: [new VizPanelLinks({ menu: new VizPanelLinksMenu({}) })],
- }),
- }),
- new SceneGridRow({
- key: 'key',
- title: 'row',
- children: [
- new DashboardGridItem({
- body: new VizPanel({
- title: 'Panel E',
- key: 'panel-2-clone-2',
- pluginId: 'table',
- }),
+ body: new DefaultGridLayoutManager({
+ grid: new SceneGridLayout({
+ children: [
+ new DashboardGridItem({
+ key: 'griditem-1',
+ x: 0,
+ body: new VizPanel({
+ title: 'Panel A',
+ key: 'panel-1',
+ pluginId: 'table',
+ $data: new SceneQueryRunner({ key: 'data-query-runner', queries: [{ refId: 'A' }] }),
}),
- new DashboardGridItem({
- body: new VizPanel({
- title: 'Panel F',
- key: 'panel-2-clone-2',
- pluginId: 'table',
- }),
+ }),
+ new DashboardGridItem({
+ body: new VizPanel({
+ title: 'Panel D',
+ key: 'panel-with-links',
+ pluginId: 'table',
+ $data: new SceneQueryRunner({ key: 'data-query-runner3', queries: [{ refId: 'A' }] }),
+ titleItems: [new VizPanelLinks({ menu: new VizPanelLinksMenu({}) })],
}),
- ],
- }),
- ],
+ }),
+ ],
+ }),
}),
...overrides,
});
diff --git a/public/app/features/dashboard-scene/utils/dashboardSceneGraph.ts b/public/app/features/dashboard-scene/utils/dashboardSceneGraph.ts
index 7f6198fd605..61ee5d75291 100644
--- a/public/app/features/dashboard-scene/utils/dashboardSceneGraph.ts
+++ b/public/app/features/dashboard-scene/utils/dashboardSceneGraph.ts
@@ -1,12 +1,9 @@
-import { VizPanel, SceneGridRow, sceneGraph, SceneGridLayout, behaviors } from '@grafana/scenes';
+import { VizPanel, sceneGraph, behaviors } from '@grafana/scenes';
import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet';
-import { DashboardGridItem } from '../scene/DashboardGridItem';
import { DashboardScene } from '../scene/DashboardScene';
import { VizPanelLinks } from '../scene/PanelLinks';
-import { getPanelIdForVizPanel } from './utils';
-
function getTimePicker(scene: DashboardScene) {
return scene.state.controls?.state.timePicker;
}
@@ -28,29 +25,7 @@ function getPanelLinks(panel: VizPanel) {
}
function getVizPanels(scene: DashboardScene): VizPanel[] {
- const panels: VizPanel[] = [];
-
- scene.state.body.forEachChild((child) => {
- if (!(child instanceof DashboardGridItem) && !(child instanceof SceneGridRow)) {
- throw new Error('Child is not a DashboardGridItem or SceneGridRow, invalid scene');
- }
-
- if (child instanceof DashboardGridItem) {
- if (child.state.body instanceof VizPanel) {
- panels.push(child.state.body);
- }
- } else if (child instanceof SceneGridRow) {
- child.forEachChild((child) => {
- if (child instanceof DashboardGridItem) {
- if (child.state.body instanceof VizPanel) {
- panels.push(child.state.body);
- }
- }
- });
- }
- });
-
- return panels;
+ return scene.state.body.getVizPanels();
}
function getDataLayers(scene: DashboardScene): DashboardDataLayerSet {
@@ -74,51 +49,7 @@ export function getCursorSync(scene: DashboardScene) {
}
export function getNextPanelId(dashboard: DashboardScene): number {
- let max = 0;
- const body = dashboard.state.body;
-
- if (!(body instanceof SceneGridLayout)) {
- throw new Error('Dashboard body is not a SceneGridLayout');
- }
-
- for (const child of body.state.children) {
- if (child instanceof DashboardGridItem) {
- const vizPanel = child.state.body;
-
- if (vizPanel) {
- const panelId = getPanelIdForVizPanel(vizPanel);
-
- if (panelId > max) {
- max = panelId;
- }
- }
- }
-
- if (child instanceof SceneGridRow) {
- //rows follow the same key pattern --- e.g.: `panel-6`
- const panelId = getPanelIdForVizPanel(child);
-
- if (panelId > max) {
- max = panelId;
- }
-
- for (const rowChild of child.state.children) {
- if (rowChild instanceof DashboardGridItem) {
- const vizPanel = rowChild.state.body;
-
- if (vizPanel) {
- const panelId = getPanelIdForVizPanel(vizPanel);
-
- if (panelId > max) {
- max = panelId;
- }
- }
- }
- }
- }
- }
-
- return max + 1;
+ return dashboard.state.body.getNextPanelId();
}
export const dashboardSceneGraph = {
diff --git a/public/app/features/dashboard-scene/utils/test-utils.ts b/public/app/features/dashboard-scene/utils/test-utils.ts
index aa55c8a7eac..8e55d67d767 100644
--- a/public/app/features/dashboard-scene/utils/test-utils.ts
+++ b/public/app/features/dashboard-scene/utils/test-utils.ts
@@ -18,6 +18,7 @@ import { DashboardDTO } from 'app/types';
import { DashboardGridItem, RepeatDirection } from '../scene/DashboardGridItem';
import { VizPanelLinks, VizPanelLinksMenu } from '../scene/PanelLinks';
import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior';
+import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLayoutManager';
export function setupLoadDashboardMock(rsp: DeepPartial, spy?: jest.Mock) {
const loadDashboardMock = (spy || jest.fn()).mockResolvedValue(rsp);
@@ -184,8 +185,10 @@ export function buildPanelRepeaterScene(options: SceneOptions, source?: VizPanel
$variables: new SceneVariableSet({
variables: [panelRepeatVariable, rowRepeatVariable],
}),
- body: new SceneGridLayout({
- children: [row],
+ body: new DefaultGridLayoutManager({
+ grid: new SceneGridLayout({
+ children: [row],
+ }),
}),
});
diff --git a/public/app/features/dashboard-scene/utils/utils.ts b/public/app/features/dashboard-scene/utils/utils.ts
index 004e152bc4c..a43bfa336ad 100644
--- a/public/app/features/dashboard-scene/utils/utils.ts
+++ b/public/app/features/dashboard-scene/utils/utils.ts
@@ -6,7 +6,6 @@ import {
MultiValueVariable,
SceneDataTransformer,
sceneGraph,
- SceneGridRow,
SceneObject,
SceneQueryRunner,
VizPanel,
@@ -19,7 +18,6 @@ import { DashboardScene } from '../scene/DashboardScene';
import { LibraryPanelBehavior } from '../scene/LibraryPanelBehavior';
import { VizPanelLinks, VizPanelLinksMenu } from '../scene/PanelLinks';
import { panelMenuBehavior } from '../scene/PanelMenuBehavior';
-import { RowActions } from '../scene/row-actions/RowActions';
import { dashboardSceneGraph } from './dashboardSceneGraph';
@@ -241,17 +239,6 @@ export function getDefaultVizPanel(dashboard: DashboardScene): VizPanel {
});
}
-export function getDefaultRow(dashboard: DashboardScene): SceneGridRow {
- const id = dashboardSceneGraph.getNextPanelId(dashboard);
-
- return new SceneGridRow({
- key: getVizPanelKeyForPanelId(id),
- title: 'Row title',
- actions: new RowActions({}),
- y: 0,
- });
-}
-
export function isLibraryPanel(vizPanel: VizPanel): boolean {
return getLibraryPanelBehavior(vizPanel) !== undefined;
}