ViewPanel: Refactoring to simplify logic by rendering the source panel instead of a clone (#93000)

* ViewPanel: Refactoring to simplify logic by rendering the source panel instead of a clone

* Moved util function to utils

* Update
This commit is contained in:
Torkel Ödegaard 2024-09-06 16:00:59 +02:00 committed by GitHub
parent 427af2e7ec
commit 3111f23bf1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 43 additions and 150 deletions

View File

@ -58,7 +58,6 @@ export class DashboardGridItem extends SceneObjectBase<DashboardGridItemState> i
this._prevRepeatValues = undefined; this._prevRepeatValues = undefined;
} }
this._oldBody = this.state.body;
this.performRepeat(); this.performRepeat();
} }
} }
@ -117,7 +116,9 @@ export class DashboardGridItem extends SceneObjectBase<DashboardGridItemState> i
return; return;
} }
this._oldBody = this.state.body;
this._prevRepeatValues = values; this._prevRepeatValues = values;
const panelToRepeat = this.state.body; const panelToRepeat = this.state.body;
const repeatedPanels: VizPanel[] = []; const repeatedPanels: VizPanel[] = [];

View File

@ -1,11 +1,10 @@
import { AppEvents } from '@grafana/data'; import { AppEvents } from '@grafana/data';
import { SceneGridLayout, SceneGridRow, SceneQueryRunner, VizPanel } from '@grafana/scenes'; import { SceneGridLayout, SceneQueryRunner, VizPanel } from '@grafana/scenes';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
import { KioskMode } from 'app/types'; import { KioskMode } from 'app/types';
import { DashboardGridItem } from './DashboardGridItem'; import { DashboardGridItem } from './DashboardGridItem';
import { DashboardScene } from './DashboardScene'; import { DashboardScene } from './DashboardScene';
import { RowRepeaterBehavior } from './RowRepeaterBehavior';
import { DashboardRepeatsProcessedEvent } from './types'; import { DashboardRepeatsProcessedEvent } from './types';
describe('DashboardSceneUrlSync', () => { describe('DashboardSceneUrlSync', () => {
@ -109,35 +108,6 @@ describe('DashboardSceneUrlSync', () => {
scene.publishEvent(new DashboardRepeatsProcessedEvent({ source: scene })); scene.publishEvent(new DashboardRepeatsProcessedEvent({ source: scene }));
expect(scene.state.viewPanelScene?.getUrlKey()).toBe('panel-1-clone-1'); expect(scene.state.viewPanelScene?.getUrlKey()).toBe('panel-1-clone-1');
}); });
it('should subscribe and update view panel if panel is in a repeated row', () => {
const scene = buildTestScene();
// fake adding row panel
const layout = scene.state.body as SceneGridLayout;
layout.setState({
children: [
new SceneGridRow({
$behaviors: [new RowRepeaterBehavior({ variableName: 'test' })],
children: [
new VizPanel({
title: 'Panel A',
key: 'panel-1',
pluginId: 'table',
}),
],
}),
],
});
scene.urlSync?.updateFromUrl({ viewPanel: 'panel-1' });
expect(scene.state.viewPanelScene?.getUrlKey()).toBeUndefined();
// Verify it subscribes to DashboardRepeatsProcessedEvent
scene.publishEvent(new DashboardRepeatsProcessedEvent({ source: scene }));
expect(scene.state.viewPanelScene?.getUrlKey()).toBe('panel-1');
});
}); });
function buildTestScene() { function buildTestScene() {

View File

@ -18,13 +18,7 @@ import { buildPanelEditScene } from '../panel-edit/PanelEditor';
import { createDashboardEditViewFor } from '../settings/utils'; import { createDashboardEditViewFor } from '../settings/utils';
import { ShareDrawer } from '../sharing/ShareDrawer/ShareDrawer'; import { ShareDrawer } from '../sharing/ShareDrawer/ShareDrawer';
import { ShareModal } from '../sharing/ShareModal'; import { ShareModal } from '../sharing/ShareModal';
import { import { findVizPanelByKey, getDashboardSceneFor, getLibraryPanelBehavior, isPanelClone } from '../utils/utils';
findVizPanelByKey,
getDashboardSceneFor,
getLibraryPanelBehavior,
isPanelClone,
isWithinUnactivatedRepeatRow,
} from '../utils/utils';
import { DashboardScene, DashboardSceneState } from './DashboardScene'; import { DashboardScene, DashboardSceneState } from './DashboardScene';
import { LibraryPanelBehavior } from './LibraryPanelBehavior'; import { LibraryPanelBehavior } from './LibraryPanelBehavior';
@ -108,11 +102,6 @@ export class DashboardSceneUrlSync implements SceneObjectUrlSyncHandler {
return; return;
} }
if (isWithinUnactivatedRepeatRow(panel)) {
this._handleViewRepeatClone(values.viewPanel);
return;
}
update.viewPanelScene = new ViewPanelScene({ panelRef: panel.getRef() }); update.viewPanelScene = new ViewPanelScene({ panelRef: panel.getRef() });
} else if (viewPanelScene && values.viewPanel === null) { } else if (viewPanelScene && values.viewPanel === null) {
update.viewPanelScene = undefined; update.viewPanelScene = undefined;
@ -236,10 +225,6 @@ class ResolveInspectPanelByKey extends SceneObjectBase<ResolveInspectPanelByKeyS
panel = dashboard.state.editPanel.state.vizManager.state.panel; panel = dashboard.state.editPanel.state.vizManager.state.panel;
} }
if (dashboard.state.viewPanelScene && dashboard.state.viewPanelScene.state.body) {
panel = dashboard.state.viewPanelScene.state.body;
}
if (panel) { if (panel) {
parent.setState({ panelRef: panel.getRef() }); parent.setState({ panelRef: panel.getRef() });
} }

