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

View File

@ -1,11 +1,10 @@
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 { KioskMode } from 'app/types';
import { DashboardGridItem } from './DashboardGridItem';
import { DashboardScene } from './DashboardScene';
import { RowRepeaterBehavior } from './RowRepeaterBehavior';
import { DashboardRepeatsProcessedEvent } from './types';
describe('DashboardSceneUrlSync', () => {
@ -109,35 +108,6 @@ describe('DashboardSceneUrlSync', () => {
scene.publishEvent(new DashboardRepeatsProcessedEvent({ source: scene }));
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() {

View File

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

View File

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

View File

@ -1,20 +1,9 @@
import {
SceneComponentProps,
SceneObjectBase,
SceneObjectRef,
SceneObjectState,
VizPanel,
sceneUtils,
SceneVariables,
SceneGridRow,
sceneGraph,
SceneVariableSet,
SceneVariable,
} from '@grafana/scenes';
import { SceneComponentProps, SceneObjectBase, SceneObjectRef, SceneObjectState, VizPanel } from '@grafana/scenes';
import { activateInActiveParents } from '../utils/utils';
interface ViewPanelSceneState extends SceneObjectState {
panelRef: SceneObjectRef<VizPanel>;
body?: VizPanel;
}
export class ViewPanelScene extends SceneObjectBase<ViewPanelSceneState> {
@ -26,47 +15,7 @@ export class ViewPanelScene extends SceneObjectBase<ViewPanelSceneState> {
public _activationHandler() {
const panel = this.state.panelRef.resolve();
const panelState = sceneUtils.cloneSceneObjectState(panel.state, {
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;
return activateInActiveParents(panel);
}
public getUrlKey() {
@ -74,20 +23,9 @@ export class ViewPanelScene extends SceneObjectBase<ViewPanelSceneState> {
}
public static Component = ({ model }: SceneComponentProps<ViewPanelScene>) => {
const { body } = model.useState();
const { panelRef } = model.useState();
const panel = panelRef.resolve();
if (!body) {
return null;
}
return <body.Component model={body} />;
return <panel.Component model={panel} />;
};
}
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 { getDataSourceSrv } from '@grafana/runtime';
import {
CancelActivationHandler,
CustomVariable,
MultiValueVariable,
SceneDataTransformer,
@ -18,7 +19,6 @@ import { DashboardScene } from '../scene/DashboardScene';
import { LibraryPanelBehavior } from '../scene/LibraryPanelBehavior';
import { VizPanelLinks, VizPanelLinksMenu } from '../scene/PanelLinks';
import { panelMenuBehavior } from '../scene/PanelMenuBehavior';
import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior';
import { RowActions } from '../scene/row-actions/RowActions';
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.
* This ensures that the scoped variable for the row is assigned and the panel is initialized with them.
* Activates any inactive parents of the scene object.
* Useful when rendering a scene object out of context of it's parent
* @returns
*/
export function isWithinUnactivatedRepeatRow(panel: VizPanel): boolean {
let row;
export function activateInActiveParents(so: SceneObject): CancelActivationHandler | undefined {
let cancel: CancelActivationHandler | undefined;
let parentCancel: CancelActivationHandler | undefined;
try {
row = sceneGraph.getAncestor(panel, SceneGridRow);
} catch (err) {
return false;
if (so.isActive) {
return cancel;
}
const hasBehavior = !!(row.state.$behaviors && row.state.$behaviors.find((b) => b instanceof RowRepeaterBehavior));
const hasVariables = !!(row.state.$variables && row.state.$variables.state.variables.length > 0);
return hasBehavior && !hasVariables;
if (so.parent) {
parentCancel = activateInActiveParents(so.parent);
}
cancel = so.activate();
return () => {
parentCancel?.();
cancel();
};
}