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
pull/83498/head
Victor Marin 1 year ago committed by GitHub
parent ecb8447a7f
commit 738e9126de
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 123
      public/app/features/dashboard-scene/scene/DashboardScene.test.tsx
  2. 98
      public/app/features/dashboard-scene/scene/DashboardScene.tsx
  3. 4
      public/app/features/dashboard-scene/scene/NavToolbarActions.test.tsx
  4. 45
      public/app/features/dashboard-scene/scene/NavToolbarActions.tsx
  5. 117
      public/app/features/dashboard-scene/utils/dashboardSceneGraph.test.ts
  6. 60
      public/app/features/dashboard-scene/utils/dashboardSceneGraph.ts
  7. 61
      public/app/features/dashboard-scene/utils/utils.ts

@ -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,26 +430,46 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
return this._initialState;
}
public addPanel(vizPanel: VizPanel): void {
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;
// move all gridItems below the new one
for (const child of sceneGridLayout.state.children) {
child.setState({
y: NEW_PANEL_HEIGHT + (child.state.y ?? 0),
// 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');
}
const sceneGridLayout = this.state.body;
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…
Cancel
Save