mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
parent
298384cea9
commit
f50624d257
@ -30,6 +30,7 @@ import { djb2Hash } from '../utils/djb2Hash';
|
|||||||
import { DashboardControls } from './DashboardControls';
|
import { DashboardControls } from './DashboardControls';
|
||||||
import { DashboardScene, DashboardSceneState } from './DashboardScene';
|
import { DashboardScene, DashboardSceneState } from './DashboardScene';
|
||||||
import { LibraryVizPanel } from './LibraryVizPanel';
|
import { LibraryVizPanel } from './LibraryVizPanel';
|
||||||
|
import { PanelRepeaterGridItem } from './PanelRepeaterGridItem';
|
||||||
|
|
||||||
jest.mock('../settings/version-history/HistorySrv');
|
jest.mock('../settings/version-history/HistorySrv');
|
||||||
jest.mock('../serialization/transformSaveModelToScene');
|
jest.mock('../serialization/transformSaveModelToScene');
|
||||||
@ -541,6 +542,118 @@ describe('DashboardScene', () => {
|
|||||||
expect(gridRow.state.children.length).toBe(1);
|
expect(gridRow.state.children.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Should duplicate a panel', () => {
|
||||||
|
const vizPanel = ((scene.state.body as SceneGridLayout).state.children[0] as SceneGridItem).state.body;
|
||||||
|
scene.duplicatePanel(vizPanel as VizPanel);
|
||||||
|
|
||||||
|
const body = scene.state.body as SceneGridLayout;
|
||||||
|
const gridItem = body.state.children[5] as SceneGridItem;
|
||||||
|
|
||||||
|
expect(body.state.children.length).toBe(6);
|
||||||
|
expect(gridItem.state.body!.state.key).toBe('panel-7');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should duplicate a library panel', () => {
|
||||||
|
const libraryPanel = ((scene.state.body as SceneGridLayout).state.children[4] as SceneGridItem).state.body;
|
||||||
|
const vizPanel = (libraryPanel as LibraryVizPanel).state.panel;
|
||||||
|
scene.duplicatePanel(vizPanel as VizPanel);
|
||||||
|
|
||||||
|
const body = scene.state.body as SceneGridLayout;
|
||||||
|
const gridItem = body.state.children[5] as SceneGridItem;
|
||||||
|
|
||||||
|
const libVizPanel = gridItem.state.body as LibraryVizPanel;
|
||||||
|
|
||||||
|
expect(body.state.children.length).toBe(6);
|
||||||
|
expect(libVizPanel.state.panelKey).toBe('panel-7');
|
||||||
|
expect(libVizPanel.state.panel?.state.key).toBe('panel-7');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should duplicate a repeated panel', () => {
|
||||||
|
const scene = buildTestScene({
|
||||||
|
body: new SceneGridLayout({
|
||||||
|
children: [
|
||||||
|
new PanelRepeaterGridItem({
|
||||||
|
key: `grid-item-1`,
|
||||||
|
width: 24,
|
||||||
|
height: 8,
|
||||||
|
repeatedPanels: [
|
||||||
|
new VizPanel({
|
||||||
|
title: 'Library Panel',
|
||||||
|
key: 'panel-1',
|
||||||
|
pluginId: 'table',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
source: new VizPanel({
|
||||||
|
title: 'Library Panel',
|
||||||
|
key: 'panel-1',
|
||||||
|
pluginId: 'table',
|
||||||
|
}),
|
||||||
|
variableName: 'custom',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const vizPanel = ((scene.state.body as SceneGridLayout).state.children[0] as PanelRepeaterGridItem).state
|
||||||
|
.repeatedPanels![0];
|
||||||
|
|
||||||
|
scene.duplicatePanel(vizPanel as VizPanel);
|
||||||
|
|
||||||
|
const body = scene.state.body as SceneGridLayout;
|
||||||
|
const gridItem = body.state.children[1] as SceneGridItem;
|
||||||
|
|
||||||
|
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 SceneGridItem
|
||||||
|
).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 SceneGridItem;
|
||||||
|
|
||||||
|
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 SceneGridItem
|
||||||
|
).state.body;
|
||||||
|
const vizPanel = (libraryPanel as LibraryVizPanel).state.panel;
|
||||||
|
|
||||||
|
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 SceneGridItem;
|
||||||
|
|
||||||
|
const libVizPanel = gridItem.state.body as LibraryVizPanel;
|
||||||
|
|
||||||
|
expect(gridRow.state.children.length).toBe(3);
|
||||||
|
expect(libVizPanel.state.panelKey).toBe('panel-7');
|
||||||
|
expect(libVizPanel.state.panel?.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', () => {
|
it('Should unlink a library panel', () => {
|
||||||
const libPanel = new LibraryVizPanel({
|
const libPanel = new LibraryVizPanel({
|
||||||
title: 'title',
|
title: 'title',
|
||||||
|
@ -43,7 +43,7 @@ import { DecoratedRevisionModel } from '../settings/VersionsEditView';
|
|||||||
import { DashboardEditView } from '../settings/utils';
|
import { DashboardEditView } from '../settings/utils';
|
||||||
import { historySrv } from '../settings/version-history';
|
import { historySrv } from '../settings/version-history';
|
||||||
import { DashboardModelCompatibilityWrapper } from '../utils/DashboardModelCompatibilityWrapper';
|
import { DashboardModelCompatibilityWrapper } from '../utils/DashboardModelCompatibilityWrapper';
|
||||||
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
|
import { dashboardSceneGraph, getLibraryVizPanelFromVizPanel } from '../utils/dashboardSceneGraph';
|
||||||
import { djb2Hash } from '../utils/djb2Hash';
|
import { djb2Hash } from '../utils/djb2Hash';
|
||||||
import { getDashboardUrl } from '../utils/urlBuilders';
|
import { getDashboardUrl } from '../utils/urlBuilders';
|
||||||
import {
|
import {
|
||||||
@ -459,7 +459,9 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const gridItem = vizPanel.parent;
|
const libraryPanel = getLibraryVizPanelFromVizPanel(vizPanel);
|
||||||
|
|
||||||
|
const gridItem = libraryPanel ? libraryPanel.parent : vizPanel.parent;
|
||||||
|
|
||||||
if (!(gridItem instanceof SceneGridItem || gridItem instanceof PanelRepeaterGridItem)) {
|
if (!(gridItem instanceof SceneGridItem || gridItem instanceof PanelRepeaterGridItem)) {
|
||||||
console.error('Trying to duplicate a panel in a layout that is not SceneGridItem or PanelRepeaterGridItem');
|
console.error('Trying to duplicate a panel in a layout that is not SceneGridItem or PanelRepeaterGridItem');
|
||||||
@ -468,25 +470,44 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
|||||||
|
|
||||||
let panelState;
|
let panelState;
|
||||||
let panelData;
|
let panelData;
|
||||||
|
let newGridItem;
|
||||||
|
const newPanelId = dashboardSceneGraph.getNextPanelId(this);
|
||||||
|
|
||||||
|
if (libraryPanel) {
|
||||||
|
const gridItemToDuplicateState = sceneUtils.cloneSceneObjectState(gridItem.state);
|
||||||
|
|
||||||
|
newGridItem = new SceneGridItem({
|
||||||
|
x: gridItemToDuplicateState.x,
|
||||||
|
y: gridItemToDuplicateState.y,
|
||||||
|
width: gridItemToDuplicateState.width,
|
||||||
|
height: gridItemToDuplicateState.height,
|
||||||
|
body: new LibraryVizPanel({
|
||||||
|
title: libraryPanel.state.title,
|
||||||
|
uid: libraryPanel.state.uid,
|
||||||
|
name: libraryPanel.state.name,
|
||||||
|
panelKey: getVizPanelKeyForPanelId(newPanelId),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
if (gridItem instanceof PanelRepeaterGridItem) {
|
if (gridItem instanceof PanelRepeaterGridItem) {
|
||||||
const { key, ...gridRepeaterSourceState } = sceneUtils.cloneSceneObjectState(gridItem.state.source.state);
|
panelState = sceneUtils.cloneSceneObjectState(gridItem.state.source.state);
|
||||||
panelState = { ...gridRepeaterSourceState };
|
|
||||||
panelData = sceneGraph.getData(gridItem.state.source).clone();
|
panelData = sceneGraph.getData(gridItem.state.source).clone();
|
||||||
} else {
|
} else {
|
||||||
const { key, ...gridItemPanelState } = sceneUtils.cloneSceneObjectState(vizPanel.state);
|
panelState = sceneUtils.cloneSceneObjectState(vizPanel.state);
|
||||||
panelState = { ...gridItemPanelState };
|
|
||||||
panelData = sceneGraph.getData(vizPanel).clone();
|
panelData = sceneGraph.getData(vizPanel).clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
// when we duplicate a panel we don't want to clone the alert state
|
// when we duplicate a panel we don't want to clone the alert state
|
||||||
delete panelData.state.data?.alertState;
|
delete panelData.state.data?.alertState;
|
||||||
|
|
||||||
const { key: gridItemKey, ...gridItemToDuplicateState } = sceneUtils.cloneSceneObjectState(gridItem.state);
|
newGridItem = new SceneGridItem({
|
||||||
|
x: gridItem.state.x,
|
||||||
const newGridItem = new SceneGridItem({
|
y: gridItem.state.y,
|
||||||
...gridItemToDuplicateState,
|
height: NEW_PANEL_HEIGHT,
|
||||||
body: new VizPanel({ ...panelState, $data: panelData }),
|
width: NEW_PANEL_WIDTH,
|
||||||
|
body: new VizPanel({ ...panelState, $data: panelData, key: getVizPanelKeyForPanelId(newPanelId) }),
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!(this.state.body instanceof SceneGridLayout)) {
|
if (!(this.state.body instanceof SceneGridLayout)) {
|
||||||
console.error('Trying to duplicate a panel in a layout that is not SceneGridLayout ');
|
console.error('Trying to duplicate a panel in a layout that is not SceneGridLayout ');
|
||||||
@ -495,6 +516,18 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
|||||||
|
|
||||||
const sceneGridLayout = this.state.body;
|
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({
|
sceneGridLayout.setState({
|
||||||
children: [...sceneGridLayout.state.children, newGridItem],
|
children: [...sceneGridLayout.state.children, newGridItem],
|
||||||
});
|
});
|
||||||
|
@ -16,6 +16,7 @@ import { DashboardControls } from '../scene/DashboardControls';
|
|||||||
import { DashboardScene, DashboardSceneState } from '../scene/DashboardScene';
|
import { DashboardScene, DashboardSceneState } from '../scene/DashboardScene';
|
||||||
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
|
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
|
||||||
import { VizPanelLinks, VizPanelLinksMenu } from '../scene/PanelLinks';
|
import { VizPanelLinks, VizPanelLinksMenu } from '../scene/PanelLinks';
|
||||||
|
import { PanelRepeaterGridItem } from '../scene/PanelRepeaterGridItem';
|
||||||
|
|
||||||
import { dashboardSceneGraph, getNextPanelId } from './dashboardSceneGraph';
|
import { dashboardSceneGraph, getNextPanelId } from './dashboardSceneGraph';
|
||||||
import { findVizPanelByKey } from './utils';
|
import { findVizPanelByKey } from './utils';
|
||||||
@ -123,7 +124,7 @@ describe('dashboardSceneGraph', () => {
|
|||||||
expect(id).toBe(4);
|
expect(id).toBe(4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should take library panels into account', () => {
|
it('should take library panels, panels in rows and panel repeaters into account', () => {
|
||||||
const scene = buildTestScene({
|
const scene = buildTestScene({
|
||||||
body: new SceneGridLayout({
|
body: new SceneGridLayout({
|
||||||
children: [
|
children: [
|
||||||
@ -151,6 +152,17 @@ describe('dashboardSceneGraph', () => {
|
|||||||
pluginId: 'table',
|
pluginId: 'table',
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
new PanelRepeaterGridItem({
|
||||||
|
source: new VizPanel({
|
||||||
|
title: 'Panel C',
|
||||||
|
key: 'panel-4',
|
||||||
|
pluginId: 'table',
|
||||||
|
}),
|
||||||
|
variableName: 'repeat',
|
||||||
|
repeatedPanels: [],
|
||||||
|
repeatDirection: 'h',
|
||||||
|
maxPerRow: 1,
|
||||||
|
}),
|
||||||
new SceneGridRow({
|
new SceneGridRow({
|
||||||
key: 'key',
|
key: 'key',
|
||||||
title: 'row',
|
title: 'row',
|
||||||
@ -178,7 +190,7 @@ describe('dashboardSceneGraph', () => {
|
|||||||
|
|
||||||
const id = getNextPanelId(scene);
|
const id = getNextPanelId(scene);
|
||||||
|
|
||||||
expect(id).toBe(4);
|
expect(id).toBe(5);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should get next panel id in a layout with rows', () => {
|
it('should get next panel id in a layout with rows', () => {
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
import { DashboardScene } from '../scene/DashboardScene';
|
import { DashboardScene } from '../scene/DashboardScene';
|
||||||
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
|
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
|
||||||
import { VizPanelLinks } from '../scene/PanelLinks';
|
import { VizPanelLinks } from '../scene/PanelLinks';
|
||||||
|
import { PanelRepeaterGridItem } from '../scene/PanelRepeaterGridItem';
|
||||||
|
|
||||||
import { getPanelIdForLibraryVizPanel, getPanelIdForVizPanel } from './utils';
|
import { getPanelIdForLibraryVizPanel, getPanelIdForVizPanel } from './utils';
|
||||||
|
|
||||||
@ -85,6 +86,21 @@ export function getNextPanelId(dashboard: DashboardScene): number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const child of body.state.children) {
|
for (const child of body.state.children) {
|
||||||
|
if (child instanceof PanelRepeaterGridItem) {
|
||||||
|
const vizPanel = child.state.source;
|
||||||
|
|
||||||
|
if (vizPanel) {
|
||||||
|
const panelId =
|
||||||
|
vizPanel instanceof LibraryVizPanel
|
||||||
|
? getPanelIdForLibraryVizPanel(vizPanel)
|
||||||
|
: getPanelIdForVizPanel(vizPanel);
|
||||||
|
|
||||||
|
if (panelId > max) {
|
||||||
|
max = panelId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (child instanceof SceneGridItem) {
|
if (child instanceof SceneGridItem) {
|
||||||
const vizPanel = child.state.body;
|
const vizPanel = child.state.body;
|
||||||
|
|
||||||
@ -130,6 +146,19 @@ export function getNextPanelId(dashboard: DashboardScene): number {
|
|||||||
return max + 1;
|
return max + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the LibraryVizPanel that corresponds to the given VizPanel if it exists
|
||||||
|
export const getLibraryVizPanelFromVizPanel = (vizPanel: VizPanel): LibraryVizPanel | null => {
|
||||||
|
if (vizPanel.parent instanceof LibraryVizPanel) {
|
||||||
|
return vizPanel.parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vizPanel.parent instanceof PanelRepeaterGridItem && vizPanel.parent.state.source instanceof LibraryVizPanel) {
|
||||||
|
return vizPanel.parent.state.source;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
export const dashboardSceneGraph = {
|
export const dashboardSceneGraph = {
|
||||||
getTimePicker,
|
getTimePicker,
|
||||||
getRefreshPicker,
|
getRefreshPicker,
|
||||||
|
Loading…
Reference in New Issue
Block a user