Scenes: Copy/paste library panels (#83962)

* Scenes: Copy/paste library panels

* more tests
This commit is contained in:
Victor Marin
2024-03-06 14:47:18 +02:00
committed by GitHub
parent 2c5b72e844
commit d269b4bf0d
2 changed files with 125 additions and 11 deletions
@@ -17,7 +17,11 @@ import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
import { VariablesChanged } from 'app/features/variables/types';
import { createWorker } from '../saving/createDetectChangesWorker';
import { buildGridItemForPanel, transformSaveModelToScene } from '../serialization/transformSaveModelToScene';
import {
buildGridItemForLibPanel,
buildGridItemForPanel,
transformSaveModelToScene,
} from '../serialization/transformSaveModelToScene';
import { DecoratedRevisionModel } from '../settings/VersionsEditView';
import { historySrv } from '../settings/version-history/HistorySrv';
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
@@ -25,6 +29,7 @@ import { djb2Hash } from '../utils/djb2Hash';
import { DashboardControls } from './DashboardControls';
import { DashboardScene, DashboardSceneState } from './DashboardScene';
import { LibraryVizPanel } from './LibraryVizPanel';
jest.mock('../settings/version-history/HistorySrv');
jest.mock('../serialization/transformSaveModelToScene');
@@ -244,7 +249,7 @@ describe('DashboardScene', () => {
const body = scene.state.body as SceneGridLayout;
const gridItem = body.state.children[0] as SceneGridItem;
expect(body.state.children.length).toBe(5);
expect(body.state.children.length).toBe(6);
expect(gridItem.state.body!.state.key).toBe('panel-5');
expect(gridItem.state.y).toBe(0);
});
@@ -255,7 +260,7 @@ describe('DashboardScene', () => {
const body = scene.state.body as SceneGridLayout;
const gridItem = body.state.children[0] as SceneGridItem;
expect(body.state.children.length).toBe(5);
expect(body.state.children.length).toBe(6);
expect(gridItem.state.body!.state.key).toBe('panel-5');
});
@@ -265,7 +270,7 @@ describe('DashboardScene', () => {
const body = scene.state.body as SceneGridLayout;
const gridRow = body.state.children[0] as SceneGridRow;
expect(body.state.children.length).toBe(3);
expect(body.state.children.length).toBe(4);
expect(gridRow.state.key).toBe('panel-5');
expect(gridRow.state.children[0].state.key).toBe('griditem-1');
expect(gridRow.state.children[1].state.key).toBe('griditem-2');
@@ -313,7 +318,7 @@ describe('DashboardScene', () => {
const body = scene.state.body as SceneGridLayout;
const gridRow = body.state.children[0] as SceneGridRow;
expect(body.state.children.length).toBe(4);
expect(body.state.children.length).toBe(5);
expect(gridRow.state.children.length).toBe(0);
});
@@ -333,6 +338,36 @@ describe('DashboardScene', () => {
expect(gridRow.state.children.length).toBe(0);
});
it('Should fail to copy a panel if it does not have a grid item parent', () => {
const vizPanel = new VizPanel({
title: 'Panel Title',
key: 'panel-5',
pluginId: 'timeseries',
});
scene.copyPanel(vizPanel);
expect(scene.state.hasCopiedPanel).toBe(false);
});
it('Should fail to copy a library panel if it does not have a grid item parent', () => {
const libVizPanel = new LibraryVizPanel({
uid: 'uid',
name: 'libraryPanel',
panelKey: 'panel-4',
title: 'Library Panel',
panel: new VizPanel({
title: 'Library Panel',
key: 'panel-4',
pluginId: 'table',
}),
});
scene.copyPanel(libVizPanel.state.panel as VizPanel);
expect(scene.state.hasCopiedPanel).toBe(false);
});
it('Should copy a panel', () => {
const vizPanel = ((scene.state.body as SceneGridLayout).state.children[0] as SceneGridItem).state.body;
scene.copyPanel(vizPanel as VizPanel);
@@ -340,6 +375,15 @@ describe('DashboardScene', () => {
expect(scene.state.hasCopiedPanel).toBe(true);
});
it('Should copy a library viz panel', () => {
const libVizPanel = ((scene.state.body as SceneGridLayout).state.children[4] as SceneGridItem).state
.body as LibraryVizPanel;
scene.copyPanel(libVizPanel.state.panel as VizPanel);
expect(scene.state.hasCopiedPanel).toBe(true);
});
it('Should paste a panel', () => {
scene.setState({ hasCopiedPanel: true });
jest.spyOn(JSON, 'parse').mockReturnThis();
@@ -359,19 +403,49 @@ describe('DashboardScene', () => {
const body = scene.state.body as SceneGridLayout;
const gridItem = body.state.children[0] as SceneGridItem;
expect(body.state.children.length).toBe(5);
expect(buildGridItemForPanel).toHaveBeenCalledTimes(1);
expect(body.state.children.length).toBe(6);
expect(gridItem.state.body!.state.key).toBe('panel-5');
expect(gridItem.state.y).toBe(0);
expect(scene.state.hasCopiedPanel).toBe(false);
});
it('Should paste a library viz panel', () => {
scene.setState({ hasCopiedPanel: true });
jest.spyOn(JSON, 'parse').mockReturnValue({ libraryPanel: { uid: 'uid', name: 'libraryPanel' } });
jest.mocked(buildGridItemForLibPanel).mockReturnValue(
new SceneGridItem({
body: new LibraryVizPanel({
title: 'Library Panel',
uid: 'uid',
name: 'libraryPanel',
panelKey: 'panel-4',
}),
})
);
scene.pastePanel();
const body = scene.state.body as SceneGridLayout;
const gridItem = body.state.children[0] as SceneGridItem;
const libVizPanel = gridItem.state.body as LibraryVizPanel;
expect(buildGridItemForLibPanel).toHaveBeenCalledTimes(1);
expect(body.state.children.length).toBe(6);
expect(libVizPanel.state.panelKey).toBe('panel-5');
expect(libVizPanel.state.panel?.state.key).toBe('panel-5');
expect(gridItem.state.y).toBe(0);
expect(scene.state.hasCopiedPanel).toBe(false);
});
it('Should create a new add library panel widget', () => {
scene.onCreateLibPanelWidget();
const body = scene.state.body as SceneGridLayout;
const gridItem = body.state.children[0] as SceneGridItem;
expect(body.state.children.length).toBe(5);
expect(body.state.children.length).toBe(6);
expect(gridItem.state.body!.state.key).toBe('panel-5');
expect(gridItem.state.y).toBe(0);
});
@@ -575,6 +649,19 @@ function buildTestScene(overrides?: Partial<DashboardSceneState>) {
$data: new SceneQueryRunner({ key: 'data-query-runner2', queries: [{ refId: 'A' }] }),
}),
}),
new SceneGridItem({
body: new LibraryVizPanel({
uid: 'uid',
name: 'libraryPanel',
panelKey: 'panel-4',
title: 'Library Panel',
panel: new VizPanel({
title: 'Library Panel',
key: 'panel-4',
pluginId: 'table',
}),
}),
}),
],
}),
...overrides,
@@ -33,7 +33,11 @@ import { PanelEditor } from '../panel-edit/PanelEditor';
import { DashboardSceneChangeTracker } from '../saving/DashboardSceneChangeTracker';
import { SaveDashboardDrawer } from '../saving/SaveDashboardDrawer';
import { DashboardSceneRenderer } from '../scene/DashboardSceneRenderer';
import { buildGridItemForPanel, transformSaveModelToScene } from '../serialization/transformSaveModelToScene';
import {
buildGridItemForLibPanel,
buildGridItemForPanel,
transformSaveModelToScene,
} from '../serialization/transformSaveModelToScene';
import { gridItemToPanel } from '../serialization/transformSceneToSaveModel';
import { DecoratedRevisionModel } from '../settings/VersionsEditView';
import { DashboardEditView } from '../settings/utils';
@@ -57,6 +61,7 @@ import {
import { AddLibraryPanelWidget } from './AddLibraryPanelWidget';
import { DashboardControls } from './DashboardControls';
import { DashboardSceneUrlSync } from './DashboardSceneUrlSync';
import { LibraryVizPanel } from './LibraryVizPanel';
import { PanelRepeaterGridItem } from './PanelRepeaterGridItem';
import { ViewPanelScene } from './ViewPanelScene';
import { setupKeyboardShortcuts } from './keyboardShortcuts';
@@ -480,7 +485,17 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
return;
}
const gridItem = vizPanel.parent;
let gridItem = vizPanel.parent;
if (vizPanel.parent instanceof LibraryVizPanel) {
const libraryVizPanel = vizPanel.parent;
if (!libraryVizPanel.parent) {
return;
}
gridItem = libraryVizPanel.parent;
}
const jsonData = gridItemToPanel(gridItem);
@@ -497,8 +512,10 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
const jsonData = store.get(LS_PANEL_COPY_KEY);
const jsonObj = JSON.parse(jsonData);
const panelModel = new PanelModel(jsonObj);
const gridItem = !panelModel.libraryPanel
? buildGridItemForPanel(panelModel)
: buildGridItemForLibPanel(panelModel);
const gridItem = buildGridItemForPanel(panelModel);
const sceneGridLayout = this.state.body;
if (!(gridItem instanceof SceneGridItem) && !(gridItem instanceof PanelRepeaterGridItem)) {
@@ -507,7 +524,17 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
const panelId = dashboardSceneGraph.getNextPanelId(this);
if (gridItem instanceof SceneGridItem && gridItem.state.body) {
if (gridItem instanceof SceneGridItem && gridItem.state.body instanceof LibraryVizPanel) {
const panelKey = getVizPanelKeyForPanelId(panelId);
gridItem.state.body.setState({ panelKey });
const vizPanel = gridItem.state.body.state.panel;
if (vizPanel instanceof VizPanel) {
vizPanel.setState({ key: panelKey });
}
} else if (gridItem instanceof SceneGridItem && gridItem.state.body) {
gridItem.state.body.setState({
key: getVizPanelKeyForPanelId(panelId),
});