DashboardScene: Refactor body property to be layout manager interface (#93738)
* Began layout refactor * fixing tests * Progress * Progress * Progress * Progress * Progress * Progress * finally no errors * Remove unused interface * Remove unused interface * fixed tests * Update * Update * Fixes to keyboard shortcuts and solo route * fix lint issues
This commit is contained in:
@@ -2785,6 +2785,10 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "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"]
|
||||
],
|
||||
|
||||
@@ -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));
|
||||
|
||||
+3
-14
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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],
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -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<DashboardSceneState>) {
|
||||
}),
|
||||
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,
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
|
||||
@@ -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<AddLibraryPanelDrawer
|
||||
|
||||
public onAddLibraryPanel = (panelInfo: LibraryPanel) => {
|
||||
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<AddLibraryPanelDrawer
|
||||
throw new Error('Trying to replace a panel that does not have a DashboardGridItem');
|
||||
}
|
||||
|
||||
gridItemToReplace.setState({ body });
|
||||
gridItemToReplace.setState({ body: newPanel });
|
||||
} else {
|
||||
const newGridItem = new DashboardGridItem({
|
||||
height: NEW_PANEL_HEIGHT,
|
||||
width: NEW_PANEL_WIDTH,
|
||||
x: 0,
|
||||
y: 0,
|
||||
body: body,
|
||||
key: `grid-item-${panelId}`,
|
||||
});
|
||||
|
||||
layout.setState({ children: [newGridItem, ...layout.state.children] });
|
||||
|
||||
if (!dashboard.state.isEditing) {
|
||||
dashboard.onEnterEditMode();
|
||||
}
|
||||
dashboard.addPanel(newPanel);
|
||||
}
|
||||
|
||||
this.onClose();
|
||||
|
||||
@@ -11,16 +11,16 @@ import {
|
||||
} from '@grafana/data';
|
||||
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
|
||||
import { setPluginImportUtils } from '@grafana/runtime';
|
||||
import { SceneDataTransformer, SceneFlexLayout, SceneGridLayout, SceneQueryRunner, VizPanel } from '@grafana/scenes';
|
||||
import { SceneDataTransformer, SceneFlexLayout, SceneQueryRunner, VizPanel } from '@grafana/scenes';
|
||||
import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard';
|
||||
import { DASHBOARD_DATASOURCE_PLUGIN_ID } from 'app/plugins/datasource/dashboard/types';
|
||||
|
||||
import { activateFullSceneTree } from '../utils/test-utils';
|
||||
|
||||
import { DashboardDatasourceBehaviour } from './DashboardDatasourceBehaviour';
|
||||
import { DashboardGridItem } from './DashboardGridItem';
|
||||
import { DashboardScene } from './DashboardScene';
|
||||
import { LibraryPanelBehavior } from './LibraryPanelBehavior';
|
||||
import { DefaultGridLayoutManager } from './layout-default/DefaultGridLayoutManager';
|
||||
|
||||
const grafanaDs = {
|
||||
id: 1,
|
||||
@@ -163,18 +163,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,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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<DashboardSceneState>) {
|
||||
}),
|
||||
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,
|
||||
});
|
||||
|
||||
@@ -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<DashboardSceneState> {
|
||||
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<DashboardSceneState> {
|
||||
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<DashboardSceneState> {
|
||||
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<DashboardSceneState> {
|
||||
}
|
||||
|
||||
// Disable grid dragging
|
||||
this.propagateEditModeChange();
|
||||
this.state.body.editModeChanged(false);
|
||||
}
|
||||
|
||||
private cleanupStateFromExplore() {
|
||||
@@ -352,10 +341,8 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
||||
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<DashboardSceneState> {
|
||||
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<DashboardSceneState> {
|
||||
}
|
||||
|
||||
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<DashboardSceneState> {
|
||||
}
|
||||
|
||||
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<DashboardSceneState> {
|
||||
}
|
||||
|
||||
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<DashboardSceneState> {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<DashboardSceneState> = {};
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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],
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
+281
@@ -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 };
|
||||
}
|
||||
+355
@@ -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<DefaultGridLayoutManagerState>
|
||||
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<DefaultGridLayoutManager>) => {
|
||||
return <model.state.grid.Component model={model.state.grid} />;
|
||||
};
|
||||
}
|
||||
@@ -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<RowActionsState> {
|
||||
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<RowActionsState> {
|
||||
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(),
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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<T> {
|
||||
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;
|
||||
|
||||
+10
-4
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
|
||||
@@ -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(<ExportButton dashboard={dashboard} />);
|
||||
|
||||
@@ -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(<ExportMenu dashboard={dashboard} />);
|
||||
}
|
||||
|
||||
@@ -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(<ShareButton dashboard={dashboard} />);
|
||||
|
||||
@@ -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<DashboardSceneState>) {
|
||||
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,
|
||||
});
|
||||
|
||||
|
||||
+2
-14
@@ -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<VizPanelState>, 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,
|
||||
});
|
||||
|
||||
|
||||
+11
-20
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
|
||||
@@ -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<string[]> => {
|
||||
let unsupportedDS = new Set<string>();
|
||||
@@ -33,63 +26,25 @@ export const getUnsupportedDashboardDatasources = async (types: string[]): Promi
|
||||
export function getPanelDatasourceTypes(scene: DashboardScene): string[] {
|
||||
const types = new Set<string>();
|
||||
|
||||
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<string>();
|
||||
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 ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Vi
|
||||
resolve(panel);
|
||||
} else {
|
||||
// If rows are repeated they could add new panel repeaters that needs to be activated
|
||||
activateAllRepeaters(dashboard.state.body);
|
||||
dashboard.state.body.activateRepeaters?.();
|
||||
}
|
||||
});
|
||||
|
||||
activateAllRepeaters(dashboard.state.body);
|
||||
});
|
||||
}
|
||||
|
||||
function activateAllRepeaters(layout: SceneObject) {
|
||||
layout.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;
|
||||
}
|
||||
}
|
||||
|
||||
// Activate any panel DashboardGridItem inside the row
|
||||
activateAllRepeaters(child);
|
||||
}
|
||||
dashboard.state.body.activateRepeaters?.();
|
||||
});
|
||||
}
|
||||
|
||||
+51
-68
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<DashboardSceneState>) {
|
||||
}),
|
||||
],
|
||||
}),
|
||||
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,
|
||||
});
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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<DashboardDTO>, 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],
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user