From a4ff2abd9dc81b1e46bc3c236ca8c6306560e2a2 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Thu, 21 Mar 2024 14:38:00 +0100 Subject: [PATCH] PanelGridItemRepeater: Refactor (#84710) * wip * serialisation tests updates * VizPanelManager test updates * ests updates * Making it green * Use DashboardGridItem instead of SceneGridItem * Cleanup tests that unnecessarily depend on dashboard grid items * Fix row repeater behavior test * Update public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx Co-authored-by: Oscar Kilhed * Fix unnecessary library panel changes detection * Fix test --------- Co-authored-by: Oscar Kilhed --- .../inspect/HelpWizard/HelpWizard.test.tsx | 12 +- .../HelpWizard/SupportSnapshotService.test.ts | 12 +- .../inspect/HelpWizard/utils.ts | 11 +- .../inspect/InspectJsonTab.test.tsx | 16 +-- .../inspect/InspectJsonTab.tsx | 38 +++-- .../panel-edit/PanelEditor.test.ts | 16 ++- .../panel-edit/PanelEditor.tsx | 81 ++--------- .../panel-edit/PanelOptions.test.tsx | 5 +- .../panel-edit/VizPanelManager.test.tsx | 15 +- .../panel-edit/VizPanelManager.tsx | 49 ++++--- .../saving/DashboardPrompt.test.tsx | 5 +- .../saving/DashboardSceneChangeTracker.ts | 4 +- .../scene/AddLibraryPanelWidget.test.tsx | 23 ++-- .../scene/AddLibraryPanelWidget.tsx | 14 +- ...em.test.tsx => DashboardGridItem.test.tsx} | 0 ...aterGridItem.tsx => DashboardGridItem.tsx} | 61 +++++--- .../scene/DashboardScene.test.tsx | 83 +++++------ .../dashboard-scene/scene/DashboardScene.tsx | 40 +++--- .../scene/DashboardSceneUrlSync.test.ts | 9 +- .../scene/LibraryVizPanel.test.ts | 15 +- .../dashboard-scene/scene/LibraryVizPanel.tsx | 16 ++- .../scene/PanelMenuBehavior.test.tsx | 4 +- .../scene/RowRepeaterBehavior.test.tsx | 4 +- .../scene/ViewPanelScene.test.tsx | 12 +- .../scene/row-actions/RowActions.tsx | 13 +- .../transformSaveModelToScene.test.ts | 41 +++--- .../transformSaveModelToScene.ts | 46 +++---- .../transformSceneToSaveModel.test.ts | 17 +-- .../transformSceneToSaveModel.ts | 130 +++++++++--------- .../settings/AnnotationsEditView.test.tsx | 24 +--- .../settings/DashboardLinksEditView.test.tsx | 24 +--- .../settings/GeneralSettingsEditView.test.tsx | 24 +--- .../settings/PermissionsEditView.test.tsx | 24 +--- .../settings/VariablesEditView.test.tsx | 6 +- .../settings/VersionsEditView.test.tsx | 24 +--- .../sharing/ShareLibraryPanelTab.tsx | 6 +- .../sharing/ShareLinkTab.test.tsx | 5 +- .../dashboard-scene/sharing/ShareModal.tsx | 10 +- .../sharing/public-dashboards/utils.ts | 12 +- .../dashboard-scene/solo/useSoloPanel.ts | 6 +- ...DashboardModelCompatibilityWrapper.test.ts | 12 +- .../DashboardModelCompatibilityWrapper.ts | 6 +- .../utils/dashboardSceneGraph.test.ts | 35 +++-- .../utils/dashboardSceneGraph.ts | 43 ++---- .../dashboard-scene/utils/test-utils.ts | 13 +- .../app/features/library-panels/state/api.ts | 22 ++- 46 files changed, 494 insertions(+), 594 deletions(-) rename public/app/features/dashboard-scene/scene/{PanelRepeaterGridItem.test.tsx => DashboardGridItem.test.tsx} (100%) rename public/app/features/dashboard-scene/scene/{PanelRepeaterGridItem.tsx => DashboardGridItem.tsx} (75%) diff --git a/public/app/features/dashboard-scene/inspect/HelpWizard/HelpWizard.test.tsx b/public/app/features/dashboard-scene/inspect/HelpWizard/HelpWizard.test.tsx index 1b42b2dad66..70b6d361137 100644 --- a/public/app/features/dashboard-scene/inspect/HelpWizard/HelpWizard.test.tsx +++ b/public/app/features/dashboard-scene/inspect/HelpWizard/HelpWizard.test.tsx @@ -3,15 +3,9 @@ import React from 'react'; import { FieldType, getDefaultTimeRange, LoadingState, toDataFrame } from '@grafana/data'; import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks'; -import { - SceneGridItem, - SceneGridLayout, - SceneQueryRunner, - SceneTimeRange, - VizPanel, - VizPanelMenu, -} from '@grafana/scenes'; +import { SceneGridLayout, 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'; @@ -76,7 +70,7 @@ async function buildTestScene() { }, body: new SceneGridLayout({ children: [ - new SceneGridItem({ + new DashboardGridItem({ key: 'griditem-1', x: 0, y: 0, diff --git a/public/app/features/dashboard-scene/inspect/HelpWizard/SupportSnapshotService.test.ts b/public/app/features/dashboard-scene/inspect/HelpWizard/SupportSnapshotService.test.ts index f8cc6fba857..4d1fc427cbd 100644 --- a/public/app/features/dashboard-scene/inspect/HelpWizard/SupportSnapshotService.test.ts +++ b/public/app/features/dashboard-scene/inspect/HelpWizard/SupportSnapshotService.test.ts @@ -1,13 +1,7 @@ import { FieldType, getDefaultTimeRange, LoadingState, toDataFrame } from '@grafana/data'; -import { - SceneGridItem, - SceneGridLayout, - SceneQueryRunner, - SceneTimeRange, - VizPanel, - VizPanelMenu, -} from '@grafana/scenes'; +import { SceneGridLayout, 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'; @@ -122,7 +116,7 @@ async function buildTestScene() { }, body: new SceneGridLayout({ children: [ - new SceneGridItem({ + new DashboardGridItem({ key: 'griditem-1', x: 0, y: 0, diff --git a/public/app/features/dashboard-scene/inspect/HelpWizard/utils.ts b/public/app/features/dashboard-scene/inspect/HelpWizard/utils.ts index bf8dce6eac1..b49c7663770 100644 --- a/public/app/features/dashboard-scene/inspect/HelpWizard/utils.ts +++ b/public/app/features/dashboard-scene/inspect/HelpWizard/utils.ts @@ -11,9 +11,10 @@ import { DataTopic, } from '@grafana/data'; import { config } from '@grafana/runtime'; -import { SceneGridItem, VizPanel } from '@grafana/scenes'; +import { VizPanel } from '@grafana/scenes'; import { GrafanaQueryType } from 'app/plugins/datasource/grafana/types'; +import { DashboardGridItem } from '../../scene/DashboardGridItem'; import { LibraryVizPanel } from '../../scene/LibraryVizPanel'; import { gridItemToPanel, vizPanelToPanel } from '../../serialization/transformSceneToSaveModel'; import { getQueryRunnerFor } from '../../utils/utils'; @@ -62,14 +63,16 @@ export function getGithubMarkdown(panel: VizPanel, snapshot: string): string { export async function getDebugDashboard(panel: VizPanel, rand: Randomize, timeRange: TimeRange) { let saveModel; + const isLibraryPanel = panel.parent instanceof LibraryVizPanel; + const gridItem = (isLibraryPanel ? panel.parent.parent : panel.parent) as DashboardGridItem; - if (panel.parent instanceof LibraryVizPanel && panel.parent.parent instanceof SceneGridItem) { + if (isLibraryPanel) { saveModel = { - ...gridItemToPanel(panel.parent.parent), + ...gridItemToPanel(gridItem), ...vizPanelToPanel(panel), }; } else { - saveModel = gridItemToPanel(panel.parent as SceneGridItem); + saveModel = gridItemToPanel(gridItem); } const dashboard = cloneDeep(embeddedDataTemplate); diff --git a/public/app/features/dashboard-scene/inspect/InspectJsonTab.test.tsx b/public/app/features/dashboard-scene/inspect/InspectJsonTab.test.tsx index 264e92daab9..a357890c034 100644 --- a/public/app/features/dashboard-scene/inspect/InspectJsonTab.test.tsx +++ b/public/app/features/dashboard-scene/inspect/InspectJsonTab.test.tsx @@ -10,17 +10,11 @@ import { } from '@grafana/data'; import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks'; import { setPluginImportUtils, setRunRequest } from '@grafana/runtime'; -import { - SceneCanvasText, - SceneDataTransformer, - SceneGridItem, - SceneGridLayout, - SceneQueryRunner, - VizPanel, -} from '@grafana/scenes'; +import { SceneCanvasText, SceneDataTransformer, SceneGridLayout, SceneQueryRunner, VizPanel } from '@grafana/scenes'; import * as libpanels from 'app/features/library-panels/state/api'; import { getStandardTransformers } from 'app/features/transformers/standardTransformers'; +import { DashboardGridItem } from '../scene/DashboardGridItem'; import { DashboardScene } from '../scene/DashboardScene'; import { LibraryVizPanel } from '../scene/LibraryVizPanel'; import { VizPanelLinks, VizPanelLinksMenu } from '../scene/PanelLinks'; @@ -146,7 +140,7 @@ describe('InspectJsonTab', () => { const panel2 = findVizPanelByKey(scene, panel.state.key)!; expect(panel2.state.title).toBe('New title'); - expect((panel2.parent as SceneGridItem).state.width!).toBe(3); + expect((panel2.parent as DashboardGridItem).state.width!).toBe(3); expect(tab.state.onClose).toHaveBeenCalled(); }); @@ -185,7 +179,7 @@ async function buildTestScene() { }, body: new SceneGridLayout({ children: [ - new SceneGridItem({ + new DashboardGridItem({ key: 'griditem-1', x: 0, y: 0, @@ -232,7 +226,7 @@ async function buildTestSceneWithLibraryPanel() { }, body: new SceneGridLayout({ children: [ - new SceneGridItem({ + new DashboardGridItem({ key: 'griditem-1', x: 0, y: 0, diff --git a/public/app/features/dashboard-scene/inspect/InspectJsonTab.tsx b/public/app/features/dashboard-scene/inspect/InspectJsonTab.tsx index 8710778da53..ce9e21371f0 100644 --- a/public/app/features/dashboard-scene/inspect/InspectJsonTab.tsx +++ b/public/app/features/dashboard-scene/inspect/InspectJsonTab.tsx @@ -8,7 +8,6 @@ import { SceneComponentProps, SceneDataTransformer, sceneGraph, - SceneGridItem, SceneGridItemStateLike, SceneObjectBase, SceneObjectRef, @@ -28,8 +27,8 @@ import { getPrettyJSON } from 'app/features/inspector/utils/utils'; import { reportPanelInspectInteraction } from 'app/features/search/page/reporting'; import { VizPanelManager } from '../panel-edit/VizPanelManager'; +import { DashboardGridItem } from '../scene/DashboardGridItem'; import { LibraryVizPanel } from '../scene/LibraryVizPanel'; -import { PanelRepeaterGridItem } from '../scene/PanelRepeaterGridItem'; import { buildGridItemForPanel } from '../serialization/transformSaveModelToScene'; import { gridItemToPanel, vizPanelToPanel } from '../serialization/transformSceneToSaveModel'; import { getDashboardSceneFor, getPanelIdForVizPanel, getQueryRunnerFor } from '../utils/utils'; @@ -110,7 +109,7 @@ export class InspectJsonTab extends SceneObjectBase { const gridItem = buildGridItemForPanel(panelModel); const newState = sceneUtils.cloneSceneObjectState(gridItem.state); - if (!(panel.parent instanceof SceneGridItem) || !(gridItem instanceof SceneGridItem)) { + if (!(panel.parent instanceof DashboardGridItem)) { console.error('Cannot update state of panel', panel, gridItem); return; } @@ -143,8 +142,13 @@ export class InspectJsonTab extends SceneObjectBase { const panel = this.state.panelRef.resolve(); + // Library panels are not editable from the inspect + if (panel.parent instanceof LibraryVizPanel) { + return false; + } + // Only support normal grid items for now and not repeated items - if (!(panel.parent instanceof SceneGridItem)) { + if (panel.parent instanceof DashboardGridItem && panel.parent.isRepeated()) { return false; } @@ -203,15 +207,23 @@ function getJsonText(show: ShowContent, panel: VizPanel): string { case 'panel-json': { reportPanelInspectInteraction(InspectTab.JSON, 'panelData'); - const parent = panel.parent!; + const isInspectingLibraryPanel = panel.parent instanceof LibraryVizPanel; + const gridItem = isInspectingLibraryPanel ? panel.parent.parent : panel.parent; - if (parent instanceof SceneGridItem || parent instanceof PanelRepeaterGridItem) { - objToStringify = gridItemToPanel(parent); - } else if (parent instanceof LibraryVizPanel) { + if (isInspectingLibraryPanel) { objToStringify = libraryPanelChildToLegacyRepresentation(panel); - } else if (parent instanceof VizPanelManager) { - objToStringify = parent.getPanelSaveModel(); + break; } + + if (panel.parent instanceof VizPanelManager) { + objToStringify = panel.parent.getPanelSaveModel(); + break; + } + + if (gridItem instanceof DashboardGridItem) { + objToStringify = gridItemToPanel(gridItem); + } + break; } @@ -245,7 +257,7 @@ function getJsonText(show: ShowContent, panel: VizPanel): string { /** * - * @param panel Must be child of a LibraryVizPanel that is in turn the child of a SceneGridItem + * @param panel Must be child of a LibraryVizPanel that is in turn the child of a DashboardGridItem * @returns object representation of the legacy library panel structure. */ function libraryPanelChildToLegacyRepresentation(panel: VizPanel<{}, {}>) { @@ -253,8 +265,8 @@ function libraryPanelChildToLegacyRepresentation(panel: VizPanel<{}, {}>) { throw 'Panel not child of LibraryVizPanel'; } - if (!(panel.parent.parent instanceof SceneGridItem)) { - throw 'LibraryPanel not child of SceneGridItem'; + if (!(panel.parent.parent instanceof DashboardGridItem)) { + throw 'LibraryPanel not child of DashboardGridItem'; } const gridItem = panel.parent.parent; diff --git a/public/app/features/dashboard-scene/panel-edit/PanelEditor.test.ts b/public/app/features/dashboard-scene/panel-edit/PanelEditor.test.ts index ff8c44c2156..af90d25282d 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelEditor.test.ts +++ b/public/app/features/dashboard-scene/panel-edit/PanelEditor.test.ts @@ -1,7 +1,8 @@ import { PanelPlugin, PanelPluginMeta, PluginType } from '@grafana/data'; -import { SceneGridItem, SceneGridLayout, VizPanel } from '@grafana/scenes'; +import { SceneGridLayout, VizPanel } from '@grafana/scenes'; import * as libAPI from 'app/features/library-panels/state/api'; +import { DashboardGridItem } from '../scene/DashboardGridItem'; import { DashboardScene } from '../scene/DashboardScene'; import { LibraryVizPanel } from '../scene/LibraryVizPanel'; import { vizPanelToPanel } from '../serialization/transformSceneToSaveModel'; @@ -38,8 +39,9 @@ describe('PanelEditor', () => { pluginId: 'text', }); + const gridItem = new DashboardGridItem({ body: panel }); + const editScene = buildPanelEditScene(panel); - const gridItem = new SceneGridItem({ body: panel }); const scene = new DashboardScene({ editPanel: editScene, isEditing: true, @@ -85,13 +87,13 @@ describe('PanelEditor', () => { panel: panel, _loadedPanel: libraryPanelModel, }); + const gridItem = new DashboardGridItem({ body: libraryPanel }); const apiCall = jest .spyOn(libAPI, 'updateLibraryVizPanel') .mockResolvedValue({ type: 'panel', ...libAPI.libraryVizPanelToSaveModel(libraryPanel), version: 2 }); const editScene = buildPanelEditScene(panel); - const gridItem = new SceneGridItem({ body: libraryPanel }); const scene = new DashboardScene({ editPanel: editScene, isEditing: true, @@ -125,6 +127,10 @@ describe('PanelEditor', () => { key: 'panel-1', pluginId: 'text', }); + new DashboardGridItem({ + body: panel, + }); + const editScene = buildPanelEditScene(panel); const scene = new DashboardScene({ editPanel: editScene, @@ -142,6 +148,10 @@ describe('PanelEditor', () => { key: 'panel-1', pluginId: 'timeseries', }); + + new DashboardGridItem({ + body: panel, + }); const editScene = buildPanelEditScene(panel); const scene = new DashboardScene({ editPanel: editScene, diff --git a/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx b/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx index cb1a6eb4d8d..c72f1253413 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx @@ -2,10 +2,10 @@ import * as H from 'history'; import { NavIndex } from '@grafana/data'; import { config, locationService } from '@grafana/runtime'; -import { SceneGridItem, SceneGridLayout, SceneObjectBase, SceneObjectState, VizPanel } from '@grafana/scenes'; +import { SceneObjectBase, SceneObjectState, VizPanel } from '@grafana/scenes'; +import { DashboardGridItem } from '../scene/DashboardGridItem'; import { LibraryVizPanel } from '../scene/LibraryVizPanel'; -import { PanelRepeaterGridItem } from '../scene/PanelRepeaterGridItem'; import { getDashboardSceneFor, getPanelIdForVizPanel } from '../utils/utils'; import { PanelDataPane } from './PanelDataPane/PanelDataPane'; @@ -104,82 +104,23 @@ export class PanelEditor extends SceneObjectBase { const panelManager = this.state.vizManager; const sourcePanel = panelManager.state.sourcePanel.resolve(); const sourcePanelParent = sourcePanel!.parent; + const isLibraryPanel = sourcePanelParent instanceof LibraryVizPanel; - const normalToRepeat = !this._initialRepeatOptions.repeat && panelManager.state.repeat; - const repeatToNormal = this._initialRepeatOptions.repeat && !panelManager.state.repeat; + const gridItem = isLibraryPanel ? sourcePanelParent.parent : sourcePanelParent; - if (sourcePanelParent instanceof LibraryVizPanel) { + if (isLibraryPanel) { // Library panels handled separately return; - } else if (sourcePanelParent instanceof SceneGridItem) { - if (normalToRepeat) { - this.replaceSceneGridItemWithPanelRepeater(sourcePanelParent); - } else { - panelManager.commitChanges(); - } - } else if (sourcePanelParent instanceof PanelRepeaterGridItem) { - if (repeatToNormal) { - this.replacePanelRepeaterWithGridItem(sourcePanelParent); - } else { - this.handleRepeatOptionChanges(sourcePanelParent); - } + } + + if (gridItem instanceof DashboardGridItem) { + this.handleRepeatOptionChanges(gridItem); } else { console.error('Unsupported scene object type'); } } - private replaceSceneGridItemWithPanelRepeater(gridItem: SceneGridItem) { - const gridLayout = gridItem.parent; - if (!(gridLayout instanceof SceneGridLayout)) { - console.error('Expected grandparent to be SceneGridLayout!'); - return; - } - - const panelManager = this.state.vizManager; - const repeatDirection = panelManager.state.repeatDirection ?? 'h'; - const repeater = new PanelRepeaterGridItem({ - key: gridItem.state.key, - x: gridItem.state.x, - y: gridItem.state.y, - width: repeatDirection === 'h' ? 24 : gridItem.state.width, - height: gridItem.state.height, - itemHeight: gridItem.state.height, - source: panelManager.getPanelCloneWithData(), - variableName: panelManager.state.repeat!, - repeatedPanels: [], - repeatDirection: panelManager.state.repeatDirection, - maxPerRow: panelManager.state.maxPerRow, - }); - gridLayout.setState({ - children: gridLayout.state.children.map((child) => (child.state.key === gridItem.state.key ? repeater : child)), - }); - } - - private replacePanelRepeaterWithGridItem(panelRepeater: PanelRepeaterGridItem) { - const gridLayout = panelRepeater.parent; - if (!(gridLayout instanceof SceneGridLayout)) { - console.error('Expected grandparent to be SceneGridLayout!'); - return; - } - - const panelManager = this.state.vizManager; - const panelClone = panelManager.getPanelCloneWithData(); - const gridItem = new SceneGridItem({ - key: panelRepeater.state.key, - x: panelRepeater.state.x, - y: panelRepeater.state.y, - width: this._initialRepeatOptions.repeatDirection === 'h' ? 8 : panelRepeater.state.width, - height: this._initialRepeatOptions.repeatDirection === 'v' ? 8 : panelRepeater.state.height, - body: panelClone, - }); - gridLayout.setState({ - children: gridLayout.state.children.map((child) => - child.state.key === panelRepeater.state.key ? gridItem : child - ), - }); - } - - private handleRepeatOptionChanges(panelRepeater: PanelRepeaterGridItem) { + private handleRepeatOptionChanges(panelRepeater: DashboardGridItem) { let width = panelRepeater.state.width ?? 1; let height = panelRepeater.state.height; @@ -195,7 +136,7 @@ export class PanelEditor extends SceneObjectBase { } panelRepeater.setState({ - source: panelManager.getPanelCloneWithData(), + body: panelManager.getPanelCloneWithData(), repeatDirection: panelManager.state.repeatDirection, variableName: panelManager.state.repeat, maxPerRow: panelManager.state.maxPerRow, diff --git a/public/app/features/dashboard-scene/panel-edit/PanelOptions.test.tsx b/public/app/features/dashboard-scene/panel-edit/PanelOptions.test.tsx index 3b03ce0e093..7d3ff9a7ade 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelOptions.test.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelOptions.test.tsx @@ -1,9 +1,10 @@ import { act, fireEvent, render } from '@testing-library/react'; import React from 'react'; -import { SceneGridItem, VizPanel } from '@grafana/scenes'; +import { VizPanel } from '@grafana/scenes'; import { OptionFilter } from 'app/features/dashboard/components/PanelEditor/OptionsPaneOptions'; +import { DashboardGridItem } from '../scene/DashboardGridItem'; import { LibraryVizPanel } from '../scene/LibraryVizPanel'; import { vizPanelToPanel } from '../serialization/transformSceneToSaveModel'; @@ -42,7 +43,7 @@ describe('PanelOptions', () => { _loadedPanel: libraryPanelModel, }); - new SceneGridItem({ body: libraryPanel }); + new DashboardGridItem({ body: libraryPanel }); const panelManger = VizPanelManager.createFor(panel); diff --git a/public/app/features/dashboard-scene/panel-edit/VizPanelManager.test.tsx b/public/app/features/dashboard-scene/panel-edit/VizPanelManager.test.tsx index 98f4a7143c7..8fa98555ea8 100644 --- a/public/app/features/dashboard-scene/panel-edit/VizPanelManager.test.tsx +++ b/public/app/features/dashboard-scene/panel-edit/VizPanelManager.test.tsx @@ -2,7 +2,7 @@ import { map, of } from 'rxjs'; import { DataQueryRequest, DataSourceApi, DataSourceInstanceSettings, LoadingState, PanelData } from '@grafana/data'; import { locationService } from '@grafana/runtime'; -import { SceneGridItem, SceneQueryRunner, VizPanel } from '@grafana/scenes'; +import { SceneQueryRunner, VizPanel } from '@grafana/scenes'; import { DataQuery, DataSourceJsonData, DataSourceRef } from '@grafana/schema'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; import { InspectTab } from 'app/features/inspector/types'; @@ -10,6 +10,7 @@ import * as libAPI from 'app/features/library-panels/state/api'; import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard'; import { DASHBOARD_DATASOURCE_PLUGIN_ID } from 'app/plugins/datasource/dashboard/types'; +import { DashboardGridItem } from '../scene/DashboardGridItem'; import { LibraryVizPanel } from '../scene/LibraryVizPanel'; import { PanelTimeRange, PanelTimeRangeState } from '../scene/PanelTimeRange'; import { transformSaveModelToScene } from '../serialization/transformSaveModelToScene'; @@ -172,6 +173,10 @@ describe('VizPanelManager', () => { }, }); + new DashboardGridItem({ + body: vizPanel, + }); + const vizPanelManager = VizPanelManager.createFor(vizPanel); expect(vizPanelManager.state.panel.state.fieldConfig.defaults.custom).toBe('Custom'); @@ -196,6 +201,10 @@ describe('VizPanelManager', () => { fieldConfig: { defaults: { custom: 'Custom' }, overrides: [] }, }); + new DashboardGridItem({ + body: vizPanel, + }); + const vizPanelManager = VizPanelManager.createFor(vizPanel); vizPanelManager.changePluginType('timeseries'); @@ -237,7 +246,7 @@ describe('VizPanelManager', () => { _loadedPanel: libraryPanelModel, }); - new SceneGridItem({ body: libraryPanel }); + new DashboardGridItem({ body: libraryPanel }); const panelManager = VizPanelManager.createFor(panel); @@ -276,7 +285,7 @@ describe('VizPanelManager', () => { _loadedPanel: libraryPanelModel, }); - const gridItem = new SceneGridItem({ body: libraryPanel }); + const gridItem = new DashboardGridItem({ body: libraryPanel }); const panelManager = VizPanelManager.createFor(panel); panelManager.unlinkLibraryPanel(); diff --git a/public/app/features/dashboard-scene/panel-edit/VizPanelManager.tsx b/public/app/features/dashboard-scene/panel-edit/VizPanelManager.tsx index 94d9e62fce0..22aa2773e5e 100644 --- a/public/app/features/dashboard-scene/panel-edit/VizPanelManager.tsx +++ b/public/app/features/dashboard-scene/panel-edit/VizPanelManager.tsx @@ -17,7 +17,6 @@ import { PanelBuilders, SceneComponentProps, SceneDataTransformer, - SceneGridItem, SceneObjectBase, SceneObjectRef, SceneObjectState, @@ -36,8 +35,8 @@ import { updateQueries } from 'app/features/query/state/updateQueries'; import { GrafanaQuery } from 'app/plugins/datasource/grafana/types'; import { QueryGroupOptions } from 'app/types'; +import { DashboardGridItem, RepeatDirection } from '../scene/DashboardGridItem'; import { LibraryVizPanel } from '../scene/LibraryVizPanel'; -import { PanelRepeaterGridItem, RepeatDirection } from '../scene/PanelRepeaterGridItem'; import { PanelTimeRange, PanelTimeRangeState } from '../scene/PanelTimeRange'; import { gridItemToPanel } from '../serialization/transformSceneToSaveModel'; import { getDashboardSceneFor, getPanelIdForVizPanel, getQueryRunnerFor } from '../utils/utils'; @@ -77,11 +76,17 @@ export class VizPanelManager extends SceneObjectBase { */ public static createFor(sourcePanel: VizPanel) { let repeatOptions: Pick = {}; - if (sourcePanel.parent instanceof PanelRepeaterGridItem) { - const { variableName: repeat, repeatDirection, maxPerRow } = sourcePanel.parent.state; - repeatOptions = { repeat, repeatDirection, maxPerRow }; + + const gridItem = sourcePanel.parent instanceof LibraryVizPanel ? sourcePanel.parent.parent : sourcePanel.parent; + + if (!(gridItem instanceof DashboardGridItem)) { + console.error('VizPanel is not a child of a dashboard grid item'); + throw new Error('VizPanel is not a child of a dashboard grid item'); } + const { variableName: repeat, repeatDirection, maxPerRow } = gridItem.state; + repeatOptions = { repeat, repeatDirection, maxPerRow }; + return new VizPanelManager({ panel: sourcePanel.clone({ $data: undefined }), $data: sourcePanel.state.$data?.clone(), @@ -345,7 +350,8 @@ export class VizPanelManager extends SceneObjectBase { } const gridItem = sourcePanel.parent.parent; - if (!(gridItem instanceof SceneGridItem)) { + + if (!(gridItem instanceof DashboardGridItem)) { throw new Error('Library panel not a child of a grid item'); } @@ -353,14 +359,21 @@ export class VizPanelManager extends SceneObjectBase { gridItem.setState({ body: newSourcePanel, }); + this.setState({ sourcePanel: newSourcePanel.getRef() }); } public commitChanges() { const sourcePanel = this.state.sourcePanel.resolve(); - if (sourcePanel.parent instanceof SceneGridItem) { + const repeatUpdate = { + variableName: this.state.repeat, + repeatDirection: this.state.repeatDirection, + maxPerRow: this.state.maxPerRow, + }; + if (sourcePanel.parent instanceof DashboardGridItem) { sourcePanel.parent.setState({ + ...repeatUpdate, body: this.state.panel.clone({ $data: this.state.$data?.clone(), }), @@ -368,7 +381,7 @@ export class VizPanelManager extends SceneObjectBase { } if (sourcePanel.parent instanceof LibraryVizPanel) { - if (sourcePanel.parent.parent instanceof SceneGridItem) { + if (sourcePanel.parent.parent instanceof DashboardGridItem) { const newLibPanel = sourcePanel.parent.clone({ panel: this.state.panel.clone({ $data: this.state.$data?.clone(), @@ -376,6 +389,7 @@ export class VizPanelManager extends SceneObjectBase { }); sourcePanel.parent.parent.setState({ body: newLibPanel, + ...repeatUpdate, }); updateLibraryVizPanel(newLibPanel!).then((p) => { if (sourcePanel.parent instanceof LibraryVizPanel) { @@ -392,17 +406,20 @@ export class VizPanelManager extends SceneObjectBase { public getPanelSaveModel(): Panel | object { const sourcePanel = this.state.sourcePanel.resolve(); - if (sourcePanel.parent instanceof SceneGridItem) { - const parentClone = sourcePanel.parent.clone({ - body: this.state.panel.clone({ - $data: this.state.$data?.clone(), - }), - }); + const isLibraryPanel = sourcePanel.parent instanceof LibraryVizPanel; + const gridItem = isLibraryPanel ? sourcePanel.parent.parent : sourcePanel.parent; - return gridItemToPanel(parentClone); + if (!(gridItem instanceof DashboardGridItem)) { + return { error: 'Unsupported panel parent' }; } - return { error: 'Unsupported panel parent' }; + const parentClone = gridItem.clone({ + body: this.state.panel.clone({ + $data: this.state.$data?.clone(), + }), + }); + + return gridItemToPanel(parentClone); } public getPanelCloneWithData(): VizPanel { diff --git a/public/app/features/dashboard-scene/saving/DashboardPrompt.test.tsx b/public/app/features/dashboard-scene/saving/DashboardPrompt.test.tsx index 81b77a80f4b..76dc822344f 100644 --- a/public/app/features/dashboard-scene/saving/DashboardPrompt.test.tsx +++ b/public/app/features/dashboard-scene/saving/DashboardPrompt.test.tsx @@ -1,7 +1,8 @@ -import { SceneGridItem, SceneGridLayout, SceneQueryRunner, SceneTimeRange, VizPanel, behaviors } from '@grafana/scenes'; +import { SceneGridLayout, 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 { transformSceneToSaveModel } from '../serialization/transformSceneToSaveModel'; @@ -137,7 +138,7 @@ function buildTestScene(overrides?: Partial) { $behaviors: [new behaviors.CursorSync({})], body: new SceneGridLayout({ children: [ - new SceneGridItem({ + new DashboardGridItem({ key: 'griditem-1', x: 0, body: new VizPanel({ diff --git a/public/app/features/dashboard-scene/saving/DashboardSceneChangeTracker.ts b/public/app/features/dashboard-scene/saving/DashboardSceneChangeTracker.ts index 50879fe173b..be1bcd00218 100644 --- a/public/app/features/dashboard-scene/saving/DashboardSceneChangeTracker.ts +++ b/public/app/features/dashboard-scene/saving/DashboardSceneChangeTracker.ts @@ -2,7 +2,6 @@ import { Unsubscribable } from 'rxjs'; import { SceneDataLayers, - SceneGridItem, SceneGridLayout, SceneObjectStateChangedEvent, SceneRefreshPicker, @@ -14,6 +13,7 @@ import { createWorker } from 'app/features/dashboard-scene/saving/createDetectCh import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer'; import { DashboardControls } from '../scene/DashboardControls'; +import { DashboardGridItem } from '../scene/DashboardGridItem'; import { DashboardScene, PERSISTED_PROPS } from '../scene/DashboardScene'; import { transformSceneToSaveModel } from '../serialization/transformSceneToSaveModel'; import { isSceneVariableInstance } from '../settings/variables/utils'; @@ -41,7 +41,7 @@ export class DashboardSceneChangeTracker { if (payload.changedObject instanceof SceneDataLayers) { this.detectChanges(); } - if (payload.changedObject instanceof SceneGridItem) { + if (payload.changedObject instanceof DashboardGridItem) { this.detectChanges(); } if (payload.changedObject instanceof SceneGridLayout) { diff --git a/public/app/features/dashboard-scene/scene/AddLibraryPanelWidget.test.tsx b/public/app/features/dashboard-scene/scene/AddLibraryPanelWidget.test.tsx index b982e2813ce..9a4fe24206f 100644 --- a/public/app/features/dashboard-scene/scene/AddLibraryPanelWidget.test.tsx +++ b/public/app/features/dashboard-scene/scene/AddLibraryPanelWidget.test.tsx @@ -1,10 +1,11 @@ -import { SceneGridItem, SceneGridLayout, SceneGridRow, SceneTimeRange } from '@grafana/scenes'; +import { SceneGridLayout, SceneGridRow, SceneTimeRange } from '@grafana/scenes'; import { LibraryPanel } from '@grafana/schema/dist/esm/index.gen'; import { DashboardScene } from '../scene/DashboardScene'; import { activateFullSceneTree } from '../utils/test-utils'; import { AddLibraryPanelWidget } from './AddLibraryPanelWidget'; +import { DashboardGridItem } from './DashboardGridItem'; import { LibraryVizPanel } from './LibraryVizPanel'; describe('AddLibraryPanelWidget', () => { @@ -39,7 +40,7 @@ describe('AddLibraryPanelWidget', () => { body.setState({ children: [ ...body.state.children, - new SceneGridItem({ + new DashboardGridItem({ key: 'griditem-2', x: 0, y: 0, @@ -53,7 +54,7 @@ describe('AddLibraryPanelWidget', () => { anotherLibPanelWidget.onCancelAddPanel(mockEvent); - const gridItem = body.state.children[0] as SceneGridItem; + const gridItem = body.state.children[0] as DashboardGridItem; expect(body.state.children.length).toBe(1); expect(gridItem.state.body!.state.key).toBe(addLibPanelWidget.state.key); @@ -67,7 +68,7 @@ describe('AddLibraryPanelWidget', () => { new SceneGridRow({ key: 'panel-2', children: [ - new SceneGridItem({ + new DashboardGridItem({ key: 'griditem-2', x: 0, y: 0, @@ -103,7 +104,7 @@ describe('AddLibraryPanelWidget', () => { }; const body = dashboard.state.body as SceneGridLayout; - const gridItem = body.state.children[0] as SceneGridItem; + const gridItem = body.state.children[0] as DashboardGridItem; expect(gridItem.state.body!).toBeInstanceOf(AddLibraryPanelWidget); @@ -121,7 +122,7 @@ describe('AddLibraryPanelWidget', () => { body.setState({ children: [ ...body.state.children, - new SceneGridItem({ + new DashboardGridItem({ key: 'griditem-2', x: 0, y: 0, @@ -145,8 +146,8 @@ describe('AddLibraryPanelWidget', () => { anotherLibPanelWidget.onAddLibraryPanel(panelInfo); - const gridItemOne = body.state.children[0] as SceneGridItem; - const gridItemTwo = body.state.children[1] as SceneGridItem; + const gridItemOne = body.state.children[0] as DashboardGridItem; + const gridItemTwo = body.state.children[1] as DashboardGridItem; expect(body.state.children.length).toBe(2); expect(gridItemOne.state.body!).toBeInstanceOf(AddLibraryPanelWidget); @@ -161,7 +162,7 @@ describe('AddLibraryPanelWidget', () => { new SceneGridRow({ key: 'panel-2', children: [ - new SceneGridItem({ + new DashboardGridItem({ key: 'griditem-2', x: 0, y: 0, @@ -190,7 +191,7 @@ describe('AddLibraryPanelWidget', () => { anotherLibPanelWidget.onAddLibraryPanel(panelInfo); const gridRow = body.state.children[0] as SceneGridRow; - const gridItem = gridRow.state.children[0] as SceneGridItem; + const gridItem = gridRow.state.children[0] as DashboardGridItem; expect(body.state.children.length).toBe(1); expect(gridItem.state.body!).toBeInstanceOf(LibraryVizPanel); @@ -230,7 +231,7 @@ async function buildTestScene() { }, body: new SceneGridLayout({ children: [ - new SceneGridItem({ + new DashboardGridItem({ key: 'griditem-1', x: 0, y: 0, diff --git a/public/app/features/dashboard-scene/scene/AddLibraryPanelWidget.tsx b/public/app/features/dashboard-scene/scene/AddLibraryPanelWidget.tsx index 3ff23786d25..9d9c7a755c7 100644 --- a/public/app/features/dashboard-scene/scene/AddLibraryPanelWidget.tsx +++ b/public/app/features/dashboard-scene/scene/AddLibraryPanelWidget.tsx @@ -3,14 +3,7 @@ import React from 'react'; import tinycolor from 'tinycolor2'; import { GrafanaTheme2 } from '@grafana/data'; -import { - SceneComponentProps, - SceneGridItem, - SceneGridLayout, - SceneGridRow, - SceneObjectBase, - SceneObjectState, -} from '@grafana/scenes'; +import { SceneComponentProps, SceneGridLayout, SceneGridRow, SceneObjectBase, SceneObjectState } from '@grafana/scenes'; import { LibraryPanel } from '@grafana/schema'; import { IconButton, useStyles2 } from '@grafana/ui'; import { Trans } from 'app/core/internationalization'; @@ -21,6 +14,7 @@ import { import { getDashboardSceneFor } from '../utils/utils'; +import { DashboardGridItem } from './DashboardGridItem'; import { DashboardScene } from './DashboardScene'; import { LibraryVizPanel } from './LibraryVizPanel'; @@ -62,7 +56,7 @@ export class AddLibraryPanelWidget extends SceneObjectBase implements SceneGridItemLike { +export class DashboardGridItem extends SceneObjectBase implements SceneGridItemLike { protected _variableDependency = new VariableDependencyConfig(this, { - variableNames: [this.state.variableName], + variableNames: this.state.variableName ? [this.state.variableName] : [], onVariableUpdateCompleted: this._onVariableUpdateCompleted.bind(this), }); - public constructor(state: PanelRepeaterGridItemState) { + public constructor(state: DashboardGridItemState) { super(state); this.addActivationHandler(() => this._activationHandler()); } private _activationHandler() { - this._subs.add(this.subscribeToState((newState, prevState) => this._handleGridResize(newState, prevState))); - this._performRepeat(); + if (this.state.variableName) { + this._subs.add(this.subscribeToState((newState, prevState) => this._handleGridResize(newState, prevState))); + this._performRepeat(); + } } private _onVariableUpdateCompleted(): void { - this._performRepeat(); + if (this.state.variableName) { + this._performRepeat(); + } } /** * Uses the current repeat item count to calculate the user intended desired itemHeight */ - private _handleGridResize(newState: PanelRepeaterGridItemState, prevState: PanelRepeaterGridItemState) { + private _handleGridResize(newState: DashboardGridItemState, prevState: DashboardGridItemState) { const itemCount = this.state.repeatedPanels?.length ?? 1; - const stateChange: Partial = {}; + const stateChange: Partial = {}; // Height changed if (newState.height === prevState.height) { @@ -84,7 +89,10 @@ export class PanelRepeaterGridItem extends SceneObjectBase = { repeatedPanels: repeatedPanels }; + const stateChange: Partial = { repeatedPanels: repeatedPanels }; const itemHeight = this.state.itemHeight ?? 10; const prevHeight = this.state.height; const maxPerRow = this.getMaxPerRow(); @@ -166,11 +173,29 @@ export class PanelRepeaterGridItem extends SceneObjectBase) => { - const { repeatedPanels, itemHeight } = model.useState(); + public isRepeated() { + return this.state.variableName !== undefined; + } + + public static Component = ({ model }: SceneComponentProps) => { + const { repeatedPanels, itemHeight, variableName, body } = model.useState(); const itemCount = repeatedPanels?.length ?? 0; const layoutStyle = useLayoutStyle(model.getRepeatDirection(), itemCount, model.getMaxPerRow(), itemHeight ?? 10); + if (!variableName) { + if (body instanceof VizPanel) { + return ; + } + + if (body instanceof LibraryVizPanel) { + return ; + } + + if (body instanceof AddLibraryPanelWidget) { + return ; + } + } + if (!repeatedPanels) { return null; } diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx index f08e544e1a4..98b0088aed5 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx @@ -1,7 +1,6 @@ import { CoreApp } from '@grafana/data'; import { sceneGraph, - SceneGridItem, SceneGridLayout, SceneTimeRange, SceneQueryRunner, @@ -28,9 +27,9 @@ import { dashboardSceneGraph } from '../utils/dashboardSceneGraph'; import { djb2Hash } from '../utils/djb2Hash'; import { DashboardControls } from './DashboardControls'; +import { DashboardGridItem } from './DashboardGridItem'; import { DashboardScene, DashboardSceneState } from './DashboardScene'; import { LibraryVizPanel } from './LibraryVizPanel'; -import { PanelRepeaterGridItem } from './PanelRepeaterGridItem'; jest.mock('../settings/version-history/HistorySrv'); jest.mock('../serialization/transformSaveModelToScene'); @@ -142,13 +141,13 @@ describe('DashboardScene', () => { }); it('A change to griditem pos should set isDirty true', () => { - const gridItem = sceneGraph.findObject(scene, (p) => p.state.key === 'griditem-1') as SceneGridItem; + const gridItem = sceneGraph.findObject(scene, (p) => p.state.key === 'griditem-1') as DashboardGridItem; gridItem.setState({ x: 10, y: 0, width: 10, height: 10 }); expect(scene.state.isDirty).toBe(true); scene.exitEditMode({ skipConfirm: true }); - const gridItem2 = sceneGraph.findObject(scene, (p) => p.state.key === 'griditem-1') as SceneGridItem; + const gridItem2 = sceneGraph.findObject(scene, (p) => p.state.key === 'griditem-1') as DashboardGridItem; expect(gridItem2.state.x).toBe(0); }); @@ -275,7 +274,7 @@ describe('DashboardScene', () => { scene.addPanel(vizPanel); const body = scene.state.body as SceneGridLayout; - const gridItem = body.state.children[0] as SceneGridItem; + const gridItem = body.state.children[0] as DashboardGridItem; expect(body.state.children.length).toBe(6); expect(gridItem.state.body!.state.key).toBe('panel-5'); @@ -286,7 +285,7 @@ describe('DashboardScene', () => { scene.onCreateNewPanel(); const body = scene.state.body as SceneGridLayout; - const gridItem = body.state.children[0] as SceneGridItem; + const gridItem = body.state.children[0] as DashboardGridItem; expect(body.state.children.length).toBe(6); expect(gridItem.state.body!.state.key).toBe('panel-7'); @@ -308,7 +307,7 @@ describe('DashboardScene', () => { const scene = buildTestScene({ body: new SceneGridLayout({ children: [ - new SceneGridItem({ + new DashboardGridItem({ key: 'griditem-1', x: 0, body: new VizPanel({ @@ -318,7 +317,7 @@ describe('DashboardScene', () => { $data: new SceneQueryRunner({ key: 'data-query-runner', queries: [{ refId: 'A' }] }), }), }), - new SceneGridItem({ + new DashboardGridItem({ key: 'griditem-2', body: new VizPanel({ title: 'Panel B', @@ -372,7 +371,7 @@ describe('DashboardScene', () => { scene.removeRow(row); - const vizPanel = (body.state.children[2] as SceneGridItem).state.body as VizPanel; + 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'); @@ -438,14 +437,14 @@ describe('DashboardScene', () => { }); it('Should copy a panel', () => { - const vizPanel = ((scene.state.body as SceneGridLayout).state.children[0] as SceneGridItem).state.body; + const vizPanel = ((scene.state.body as SceneGridLayout).state.children[0] as DashboardGridItem).state.body; scene.copyPanel(vizPanel as VizPanel); 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 + const libVizPanel = ((scene.state.body as SceneGridLayout).state.children[4] as DashboardGridItem).state .body as LibraryVizPanel; scene.copyPanel(libVizPanel.state.panel as VizPanel); @@ -457,7 +456,7 @@ describe('DashboardScene', () => { scene.setState({ hasCopiedPanel: true }); jest.spyOn(JSON, 'parse').mockReturnThis(); jest.mocked(buildGridItemForPanel).mockReturnValue( - new SceneGridItem({ + new DashboardGridItem({ key: 'griditem-9', body: new VizPanel({ title: 'Panel A', @@ -470,7 +469,7 @@ describe('DashboardScene', () => { scene.pastePanel(); const body = scene.state.body as SceneGridLayout; - const gridItem = body.state.children[0] as SceneGridItem; + const gridItem = body.state.children[0] as DashboardGridItem; expect(buildGridItemForPanel).toHaveBeenCalledTimes(1); expect(body.state.children.length).toBe(6); @@ -483,7 +482,7 @@ describe('DashboardScene', () => { scene.setState({ hasCopiedPanel: true }); jest.spyOn(JSON, 'parse').mockReturnValue({ libraryPanel: { uid: 'uid', name: 'libraryPanel' } }); jest.mocked(buildGridItemForLibPanel).mockReturnValue( - new SceneGridItem({ + new DashboardGridItem({ body: new LibraryVizPanel({ title: 'Library Panel', uid: 'uid', @@ -496,7 +495,7 @@ describe('DashboardScene', () => { scene.pastePanel(); const body = scene.state.body as SceneGridLayout; - const gridItem = body.state.children[0] as SceneGridItem; + const gridItem = body.state.children[0] as DashboardGridItem; const libVizPanel = gridItem.state.body as LibraryVizPanel; @@ -512,7 +511,7 @@ describe('DashboardScene', () => { scene.onCreateLibPanelWidget(); const body = scene.state.body as SceneGridLayout; - const gridItem = body.state.children[0] as SceneGridItem; + const gridItem = body.state.children[0] as DashboardGridItem; expect(body.state.children.length).toBe(6); expect(gridItem.state.body!.state.key).toBe('panel-7'); @@ -520,7 +519,7 @@ describe('DashboardScene', () => { }); it('Should remove a panel', () => { - const vizPanel = ((scene.state.body as SceneGridLayout).state.children[0] as SceneGridItem).state.body; + 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; @@ -529,7 +528,8 @@ describe('DashboardScene', () => { it('Should remove a panel within a row', () => { const vizPanel = ( - ((scene.state.body as SceneGridLayout).state.children[2] as SceneGridRow).state.children[0] as SceneGridItem + ((scene.state.body as SceneGridLayout).state.children[2] as SceneGridRow).state + .children[0] as DashboardGridItem ).state.body; scene.removePanel(vizPanel as VizPanel); @@ -539,7 +539,7 @@ describe('DashboardScene', () => { }); it('Should remove a library panel', () => { - const libraryPanel = ((scene.state.body as SceneGridLayout).state.children[4] as SceneGridItem).state.body; + const libraryPanel = ((scene.state.body as SceneGridLayout).state.children[4] as DashboardGridItem).state.body; const vizPanel = (libraryPanel as LibraryVizPanel).state.panel; scene.removePanel(vizPanel as VizPanel); @@ -549,7 +549,8 @@ describe('DashboardScene', () => { 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 SceneGridItem + ((scene.state.body as SceneGridLayout).state.children[2] as SceneGridRow).state + .children[1] as DashboardGridItem ).state.body; const vizPanel = (libraryPanel as LibraryVizPanel).state.panel; @@ -561,23 +562,23 @@ describe('DashboardScene', () => { }); it('Should duplicate a panel', () => { - const vizPanel = ((scene.state.body as SceneGridLayout).state.children[0] as SceneGridItem).state.body; + 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 SceneGridItem; + 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 duplicate a library panel', () => { - const libraryPanel = ((scene.state.body as SceneGridLayout).state.children[4] as SceneGridItem).state.body; + const libraryPanel = ((scene.state.body as SceneGridLayout).state.children[4] as DashboardGridItem).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 gridItem = body.state.children[5] as DashboardGridItem; const libVizPanel = gridItem.state.body as LibraryVizPanel; @@ -590,7 +591,7 @@ describe('DashboardScene', () => { const scene = buildTestScene({ body: new SceneGridLayout({ children: [ - new PanelRepeaterGridItem({ + new DashboardGridItem({ key: `grid-item-1`, width: 24, height: 8, @@ -601,7 +602,7 @@ describe('DashboardScene', () => { pluginId: 'table', }), ], - source: new VizPanel({ + body: new VizPanel({ title: 'Library Panel', key: 'panel-1', pluginId: 'table', @@ -612,13 +613,13 @@ describe('DashboardScene', () => { }), }); - const vizPanel = ((scene.state.body as SceneGridLayout).state.children[0] as PanelRepeaterGridItem).state + 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 SceneGridItem; + const gridItem = body.state.children[1] as DashboardGridItem; expect(body.state.children.length).toBe(2); expect(gridItem.state.body!.state.key).toBe('panel-2'); @@ -626,13 +627,14 @@ describe('DashboardScene', () => { 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 + ((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 SceneGridItem; + const gridItem = gridRow.state.children[2] as DashboardGridItem; expect(gridRow.state.children.length).toBe(3); expect(gridItem.state.body!.state.key).toBe('panel-7'); @@ -640,7 +642,8 @@ describe('DashboardScene', () => { 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 + ((scene.state.body as SceneGridLayout).state.children[2] as SceneGridRow).state + .children[1] as DashboardGridItem ).state.body; const vizPanel = (libraryPanel as LibraryVizPanel).state.panel; @@ -648,7 +651,7 @@ describe('DashboardScene', () => { const body = scene.state.body as SceneGridLayout; const gridRow = body.state.children[2] as SceneGridRow; - const gridItem = gridRow.state.children[2] as SceneGridItem; + const gridItem = gridRow.state.children[2] as DashboardGridItem; const libVizPanel = gridItem.state.body as LibraryVizPanel; @@ -688,7 +691,7 @@ describe('DashboardScene', () => { const scene = buildTestScene({ body: new SceneGridLayout({ children: [ - new SceneGridItem({ + new DashboardGridItem({ key: 'griditem-2', body: libPanel, }), @@ -699,7 +702,7 @@ describe('DashboardScene', () => { scene.unlinkLibraryPanel(libPanel); const body = scene.state.body as SceneGridLayout; - const gridItem = body.state.children[0] as SceneGridItem; + const gridItem = body.state.children[0] as DashboardGridItem; expect(body.state.children.length).toBe(1); expect(gridItem.state.body).toBeInstanceOf(VizPanel); @@ -865,7 +868,7 @@ function buildTestScene(overrides?: Partial) { $behaviors: [new behaviors.CursorSync({})], body: new SceneGridLayout({ children: [ - new SceneGridItem({ + new DashboardGridItem({ key: 'griditem-1', x: 0, body: new VizPanel({ @@ -875,7 +878,7 @@ function buildTestScene(overrides?: Partial) { $data: new SceneQueryRunner({ key: 'data-query-runner', queries: [{ refId: 'A' }] }), }), }), - new SceneGridItem({ + new DashboardGridItem({ key: 'griditem-2', body: new VizPanel({ title: 'Panel B', @@ -886,14 +889,14 @@ function buildTestScene(overrides?: Partial) { new SceneGridRow({ key: 'panel-3', children: [ - new SceneGridItem({ + new DashboardGridItem({ body: new VizPanel({ title: 'Panel C', key: 'panel-4', pluginId: 'table', }), }), - new SceneGridItem({ + new DashboardGridItem({ body: new LibraryVizPanel({ uid: 'uid', name: 'libraryPanel', @@ -908,7 +911,7 @@ function buildTestScene(overrides?: Partial) { }), ], }), - new SceneGridItem({ + new DashboardGridItem({ body: new VizPanel({ title: 'Panel B', key: 'panel-2-clone-1', @@ -916,7 +919,7 @@ function buildTestScene(overrides?: Partial) { $data: new SceneQueryRunner({ key: 'data-query-runner2', queries: [{ refId: 'A' }] }), }), }), - new SceneGridItem({ + new DashboardGridItem({ body: new LibraryVizPanel({ uid: 'uid', name: 'libraryPanel', diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.tsx index 23dd39f55ed..a3ad85e0913 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.tsx @@ -6,7 +6,6 @@ import { getUrlSyncManager, SceneFlexLayout, sceneGraph, - SceneGridItem, SceneGridLayout, SceneGridRow, SceneObject, @@ -59,10 +58,10 @@ import { import { AddLibraryPanelWidget } from './AddLibraryPanelWidget'; import { DashboardControls } from './DashboardControls'; +import { DashboardGridItem } from './DashboardGridItem'; import { DashboardSceneRenderer } from './DashboardSceneRenderer'; import { DashboardSceneUrlSync } from './DashboardSceneUrlSync'; import { LibraryVizPanel } from './LibraryVizPanel'; -import { PanelRepeaterGridItem } from './PanelRepeaterGridItem'; import { ScopesScene } from './ScopesScene'; import { ViewPanelScene } from './ViewPanelScene'; import { setupKeyboardShortcuts } from './keyboardShortcuts'; @@ -464,7 +463,7 @@ export class DashboardScene extends SceneObjectBase { const sceneGridLayout = this.state.body; const panelId = getPanelIdForVizPanel(vizPanel); - const newGridItem = new SceneGridItem({ + const newGridItem = new DashboardGridItem({ height: NEW_PANEL_HEIGHT, width: NEW_PANEL_WIDTH, x: 0, @@ -487,8 +486,8 @@ export class DashboardScene extends SceneObjectBase { const gridItem = libraryPanel ? libraryPanel.parent : vizPanel.parent; - if (!(gridItem instanceof SceneGridItem || gridItem instanceof PanelRepeaterGridItem)) { - console.error('Trying to duplicate a panel in a layout that is not SceneGridItem or PanelRepeaterGridItem'); + if (!(gridItem instanceof DashboardGridItem)) { + console.error('Trying to duplicate a panel in a layout that is not DashboardGridItem'); return; } @@ -500,7 +499,7 @@ export class DashboardScene extends SceneObjectBase { if (libraryPanel) { const gridItemToDuplicateState = sceneUtils.cloneSceneObjectState(gridItem.state); - newGridItem = new SceneGridItem({ + newGridItem = new DashboardGridItem({ x: gridItemToDuplicateState.x, y: gridItemToDuplicateState.y, width: gridItemToDuplicateState.width, @@ -513,9 +512,9 @@ export class DashboardScene extends SceneObjectBase { }), }); } else { - if (gridItem instanceof PanelRepeaterGridItem) { - panelState = sceneUtils.cloneSceneObjectState(gridItem.state.source.state); - panelData = sceneGraph.getData(gridItem.state.source).clone(); + 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(); @@ -524,7 +523,7 @@ export class DashboardScene extends SceneObjectBase { // when we duplicate a panel we don't want to clone the alert state delete panelData.state.data?.alertState; - newGridItem = new SceneGridItem({ + newGridItem = new DashboardGridItem({ x: gridItem.state.x, y: gridItem.state.y, height: NEW_PANEL_HEIGHT, @@ -574,6 +573,11 @@ export class DashboardScene extends SceneObjectBase { gridItem = libraryVizPanel.parent; } + if (!(gridItem instanceof DashboardGridItem)) { + console.error('Trying to copy a panel that is not DashboardGridItem child'); + throw new Error('Trying to copy a panel that is not DashboardGridItem child'); + } + const jsonData = gridItemToPanel(gridItem); store.set(LS_PANEL_COPY_KEY, JSON.stringify(jsonData)); @@ -595,13 +599,13 @@ export class DashboardScene extends SceneObjectBase { const sceneGridLayout = this.state.body; - if (!(gridItem instanceof SceneGridItem) && !(gridItem instanceof PanelRepeaterGridItem)) { + if (!(gridItem instanceof DashboardGridItem)) { throw new Error('Cannot paste invalid grid item'); } const panelId = dashboardSceneGraph.getNextPanelId(this); - if (gridItem instanceof SceneGridItem && gridItem.state.body instanceof LibraryVizPanel) { + if (gridItem instanceof DashboardGridItem && gridItem.state.body instanceof LibraryVizPanel) { const panelKey = getVizPanelKeyForPanelId(panelId); gridItem.state.body.setState({ panelKey }); @@ -611,12 +615,12 @@ export class DashboardScene extends SceneObjectBase { if (vizPanel instanceof VizPanel) { vizPanel.setState({ key: panelKey }); } - } else if (gridItem instanceof SceneGridItem && gridItem.state.body) { + } else if (gridItem instanceof DashboardGridItem && gridItem.state.body) { gridItem.state.body.setState({ key: getVizPanelKeyForPanelId(panelId), }); - } else if (gridItem instanceof PanelRepeaterGridItem) { - gridItem.state.source.setState({ + } else if (gridItem instanceof DashboardGridItem) { + gridItem.state.body.setState({ key: getVizPanelKeyForPanelId(panelId), }); } @@ -687,8 +691,8 @@ export class DashboardScene extends SceneObjectBase { const gridItem = panel.parent; - if (!(gridItem instanceof SceneGridItem || gridItem instanceof PanelRepeaterGridItem)) { - console.error('Trying to duplicate a panel in a layout that is not SceneGridItem or PanelRepeaterGridItem'); + if (!(gridItem instanceof DashboardGridItem)) { + console.error('Trying to unlinka a lib panel in a layout that is not DashboardGridItem'); return; } @@ -737,7 +741,7 @@ export class DashboardScene extends SceneObjectBase { const panelId = dashboardSceneGraph.getNextPanelId(this); - const newGridItem = new SceneGridItem({ + const newGridItem = new DashboardGridItem({ height: NEW_PANEL_HEIGHT, width: NEW_PANEL_WIDTH, x: 0, diff --git a/public/app/features/dashboard-scene/scene/DashboardSceneUrlSync.test.ts b/public/app/features/dashboard-scene/scene/DashboardSceneUrlSync.test.ts index bfdd5587c83..dc0ecacfd7c 100644 --- a/public/app/features/dashboard-scene/scene/DashboardSceneUrlSync.test.ts +++ b/public/app/features/dashboard-scene/scene/DashboardSceneUrlSync.test.ts @@ -1,7 +1,8 @@ import { AppEvents } from '@grafana/data'; -import { SceneGridItem, SceneGridLayout, SceneQueryRunner, VizPanel } from '@grafana/scenes'; +import { SceneGridLayout, SceneQueryRunner, VizPanel } from '@grafana/scenes'; import appEvents from 'app/core/app_events'; +import { DashboardGridItem } from './DashboardGridItem'; import { DashboardScene } from './DashboardScene'; import { DashboardRepeatsProcessedEvent } from './types'; @@ -42,7 +43,7 @@ describe('DashboardSceneUrlSync', () => { const layout = scene.state.body as SceneGridLayout; layout.setState({ children: [ - new SceneGridItem({ + new DashboardGridItem({ key: 'griditem-1', x: 0, body: new VizPanel({ @@ -66,7 +67,7 @@ function buildTestScene() { uid: 'dash-1', body: new SceneGridLayout({ children: [ - new SceneGridItem({ + new DashboardGridItem({ key: 'griditem-1', x: 0, body: new VizPanel({ @@ -76,7 +77,7 @@ function buildTestScene() { $data: new SceneQueryRunner({ key: 'data-query-runner', queries: [{ refId: 'A' }] }), }), }), - new SceneGridItem({ + new DashboardGridItem({ body: new VizPanel({ title: 'Panel B', key: 'panel-2', diff --git a/public/app/features/dashboard-scene/scene/LibraryVizPanel.test.ts b/public/app/features/dashboard-scene/scene/LibraryVizPanel.test.ts index 39968af0702..f6fdeab02ea 100644 --- a/public/app/features/dashboard-scene/scene/LibraryVizPanel.test.ts +++ b/public/app/features/dashboard-scene/scene/LibraryVizPanel.test.ts @@ -5,12 +5,12 @@ import { http, HttpResponse } from 'msw'; import { setupServer, SetupServerApi } from 'msw/node'; import { setBackendSrv } from '@grafana/runtime'; -import { SceneGridItem, SceneGridLayout, VizPanel } from '@grafana/scenes'; +import { SceneGridLayout, VizPanel } from '@grafana/scenes'; import { LibraryPanel } from '@grafana/schema'; import { backendSrv } from 'app/core/services/backend_srv'; +import { DashboardGridItem } from './DashboardGridItem'; import { LibraryVizPanel } from './LibraryVizPanel'; -import { PanelRepeaterGridItem } from './PanelRepeaterGridItem'; describe('LibraryVizPanel', () => { const server = setupServer(); @@ -42,7 +42,7 @@ describe('LibraryVizPanel', () => { }); }); - it('should change parent from SceneGridItem to PanelRepeaterGridItem if repeat is set', async () => { + it('should configure repeat options of DashboardGridIem if repeat is set', async () => { setUpApiMock(server, { model: { repeat: 'query0', repeatDirection: 'h' } }); const libVizPanel = new LibraryVizPanel({ name: 'My Library Panel', @@ -52,12 +52,17 @@ describe('LibraryVizPanel', () => { }); const layout = new SceneGridLayout({ - children: [new SceneGridItem({ body: libVizPanel })], + children: [new DashboardGridItem({ body: libVizPanel })], }); + layout.activate(); libVizPanel.activate(); + await waitFor(() => { - expect(layout.state.children[0]).toBeInstanceOf(PanelRepeaterGridItem); + expect(layout.state.children[0]).toBeInstanceOf(DashboardGridItem); + expect((layout.state.children[0] as DashboardGridItem).state.variableName).toBe('query0'); + expect((layout.state.children[0] as DashboardGridItem).state.repeatDirection).toBe('h'); + expect((layout.state.children[0] as DashboardGridItem).state.maxPerRow).toBe(4); }); }); }); diff --git a/public/app/features/dashboard-scene/scene/LibraryVizPanel.tsx b/public/app/features/dashboard-scene/scene/LibraryVizPanel.tsx index 6efd02644ef..53b9c2ddc42 100644 --- a/public/app/features/dashboard-scene/scene/LibraryVizPanel.tsx +++ b/public/app/features/dashboard-scene/scene/LibraryVizPanel.tsx @@ -2,7 +2,6 @@ import React from 'react'; import { SceneComponentProps, - SceneGridItem, SceneGridLayout, SceneObjectBase, SceneObjectState, @@ -16,10 +15,10 @@ import { getLibraryPanel } from 'app/features/library-panels/state/api'; import { createPanelDataProvider } from '../utils/createPanelDataProvider'; +import { DashboardGridItem } from './DashboardGridItem'; import { VizPanelLinks, VizPanelLinksMenu } from './PanelLinks'; import { panelLinksBehavior, panelMenuBehavior } from './PanelMenuBehavior'; import { PanelNotices } from './PanelNotices'; -import { PanelRepeaterGridItem } from './PanelRepeaterGridItem'; interface LibraryVizPanelState extends SceneObjectState { // Library panels use title from dashboard JSON's panel model, not from library panel definition, hence we pass it. @@ -81,16 +80,16 @@ export class LibraryVizPanel extends SceneObjectBase { const panel = new VizPanel(vizPanelState); const gridItem = this.parent; - if (libPanelModel.repeat && gridItem instanceof SceneGridItem && gridItem.parent instanceof SceneGridLayout) { + if (libPanelModel.repeat && gridItem instanceof DashboardGridItem && gridItem.parent instanceof SceneGridLayout) { this._parent = undefined; - const repeater = new PanelRepeaterGridItem({ + const repeater = new DashboardGridItem({ key: gridItem.state.key, x: gridItem.state.x, y: gridItem.state.y, width: libPanelModel.repeatDirection === 'h' ? 24 : gridItem.state.width, height: gridItem.state.height, itemHeight: gridItem.state.height, - source: this, + body: this, variableName: libPanelModel.repeat, repeatedPanels: [], repeatDirection: libPanelModel.repeatDirection === 'h' ? 'h' : 'v', @@ -112,6 +111,13 @@ export class LibraryVizPanel extends SceneObjectBase { try { const libPanel = await getLibraryPanel(this.state.uid, true); this.setPanelFromLibPanel(libPanel); + if (this.parent instanceof DashboardGridItem) { + this.parent.setState({ + variableName: libPanel.model.repeat, + repeatDirection: libPanel.model.repeatDirection === 'h' ? 'h' : 'v', + maxPerRow: libPanel.model.maxPerRow, + }); + } } catch (err) { vizPanel.setState({ _pluginLoadError: `Unable to load library panel: ${this.state.uid}`, diff --git a/public/app/features/dashboard-scene/scene/PanelMenuBehavior.test.tsx b/public/app/features/dashboard-scene/scene/PanelMenuBehavior.test.tsx index d6080eed261..5863a58fe47 100644 --- a/public/app/features/dashboard-scene/scene/PanelMenuBehavior.test.tsx +++ b/public/app/features/dashboard-scene/scene/PanelMenuBehavior.test.tsx @@ -11,7 +11,6 @@ import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks'; import { getPluginLinkExtensions, locationService } from '@grafana/runtime'; import { LocalValueVariable, - SceneGridItem, SceneGridLayout, SceneQueryRunner, SceneTimeRange, @@ -24,6 +23,7 @@ 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'; @@ -590,7 +590,7 @@ async function buildTestScene(options: SceneOptions) { }, body: new SceneGridLayout({ children: [ - new SceneGridItem({ + new DashboardGridItem({ key: 'griditem-1', x: 0, y: 0, diff --git a/public/app/features/dashboard-scene/scene/RowRepeaterBehavior.test.tsx b/public/app/features/dashboard-scene/scene/RowRepeaterBehavior.test.tsx index e6d18e149b7..d774e15dde5 100644 --- a/public/app/features/dashboard-scene/scene/RowRepeaterBehavior.test.tsx +++ b/public/app/features/dashboard-scene/scene/RowRepeaterBehavior.test.tsx @@ -1,9 +1,9 @@ import { EmbeddedScene, SceneCanvasText, - SceneGridItem, SceneGridLayout, SceneGridRow, + SceneGridItem, SceneTimeRange, SceneVariableSet, TestVariable, @@ -12,7 +12,7 @@ import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from 'app/features/variables/co import { activateFullSceneTree } from '../utils/test-utils'; -import { RepeatDirection } from './PanelRepeaterGridItem'; +import { RepeatDirection } from './DashboardGridItem'; import { RowRepeaterBehavior } from './RowRepeaterBehavior'; describe('RowRepeaterBehavior', () => { diff --git a/public/app/features/dashboard-scene/scene/ViewPanelScene.test.tsx b/public/app/features/dashboard-scene/scene/ViewPanelScene.test.tsx index b566841d04d..d0914330094 100644 --- a/public/app/features/dashboard-scene/scene/ViewPanelScene.test.tsx +++ b/public/app/features/dashboard-scene/scene/ViewPanelScene.test.tsx @@ -1,12 +1,6 @@ -import { - LocalValueVariable, - SceneGridItem, - SceneGridLayout, - SceneGridRow, - SceneVariableSet, - VizPanel, -} from '@grafana/scenes'; +import { LocalValueVariable, SceneGridLayout, SceneGridRow, SceneVariableSet, VizPanel } from '@grafana/scenes'; +import { DashboardGridItem } from './DashboardGridItem'; import { DashboardScene } from './DashboardScene'; import { ViewPanelScene } from './ViewPanelScene'; @@ -56,7 +50,7 @@ function buildScene(options?: SceneOptions) { : undefined, height: 1, children: [ - new SceneGridItem({ + new DashboardGridItem({ body: panel, }), ], diff --git a/public/app/features/dashboard-scene/scene/row-actions/RowActions.tsx b/public/app/features/dashboard-scene/scene/row-actions/RowActions.tsx index ec9b123be47..cab93652117 100644 --- a/public/app/features/dashboard-scene/scene/row-actions/RowActions.tsx +++ b/public/app/features/dashboard-scene/scene/row-actions/RowActions.tsx @@ -4,12 +4,10 @@ import React from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { SceneComponentProps, - SceneGridItem, SceneGridLayout, SceneGridRow, SceneObjectBase, SceneObjectState, - SceneQueryRunner, VizPanel, } from '@grafana/scenes'; import { Icon, TextLink, useStyles2 } from '@grafana/ui'; @@ -17,7 +15,8 @@ import appEvents from 'app/core/app_events'; import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard'; import { ShowConfirmModalEvent } from 'app/types/events'; -import { getDashboardSceneFor } from '../../utils/utils'; +import { getDashboardSceneFor, getQueryRunnerFor } from '../../utils/utils'; +import { DashboardGridItem } from '../DashboardGridItem'; import { DashboardScene } from '../DashboardScene'; import { RowRepeaterBehavior } from '../RowRepeaterBehavior'; @@ -122,12 +121,14 @@ export class RowActions extends SceneObjectBase { const gridItems = row.state.children; const isAnyPanelUsingDashboardDS = gridItems.some((gridItem) => { - if (!(gridItem instanceof SceneGridItem)) { + if (!(gridItem instanceof DashboardGridItem)) { return false; } - if (gridItem.state.body instanceof VizPanel && gridItem.state.body.state.$data instanceof SceneQueryRunner) { - return gridItem.state.body.state.$data?.state.datasource?.uid === SHARED_DASHBOARD_QUERY; + const vizPanel = gridItem.state.body; + if (vizPanel instanceof VizPanel) { + const runner = getQueryRunnerFor(vizPanel); + return runner?.state.datasource?.uid === SHARED_DASHBOARD_QUERY; } return false; diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts index 2d937285f1c..6e45ece6e34 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts @@ -22,7 +22,6 @@ import { SceneDataLayerControls, SceneDataLayers, SceneDataTransformer, - SceneGridItem, SceneGridLayout, SceneGridRow, SceneQueryRunner, @@ -43,8 +42,8 @@ import { DASHBOARD_DATASOURCE_PLUGIN_ID } from 'app/plugins/datasource/dashboard import { DashboardDataDTO } from 'app/types'; import { AddLibraryPanelWidget } from '../scene/AddLibraryPanelWidget'; +import { DashboardGridItem } from '../scene/DashboardGridItem'; import { LibraryVizPanel } from '../scene/LibraryVizPanel'; -import { PanelRepeaterGridItem } from '../scene/PanelRepeaterGridItem'; import { PanelTimeRange } from '../scene/PanelTimeRange'; import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior'; import { NEW_LINK } from '../settings/links/utils'; @@ -224,11 +223,11 @@ describe('transformSaveModelToScene', () => { expect(rowScene.state.y).toEqual(row.gridPos!.y); expect(rowScene.state.isCollapsed).toEqual(row.collapsed); expect(rowScene.state.children).toHaveLength(3); - expect(rowScene.state.children[0]).toBeInstanceOf(SceneGridItem); - expect(rowScene.state.children[1]).toBeInstanceOf(SceneGridItem); - expect(rowScene.state.children[2]).toBeInstanceOf(SceneGridItem); - expect((rowScene.state.children[1] as SceneGridItem).state.body!).toBeInstanceOf(AddLibraryPanelWidget); - expect((rowScene.state.children[2] as SceneGridItem).state.body!).toBeInstanceOf(LibraryVizPanel); + expect(rowScene.state.children[0]).toBeInstanceOf(DashboardGridItem); + expect(rowScene.state.children[1]).toBeInstanceOf(DashboardGridItem); + expect(rowScene.state.children[2]).toBeInstanceOf(DashboardGridItem); + expect((rowScene.state.children[1] as DashboardGridItem).state.body!).toBeInstanceOf(AddLibraryPanelWidget); + expect((rowScene.state.children[2] as DashboardGridItem).state.body!).toBeInstanceOf(LibraryVizPanel); }); it('should create panels within expanded row', () => { @@ -335,16 +334,16 @@ describe('transformSaveModelToScene', () => { expect(body.state.children).toHaveLength(5); expect(body).toBeInstanceOf(SceneGridLayout); // Panel out of row - expect(body.state.children[0]).toBeInstanceOf(SceneGridItem); - const panelOutOfRowVizPanel = body.state.children[0] as SceneGridItem; + expect(body.state.children[0]).toBeInstanceOf(DashboardGridItem); + const panelOutOfRowVizPanel = body.state.children[0] as DashboardGridItem; expect((panelOutOfRowVizPanel.state.body as VizPanel)?.state.title).toBe(panelOutOfRow.title); // widget lib panel out of row - expect(body.state.children[1]).toBeInstanceOf(SceneGridItem); - const panelOutOfRowWidget = body.state.children[1] as SceneGridItem; + expect(body.state.children[1]).toBeInstanceOf(DashboardGridItem); + const panelOutOfRowWidget = body.state.children[1] as DashboardGridItem; expect(panelOutOfRowWidget.state.body!).toBeInstanceOf(AddLibraryPanelWidget); // lib panel out of row - expect(body.state.children[2]).toBeInstanceOf(SceneGridItem); - const panelOutOfRowLibVizPanel = body.state.children[2] as SceneGridItem; + expect(body.state.children[2]).toBeInstanceOf(DashboardGridItem); + const panelOutOfRowLibVizPanel = body.state.children[2] as DashboardGridItem; expect(panelOutOfRowLibVizPanel.state.body!).toBeInstanceOf(LibraryVizPanel); // Row with panels expect(body.state.children[3]).toBeInstanceOf(SceneGridRow); @@ -352,13 +351,13 @@ describe('transformSaveModelToScene', () => { expect(rowWithPanelsScene.state.title).toBe(rowWithPanel.title); expect(rowWithPanelsScene.state.key).toBe('panel-10'); expect(rowWithPanelsScene.state.children).toHaveLength(3); - const widget = rowWithPanelsScene.state.children[1] as SceneGridItem; + const widget = rowWithPanelsScene.state.children[1] as DashboardGridItem; expect(widget.state.body!).toBeInstanceOf(AddLibraryPanelWidget); - const libPanel = rowWithPanelsScene.state.children[2] as SceneGridItem; + const libPanel = rowWithPanelsScene.state.children[2] as DashboardGridItem; expect(libPanel.state.body!).toBeInstanceOf(LibraryVizPanel); // Panel within row - expect(rowWithPanelsScene.state.children[0]).toBeInstanceOf(SceneGridItem); - const panelInRowVizPanel = rowWithPanelsScene.state.children[0] as SceneGridItem; + expect(rowWithPanelsScene.state.children[0]).toBeInstanceOf(DashboardGridItem); + const panelInRowVizPanel = rowWithPanelsScene.state.children[0] as DashboardGridItem; expect((panelInRowVizPanel.state.body as VizPanel).state.title).toBe(panelInRow.title); // Empty row expect(body.state.children[4]).toBeInstanceOf(SceneGridRow); @@ -506,7 +505,7 @@ describe('transformSaveModelToScene', () => { }; const gridItem = buildGridItemForPanel(new PanelModel(panel)); - const repeater = gridItem as PanelRepeaterGridItem; + const repeater = gridItem as DashboardGridItem; expect(repeater.state.maxPerRow).toBe(8); expect(repeater.state.variableName).toBe('server'); @@ -1321,11 +1320,11 @@ describe('transformSaveModelToScene', () => { }); }); -function buildGridItemForTest(saveModel: Partial): { gridItem: SceneGridItem; vizPanel: VizPanel } { +function buildGridItemForTest(saveModel: Partial): { gridItem: DashboardGridItem; vizPanel: VizPanel } { const gridItem = buildGridItemForPanel(new PanelModel(saveModel)); - if (gridItem instanceof SceneGridItem) { + if (gridItem instanceof DashboardGridItem) { return { gridItem, vizPanel: gridItem.state.body as VizPanel }; } - throw new Error('buildGridItemForPanel to return SceneGridItem'); + throw new Error('buildGridItemForPanel to return DashboardGridItem'); } diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts index 015146d7b5d..797447039d7 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts @@ -15,7 +15,6 @@ import { ConstantVariable, IntervalVariable, SceneRefreshPicker, - SceneGridItem, SceneObject, VizPanelMenu, behaviors, @@ -38,13 +37,13 @@ import { AddLibraryPanelWidget } from '../scene/AddLibraryPanelWidget'; import { AlertStatesDataLayer } from '../scene/AlertStatesDataLayer'; import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer'; import { DashboardControls } from '../scene/DashboardControls'; +import { DashboardGridItem, RepeatDirection } from '../scene/DashboardGridItem'; import { registerDashboardMacro } from '../scene/DashboardMacro'; import { DashboardScene } from '../scene/DashboardScene'; import { LibraryVizPanel } from '../scene/LibraryVizPanel'; import { VizPanelLinks, VizPanelLinksMenu } from '../scene/PanelLinks'; import { panelLinksBehavior, panelMenuBehavior } from '../scene/PanelMenuBehavior'; import { PanelNotices } from '../scene/PanelNotices'; -import { PanelRepeaterGridItem } from '../scene/PanelRepeaterGridItem'; import { PanelTimeRange } from '../scene/PanelTimeRange'; import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior'; import { RowActions } from '../scene/row-actions/RowActions'; @@ -447,7 +446,7 @@ export function buildGridItemForLibraryPanelWidget(panel: PanelModel) { key: getVizPanelKeyForPanelId(panel.id), }); - return new SceneGridItem({ + return new DashboardGridItem({ body, y: panel.gridPos.y, x: panel.gridPos.x, @@ -468,16 +467,26 @@ export function buildGridItemForLibPanel(panel: PanelModel) { panelKey: getVizPanelKeyForPanelId(panel.id), }); - return new SceneGridItem({ - body, + return new DashboardGridItem({ + key: `grid-item-${panel.id}`, y: panel.gridPos.y, x: panel.gridPos.x, width: panel.gridPos.w, height: panel.gridPos.h, + itemHeight: panel.gridPos.h, + body, }); } -export function buildGridItemForPanel(panel: PanelModel): SceneGridItemLike { +export function buildGridItemForPanel(panel: PanelModel): DashboardGridItem { + const repeatDirection: RepeatDirection = panel.repeatDirection === 'h' ? 'h' : 'v'; + const repeatOptions = panel.repeat + ? { + variableName: panel.repeat, + repeatDirection, + } + : {}; + const titleItems: SceneObject[] = []; titleItems.push( @@ -518,33 +527,18 @@ export function buildGridItemForPanel(panel: PanelModel): SceneGridItemLike { }); } - if (panel.repeat) { - const repeatDirection = panel.repeatDirection === 'h' ? 'h' : 'v'; - - return new PanelRepeaterGridItem({ - key: `grid-item-${panel.id}`, - x: panel.gridPos.x, - y: panel.gridPos.y, - width: repeatDirection === 'h' ? 24 : panel.gridPos.w, - height: panel.gridPos.h, - itemHeight: panel.gridPos.h, - source: new VizPanel(vizPanelState), - variableName: panel.repeat, - repeatedPanels: [], - repeatDirection: repeatDirection, - maxPerRow: panel.maxPerRow, - }); - } - const body = new VizPanel(vizPanelState); - return new SceneGridItem({ + return new DashboardGridItem({ key: `grid-item-${panel.id}`, x: panel.gridPos.x, y: panel.gridPos.y, - width: panel.gridPos.w, + width: repeatDirection === 'h' ? 24 : panel.gridPos.w, height: panel.gridPos.h, + itemHeight: panel.gridPos.h, body, + maxPerRow: panel.maxPerRow, + ...repeatOptions, }); } diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts index 7c3dadd80c9..741d23b3c9a 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts @@ -16,21 +16,14 @@ import { } from '@grafana/data'; import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks'; import { getPluginLinkExtensions, setPluginImportUtils } from '@grafana/runtime'; -import { - MultiValueVariable, - SceneDataLayers, - SceneGridItem, - SceneGridItemLike, - SceneGridLayout, - SceneGridRow, - VizPanel, -} from '@grafana/scenes'; +import { MultiValueVariable, SceneDataLayers, SceneGridLayout, 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'; import { reduceTransformRegistryItem } from 'app/features/transformers/editors/ReduceTransformerEditor'; import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard'; +import { DashboardGridItem } from '../scene/DashboardGridItem'; import { LibraryVizPanel } from '../scene/LibraryVizPanel'; import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior'; import { NEW_LINK } from '../settings/links/utils'; @@ -210,7 +203,9 @@ describe('transformSceneToSaveModel', () => { describe('Given a scene with rows', () => { it('Should transform back to persisted model', () => { const scene = transformSaveModelToScene({ dashboard: repeatingRowsAndPanelsDashboardJson as any, meta: {} }); + const saveModel = transformSceneToSaveModel(scene); + const row2: RowPanel = saveModel.panels![2] as RowPanel; expect(row2.type).toBe('row'); @@ -347,7 +342,7 @@ describe('transformSceneToSaveModel', () => { }), }); - const panel = new SceneGridItem({ + const panel = new DashboardGridItem({ body: libVizPanel, y: 0, x: 0, @@ -987,7 +982,7 @@ describe('transformSceneToSaveModel', () => { }); }); -export function buildGridItemFromPanelSchema(panel: Partial): SceneGridItemLike { +export function buildGridItemFromPanelSchema(panel: Partial) { if (panel.libraryPanel) { return buildGridItemForLibPanel(new PanelModel(panel))!; } else if (panel.type === 'add-library-panel') { diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts index b1329a4828c..cbe61e0a242 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts @@ -4,7 +4,6 @@ import { isEmptyObject, ScopedVars, TimeRange } from '@grafana/data'; import { behaviors, SceneDataLayers, - SceneGridItem, SceneGridItemLike, SceneGridLayout, SceneGridRow, @@ -34,9 +33,9 @@ import { DASHBOARD_SCHEMA_VERSION } from 'app/features/dashboard/state/Dashboard import { GrafanaQueryType } from 'app/plugins/datasource/grafana/types'; import { AddLibraryPanelWidget } from '../scene/AddLibraryPanelWidget'; +import { DashboardGridItem } from '../scene/DashboardGridItem'; import { DashboardScene } from '../scene/DashboardScene'; import { LibraryVizPanel } from '../scene/LibraryVizPanel'; -import { PanelRepeaterGridItem } from '../scene/PanelRepeaterGridItem'; import { PanelTimeRange } from '../scene/PanelTimeRange'; import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior'; import { dashboardSceneGraph } from '../utils/dashboardSceneGraph'; @@ -59,12 +58,13 @@ export function transformSceneToSaveModel(scene: DashboardScene, isSnapshot = fa if (body instanceof SceneGridLayout) { for (const child of body.state.children) { - if (child instanceof SceneGridItem) { - panels.push(gridItemToPanel(child, isSnapshot)); - } - - if (child instanceof PanelRepeaterGridItem) { - panels = panels.concat(panelRepeaterToPanels(child, isSnapshot)); + if (child instanceof DashboardGridItem) { + // handle panel repeater scenatio + if (child.state.variableName) { + panels = panels.concat(panelRepeaterToPanels(child, isSnapshot)); + } else { + panels.push(gridItemToPanel(child, isSnapshot)); + } } if (child instanceof SceneGridRow) { @@ -155,62 +155,47 @@ export function libraryVizPanelToPanel(libPanel: LibraryVizPanel, gridPos: GridP } as Panel; } -export function gridItemToPanel(gridItem: SceneGridItemLike, isSnapshot = false): Panel { +export function gridItemToPanel(gridItem: DashboardGridItem, isSnapshot = false): Panel { let vizPanel: VizPanel | undefined; let x = 0, y = 0, w = 0, h = 0; - if (gridItem instanceof SceneGridItem) { - // Handle library panels, early exit - if (gridItem.state.body instanceof LibraryVizPanel) { - x = gridItem.state.x ?? 0; - y = gridItem.state.y ?? 0; - w = gridItem.state.width ?? 0; - h = gridItem.state.height ?? 0; - - return libraryVizPanelToPanel(gridItem.state.body, { x, y, w, h }); - } - - // Handle library panel widget as well and exit early - if (gridItem.state.body instanceof AddLibraryPanelWidget) { - x = gridItem.state.x ?? 0; - y = gridItem.state.y ?? 0; - w = gridItem.state.width ?? 0; - h = gridItem.state.height ?? 0; - - return { - id: getPanelIdForVizPanel(gridItem.state.body), - type: 'add-library-panel', - gridPos: { x, y, w, h }, - }; - } - - if (!(gridItem.state.body instanceof VizPanel)) { - throw new Error('SceneGridItem body expected to be VizPanel'); - } - - vizPanel = gridItem.state.body; - x = gridItem.state.x ?? 0; - y = gridItem.state.y ?? 0; - w = gridItem.state.width ?? 0; - h = gridItem.state.height ?? 0; - } - - if (gridItem instanceof PanelRepeaterGridItem) { + // Handle library panels, early exit + if (gridItem.state.body instanceof LibraryVizPanel) { x = gridItem.state.x ?? 0; y = gridItem.state.y ?? 0; w = gridItem.state.width ?? 0; h = gridItem.state.height ?? 0; - if (gridItem.state.source instanceof LibraryVizPanel) { - return libraryVizPanelToPanel(gridItem.state.source, { x, y, w, h }); - } else { - vizPanel = gridItem.state.source; - } + return libraryVizPanelToPanel(gridItem.state.body, { x, y, w, h }); } + // Handle library panel widget as well and exit early + if (gridItem.state.body instanceof AddLibraryPanelWidget) { + x = gridItem.state.x ?? 0; + y = gridItem.state.y ?? 0; + w = gridItem.state.width ?? 0; + h = gridItem.state.height ?? 0; + + return { + id: getPanelIdForVizPanel(gridItem.state.body), + type: 'add-library-panel', + gridPos: { x, y, w, h }, + }; + } + + if (!(gridItem.state.body instanceof VizPanel)) { + throw new Error('DashboardGridItem body expected to be VizPanel'); + } + + vizPanel = gridItem.state.body; + x = gridItem.state.x ?? 0; + y = gridItem.state.y ?? 0; + w = gridItem.state.width ?? 0; + h = gridItem.state.height ?? 0; + if (!vizPanel) { throw new Error('Unsupported grid item type'); } @@ -247,10 +232,17 @@ export function vizPanelToPanel( panel.hideTimeOverride = panelTime.state.hideTimeOverride; } - if (gridItem instanceof PanelRepeaterGridItem) { - panel.repeat = gridItem.state.variableName; - panel.maxPerRow = gridItem.state.maxPerRow; - panel.repeatDirection = gridItem.getRepeatDirection(); + if (gridItem instanceof DashboardGridItem) { + if (gridItem.state.variableName) { + panel.repeat = gridItem.state.variableName; + } + + if (gridItem.state.maxPerRow) { + panel.maxPerRow = gridItem.state.maxPerRow; + } + if (gridItem.state.repeatDirection) { + panel.repeatDirection = gridItem.getRepeatDirection(); + } } const panelLinks = dashboardSceneGraph.getPanelLinks(vizPanel); @@ -322,15 +314,16 @@ function vizPanelDataToPanel( return panel; } -export function panelRepeaterToPanels(repeater: PanelRepeaterGridItem, isSnapshot = false): Panel[] { +export function panelRepeaterToPanels(repeater: DashboardGridItem, isSnapshot = false): Panel[] { if (!isSnapshot) { return [gridItemToPanel(repeater)]; } else { - if (repeater.state.source instanceof LibraryVizPanel) { + if (repeater.state.body instanceof LibraryVizPanel) { const { x = 0, y = 0, width: w = 0, height: h = 0 } = repeater.state; - return [libraryVizPanelToPanel(repeater.state.source, { x, y, w, h })]; + return [libraryVizPanelToPanel(repeater.state.body, { x, y, w, h })]; } + // console.log('repeater.state', repeater.state); if (repeater.state.repeatedPanels) { const itemHeight = repeater.state.itemHeight ?? 10; const rowCount = Math.ceil(repeater.state.repeatedPanels!.length / repeater.getMaxPerRow()); @@ -427,16 +420,23 @@ export function gridRowToSaveModel(gridRow: SceneGridRow, panelsArray: Array { - if (c instanceof PanelRepeaterGridItem) { - // Perform snapshot only for uncollapsed rows - panelsInsideRow = panelsInsideRow.concat(panelRepeaterToPanels(c, !collapsed)); - } else { - // Perform snapshot only for uncollapsed panels - panelsInsideRow.push(gridItemToPanel(c, !collapsed)); + if (c instanceof DashboardGridItem) { + if (c.state.variableName) { + // Perform snapshot only for uncollapsed rows + panelsInsideRow = panelsInsideRow.concat(panelRepeaterToPanels(c, !collapsed)); + } else { + // Perform snapshot only for uncollapsed panels + panelsInsideRow.push(gridItemToPanel(c, !collapsed)); + } } }); } else { - panelsInsideRow = gridRow.state.children.map((c) => gridItemToPanel(c)); + panelsInsideRow = gridRow.state.children.map((c) => { + if (!(c instanceof DashboardGridItem)) { + throw new Error('Row child expected to be DashboardGridItem'); + } + return gridItemToPanel(c); + }); } if (gridRow.state.isCollapsed) { diff --git a/public/app/features/dashboard-scene/settings/AnnotationsEditView.test.tsx b/public/app/features/dashboard-scene/settings/AnnotationsEditView.test.tsx index a895ec2a678..d422842e14f 100644 --- a/public/app/features/dashboard-scene/settings/AnnotationsEditView.test.tsx +++ b/public/app/features/dashboard-scene/settings/AnnotationsEditView.test.tsx @@ -1,9 +1,7 @@ import { map, of } from 'rxjs'; import { AnnotationQuery, DataQueryRequest, DataSourceApi, LoadingState, PanelData } from '@grafana/data'; -import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks'; -import { setPluginImportUtils } from '@grafana/runtime'; -import { SceneDataLayers, SceneGridItem, SceneGridLayout, SceneTimeRange, VizPanel, dataLayers } from '@grafana/scenes'; +import { SceneDataLayers, SceneGridLayout, SceneTimeRange, dataLayers } from '@grafana/scenes'; import { AlertStatesDataLayer } from '../scene/AlertStatesDataLayer'; import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer'; @@ -47,11 +45,6 @@ jest.mock('@grafana/runtime', () => ({ }, })); -setPluginImportUtils({ - importPanelPlugin: (id: string) => Promise.resolve(getPanelPlugin({})), - getPanelPluginFromCache: (id: string) => undefined, -}); - describe('AnnotationsEditView', () => { describe('Dashboard annotations state', () => { let annotationsView: AnnotationsEditView; @@ -189,20 +182,7 @@ async function buildTestScene() { ], }), body: new SceneGridLayout({ - children: [ - new SceneGridItem({ - key: 'griditem-1', - x: 0, - y: 0, - width: 10, - height: 12, - body: new VizPanel({ - title: 'Panel A', - key: 'panel-1', - pluginId: 'table', - }), - }), - ], + children: [], }), editview: annotationsView, }); diff --git a/public/app/features/dashboard-scene/settings/DashboardLinksEditView.test.tsx b/public/app/features/dashboard-scene/settings/DashboardLinksEditView.test.tsx index 508630a2561..810d1125de0 100644 --- a/public/app/features/dashboard-scene/settings/DashboardLinksEditView.test.tsx +++ b/public/app/features/dashboard-scene/settings/DashboardLinksEditView.test.tsx @@ -2,9 +2,7 @@ import { render as RTLRender } from '@testing-library/react'; import React from 'react'; import { TestProvider } from 'test/helpers/TestProvider'; -import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks'; -import { setPluginImportUtils } from '@grafana/runtime'; -import { SceneGridItem, SceneGridLayout, SceneTimeRange, VizPanel, behaviors } from '@grafana/scenes'; +import { SceneGridLayout, SceneTimeRange, behaviors } from '@grafana/scenes'; import { DashboardCursorSync } from '@grafana/schema'; import { DashboardControls } from '../scene/DashboardControls'; @@ -25,11 +23,6 @@ jest.mock('react-router-dom', () => ({ }), })); -setPluginImportUtils({ - importPanelPlugin: (id: string) => Promise.resolve(getPanelPlugin({})), - getPanelPluginFromCache: (id: string) => undefined, -}); - function render(component: React.ReactNode) { return RTLRender({component}); } @@ -231,20 +224,7 @@ async function buildTestScene() { canEdit: true, }, body: new SceneGridLayout({ - children: [ - new SceneGridItem({ - key: 'griditem-1', - x: 0, - y: 0, - width: 10, - height: 12, - body: new VizPanel({ - title: 'Panel A', - key: 'panel-1', - pluginId: 'table', - }), - }), - ], + children: [], }), editview: settings, }); diff --git a/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.test.tsx b/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.test.tsx index d438483300a..e1e55da7e44 100644 --- a/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.test.tsx +++ b/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.test.tsx @@ -1,6 +1,4 @@ -import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks'; -import { setPluginImportUtils } from '@grafana/runtime'; -import { behaviors, SceneGridLayout, SceneGridItem, SceneTimeRange, VizPanel } from '@grafana/scenes'; +import { behaviors, SceneGridLayout, SceneTimeRange } from '@grafana/scenes'; import { DashboardCursorSync } from '@grafana/schema'; import { DashboardControls } from '../scene/DashboardControls'; @@ -9,11 +7,6 @@ import { activateFullSceneTree } from '../utils/test-utils'; import { GeneralSettingsEditView } from './GeneralSettingsEditView'; -setPluginImportUtils({ - importPanelPlugin: (id: string) => Promise.resolve(getPanelPlugin({})), - getPanelPluginFromCache: (id: string) => undefined, -}); - describe('GeneralSettingsEditView', () => { describe('Dashboard state', () => { let dashboard: DashboardScene; @@ -129,20 +122,7 @@ async function buildTestScene() { canEdit: true, }, body: new SceneGridLayout({ - children: [ - new SceneGridItem({ - key: 'griditem-1', - x: 0, - y: 0, - width: 10, - height: 12, - body: new VizPanel({ - title: 'Panel A', - key: 'panel-1', - pluginId: 'table', - }), - }), - ], + children: [], }), editview: settings, }); diff --git a/public/app/features/dashboard-scene/settings/PermissionsEditView.test.tsx b/public/app/features/dashboard-scene/settings/PermissionsEditView.test.tsx index 45bee212ab5..4bb9868502f 100644 --- a/public/app/features/dashboard-scene/settings/PermissionsEditView.test.tsx +++ b/public/app/features/dashboard-scene/settings/PermissionsEditView.test.tsx @@ -1,17 +1,10 @@ -import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks'; -import { setPluginImportUtils } from '@grafana/runtime'; -import { SceneGridItem, SceneGridLayout, SceneTimeRange, VizPanel } from '@grafana/scenes'; +import { SceneGridLayout, SceneTimeRange } from '@grafana/scenes'; import { DashboardScene } from '../scene/DashboardScene'; import { activateFullSceneTree } from '../utils/test-utils'; import { PermissionsEditView } from './PermissionsEditView'; -setPluginImportUtils({ - importPanelPlugin: (id: string) => Promise.resolve(getPanelPlugin({})), - getPanelPluginFromCache: (id: string) => undefined, -}); - describe('PermissionsEditView', () => { describe('Dashboard permissions state', () => { let dashboard: DashboardScene; @@ -44,20 +37,7 @@ async function buildTestScene() { canEdit: true, }, body: new SceneGridLayout({ - children: [ - new SceneGridItem({ - key: 'griditem-1', - x: 0, - y: 0, - width: 10, - height: 12, - body: new VizPanel({ - title: 'Panel A', - key: 'panel-1', - pluginId: 'table', - }), - }), - ], + children: [], }), editview: permissionsView, }); diff --git a/public/app/features/dashboard-scene/settings/VariablesEditView.test.tsx b/public/app/features/dashboard-scene/settings/VariablesEditView.test.tsx index ac39ac3c603..a547fa10d4f 100644 --- a/public/app/features/dashboard-scene/settings/VariablesEditView.test.tsx +++ b/public/app/features/dashboard-scene/settings/VariablesEditView.test.tsx @@ -13,7 +13,6 @@ import { setPluginImportUtils, setRunRequest } from '@grafana/runtime'; import { SceneVariableSet, CustomVariable, - SceneGridItem, SceneGridLayout, VizPanel, AdHocFiltersVariable, @@ -23,6 +22,7 @@ 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 { activateFullSceneTree } from '../utils/test-utils'; @@ -289,7 +289,7 @@ describe('VariablesEditView', () => { // 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 SceneGridItem).state.body as VizPanel; + ((dashboard.state.body as SceneGridLayout).state.children[0] as DashboardGridItem).state.body as VizPanel; expect(getSourceVariable().getValue()).toBe('test'); // Using description to get the interpolated value @@ -344,7 +344,7 @@ async function buildTestScene() { }), body: new SceneGridLayout({ children: [ - new SceneGridItem({ + new DashboardGridItem({ key: 'griditem-1', x: 0, body: new VizPanel({ diff --git a/public/app/features/dashboard-scene/settings/VersionsEditView.test.tsx b/public/app/features/dashboard-scene/settings/VersionsEditView.test.tsx index ee702bd8d6a..f9024d2b8ed 100644 --- a/public/app/features/dashboard-scene/settings/VersionsEditView.test.tsx +++ b/public/app/features/dashboard-scene/settings/VersionsEditView.test.tsx @@ -1,6 +1,4 @@ -import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks'; -import { setPluginImportUtils } from '@grafana/runtime'; -import { SceneGridItem, SceneGridLayout, SceneTimeRange, VizPanel } from '@grafana/scenes'; +import { SceneGridLayout, SceneTimeRange } from '@grafana/scenes'; import { DashboardScene } from '../scene/DashboardScene'; import { activateFullSceneTree } from '../utils/test-utils'; @@ -10,11 +8,6 @@ import { historySrv } from './version-history'; jest.mock('./version-history/HistorySrv'); -setPluginImportUtils({ - importPanelPlugin: (id: string) => Promise.resolve(getPanelPlugin({})), - getPanelPluginFromCache: (id: string) => undefined, -}); - describe('VersionsEditView', () => { describe('Dashboard versions state', () => { let dashboard: DashboardScene; @@ -170,20 +163,7 @@ async function buildTestScene() { canEdit: true, }, body: new SceneGridLayout({ - children: [ - new SceneGridItem({ - key: 'griditem-1', - x: 0, - y: 0, - width: 10, - height: 12, - body: new VizPanel({ - title: 'Panel A', - key: 'panel-1', - pluginId: 'table', - }), - }), - ], + children: [], }), editview: versionsView, }); diff --git a/public/app/features/dashboard-scene/sharing/ShareLibraryPanelTab.tsx b/public/app/features/dashboard-scene/sharing/ShareLibraryPanelTab.tsx index 36f545fd265..989e1f79d33 100644 --- a/public/app/features/dashboard-scene/sharing/ShareLibraryPanelTab.tsx +++ b/public/app/features/dashboard-scene/sharing/ShareLibraryPanelTab.tsx @@ -1,13 +1,13 @@ import React from 'react'; -import { SceneComponentProps, SceneGridItem, SceneObjectBase, SceneObjectRef, VizPanel } from '@grafana/scenes'; +import { SceneComponentProps, SceneObjectBase, SceneObjectRef, VizPanel } from '@grafana/scenes'; import { t } from 'app/core/internationalization'; import { ShareLibraryPanel } from 'app/features/dashboard/components/ShareModal/ShareLibraryPanel'; import { shareDashboardType } from 'app/features/dashboard/components/ShareModal/utils'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; +import { DashboardGridItem } from '../scene/DashboardGridItem'; import { DashboardScene } from '../scene/DashboardScene'; -import { PanelRepeaterGridItem } from '../scene/PanelRepeaterGridItem'; import { gridItemToPanel, transformSceneToSaveModel } from '../serialization/transformSceneToSaveModel'; import { SceneShareTabState } from './types'; @@ -35,7 +35,7 @@ function ShareLibraryPanelTabRenderer({ model }: SceneComponentProps implements Moda if (panelRef) { tabs.push(new SharePanelEmbedTab({ panelRef, dashboardRef })); - - if (panelRef.resolve() instanceof VizPanel) { - tabs.push(new ShareLibraryPanelTab({ panelRef, dashboardRef, modalRef: this.getRef() })); + const panel = panelRef.resolve(); + const isLibraryPanel = panel.parent instanceof LibraryVizPanel; + if (panel instanceof VizPanel) { + if (!isLibraryPanel) { + tabs.push(new ShareLibraryPanelTab({ panelRef, dashboardRef, modalRef: this.getRef() })); + } } } diff --git a/public/app/features/dashboard-scene/sharing/public-dashboards/utils.ts b/public/app/features/dashboard-scene/sharing/public-dashboards/utils.ts index 5bd7a46b806..ba10cb5e976 100644 --- a/public/app/features/dashboard-scene/sharing/public-dashboards/utils.ts +++ b/public/app/features/dashboard-scene/sharing/public-dashboards/utils.ts @@ -2,7 +2,6 @@ import { DataSourceWithBackend } from '@grafana/runtime'; import { SceneGridItemLike, VizPanel, - SceneGridItem, SceneQueryRunner, SceneDataTransformer, SceneGridLayout, @@ -11,9 +10,9 @@ import { 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 { LibraryVizPanel } from '../../scene/LibraryVizPanel'; -import { PanelRepeaterGridItem } from '../../scene/PanelRepeaterGridItem'; export const getUnsupportedDashboardDatasources = async (types: string[]): Promise => { let unsupportedDS = new Set(); @@ -41,7 +40,7 @@ export function getPanelDatasourceTypes(scene: DashboardScene): string[] { } for (const child of body.state.children) { - if (child instanceof SceneGridItem) { + if (child instanceof DashboardGridItem) { const ts = panelDatasourceTypes(child); for (const t of ts) { types.add(t); @@ -66,16 +65,15 @@ function rowTypes(gridRow: SceneGridRow) { function panelDatasourceTypes(gridItem: SceneGridItemLike) { let vizPanel: VizPanel | LibraryVizPanel | undefined; - if (gridItem instanceof SceneGridItem) { + + if (gridItem instanceof DashboardGridItem) { if (gridItem.state.body instanceof LibraryVizPanel) { vizPanel = gridItem.state.body.state.panel; } else if (gridItem.state.body instanceof VizPanel) { vizPanel = gridItem.state.body; } else { - throw new Error('SceneGridItem body expected to be VizPanel'); + throw new Error('DashboardGridItem body expected to be VizPanel'); } - } else if (gridItem instanceof PanelRepeaterGridItem) { - vizPanel = gridItem.state.source; } if (!vizPanel) { diff --git a/public/app/features/dashboard-scene/solo/useSoloPanel.ts b/public/app/features/dashboard-scene/solo/useSoloPanel.ts index 9ca7d2139bd..27acec7d414 100644 --- a/public/app/features/dashboard-scene/solo/useSoloPanel.ts +++ b/public/app/features/dashboard-scene/solo/useSoloPanel.ts @@ -2,8 +2,8 @@ import { useState, useEffect } from 'react'; import { VizPanel, SceneObject, SceneGridRow, getUrlSyncManager } from '@grafana/scenes'; +import { DashboardGridItem } from '../scene/DashboardGridItem'; import { DashboardScene } from '../scene/DashboardScene'; -import { PanelRepeaterGridItem } from '../scene/PanelRepeaterGridItem'; import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior'; import { DashboardRepeatsProcessedEvent } from '../scene/types'; import { findVizPanelByKey, isPanelClone } from '../utils/utils'; @@ -64,7 +64,7 @@ function findRepeatClone(dashboard: DashboardScene, panelId: string): Promise { - if (child instanceof PanelRepeaterGridItem && !child.isActive) { + if (child instanceof DashboardGridItem && !child.isActive) { child.activate(); return; } @@ -77,7 +77,7 @@ function activateAllRepeaters(layout: SceneObject) { } } - // Activate any panel PanelRepeaterGridItem inside the row + // Activate any panel DashboardGridItem inside the row activateAllRepeaters(child); } }); diff --git a/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.test.ts b/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.test.ts index be5d199260a..62d33d70d1b 100644 --- a/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.test.ts +++ b/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.test.ts @@ -1,7 +1,6 @@ import { TimeRangeUpdatedEvent } from '@grafana/runtime'; import { behaviors, - SceneGridItem, SceneGridLayout, SceneQueryRunner, SceneTimeRange, @@ -15,6 +14,7 @@ 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 { NEW_LINK } from '../settings/links/utils'; @@ -175,7 +175,7 @@ function setup() { }), body: new SceneGridLayout({ children: [ - new SceneGridItem({ + new DashboardGridItem({ key: 'griditem-1', x: 0, body: new VizPanel({ @@ -189,7 +189,7 @@ function setup() { }), }), }), - new SceneGridItem({ + new DashboardGridItem({ body: new VizPanel({ title: 'Panel with no queries', key: 'panel-2', @@ -197,7 +197,7 @@ function setup() { }), }), - new SceneGridItem({ + new DashboardGridItem({ body: new VizPanel({ title: 'Panel with a shared query', key: 'panel-3', @@ -210,7 +210,7 @@ function setup() { }), }), - new SceneGridItem({ + new DashboardGridItem({ body: new VizPanel({ title: 'Panel with a regular data source query and transformations', key: 'panel-4', @@ -225,7 +225,7 @@ function setup() { }), }), }), - new SceneGridItem({ + new DashboardGridItem({ body: new VizPanel({ title: 'Panel with a shared query and transformations', key: 'panel-4', diff --git a/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts b/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts index 21b83ce910b..3975c07263e 100644 --- a/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts +++ b/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts @@ -6,13 +6,13 @@ import { behaviors, SceneDataLayers, sceneGraph, - SceneGridItem, SceneGridLayout, SceneGridRow, SceneObject, VizPanel, } from '@grafana/scenes'; +import { DashboardGridItem } from '../scene/DashboardGridItem'; import { DashboardScene } from '../scene/DashboardScene'; import { dataLayersToAnnotations } from '../serialization/dataLayersToAnnotations'; @@ -176,8 +176,8 @@ export class DashboardModelCompatibilityWrapper { } const gridItem = vizPanel.parent; - if (!(gridItem instanceof SceneGridItem)) { - console.error('Trying to remove a panel that is not wrapped in SceneGridItem'); + if (!(gridItem instanceof DashboardGridItem)) { + console.error('Trying to remove a panel that is not wrapped in DashboardGridItem'); return; } diff --git a/public/app/features/dashboard-scene/utils/dashboardSceneGraph.test.ts b/public/app/features/dashboard-scene/utils/dashboardSceneGraph.test.ts index bc50b5d889f..aee47e225bc 100644 --- a/public/app/features/dashboard-scene/utils/dashboardSceneGraph.test.ts +++ b/public/app/features/dashboard-scene/utils/dashboardSceneGraph.test.ts @@ -1,6 +1,5 @@ import { SceneDataLayers, - SceneGridItem, SceneGridLayout, SceneGridRow, SceneQueryRunner, @@ -13,10 +12,10 @@ import { DashboardCursorSync } from '@grafana/schema'; import { AlertStatesDataLayer } from '../scene/AlertStatesDataLayer'; import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer'; import { DashboardControls } from '../scene/DashboardControls'; +import { DashboardGridItem } from '../scene/DashboardGridItem'; import { DashboardScene, DashboardSceneState } from '../scene/DashboardScene'; import { LibraryVizPanel } from '../scene/LibraryVizPanel'; import { VizPanelLinks, VizPanelLinksMenu } from '../scene/PanelLinks'; -import { PanelRepeaterGridItem } from '../scene/PanelRepeaterGridItem'; import { dashboardSceneGraph, getNextPanelId } from './dashboardSceneGraph'; import { findVizPanelByKey } from './utils'; @@ -94,21 +93,21 @@ describe('dashboardSceneGraph', () => { const scene = buildTestScene({ body: new SceneGridLayout({ children: [ - new SceneGridItem({ + new DashboardGridItem({ body: new VizPanel({ title: 'Panel A', key: 'panel-1', pluginId: 'table', }), }), - new SceneGridItem({ + new DashboardGridItem({ body: new VizPanel({ title: 'Panel B', key: 'panel-2', pluginId: 'table', }), }), - new SceneGridItem({ + new DashboardGridItem({ body: new VizPanel({ title: 'Panel C', key: 'panel-3', @@ -128,7 +127,7 @@ describe('dashboardSceneGraph', () => { const scene = buildTestScene({ body: new SceneGridLayout({ children: [ - new SceneGridItem({ + new DashboardGridItem({ key: 'griditem-1', x: 0, body: new VizPanel({ @@ -137,7 +136,7 @@ describe('dashboardSceneGraph', () => { pluginId: 'table', }), }), - new SceneGridItem({ + new DashboardGridItem({ body: new LibraryVizPanel({ uid: 'uid', name: 'LibPanel', @@ -145,15 +144,15 @@ describe('dashboardSceneGraph', () => { panelKey: 'panel-2', }), }), - new SceneGridItem({ + new DashboardGridItem({ body: new VizPanel({ title: 'Panel C', key: 'panel-2-clone-1', pluginId: 'table', }), }), - new PanelRepeaterGridItem({ - source: new VizPanel({ + new DashboardGridItem({ + body: new VizPanel({ title: 'Panel C', key: 'panel-4', pluginId: 'table', @@ -167,14 +166,14 @@ describe('dashboardSceneGraph', () => { key: 'key', title: 'row', children: [ - new SceneGridItem({ + new DashboardGridItem({ body: new VizPanel({ title: 'Panel E', key: 'panel-2-clone-2', pluginId: 'table', }), }), - new SceneGridItem({ + new DashboardGridItem({ body: new LibraryVizPanel({ uid: 'uid', name: 'LibPanel', @@ -264,7 +263,7 @@ function buildTestScene(overrides?: Partial) { }), body: new SceneGridLayout({ children: [ - new SceneGridItem({ + new DashboardGridItem({ key: 'griditem-1', x: 0, body: new VizPanel({ @@ -274,14 +273,14 @@ function buildTestScene(overrides?: Partial) { $data: new SceneQueryRunner({ key: 'data-query-runner', queries: [{ refId: 'A' }] }), }), }), - new SceneGridItem({ + new DashboardGridItem({ body: new VizPanel({ title: 'Panel B', key: 'panel-2', pluginId: 'table', }), }), - new SceneGridItem({ + new DashboardGridItem({ body: new VizPanel({ title: 'Panel C', key: 'panel-2-clone-1', @@ -289,7 +288,7 @@ function buildTestScene(overrides?: Partial) { $data: new SceneQueryRunner({ key: 'data-query-runner2', queries: [{ refId: 'A' }] }), }), }), - new SceneGridItem({ + new DashboardGridItem({ body: new VizPanel({ title: 'Panel D', key: 'panel-with-links', @@ -302,14 +301,14 @@ function buildTestScene(overrides?: Partial) { key: 'key', title: 'row', children: [ - new SceneGridItem({ + new DashboardGridItem({ body: new VizPanel({ title: 'Panel E', key: 'panel-2-clone-2', pluginId: 'table', }), }), - new SceneGridItem({ + new DashboardGridItem({ body: new VizPanel({ title: 'Panel F', key: 'panel-2-clone-2', diff --git a/public/app/features/dashboard-scene/utils/dashboardSceneGraph.ts b/public/app/features/dashboard-scene/utils/dashboardSceneGraph.ts index 6980a1c5253..ff6abf43b8a 100644 --- a/public/app/features/dashboard-scene/utils/dashboardSceneGraph.ts +++ b/public/app/features/dashboard-scene/utils/dashboardSceneGraph.ts @@ -1,17 +1,9 @@ -import { - VizPanel, - SceneGridItem, - SceneGridRow, - SceneDataLayers, - sceneGraph, - SceneGridLayout, - behaviors, -} from '@grafana/scenes'; +import { VizPanel, SceneGridRow, SceneDataLayers, sceneGraph, SceneGridLayout, behaviors } from '@grafana/scenes'; +import { DashboardGridItem } from '../scene/DashboardGridItem'; import { DashboardScene } from '../scene/DashboardScene'; import { LibraryVizPanel } from '../scene/LibraryVizPanel'; import { VizPanelLinks } from '../scene/PanelLinks'; -import { PanelRepeaterGridItem } from '../scene/PanelRepeaterGridItem'; import { getPanelIdForLibraryVizPanel, getPanelIdForVizPanel } from './utils'; @@ -39,13 +31,17 @@ function getVizPanels(scene: DashboardScene): VizPanel[] { const panels: VizPanel[] = []; scene.state.body.forEachChild((child) => { - if (child instanceof SceneGridItem) { + 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 SceneGridItem) { + if (child instanceof DashboardGridItem) { if (child.state.body instanceof VizPanel) { panels.push(child.state.body); } @@ -86,22 +82,7 @@ export function getNextPanelId(dashboard: DashboardScene): number { } 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 DashboardGridItem) { const vizPanel = child.state.body; if (vizPanel) { @@ -125,7 +106,7 @@ export function getNextPanelId(dashboard: DashboardScene): number { } for (const rowChild of child.state.children) { - if (rowChild instanceof SceneGridItem) { + if (rowChild instanceof DashboardGridItem) { const vizPanel = rowChild.state.body; if (vizPanel) { @@ -152,8 +133,8 @@ export const getLibraryVizPanelFromVizPanel = (vizPanel: VizPanel): LibraryVizPa return vizPanel.parent; } - if (vizPanel.parent instanceof PanelRepeaterGridItem && vizPanel.parent.state.source instanceof LibraryVizPanel) { - return vizPanel.parent.state.source; + if (vizPanel.parent instanceof DashboardGridItem && vizPanel.parent.state.body instanceof LibraryVizPanel) { + return vizPanel.parent.state.body; } return null; diff --git a/public/app/features/dashboard-scene/utils/test-utils.ts b/public/app/features/dashboard-scene/utils/test-utils.ts index b807efcdc69..ad7b377fb28 100644 --- a/public/app/features/dashboard-scene/utils/test-utils.ts +++ b/public/app/features/dashboard-scene/utils/test-utils.ts @@ -2,7 +2,6 @@ import { DeepPartial, EmbeddedScene, SceneDeactivationHandler, - SceneGridItem, SceneGridLayout, SceneGridRow, SceneObject, @@ -15,9 +14,9 @@ import { DashboardLoaderSrv, setDashboardLoaderSrv } from 'app/features/dashboar import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from 'app/features/variables/constants'; import { DashboardDTO } from 'app/types'; +import { DashboardGridItem, RepeatDirection } from '../scene/DashboardGridItem'; import { LibraryVizPanel } from '../scene/LibraryVizPanel'; import { VizPanelLinks, VizPanelLinksMenu } from '../scene/PanelLinks'; -import { PanelRepeaterGridItem, RepeatDirection } from '../scene/PanelRepeaterGridItem'; import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior'; export function setupLoadDashboardMock(rsp: DeepPartial, spy?: jest.Mock) { @@ -103,13 +102,13 @@ interface SceneOptions { export function buildPanelRepeaterScene(options: SceneOptions, source?: VizPanel | LibraryVizPanel) { const defaults = { usePanelRepeater: true, ...options }; - const repeater = new PanelRepeaterGridItem({ + const withRepeat = new DashboardGridItem({ variableName: 'server', repeatedPanels: [], repeatDirection: options.repeatDirection, maxPerRow: options.maxPerRow, itemHeight: options.itemHeight, - source: + body: source ?? new VizPanel({ title: 'Panel $server', @@ -119,7 +118,7 @@ export function buildPanelRepeaterScene(options: SceneOptions, source?: VizPanel y: options.y || 0, }); - const gridItem = new SceneGridItem({ + const withoutRepeat = new DashboardGridItem({ x: 0, y: 0, width: 10, @@ -131,7 +130,7 @@ export function buildPanelRepeaterScene(options: SceneOptions, source?: VizPanel }), }); - const rowChildren = defaults.usePanelRepeater ? repeater : gridItem; + const rowChildren = defaults.usePanelRepeater ? withRepeat : withoutRepeat; const row = new SceneGridRow({ $behaviors: defaults.useRowRepeater @@ -189,5 +188,5 @@ export function buildPanelRepeaterScene(options: SceneOptions, source?: VizPanel }), }); - return { scene, repeater, row, variable: panelRepeatVariable }; + return { scene, repeater: withRepeat, row, variable: panelRepeatVariable }; } diff --git a/public/app/features/library-panels/state/api.ts b/public/app/features/library-panels/state/api.ts index cf224b52bc7..d5611d98307 100644 --- a/public/app/features/library-panels/state/api.ts +++ b/public/app/features/library-panels/state/api.ts @@ -2,6 +2,7 @@ import { lastValueFrom } from 'rxjs'; import { defaultDashboard } from '@grafana/schema'; import { DashboardModel } from 'app/features/dashboard/state'; +import { DashboardGridItem } from 'app/features/dashboard-scene/scene/DashboardGridItem'; import { LibraryVizPanel } from 'app/features/dashboard-scene/scene/LibraryVizPanel'; import { vizPanelToPanel } from 'app/features/dashboard-scene/serialization/transformSceneToSaveModel'; @@ -138,12 +139,29 @@ export async function getConnectedDashboards(uid: string): Promise { const { uid, folderUID, name, model, version, kind } = libraryVizPanelToSaveModel(libraryPanel); + + console.log('updateLibraryVizPanel', model); const { result } = await getBackendSrv().patch(`/api/library-elements/${uid}`, { folderUID, name,