mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Scenes: Add new row and copy/paste functionalities (#83231)
* wip * tests + refactor ad panel func * Add row functionality * update row state only when there are children * Add new row + copy paste panels * Add library panel functionality * tests * PR mods * reafctor + tests * reafctor * fix test * refactor * fix bug on cancelling lib widget * dashboard now saves with lib panel widget * add lib panels widget works in rows as well * split add lib panel func to another PR
This commit is contained in:
parent
ecb8447a7f
commit
738e9126de
@ -15,7 +15,7 @@ import appEvents from 'app/core/app_events';
|
||||
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
||||
import { VariablesChanged } from 'app/features/variables/types';
|
||||
|
||||
import { transformSaveModelToScene } from '../serialization/transformSaveModelToScene';
|
||||
import { buildGridItemForPanel, transformSaveModelToScene } from '../serialization/transformSaveModelToScene';
|
||||
import { DecoratedRevisionModel } from '../settings/VersionsEditView';
|
||||
import { historySrv } from '../settings/version-history/HistorySrv';
|
||||
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
|
||||
@ -26,6 +26,7 @@ import { DashboardScene, DashboardSceneState } from './DashboardScene';
|
||||
|
||||
jest.mock('../settings/version-history/HistorySrv');
|
||||
jest.mock('../serialization/transformSaveModelToScene');
|
||||
jest.mock('../serialization/transformSceneToSaveModel');
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getDataSourceSrv: () => {
|
||||
@ -133,7 +134,7 @@ describe('DashboardScene', () => {
|
||||
it('Should add a new panel to the dashboard', () => {
|
||||
const vizPanel = new VizPanel({
|
||||
title: 'Panel Title',
|
||||
key: 'panel-4',
|
||||
key: 'panel-5',
|
||||
pluginId: 'timeseries',
|
||||
$data: new SceneQueryRunner({ key: 'data-query-runner', queries: [{ refId: 'A' }] }),
|
||||
});
|
||||
@ -143,9 +144,9 @@ describe('DashboardScene', () => {
|
||||
const body = scene.state.body as SceneGridLayout;
|
||||
const gridItem = body.state.children[0] as SceneGridItem;
|
||||
|
||||
expect(scene.state.isDirty).toBe(true);
|
||||
expect(body.state.children.length).toBe(5);
|
||||
expect(gridItem.state.body!.state.key).toBe('panel-4');
|
||||
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', () => {
|
||||
@ -154,9 +155,114 @@ describe('DashboardScene', () => {
|
||||
const body = scene.state.body as SceneGridLayout;
|
||||
const gridItem = body.state.children[0] as SceneGridItem;
|
||||
|
||||
expect(scene.state.isDirty).toBe(true);
|
||||
expect(body.state.children.length).toBe(5);
|
||||
expect(gridItem.state.body!.state.key).toBe('panel-4');
|
||||
expect(gridItem.state.body!.state.key).toBe('panel-5');
|
||||
});
|
||||
|
||||
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(body.state.children.length).toBe(3);
|
||||
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');
|
||||
});
|
||||
|
||||
it('Should create a row and add all panels in the dashboard under it', () => {
|
||||
const scene = buildTestScene({
|
||||
body: new SceneGridLayout({
|
||||
children: [
|
||||
new SceneGridItem({
|
||||
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 SceneGridItem({
|
||||
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(4);
|
||||
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 copy a panel', () => {
|
||||
const vizPanel = ((scene.state.body as SceneGridLayout).state.children[0] as SceneGridItem).state.body;
|
||||
scene.copyPanel(vizPanel as VizPanel);
|
||||
|
||||
expect(scene.state.hasCopiedPanel).toBe(true);
|
||||
});
|
||||
|
||||
it('Should paste a panel', () => {
|
||||
scene.setState({ hasCopiedPanel: true });
|
||||
jest.spyOn(JSON, 'parse').mockReturnThis();
|
||||
jest.mocked(buildGridItemForPanel).mockReturnValue(
|
||||
new SceneGridItem({
|
||||
key: 'griditem-9',
|
||||
body: new VizPanel({
|
||||
title: 'Panel A',
|
||||
key: 'panel-9',
|
||||
pluginId: 'table',
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
scene.pastePanel();
|
||||
|
||||
const body = scene.state.body as SceneGridLayout;
|
||||
const gridItem = body.state.children[0] as SceneGridItem;
|
||||
|
||||
expect(body.state.children.length).toBe(5);
|
||||
expect(gridItem.state.body!.state.key).toBe('panel-5');
|
||||
expect(gridItem.state.y).toBe(0);
|
||||
expect(scene.state.hasCopiedPanel).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -275,6 +381,7 @@ function buildTestScene(overrides?: Partial<DashboardSceneState>) {
|
||||
}),
|
||||
}),
|
||||
new SceneGridItem({
|
||||
key: 'griditem-2',
|
||||
body: new VizPanel({
|
||||
title: 'Panel B',
|
||||
key: 'panel-2',
|
||||
@ -282,12 +389,12 @@ function buildTestScene(overrides?: Partial<DashboardSceneState>) {
|
||||
}),
|
||||
}),
|
||||
new SceneGridRow({
|
||||
key: 'gridrow-1',
|
||||
key: 'panel-3',
|
||||
children: [
|
||||
new SceneGridItem({
|
||||
body: new VizPanel({
|
||||
title: 'Panel C',
|
||||
key: 'panel-3',
|
||||
key: 'panel-4',
|
||||
pluginId: 'table',
|
||||
}),
|
||||
}),
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
sceneGraph,
|
||||
SceneGridItem,
|
||||
SceneGridLayout,
|
||||
SceneGridRow,
|
||||
SceneObject,
|
||||
SceneObjectBase,
|
||||
SceneObjectState,
|
||||
@ -28,7 +29,7 @@ import { LS_PANEL_COPY_KEY } from 'app/core/constants';
|
||||
import { getNavModel } from 'app/core/selectors/navModel';
|
||||
import store from 'app/core/store';
|
||||
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
||||
import { DashboardModel } from 'app/features/dashboard/state';
|
||||
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
||||
import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher';
|
||||
import { VariablesChanged } from 'app/features/variables/types';
|
||||
import { DashboardDTO, DashboardMeta, SaveDashboardResponseDTO } from 'app/types';
|
||||
@ -37,12 +38,13 @@ import { ShowConfirmModalEvent } from 'app/types/events';
|
||||
import { PanelEditor } from '../panel-edit/PanelEditor';
|
||||
import { SaveDashboardDrawer } from '../saving/SaveDashboardDrawer';
|
||||
import { DashboardSceneRenderer } from '../scene/DashboardSceneRenderer';
|
||||
import { transformSaveModelToScene } from '../serialization/transformSaveModelToScene';
|
||||
import { buildGridItemForPanel, transformSaveModelToScene } from '../serialization/transformSaveModelToScene';
|
||||
import { gridItemToPanel } from '../serialization/transformSceneToSaveModel';
|
||||
import { DecoratedRevisionModel } from '../settings/VersionsEditView';
|
||||
import { DashboardEditView } from '../settings/utils';
|
||||
import { historySrv } from '../settings/version-history';
|
||||
import { DashboardModelCompatibilityWrapper } from '../utils/DashboardModelCompatibilityWrapper';
|
||||
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
|
||||
import { djb2Hash } from '../utils/djb2Hash';
|
||||
import { getDashboardUrl } from '../utils/urlBuilders';
|
||||
import {
|
||||
@ -50,8 +52,10 @@ import {
|
||||
NEW_PANEL_WIDTH,
|
||||
forceRenderChildren,
|
||||
getClosestVizPanel,
|
||||
getDefaultRow,
|
||||
getDefaultVizPanel,
|
||||
getPanelIdForVizPanel,
|
||||
getVizPanelKeyForPanelId,
|
||||
isPanelClone,
|
||||
} from '../utils/utils';
|
||||
|
||||
@ -102,6 +106,8 @@ export interface DashboardSceneState extends SceneObjectState {
|
||||
editPanel?: PanelEditor;
|
||||
/** Scene object that handles the current drawer or modal */
|
||||
overlay?: SceneObject;
|
||||
/** True when a user copies a panel in the dashboard */
|
||||
hasCopiedPanel?: boolean;
|
||||
isEmpty?: boolean;
|
||||
}
|
||||
|
||||
@ -142,6 +148,7 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
||||
editable: true,
|
||||
body: state.body ?? new SceneFlexLayout({ children: [] }),
|
||||
links: state.links ?? [],
|
||||
hasCopiedPanel: store.exists(LS_PANEL_COPY_KEY),
|
||||
...state,
|
||||
});
|
||||
|
||||
@ -423,6 +430,31 @@ 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 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');
|
||||
@ -430,19 +462,14 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
||||
|
||||
const sceneGridLayout = this.state.body;
|
||||
|
||||
// move all gridItems below the new one
|
||||
for (const child of sceneGridLayout.state.children) {
|
||||
child.setState({
|
||||
y: NEW_PANEL_HEIGHT + (child.state.y ?? 0),
|
||||
});
|
||||
}
|
||||
|
||||
const panelId = getPanelIdForVizPanel(vizPanel);
|
||||
const newGridItem = new SceneGridItem({
|
||||
height: NEW_PANEL_HEIGHT,
|
||||
width: NEW_PANEL_WIDTH,
|
||||
x: 0,
|
||||
y: 0,
|
||||
body: vizPanel,
|
||||
key: `grid-item-${panelId}`,
|
||||
});
|
||||
|
||||
sceneGridLayout.setState({
|
||||
@ -457,7 +484,7 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
||||
|
||||
const gridItem = vizPanel.parent;
|
||||
|
||||
if (!(gridItem instanceof SceneGridItem || PanelRepeaterGridItem)) {
|
||||
if (!(gridItem instanceof SceneGridItem || gridItem instanceof PanelRepeaterGridItem)) {
|
||||
console.error('Trying to duplicate a panel in a layout that is not SceneGridItem or PanelRepeaterGridItem');
|
||||
return;
|
||||
}
|
||||
@ -506,7 +533,52 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
||||
const jsonData = gridItemToPanel(gridItem);
|
||||
|
||||
store.set(LS_PANEL_COPY_KEY, JSON.stringify(jsonData));
|
||||
appEvents.emit(AppEvents.alertSuccess, ['Panel copied. Click **Add panel** icon to paste.']);
|
||||
appEvents.emit(AppEvents.alertSuccess, ['Panel copied. Use **Paste panel** toolbar action to paste.']);
|
||||
this.setState({ hasCopiedPanel: true });
|
||||
}
|
||||
|
||||
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 SceneGridItem) && !(gridItem instanceof PanelRepeaterGridItem)) {
|
||||
throw new Error('Cannot paste invalid grid item');
|
||||
}
|
||||
|
||||
const panelId = dashboardSceneGraph.getNextPanelId(this);
|
||||
|
||||
if (gridItem instanceof SceneGridItem && gridItem.state.body) {
|
||||
gridItem.state.body.setState({
|
||||
key: getVizPanelKeyForPanelId(panelId),
|
||||
});
|
||||
} else if (gridItem instanceof PanelRepeaterGridItem) {
|
||||
gridItem.state.source.setState({
|
||||
key: getVizPanelKeyForPanelId(panelId),
|
||||
});
|
||||
}
|
||||
|
||||
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.setState({ hasCopiedPanel: false });
|
||||
store.delete(LS_PANEL_COPY_KEY);
|
||||
}
|
||||
|
||||
public showModal(modal: SceneObject) {
|
||||
@ -540,6 +612,14 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
||||
locationService.partial({ editview: 'settings' });
|
||||
};
|
||||
|
||||
public onCreateNewRow() {
|
||||
const row = getDefaultRow(this);
|
||||
|
||||
this.addRow(row);
|
||||
|
||||
return getPanelIdForVizPanel(row);
|
||||
}
|
||||
|
||||
public onCreateNewPanel(): number {
|
||||
const vizPanel = getDefaultVizPanel(this);
|
||||
|
||||
|
@ -16,6 +16,8 @@ describe('NavToolbarActions', () => {
|
||||
|
||||
expect(screen.queryByText('Save dashboard')).not.toBeInTheDocument();
|
||||
expect(screen.queryByLabelText('Add visualization')).not.toBeInTheDocument();
|
||||
expect(screen.queryByLabelText('Add row')).not.toBeInTheDocument();
|
||||
expect(screen.queryByLabelText('Paste panel')).not.toBeInTheDocument();
|
||||
expect(await screen.findByText('Edit')).toBeInTheDocument();
|
||||
expect(await screen.findByText('Share')).toBeInTheDocument();
|
||||
});
|
||||
@ -28,6 +30,8 @@ describe('NavToolbarActions', () => {
|
||||
expect(await screen.findByText('Save dashboard')).toBeInTheDocument();
|
||||
expect(await screen.findByText('Exit edit')).toBeInTheDocument();
|
||||
expect(await screen.findByLabelText('Add visualization')).toBeInTheDocument();
|
||||
expect(await screen.findByLabelText('Add row')).toBeInTheDocument();
|
||||
expect(await screen.findByLabelText('Paste panel')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Edit')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Share')).not.toBeInTheDocument();
|
||||
});
|
||||
|
@ -37,12 +37,22 @@ NavToolbarActions.displayName = 'NavToolbarActions';
|
||||
* This part is split into a separate component to help test this
|
||||
*/
|
||||
export function ToolbarActions({ dashboard }: Props) {
|
||||
const { isEditing, viewPanelScene, isDirty, uid, meta, editview, editPanel } = dashboard.useState();
|
||||
const {
|
||||
isEditing,
|
||||
viewPanelScene,
|
||||
isDirty,
|
||||
uid,
|
||||
meta,
|
||||
editview,
|
||||
editPanel,
|
||||
hasCopiedPanel: copiedPanel,
|
||||
} = dashboard.useState();
|
||||
const canSaveAs = contextSrv.hasEditPermissionInFolders;
|
||||
const toolbarActions: ToolbarAction[] = [];
|
||||
const buttonWithExtraMargin = useStyles2(getStyles);
|
||||
const isEditingPanel = Boolean(editPanel);
|
||||
const isViewingPanel = Boolean(viewPanelScene);
|
||||
const hasCopiedPanel = Boolean(copiedPanel);
|
||||
|
||||
toolbarActions.push({
|
||||
group: 'icon-actions',
|
||||
@ -61,6 +71,39 @@ export function ToolbarActions({ dashboard }: Props) {
|
||||
),
|
||||
});
|
||||
|
||||
toolbarActions.push({
|
||||
group: 'icon-actions',
|
||||
condition: isEditing && !editview && !isViewingPanel && !isEditingPanel,
|
||||
render: () => (
|
||||
<ToolbarButton
|
||||
key="add-row"
|
||||
tooltip={'Add row'}
|
||||
icon="wrap-text"
|
||||
onClick={() => {
|
||||
dashboard.onCreateNewRow();
|
||||
DashboardInteractions.toolbarAddButtonClicked({ item: 'add_row' });
|
||||
}}
|
||||
/>
|
||||
),
|
||||
});
|
||||
|
||||
toolbarActions.push({
|
||||
group: 'icon-actions',
|
||||
condition: isEditing && !editview && !isViewingPanel && !isEditingPanel,
|
||||
render: () => (
|
||||
<ToolbarButton
|
||||
key="paste-panel"
|
||||
disabled={!hasCopiedPanel}
|
||||
tooltip={'Paste panel'}
|
||||
icon="copy"
|
||||
onClick={() => {
|
||||
dashboard.pastePanel();
|
||||
DashboardInteractions.toolbarAddButtonClicked({ item: 'paste_panel' });
|
||||
}}
|
||||
/>
|
||||
),
|
||||
});
|
||||
|
||||
toolbarActions.push({
|
||||
group: 'icon-actions',
|
||||
condition: uid && !editview && Boolean(meta.canStar) && !isEditingPanel && !isEditing,
|
||||
|
@ -12,9 +12,10 @@ import { AlertStatesDataLayer } from '../scene/AlertStatesDataLayer';
|
||||
import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer';
|
||||
import { DashboardControls } from '../scene/DashboardControls';
|
||||
import { DashboardScene, DashboardSceneState } from '../scene/DashboardScene';
|
||||
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
|
||||
import { VizPanelLinks, VizPanelLinksMenu } from '../scene/PanelLinks';
|
||||
|
||||
import { dashboardSceneGraph } from './dashboardSceneGraph';
|
||||
import { dashboardSceneGraph, getNextPanelId } from './dashboardSceneGraph';
|
||||
import { findVizPanelByKey } from './utils';
|
||||
|
||||
describe('dashboardSceneGraph', () => {
|
||||
@ -84,6 +85,120 @@ describe('dashboardSceneGraph', () => {
|
||||
expect(() => dashboardSceneGraph.getDataLayers(scene)).toThrow('SceneDataLayers not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getNextPanelId', () => {
|
||||
it('should get next panel id in a simple 3 panel layout', () => {
|
||||
const scene = buildTestScene({
|
||||
body: new SceneGridLayout({
|
||||
children: [
|
||||
new SceneGridItem({
|
||||
body: new VizPanel({
|
||||
title: 'Panel A',
|
||||
key: 'panel-1',
|
||||
pluginId: 'table',
|
||||
}),
|
||||
}),
|
||||
new SceneGridItem({
|
||||
body: new VizPanel({
|
||||
title: 'Panel B',
|
||||
key: 'panel-2',
|
||||
pluginId: 'table',
|
||||
}),
|
||||
}),
|
||||
new SceneGridItem({
|
||||
body: new VizPanel({
|
||||
title: 'Panel C',
|
||||
key: 'panel-3',
|
||||
pluginId: 'table',
|
||||
}),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
const id = getNextPanelId(scene);
|
||||
|
||||
expect(id).toBe(4);
|
||||
});
|
||||
|
||||
it('should take library panels into account', () => {
|
||||
const scene = buildTestScene({
|
||||
body: new SceneGridLayout({
|
||||
children: [
|
||||
new SceneGridItem({
|
||||
key: 'griditem-1',
|
||||
x: 0,
|
||||
body: new VizPanel({
|
||||
title: 'Panel A',
|
||||
key: 'panel-1',
|
||||
pluginId: 'table',
|
||||
}),
|
||||
}),
|
||||
new SceneGridItem({
|
||||
body: new LibraryVizPanel({
|
||||
uid: 'uid',
|
||||
name: 'LibPanel',
|
||||
title: 'Library Panel',
|
||||
panelKey: 'panel-2',
|
||||
}),
|
||||
}),
|
||||
new SceneGridItem({
|
||||
body: new VizPanel({
|
||||
title: 'Panel C',
|
||||
key: 'panel-2-clone-1',
|
||||
pluginId: 'table',
|
||||
}),
|
||||
}),
|
||||
new SceneGridRow({
|
||||
key: 'key',
|
||||
title: 'row',
|
||||
children: [
|
||||
new SceneGridItem({
|
||||
body: new VizPanel({
|
||||
title: 'Panel E',
|
||||
key: 'panel-2-clone-2',
|
||||
pluginId: 'table',
|
||||
}),
|
||||
}),
|
||||
new SceneGridItem({
|
||||
body: new LibraryVizPanel({
|
||||
uid: 'uid',
|
||||
name: 'LibPanel',
|
||||
title: 'Library Panel',
|
||||
panelKey: 'panel-3',
|
||||
}),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
const id = getNextPanelId(scene);
|
||||
|
||||
expect(id).toBe(4);
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function buildTestScene(overrides?: Partial<DashboardSceneState>) {
|
||||
|
@ -1,8 +1,11 @@
|
||||
import { VizPanel, SceneGridItem, SceneGridRow, SceneDataLayers, sceneGraph } from '@grafana/scenes';
|
||||
import { VizPanel, SceneGridItem, SceneGridRow, SceneDataLayers, sceneGraph, SceneGridLayout } from '@grafana/scenes';
|
||||
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
|
||||
import { VizPanelLinks } from '../scene/PanelLinks';
|
||||
|
||||
import { getPanelIdForLibraryVizPanel, getPanelIdForVizPanel } from './utils';
|
||||
|
||||
function getTimePicker(scene: DashboardScene) {
|
||||
return scene.state.controls?.state.timePicker;
|
||||
}
|
||||
@ -55,10 +58,65 @@ function getDataLayers(scene: DashboardScene): SceneDataLayers {
|
||||
return data;
|
||||
}
|
||||
|
||||
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 SceneGridItem) {
|
||||
const vizPanel = child.state.body;
|
||||
|
||||
if (vizPanel) {
|
||||
const panelId =
|
||||
vizPanel instanceof LibraryVizPanel
|
||||
? getPanelIdForLibraryVizPanel(vizPanel)
|
||||
: 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 SceneGridItem) {
|
||||
const vizPanel = rowChild.state.body;
|
||||
|
||||
if (vizPanel) {
|
||||
const panelId =
|
||||
vizPanel instanceof LibraryVizPanel
|
||||
? getPanelIdForLibraryVizPanel(vizPanel)
|
||||
: getPanelIdForVizPanel(vizPanel);
|
||||
|
||||
if (panelId > max) {
|
||||
max = panelId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return max + 1;
|
||||
}
|
||||
|
||||
export const dashboardSceneGraph = {
|
||||
getTimePicker,
|
||||
getRefreshPicker,
|
||||
getPanelLinks,
|
||||
getVizPanels,
|
||||
getDataLayers,
|
||||
getNextPanelId,
|
||||
};
|
||||
|
@ -4,8 +4,6 @@ import {
|
||||
MultiValueVariable,
|
||||
SceneDataTransformer,
|
||||
sceneGraph,
|
||||
SceneGridItem,
|
||||
SceneGridLayout,
|
||||
SceneGridRow,
|
||||
SceneObject,
|
||||
SceneQueryRunner,
|
||||
@ -19,6 +17,8 @@ import { LibraryVizPanel } from '../scene/LibraryVizPanel';
|
||||
import { VizPanelLinks, VizPanelLinksMenu } from '../scene/PanelLinks';
|
||||
import { panelMenuBehavior } from '../scene/PanelMenuBehavior';
|
||||
|
||||
import { dashboardSceneGraph } from './dashboardSceneGraph';
|
||||
|
||||
export const NEW_PANEL_HEIGHT = 8;
|
||||
export const NEW_PANEL_WIDTH = 12;
|
||||
|
||||
@ -30,8 +30,12 @@ export function getPanelIdForVizPanel(panel: SceneObject): number {
|
||||
return parseInt(panel.state.key!.replace('panel-', ''), 10);
|
||||
}
|
||||
|
||||
export function getPanelIdForLibraryVizPanel(panel: LibraryVizPanel): number {
|
||||
return parseInt(panel.state.panelKey!.replace('panel-', ''), 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will also try lookup based on panelId
|
||||
* This will also try lookup based on panelId
|
||||
*/
|
||||
export function findVizPanelByKey(scene: SceneObject, key: string | undefined): VizPanel | null {
|
||||
if (!key) {
|
||||
@ -201,47 +205,8 @@ export function isPanelClone(key: string) {
|
||||
return key.includes('clone');
|
||||
}
|
||||
|
||||
export function getNextPanelId(dashboard: DashboardScene) {
|
||||
let max = 0;
|
||||
const body = dashboard.state.body;
|
||||
|
||||
if (body instanceof SceneGridLayout) {
|
||||
for (const child of body.state.children) {
|
||||
if (child instanceof SceneGridItem) {
|
||||
const vizPanel = child.state.body;
|
||||
|
||||
if (vizPanel instanceof VizPanel) {
|
||||
const panelId = getPanelIdForVizPanel(vizPanel);
|
||||
|
||||
if (panelId > max) {
|
||||
max = panelId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (child instanceof SceneGridRow) {
|
||||
for (const rowChild of child.state.children) {
|
||||
if (rowChild instanceof SceneGridItem) {
|
||||
const vizPanel = rowChild.state.body;
|
||||
|
||||
if (vizPanel instanceof VizPanel) {
|
||||
const panelId = getPanelIdForVizPanel(vizPanel);
|
||||
|
||||
if (panelId > max) {
|
||||
max = panelId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return max + 1;
|
||||
}
|
||||
|
||||
export function getDefaultVizPanel(dashboard: DashboardScene): VizPanel {
|
||||
const panelId = getNextPanelId(dashboard);
|
||||
const panelId = dashboardSceneGraph.getNextPanelId(dashboard);
|
||||
|
||||
return new VizPanel({
|
||||
title: 'Panel Title',
|
||||
@ -261,6 +226,16 @@ 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',
|
||||
y: 0,
|
||||
});
|
||||
}
|
||||
|
||||
export function isLibraryPanelChild(vizPanel: VizPanel) {
|
||||
return vizPanel.parent instanceof LibraryVizPanel;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user