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 <oscar.kilhed@grafana.com>

* Fix unnecessary library panel changes detection

* Fix test

---------

Co-authored-by: Oscar Kilhed <oscar.kilhed@grafana.com>
This commit is contained in:
Dominik Prokop 2024-03-21 14:38:00 +01:00 committed by GitHub
parent 20bb9d60dc
commit a4ff2abd9d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
46 changed files with 494 additions and 594 deletions

View File

@ -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,

View File

@ -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,

View File

@ -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);

View File

@ -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,

View File

@ -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<InspectJsonTabState> {
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<InspectJsonTabState> {
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;

View File

@ -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,

View File

@ -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<PanelEditorState> {
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<PanelEditorState> {
}
panelRepeater.setState({
source: panelManager.getPanelCloneWithData(),
body: panelManager.getPanelCloneWithData(),
repeatDirection: panelManager.state.repeatDirection,
variableName: panelManager.state.repeat,
maxPerRow: panelManager.state.maxPerRow,

View File

@ -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);

View File

@ -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();

View File

@ -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<VizPanelManagerState> {
*/
public static createFor(sourcePanel: VizPanel) {
let repeatOptions: Pick<VizPanelManagerState, 'repeat' | 'repeatDirection' | 'maxPerRow'> = {};
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<VizPanelManagerState> {
}
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<VizPanelManagerState> {
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<VizPanelManagerState> {
}
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<VizPanelManagerState> {
});
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<VizPanelManagerState> {
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 {

View File

@ -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<DashboardSceneState>) {
$behaviors: [new behaviors.CursorSync({})],
body: new SceneGridLayout({
children: [
new SceneGridItem({
new DashboardGridItem({
key: 'griditem-1',
x: 0,
body: new VizPanel({

View File

@ -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) {

View File

@ -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,

View File

@ -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<AddLibraryPanelWidget
const rowChildren = [];
for (const rowChild of child.state.children) {
if (rowChild instanceof SceneGridItem && rowChild.state.key !== this.parent?.state.key) {
if (rowChild instanceof DashboardGridItem && rowChild.state.key !== this.parent?.state.key) {
rowChildren.push(rowChild);
}
}
@ -86,7 +80,7 @@ export class AddLibraryPanelWidget extends SceneObjectBase<AddLibraryPanelWidget
panelKey: this.state.key,
});
if (this.parent instanceof SceneGridItem) {
if (this.parent instanceof DashboardGridItem) {
this.parent.setState({ body });
}
};

View File

@ -22,14 +22,15 @@ import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN } from 'app/core/constants';
import { getMultiVariableValues } from '../utils/utils';
import { AddLibraryPanelWidget } from './AddLibraryPanelWidget';
import { LibraryVizPanel } from './LibraryVizPanel';
import { repeatPanelMenuBehavior } from './PanelMenuBehavior';
import { DashboardRepeatsProcessedEvent } from './types';
interface PanelRepeaterGridItemState extends SceneGridItemStateLike {
source: VizPanel | LibraryVizPanel;
interface DashboardGridItemState extends SceneGridItemStateLike {
body: VizPanel | LibraryVizPanel | AddLibraryPanelWidget;
repeatedPanels?: VizPanel[];
variableName: string;
variableName?: string;
itemHeight?: number;
repeatDirection?: RepeatDirection;
maxPerRow?: number;
@ -37,33 +38,37 @@ interface PanelRepeaterGridItemState extends SceneGridItemStateLike {
export type RepeatDirection = 'v' | 'h';
export class PanelRepeaterGridItem extends SceneObjectBase<PanelRepeaterGridItemState> implements SceneGridItemLike {
export class DashboardGridItem extends SceneObjectBase<DashboardGridItemState> 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<PanelRepeaterGridItemState> = {};
const stateChange: Partial<DashboardGridItemState> = {};
// Height changed
if (newState.height === prevState.height) {
@ -84,7 +89,10 @@ export class PanelRepeaterGridItem extends SceneObjectBase<PanelRepeaterGridItem
}
private _performRepeat() {
if (this._variableDependency.hasDependencyInLoadingState()) {
if (this.state.body instanceof AddLibraryPanelWidget) {
return;
}
if (!this.state.variableName || this._variableDependency.hasDependencyInLoadingState()) {
return;
}
@ -99,12 +107,11 @@ export class PanelRepeaterGridItem extends SceneObjectBase<PanelRepeaterGridItem
});
if (!(variable instanceof MultiValueVariable)) {
console.error('PanelRepeaterGridItem: Variable is not a MultiValueVariable');
console.error('DashboardGridItem: Variable is not a MultiValueVariable');
return;
}
let panelToRepeat =
this.state.source instanceof LibraryVizPanel ? this.state.source.state.panel! : this.state.source;
let panelToRepeat = this.state.body instanceof LibraryVizPanel ? this.state.body.state.panel! : this.state.body;
const { values, texts } = getMultiVariableValues(variable);
const repeatedPanels: VizPanel[] = [];
@ -128,7 +135,7 @@ export class PanelRepeaterGridItem extends SceneObjectBase<PanelRepeaterGridItem
}
const direction = this.getRepeatDirection();
const stateChange: Partial<PanelRepeaterGridItemState> = { repeatedPanels: repeatedPanels };
const stateChange: Partial<DashboardGridItemState> = { 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<PanelRepeaterGridItem
return 'panel-repeater-grid-item';
}
public static Component = ({ model }: SceneComponentProps<PanelRepeaterGridItem>) => {
const { repeatedPanels, itemHeight } = model.useState();
public isRepeated() {
return this.state.variableName !== undefined;
}
public static Component = ({ model }: SceneComponentProps<DashboardGridItem>) => {
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 <body.Component model={body} key={body.state.key} />;
}
if (body instanceof LibraryVizPanel) {
return <body.Component model={body} key={body.state.key} />;
}
if (body instanceof AddLibraryPanelWidget) {
return <body.Component model={body} key={body.state.key} />;
}
}
if (!repeatedPanels) {
return null;
}

View File

@ -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<DashboardSceneState>) {
$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<DashboardSceneState>) {
$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<DashboardSceneState>) {
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<DashboardSceneState>) {
}),
],
}),
new SceneGridItem({
new DashboardGridItem({
body: new VizPanel({
title: 'Panel B',
key: 'panel-2-clone-1',
@ -916,7 +919,7 @@ function buildTestScene(overrides?: Partial<DashboardSceneState>) {
$data: new SceneQueryRunner({ key: 'data-query-runner2', queries: [{ refId: 'A' }] }),
}),
}),
new SceneGridItem({
new DashboardGridItem({
body: new LibraryVizPanel({
uid: 'uid',
name: 'libraryPanel',

View File

@ -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<DashboardSceneState> {
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<DashboardSceneState> {
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<DashboardSceneState> {
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<DashboardSceneState> {
}),
});
} 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<DashboardSceneState> {
// 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<DashboardSceneState> {
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<DashboardSceneState> {
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<DashboardSceneState> {
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<DashboardSceneState> {
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<DashboardSceneState> {
const panelId = dashboardSceneGraph.getNextPanelId(this);
const newGridItem = new SceneGridItem({
const newGridItem = new DashboardGridItem({
height: NEW_PANEL_HEIGHT,
width: NEW_PANEL_WIDTH,
x: 0,

View File

@ -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',

View File

@ -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);
});
});
});

View File

@ -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<LibraryVizPanelState> {
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<LibraryVizPanelState> {
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}`,

View File

@ -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,

View File

@ -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', () => {

View File

@ -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,
}),
],

View File

@ -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<RowActionsState> {
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;

View File

@ -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<Panel>): { gridItem: SceneGridItem; vizPanel: VizPanel } {
function buildGridItemForTest(saveModel: Partial<Panel>): { 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');
}

View File

@ -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,
});
}

View File

@ -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<Panel>): SceneGridItemLike {
export function buildGridItemFromPanelSchema(panel: Partial<Panel>) {
if (panel.libraryPanel) {
return buildGridItemForLibPanel(new PanelModel(panel))!;
} else if (panel.type === 'add-library-panel') {

View File

@ -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<Pan
if (isSnapshot) {
gridRow.state.children.forEach((c) => {
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) {

View File

@ -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,
});

View File

@ -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(<TestProvider>{component}</TestProvider>);
}
@ -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,
});

View File

@ -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,
});

View File

@ -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,
});

View File

@ -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({

View File

@ -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,
});

View File

@ -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<ShareLibrar
const vizPanel = panelRef.resolve();
if (vizPanel.parent instanceof SceneGridItem || vizPanel.parent instanceof PanelRepeaterGridItem) {
if (vizPanel.parent instanceof DashboardGridItem) {
const dashboardScene = dashboardRef.resolve();
const panelJson = gridItemToPanel(vizPanel.parent);
const panelModel = new PanelModel(panelJson);

View File

@ -6,8 +6,9 @@ import React from 'react';
import { dateTime } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { config, locationService } from '@grafana/runtime';
import { SceneGridItem, SceneGridLayout, SceneTimeRange, VizPanel } from '@grafana/scenes';
import { SceneGridLayout, SceneTimeRange, VizPanel } from '@grafana/scenes';
import { DashboardGridItem } from '../scene/DashboardGridItem';
import { DashboardScene } from '../scene/DashboardScene';
import { ShareLinkTab } from './ShareLinkTab';
@ -105,7 +106,7 @@ function buildAndRenderScenario(options: ScenarioOptions) {
$timeRange: new SceneTimeRange({}),
body: new SceneGridLayout({
children: [
new SceneGridItem({
new DashboardGridItem({
key: 'griditem-1',
x: 0,
y: 0,

View File

@ -8,6 +8,7 @@ import { t } from 'app/core/internationalization';
import { isPublicDashboardsEnabled } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboardUtils';
import { DashboardScene } from '../scene/DashboardScene';
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
import { DashboardInteractions } from '../utils/interactions';
import { getDashboardSceneFor } from '../utils/utils';
@ -56,9 +57,12 @@ export class ShareModal extends SceneObjectBase<ShareModalState> 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() }));
}
}
}

View File

@ -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<string[]> => {
let unsupportedDS = new Set<string>();
@ -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) {

View File

@ -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<Vi
function activateAllRepeaters(layout: SceneObject) {
layout.forEachChild((child) => {
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);
}
});

View File

@ -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',

View File

@ -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;
}

View File

@ -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<DashboardSceneState>) {
}),
body: new SceneGridLayout({
children: [
new SceneGridItem({
new DashboardGridItem({
key: 'griditem-1',
x: 0,
body: new VizPanel({
@ -274,14 +273,14 @@ function buildTestScene(overrides?: Partial<DashboardSceneState>) {
$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<DashboardSceneState>) {
$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<DashboardSceneState>) {
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',

View File

@ -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;

View File

@ -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<DashboardDTO>, 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 };
}

View File

@ -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<DashboardSear
export function libraryVizPanelToSaveModel(libraryPanel: LibraryVizPanel) {
const { panel, uid, name, _loadedPanel } = libraryPanel.state;
const gridItem = libraryPanel.parent;
if (!gridItem || !(gridItem instanceof DashboardGridItem)) {
throw new Error('LibraryVizPanel must be a child of DashboardGridItem');
}
const saveModel = {
uid,
folderUID: _loadedPanel?.folderUid,
name,
version: _loadedPanel?.version || 0,
model: vizPanelToPanel(panel!),
model: vizPanelToPanel(
panel!,
{
x: gridItem.state.x ?? 0,
y: gridItem.state.y ?? 0,
w: gridItem.state.width ?? 0,
h: gridItem.state.height ?? 0,
},
false,
gridItem
),
kind: LibraryElementKind.Panel,
};
return saveModel;
@ -151,6 +169,8 @@ export function libraryVizPanelToSaveModel(libraryPanel: LibraryVizPanel) {
export async function updateLibraryVizPanel(libraryPanel: LibraryVizPanel): Promise<LibraryElementDTO> {
const { uid, folderUID, name, model, version, kind } = libraryVizPanelToSaveModel(libraryPanel);
console.log('updateLibraryVizPanel', model);
const { result } = await getBackendSrv().patch(`/api/library-elements/${uid}`, {
folderUID,
name,