View File

@ -4,19 +4,19 @@ import { DashboardGridItem } from './DashboardGridItem';
import { DashboardScene } from './DashboardScene'; import { DashboardScene } from './DashboardScene';
import { ViewPanelScene } from './ViewPanelScene'; import { ViewPanelScene } from './ViewPanelScene';
jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'),
getPluginImportUtils: () => ({
getPanelPluginFromCache: jest.fn(() => {}),
}),
}));
describe('ViewPanelScene', () => { describe('ViewPanelScene', () => {
it('Should build scene on activate', () => { it('Should activate panel parents', () => {
const { viewPanelScene } = buildScene(); const { viewPanelScene, dashboard } = buildScene();
viewPanelScene.activate(); viewPanelScene.activate();
expect(viewPanelScene.state.body).toBeDefined(); expect(viewPanelScene.state.panelRef.resolve().isActive).toBe(true);
}); expect(dashboard.state.body.isActive).toBe(true);
it('Should look copy row variable scope', () => {
const { viewPanelScene } = buildScene({ rowVariables: true, panelVariables: true });
viewPanelScene.activate();
const variables = viewPanelScene.state.body?.state.$variables;
expect(variables?.state.variables.length).toBe(2);
}); });
}); });
@ -29,11 +29,6 @@ function buildScene(options?: SceneOptions) {
// builds a scene how it looks like after row and panel repeats are processed // builds a scene how it looks like after row and panel repeats are processed
const panel = new VizPanel({ const panel = new VizPanel({
key: 'panel-22', key: 'panel-22',
$variables: options?.panelVariables
? new SceneVariableSet({
variables: [new LocalValueVariable({ value: 'panel-var-value' })],
})
: undefined,
}); });
const dashboard = new DashboardScene({ const dashboard = new DashboardScene({
@ -43,11 +38,9 @@ function buildScene(options?: SceneOptions) {
x: 0, x: 0,
y: 10, y: 10,
width: 24, width: 24,
$variables: options?.rowVariables $variables: new SceneVariableSet({
? new SceneVariableSet({ variables: [new LocalValueVariable({ value: 'row-var-value' })],
variables: [new LocalValueVariable({ value: 'row-var-value' })], }),
})
: undefined,
height: 1, height: 1,
children: [ children: [
new DashboardGridItem({ new DashboardGridItem({

View File

@ -1,20 +1,9 @@
import { import { SceneComponentProps, SceneObjectBase, SceneObjectRef, SceneObjectState, VizPanel } from '@grafana/scenes';
SceneComponentProps,
SceneObjectBase, import { activateInActiveParents } from '../utils/utils';
SceneObjectRef,
SceneObjectState,
VizPanel,
sceneUtils,
SceneVariables,
SceneGridRow,
sceneGraph,
SceneVariableSet,
SceneVariable,
} from '@grafana/scenes';
interface ViewPanelSceneState extends SceneObjectState { interface ViewPanelSceneState extends SceneObjectState {
panelRef: SceneObjectRef<VizPanel>; panelRef: SceneObjectRef<VizPanel>;
body?: VizPanel;
} }
export class ViewPanelScene extends SceneObjectBase<ViewPanelSceneState> { export class ViewPanelScene extends SceneObjectBase<ViewPanelSceneState> {
@ -26,47 +15,7 @@ export class ViewPanelScene extends SceneObjectBase<ViewPanelSceneState> {
public _activationHandler() { public _activationHandler() {
const panel = this.state.panelRef.resolve(); const panel = this.state.panelRef.resolve();
const panelState = sceneUtils.cloneSceneObjectState(panel.state, { return activateInActiveParents(panel);
key: panel.state.key + '-view',
$variables: this.getScopedVariables(panel),
});
const body = new VizPanel(panelState);
this.setState({ body });
return () => {
// Make sure we preserve data state
if (body.state.$data) {
panel.setState({ $data: body.state.$data.clone() });
}
};
}
// In case the panel is inside a repeated row
private getScopedVariables(panel: VizPanel): SceneVariables | undefined {
const row = tryGetParentRow(panel);
const variables: SceneVariable[] = [];
// Because we are rendering the panel outside it's potential row context we need to copy the row (scoped) varables
if (row && row.state.$variables) {
for (const variable of row.state.$variables.state.variables) {
variables.push(variable.clone());
}
}
// If we have local scoped panel variables we need to add the row variables to it
if (panel.state.$variables) {
for (const variable of panel.state.$variables.state.variables) {
variables.push(variable.clone());
}
}
if (variables.length > 0) {
return new SceneVariableSet({ variables });
}
return undefined;
} }
public getUrlKey() { public getUrlKey() {
@ -74,20 +23,9 @@ export class ViewPanelScene extends SceneObjectBase<ViewPanelSceneState> {
} }
public static Component = ({ model }: SceneComponentProps<ViewPanelScene>) => { public static Component = ({ model }: SceneComponentProps<ViewPanelScene>) => {
const { body } = model.useState(); const { panelRef } = model.useState();
const panel = panelRef.resolve();
if (!body) { return <panel.Component model={panel} />;
return null;
}
return <body.Component model={body} />;
}; };
} }
function tryGetParentRow(panel: VizPanel): SceneGridRow | undefined {
try {
return sceneGraph.getAncestor(panel, SceneGridRow);
} catch {
return undefined;
}
}

View File

@ -1,6 +1,7 @@
import { getDataSourceRef, IntervalVariableModel } from '@grafana/data'; import { getDataSourceRef, IntervalVariableModel } from '@grafana/data';
import { getDataSourceSrv } from '@grafana/runtime'; import { getDataSourceSrv } from '@grafana/runtime';
import { import {
CancelActivationHandler,
CustomVariable, CustomVariable,
MultiValueVariable, MultiValueVariable,
SceneDataTransformer, SceneDataTransformer,
@ -18,7 +19,6 @@ import { DashboardScene } from '../scene/DashboardScene';
import { LibraryPanelBehavior } from '../scene/LibraryPanelBehavior'; import { LibraryPanelBehavior } from '../scene/LibraryPanelBehavior';
import { VizPanelLinks, VizPanelLinksMenu } from '../scene/PanelLinks'; import { VizPanelLinks, VizPanelLinksMenu } from '../scene/PanelLinks';
import { panelMenuBehavior } from '../scene/PanelMenuBehavior'; import { panelMenuBehavior } from '../scene/PanelMenuBehavior';
import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior';
import { RowActions } from '../scene/row-actions/RowActions'; import { RowActions } from '../scene/row-actions/RowActions';
import { dashboardSceneGraph } from './dashboardSceneGraph'; import { dashboardSceneGraph } from './dashboardSceneGraph';
@ -267,20 +267,26 @@ export function getLibraryPanelBehavior(vizPanel: VizPanel): LibraryPanelBehavio
} }
/** /**
* If the panel is within a repeated row, it must wait until the row resolves the variables. * Activates any inactive parents of the scene object.
* This ensures that the scoped variable for the row is assigned and the panel is initialized with them. * Useful when rendering a scene object out of context of it's parent
* @returns
*/ */
export function isWithinUnactivatedRepeatRow(panel: VizPanel): boolean { export function activateInActiveParents(so: SceneObject): CancelActivationHandler | undefined {
let row; let cancel: CancelActivationHandler | undefined;
let parentCancel: CancelActivationHandler | undefined;
try { if (so.isActive) {
row = sceneGraph.getAncestor(panel, SceneGridRow); return cancel;
} catch (err) {
return false;
} }
const hasBehavior = !!(row.state.$behaviors && row.state.$behaviors.find((b) => b instanceof RowRepeaterBehavior)); if (so.parent) {
const hasVariables = !!(row.state.$variables && row.state.$variables.state.variables.length > 0); parentCancel = activateInActiveParents(so.parent);
}
return hasBehavior && !hasVariables; cancel = so.activate();
return () => {
parentCancel?.();
cancel();
};
} }