diff --git a/e2e/various-suite/solo-route.spec.ts b/e2e/various-suite/solo-route.spec.ts index c508ace3aef1..cfea359cff20 100644 --- a/e2e/various-suite/solo-route.spec.ts +++ b/e2e/various-suite/solo-route.spec.ts @@ -35,7 +35,7 @@ describe('Solo Route', () => { it('Can view solo in repeaterd row and panel in scenes', () => { // open Panel Tests - Graph NG e2e.pages.SoloPanel.visit( - 'Repeating-rows-uid/repeating-rows?orgId=1&var-server=A&var-server=B&var-server=D&var-pod=1&var-pod=2&var-pod=3&panelId=panel-2-row-2-clone-2&__feature.dashboardSceneSolo=true' + 'Repeating-rows-uid/repeating-rows?orgId=1&var-server=A&var-server=B&var-server=D&var-pod=1&var-pod=2&var-pod=3&panelId=panel-2-clone-D-clone-2&__feature.dashboardSceneSolo=true' ); e2e.components.Panels.Panel.title('server = D, pod = Sod').should('exist'); diff --git a/package.json b/package.json index f701aaeeff6f..fcfbff7e3c76 100644 --- a/package.json +++ b/package.json @@ -258,7 +258,7 @@ "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", "@grafana/saga-icons": "workspace:*", - "@grafana/scenes": "^4.14.0", + "@grafana/scenes": "^4.21.0", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx b/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx index 15bc9207b278..501b415d601f 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx @@ -113,16 +113,17 @@ export class PanelEditor extends SceneObjectBase { return; } - if (gridItem instanceof DashboardGridItem) { - this.handleRepeatOptionChanges(gridItem); - } else { + if (!(gridItem instanceof DashboardGridItem)) { console.error('Unsupported scene object type'); + return; } + + this.commitChangesToSource(gridItem); } - private handleRepeatOptionChanges(panelRepeater: DashboardGridItem) { - let width = panelRepeater.state.width ?? 1; - let height = panelRepeater.state.height; + private commitChangesToSource(gridItem: DashboardGridItem) { + let width = gridItem.state.width ?? 1; + let height = gridItem.state.height; const panelManager = this.state.vizManager; const horizontalToVertical = @@ -130,12 +131,12 @@ export class PanelEditor extends SceneObjectBase { const verticalToHorizontal = this._initialRepeatOptions.repeatDirection === 'v' && panelManager.state.repeatDirection === 'h'; if (horizontalToVertical) { - width = Math.floor(width / (panelRepeater.state.maxPerRow ?? 1)); + width = Math.floor(width / (gridItem.state.maxPerRow ?? 1)); } else if (verticalToHorizontal) { width = 24; } - panelRepeater.setState({ + gridItem.setState({ body: panelManager.state.panel.clone(), repeatDirection: panelManager.state.repeatDirection, variableName: panelManager.state.repeat, diff --git a/public/app/features/dashboard-scene/panel-edit/VizPanelManager.test.tsx b/public/app/features/dashboard-scene/panel-edit/VizPanelManager.test.tsx index 590c519956ae..844da6d0e232 100644 --- a/public/app/features/dashboard-scene/panel-edit/VizPanelManager.test.tsx +++ b/public/app/features/dashboard-scene/panel-edit/VizPanelManager.test.tsx @@ -4,7 +4,14 @@ import { DataQueryRequest, DataSourceApi, DataSourceInstanceSettings, LoadingSta import { calculateFieldTransformer } from '@grafana/data/src/transformations/transformers/calculateField'; import { mockTransformationsRegistry } from '@grafana/data/src/utils/tests/mockTransformationsRegistry'; import { config, locationService } from '@grafana/runtime'; -import { SceneQueryRunner, VizPanel } from '@grafana/scenes'; +import { + LocalValueVariable, + SceneGridRow, + SceneQueryRunner, + SceneVariableSet, + VizPanel, + sceneGraph, +} 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'; @@ -813,6 +820,25 @@ describe('VizPanelManager', () => { expect(vizPanelManager.state.datasource).toEqual(ds1Mock); expect(vizPanelManager.state.dsSettings).toEqual(instance1SettingsMock); }); + + describe('Given a panel inside repeated row', () => { + it('Should include row variable scope', () => { + const { panel } = setupTest('panel-9'); + + const row = panel.parent?.parent; + if (!(row instanceof SceneGridRow)) { + throw new Error('Did not find parent row'); + } + + row.setState({ + $variables: new SceneVariableSet({ variables: [new LocalValueVariable({ name: 'hello', value: 'A' })] }), + }); + + const editor = buildPanelEditScene(panel); + const variable = sceneGraph.lookupVariable('hello', editor.state.vizManager); + expect(variable?.getValue()).toBe('A'); + }); + }); }); const setupTest = (panelId: string) => { diff --git a/public/app/features/dashboard-scene/panel-edit/VizPanelManager.tsx b/public/app/features/dashboard-scene/panel-edit/VizPanelManager.tsx index 2892dcd16631..530bce2a9eb5 100644 --- a/public/app/features/dashboard-scene/panel-edit/VizPanelManager.tsx +++ b/public/app/features/dashboard-scene/panel-edit/VizPanelManager.tsx @@ -23,6 +23,7 @@ import { SceneObjectState, SceneObjectStateChangedEvent, SceneQueryRunner, + SceneVariables, VizPanel, sceneUtils, } from '@grafana/scenes'; @@ -91,7 +92,14 @@ export class VizPanelManager extends SceneObjectBase { const { variableName: repeat, repeatDirection, maxPerRow } = gridItem.state; repeatOptions = { repeat, repeatDirection, maxPerRow }; + let variables: SceneVariables | undefined; + + if (gridItem.parent?.state.$variables) { + variables = gridItem.parent.state.$variables.clone(); + } + return new VizPanelManager({ + $variables: variables, panel: sourcePanel.clone(), sourcePanel: sourcePanel.getRef(), ...repeatOptions, diff --git a/public/app/features/dashboard-scene/panel-edit/testfiles/testDashboard.ts b/public/app/features/dashboard-scene/panel-edit/testfiles/testDashboard.ts index c583b089e457..a439bb2541df 100644 --- a/public/app/features/dashboard-scene/panel-edit/testfiles/testDashboard.ts +++ b/public/app/features/dashboard-scene/panel-edit/testfiles/testDashboard.ts @@ -591,6 +591,18 @@ export const panelWithQueriesAndMixedDatasource = { type: 'timeseries', }; +const row = { + id: 8, + type: 'row', + gridPos: { h: 1, w: 24, x: 0, y: 20 }, +}; + +const rowChild = { + id: 9, + type: 'timeseries', + gridPos: { h: 2, w: 24, x: 0, y: 21 }, +}; + export const testDashboard = { annotations: { list: [ @@ -622,6 +634,8 @@ export const testDashboard = { panelWithNoDataSource, panelWithDataSourceNotFound, panelWithQueriesAndMixedDatasource, + row, + rowChild, ], refresh: '', schemaVersion: 39, diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.tsx index c267b3cc5b57..6acd2fef7c3a 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.tsx @@ -64,6 +64,7 @@ import { DashboardGridItem } from './DashboardGridItem'; import { DashboardSceneRenderer } from './DashboardSceneRenderer'; import { DashboardSceneUrlSync } from './DashboardSceneUrlSync'; import { LibraryVizPanel } from './LibraryVizPanel'; +import { RowRepeaterBehavior } from './RowRepeaterBehavior'; import { ScopesScene } from './ScopesScene'; import { ViewPanelScene } from './ViewPanelScene'; import { setupKeyboardShortcuts } from './keyboardShortcuts'; @@ -127,7 +128,7 @@ export class DashboardScene extends SceneObjectBase { /** * Get notified when variables change */ - protected _variableDependency = new DashboardVariableDependency(); + protected _variableDependency = new DashboardVariableDependency(this); /** * State before editing started @@ -847,6 +848,8 @@ export class DashboardScene extends SceneObjectBase { export class DashboardVariableDependency implements SceneVariableDependencyConfigLike { private _emptySet = new Set(); + public constructor(private _dashboard: DashboardScene) {} + getNames(): Set { return this._emptySet; } @@ -860,5 +863,28 @@ export class DashboardVariableDependency implements SceneVariableDependencyConfi // Temp solution for some core panels (like dashlist) to know that variables have changed appEvents.publish(new VariablesChanged({ refreshAll: true, panelIds: [] })); } + + /** + * Propagate variable changes to repeat row behavior as it does not get it when it's nested under local value + * The first repeated row has the row repeater behavior but it also has a local SceneVariableSet with a local variable value + */ + const layout = this._dashboard.state.body; + if (!(layout instanceof SceneGridLayout)) { + return; + } + + for (const child of layout.state.children) { + if (!(child instanceof SceneGridRow) || !child.state.$behaviors) { + continue; + } + + for (const behavior of child.state.$behaviors) { + if (behavior instanceof RowRepeaterBehavior) { + if (behavior.isWaitingForVariables || (behavior.state.variableName === variable.state.name && hasChanged)) { + behavior.performRepeat(); + } + } + } + } } } diff --git a/public/app/features/dashboard-scene/scene/RowRepeaterBehavior.test.tsx b/public/app/features/dashboard-scene/scene/RowRepeaterBehavior.test.tsx index 614139d89339..7b13b67d9b42 100644 --- a/public/app/features/dashboard-scene/scene/RowRepeaterBehavior.test.tsx +++ b/public/app/features/dashboard-scene/scene/RowRepeaterBehavior.test.tsx @@ -1,5 +1,4 @@ import { - EmbeddedScene, SceneCanvasText, SceneGridLayout, SceneGridRow, @@ -13,14 +12,21 @@ import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from 'app/features/variables/co import { activateFullSceneTree } from '../utils/test-utils'; import { RepeatDirection } from './DashboardGridItem'; +import { DashboardScene } from './DashboardScene'; import { RowRepeaterBehavior } from './RowRepeaterBehavior'; +import { RowActions } from './row-actions/RowActions'; describe('RowRepeaterBehavior', () => { describe('Given scene with variable with 5 values', () => { - let scene: EmbeddedScene, grid: SceneGridLayout; + let scene: DashboardScene, grid: SceneGridLayout, repeatBehavior: RowRepeaterBehavior; + let gridStateUpdates: unknown[]; beforeEach(async () => { - ({ scene, grid } = buildScene({ variableQueryTime: 0 })); + ({ scene, grid, repeatBehavior } = buildScene({ variableQueryTime: 0 })); + + gridStateUpdates = []; + grid.subscribeToState((state) => gridStateUpdates.push(state)); + activateFullSceneTree(scene); await new Promise((r) => setTimeout(r, 1)); }); @@ -28,17 +34,20 @@ describe('RowRepeaterBehavior', () => { it('Should repeat row', () => { // Verify that panel above row remains expect(grid.state.children[0]).toBeInstanceOf(SceneGridItem); + // Verify that first row still has repeat behavior const row1 = grid.state.children[1] as SceneGridRow; expect(row1.state.$behaviors?.[0]).toBeInstanceOf(RowRepeaterBehavior); - expect(row1.state.$variables!.state.variables[0].getValue()).toBe('1'); + expect(row1.state.$variables!.state.variables[0].getValue()).toBe('A1'); + expect(row1.state.actions).toBeDefined(); const row2 = grid.state.children[2] as SceneGridRow; expect(row2.state.$variables!.state.variables[0].getValueText?.()).toBe('B'); + expect(row2.state.actions).toBeUndefined(); // Should give repeated panels unique keys const gridItem = row2.state.children[0] as SceneGridItem; - expect(gridItem.state.body?.state.key).toBe('canvas-1-row-1'); + expect(gridItem.state.body?.state.key).toBe('canvas-1-clone-B1'); }); it('Should push row at the bottom down', () => { @@ -66,24 +75,34 @@ describe('RowRepeaterBehavior', () => { it('Should handle second repeat cycle and update remove old repeats', async () => { // trigger another repeat cycle by changing the variable const variable = scene.state.$variables!.state.variables[0] as TestVariable; - variable.changeValueTo(['2', '3']); + variable.changeValueTo(['B1', 'C1']); await new Promise((r) => setTimeout(r, 1)); // should now only have 2 repeated rows (and the panel above + the row at the bottom) expect(grid.state.children.length).toBe(4); }); + + it('Should ignore repeat process if variable values are the same', async () => { + // trigger another repeat cycle by changing the variable + repeatBehavior.performRepeat(); + + await new Promise((r) => setTimeout(r, 1)); + + expect(gridStateUpdates.length).toBe(1); + }); }); describe('Given scene empty row', () => { - let scene: EmbeddedScene; + let scene: DashboardScene; let grid: SceneGridLayout; - let repeatBehavior: RowRepeaterBehavior; + let rowToRepeat: SceneGridRow; beforeEach(async () => { - ({ scene, grid, repeatBehavior } = buildScene({ variableQueryTime: 0 })); + ({ scene, grid, rowToRepeat } = buildScene({ variableQueryTime: 0 })); + + rowToRepeat.setState({ children: [] }); - repeatBehavior.setState({ sources: [] }); activateFullSceneTree(scene); await new Promise((r) => setTimeout(r, 1)); }); @@ -108,21 +127,7 @@ interface SceneOptions { } function buildScene(options: SceneOptions) { - const repeatBehavior = new RowRepeaterBehavior({ - variableName: 'server', - sources: [ - new SceneGridItem({ - x: 0, - y: 11, - width: 24, - height: 5, - body: new SceneCanvasText({ - key: 'canvas-1', - text: 'Panel inside repeated row, server = $server', - }), - }), - ], - }); + const repeatBehavior = new RowRepeaterBehavior({ variableName: 'server' }); const grid = new SceneGridLayout({ children: [ @@ -140,7 +145,20 @@ function buildScene(options: SceneOptions) { y: 10, width: 24, height: 1, + actions: new RowActions({}), $behaviors: [repeatBehavior], + children: [ + new SceneGridItem({ + x: 0, + y: 11, + width: 24, + height: 5, + body: new SceneCanvasText({ + key: 'canvas-1', + text: 'Panel inside repeated row, server = $server', + }), + }), + ], }), new SceneGridRow({ x: 0, @@ -148,6 +166,7 @@ function buildScene(options: SceneOptions) { width: 24, height: 5, title: 'Row at the bottom', + children: [ new SceneGridItem({ key: 'griditem-2', @@ -172,7 +191,7 @@ function buildScene(options: SceneOptions) { ], }); - const scene = new EmbeddedScene({ + const scene = new DashboardScene({ $timeRange: new SceneTimeRange({ from: 'now-6h', to: 'now' }), $variables: new SceneVariableSet({ variables: [ @@ -185,11 +204,11 @@ function buildScene(options: SceneOptions) { includeAll: true, delayMs: options.variableQueryTime, optionsToReturn: [ - { label: 'A', value: '1' }, - { label: 'B', value: '2' }, - { label: 'C', value: '3' }, - { label: 'D', value: '4' }, - { label: 'E', value: '5' }, + { label: 'A', value: 'A1' }, + { label: 'B', value: 'B1' }, + { label: 'C', value: 'C1' }, + { label: 'D', value: 'D1' }, + { label: 'E', value: 'E1' }, ], }), ], @@ -197,5 +216,7 @@ function buildScene(options: SceneOptions) { body: grid, }); - return { scene, grid, repeatBehavior }; + const rowToRepeat = repeatBehavior.parent as SceneGridRow; + + return { scene, grid, repeatBehavior, rowToRepeat }; } diff --git a/public/app/features/dashboard-scene/scene/RowRepeaterBehavior.ts b/public/app/features/dashboard-scene/scene/RowRepeaterBehavior.ts index 609e983be38e..11970f887b74 100644 --- a/public/app/features/dashboard-scene/scene/RowRepeaterBehavior.ts +++ b/public/app/features/dashboard-scene/scene/RowRepeaterBehavior.ts @@ -1,3 +1,5 @@ +import { isEqual } from 'lodash'; + import { LocalValueVariable, MultiValueVariable, @@ -18,7 +20,6 @@ import { DashboardRepeatsProcessedEvent } from './types'; interface RowRepeaterBehaviorState extends SceneObjectState { variableName: string; - sources: SceneGridItemLike[]; } /** @@ -28,9 +29,12 @@ interface RowRepeaterBehaviorState extends SceneObjectState { export class RowRepeaterBehavior extends SceneObjectBase { protected _variableDependency = new VariableDependencyConfig(this, { variableNames: [this.state.variableName], - onVariableUpdateCompleted: this._onVariableUpdateCompleted.bind(this), + onVariableUpdateCompleted: () => {}, }); + public isWaitingForVariables = false; + private _prevRepeatValues?: VariableValueSingle[]; + public constructor(state: RowRepeaterBehaviorState) { super(state); @@ -38,15 +42,31 @@ export class RowRepeaterBehavior extends SceneObjectBase 0 ? `${source.state.key}-clone-${localValue}` : source.state.key; + const itemClone = source.clone({ key: itemKey, y: itemY }); //Make sure all the child scene objects have unique keys - ensureUniqueKeys(itemClone, index); + if (index > 0) { + ensureUniqueKeys(itemClone, localValue); + } children.push(itemClone); @@ -104,7 +123,7 @@ export class RowRepeaterBehavior extends SceneObjectBase b !== this), $variables: undefined }); + } } function getRowContentHeight(panels: SceneGridItemLike[]): number { @@ -207,9 +238,9 @@ function getLayoutChildrenFilterOutRepeatClones(layout: SceneGridLayout, rowToRe }); } -function ensureUniqueKeys(item: SceneGridItemLike, rowIndex: number) { +function ensureUniqueKeys(item: SceneGridItemLike, localValue: VariableValueSingle) { item.forEachChild((child) => { - child.setState({ key: `${child.state.key}-row-${rowIndex}` }); - ensureUniqueKeys(child, rowIndex); + child.setState({ key: `${child.state.key}-clone-${localValue}` }); + ensureUniqueKeys(child, localValue); }); } diff --git a/public/app/features/dashboard-scene/scene/row-actions/RowActions.tsx b/public/app/features/dashboard-scene/scene/row-actions/RowActions.tsx index cab936521170..bc38c83bde84 100644 --- a/public/app/features/dashboard-scene/scene/row-actions/RowActions.tsx +++ b/public/app/features/dashboard-scene/scene/row-actions/RowActions.tsx @@ -2,14 +2,7 @@ import { css } from '@emotion/css'; import React from 'react'; import { GrafanaTheme2 } from '@grafana/data'; -import { - SceneComponentProps, - SceneGridLayout, - SceneGridRow, - SceneObjectBase, - SceneObjectState, - VizPanel, -} from '@grafana/scenes'; +import { SceneComponentProps, SceneGridRow, SceneObjectBase, SceneObjectState, VizPanel } from '@grafana/scenes'; import { Icon, TextLink, useStyles2 } from '@grafana/ui'; import appEvents from 'app/core/app_events'; import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard'; @@ -25,31 +18,6 @@ import { RowOptionsButton } from './RowOptionsButton'; export interface RowActionsState extends SceneObjectState {} export class RowActions extends SceneObjectBase { - private updateLayout(rowClone: SceneGridRow): void { - const row = this.getParent(); - - const layout = this.getDashboard().state.body; - - if (!(layout instanceof SceneGridLayout)) { - throw new Error('Layout is not a SceneGridLayout'); - } - - // remove the repeated rows - const children = layout.state.children.filter((child) => !child.state.key?.startsWith(`${row.state.key}-clone-`)); - - // get the index to replace later - const index = children.indexOf(row); - - if (index === -1) { - throw new Error('Parent row not found in layout children'); - } - - // replace the row with the clone - layout.setState({ - children: [...children.slice(0, index), rowClone, ...children.slice(index + 1)], - }); - } - public getParent(): SceneGridRow { if (!(this.parent instanceof SceneGridRow)) { throw new Error('RowActions must have a SceneGridRow parent'); @@ -64,39 +32,26 @@ export class RowActions extends SceneObjectBase { public onUpdate = (title: string, repeat?: string | null): void => { const row = this.getParent(); + let repeatBehavior: RowRepeaterBehavior | undefined; - // return early if there is no repeat - if (!repeat) { - const clone = row.clone(); - - // remove the row repeater behaviour, leave the rest - clone.setState({ - title, - $behaviors: row.state.$behaviors?.filter((b) => !(b instanceof RowRepeaterBehavior)) ?? [], - }); - - this.updateLayout(clone); - - return; + if (row.state.$behaviors) { + for (let b of row.state.$behaviors) { + if (b instanceof RowRepeaterBehavior) { + repeatBehavior = b; + } + } } - const children = row.state.children.map((child) => child.clone()); - - const newBehaviour = new RowRepeaterBehavior({ - variableName: repeat, - sources: children, - }); - - // get rest of behaviors except the old row repeater, if any, and push new one - const behaviors = row.state.$behaviors?.filter((b) => !(b instanceof RowRepeaterBehavior)) ?? []; - behaviors.push(newBehaviour); - - row.setState({ - title, - $behaviors: behaviors, - }); + if (repeat && !repeatBehavior) { + const repeatBehavior = new RowRepeaterBehavior({ variableName: repeat }); + row.setState({ $behaviors: [...(row.state.$behaviors ?? []), repeatBehavior] }); + } else if (repeatBehavior) { + repeatBehavior.removeBehavior(); + } - newBehaviour.activate(); + if (title !== row.state.title) { + row.setState({ title }); + } }; public onDelete = () => { diff --git a/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModel.test.ts.snap b/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModel.test.ts.snap index 11dc59ba56e3..48a27c18ea23 100644 --- a/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModel.test.ts.snap +++ b/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModel.test.ts.snap @@ -141,6 +141,101 @@ exports[`transformSceneToSaveModel Given a scene with rows Should transform back "title": "Row for server $server", "type": "row", }, + { + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A", + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic", + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false, + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear", + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none", + }, + "thresholdsStyle": { + "mode": "off", + }, + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + }, + { + "color": "red", + "value": 80, + }, + ], + }, + }, + "overrides": [], + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 4, + }, + "id": 2, + "maxPerRow": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true, + }, + "tooltip": { + "mode": "single", + "sort": "none", + }, + }, + "repeat": "pod", + "repeatDirection": "h", + "targets": [ + { + "alias": "server = $server, pod id = $pod ", + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A", + }, + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 1, + }, + ], + "title": "server = $server, pod = $pod", + "type": "timeseries", + }, { "collapsed": true, "gridPos": { diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts index e7c7b83aae37..ad33a4299dd1 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts @@ -176,13 +176,7 @@ function createRowFromPanelModel(row: PanelModel, content: SceneGridItemLike[]): if (row.repeat) { // For repeated rows the children are stored in the behavior - children = []; - behaviors = [ - new RowRepeaterBehavior({ - variableName: row.repeat, - sources: content, - }), - ]; + behaviors = [new RowRepeaterBehavior({ variableName: row.repeat })]; } return new SceneGridRow({ diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts index 0f506c11c4bc..d20e8c052866 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts @@ -226,7 +226,7 @@ describe('transformSceneToSaveModel', () => { const rowRepeater = rowWithRepeat.state.$behaviors![0] as RowRepeaterBehavior; // trigger row repeater - rowRepeater.variableDependency?.variableUpdateCompleted(variable, true); + rowRepeater.performRepeat(); // Make sure the repeated rows have been added to runtime scene model expect(grid.state.children.length).toBe(5); diff --git a/public/app/features/dashboard-scene/utils/test-utils.ts b/public/app/features/dashboard-scene/utils/test-utils.ts index 2f5a3f24ef0f..235dae85035f 100644 --- a/public/app/features/dashboard-scene/utils/test-utils.ts +++ b/public/app/features/dashboard-scene/utils/test-utils.ts @@ -68,6 +68,11 @@ export function mockResizeObserver() { export function activateFullSceneTree(scene: SceneObject): SceneDeactivationHandler { const deactivationHandlers: SceneDeactivationHandler[] = []; + // Important that variables are activated before other children + if (scene.state.$variables) { + deactivationHandlers.push(activateFullSceneTree(scene.state.$variables)); + } + scene.forEachChild((child) => { // For query runners which by default use the container width for maxDataPoints calculation we are setting a width. // In real life this is done by the React component when VizPanel is rendered. @@ -130,18 +135,9 @@ export function buildPanelRepeaterScene(options: SceneOptions, source?: VizPanel }), }); - const rowChildren = defaults.usePanelRepeater ? withRepeat : withoutRepeat; - const row = new SceneGridRow({ - $behaviors: defaults.useRowRepeater - ? [ - new RowRepeaterBehavior({ - variableName: 'handler', - sources: [rowChildren], - }), - ] - : [], - children: defaults.useRowRepeater ? [] : [rowChildren], + $behaviors: defaults.useRowRepeater ? [new RowRepeaterBehavior({ variableName: 'handler' })] : [], + children: [defaults.usePanelRepeater ? withRepeat : withoutRepeat], }); const panelRepeatVariable = new TestVariable({ diff --git a/public/app/features/dashboard-scene/utils/utils.ts b/public/app/features/dashboard-scene/utils/utils.ts index 7a2bdf0fa023..16f6eac8b905 100644 --- a/public/app/features/dashboard-scene/utils/utils.ts +++ b/public/app/features/dashboard-scene/utils/utils.ts @@ -64,7 +64,20 @@ function findVizPanelInternal(scene: SceneObject, key: string | undefined): VizP return null; } - const panel = sceneGraph.findObject(scene, (obj) => obj.state.key === key); + const panel = sceneGraph.findObject(scene, (obj) => { + const objKey = obj.state.key!; + + if (objKey === key) { + return true; + } + + if (!(obj instanceof VizPanel)) { + return false; + } + + return false; + }); + if (panel) { if (panel instanceof VizPanel) { return panel; diff --git a/yarn.lock b/yarn.lock index 63f72d7a9d8b..f5601094b963 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3938,9 +3938,9 @@ __metadata: languageName: unknown linkType: soft -"@grafana/scenes@npm:^4.14.0": - version: 4.20.0 - resolution: "@grafana/scenes@npm:4.20.0" +"@grafana/scenes@npm:^4.21.0": + version: 4.21.0 + resolution: "@grafana/scenes@npm:4.21.0" dependencies: "@grafana/e2e-selectors": "npm:^10.4.1" react-grid-layout: "npm:1.3.4" @@ -3954,7 +3954,7 @@ __metadata: "@grafana/ui": ^10.4.1 react: ^18.0.0 react-dom: ^18.0.0 - checksum: 10/6acd11a1dbb7f79d41704024649f12ebd1bbfb451e6edff3b5a7dba237d1c6cb7934b06f8bb3fde732b676ce621aee32a66ecddea155893c1eadc93f41ad9d2f + checksum: 10/6ce273c3a2969fedb0dda89cbe97efe1b93b7b5e67844da75f2dd08356a11d51d5153f56e5057ca5e2b37bab728a5d23794001b03b1a591e647fd80c1a58d3d6 languageName: node linkType: hard @@ -4662,8 +4662,8 @@ __metadata: linkType: hard "@kusto/monaco-kusto@npm:^10.0.0": - version: 10.0.21 - resolution: "@kusto/monaco-kusto@npm:10.0.21" + version: 10.0.20 + resolution: "@kusto/monaco-kusto@npm:10.0.20" dependencies: "@kusto/language-service": "npm:0.0.278" "@kusto/language-service-next": "npm:11.5.3" @@ -4674,7 +4674,7 @@ __metadata: monaco-editor: ^0.46.0 bin: copyMonacoFilesAMD: copyMonacoFilesAMD.js - checksum: 10/81640b12337239a90fde4432cf32e59d9ab77c8fc7442b20f5d16872f2084a750cd5c7654e61147ad68d40f55935d30115eb8b4d42e3a79c672e7934ce8efade + checksum: 10/050cd997e8c30328cd2ac181a601bd6cc174023bf482d04e0e2655eb935a53c6dca25731cc0ac6fd365d3e0c388a9335896b156c763e86f3ce74bb0c19758154 languageName: node linkType: hard @@ -15434,16 +15434,16 @@ __metadata: linkType: hard "dompurify@npm:^2.2.0": - version: 2.5.3 - resolution: "dompurify@npm:2.5.3" - checksum: 10/e5b4325e0b643bfd08c1d8500769d970924a1943b87976fb30c4e55d08bd7c3e7a09c1e1d1cb7f33425f72c1d643448c09e81209ef89a3e3fd01c4d713c94bc5 + version: 2.5.2 + resolution: "dompurify@npm:2.5.2" + checksum: 10/18b292c489c2056de4f1b4492985d01a7c09e88bf72a74291527ff2493c2132d8a6a542cf23a1263c0e0f97aeb43d0081091bdd20553a8ecc3858fcd2bb9a968 languageName: node linkType: hard "dompurify@npm:^3.0.0": - version: 3.1.3 - resolution: "dompurify@npm:3.1.3" - checksum: 10/bb1badf23e8b8c32e116339ae70842465f35706be0d3b2c38a392f3ee1f32e73dbabee6462e9e89406a527e837100b75002b86d8f386937663448cbdf714c466 + version: 3.1.2 + resolution: "dompurify@npm:3.1.2" + checksum: 10/9d5f4464d6b52aa540ac362a8e4354adc7dc33ef05a2b0a109cfcc1ba63c011b5ff38bbbf4f6b5a893166d976cbbd665c7aa4f5571cd010c812dc63a1f701f8b languageName: node linkType: hard @@ -15573,13 +15573,13 @@ __metadata: linkType: hard "ejs@npm:^3.1.7, ejs@npm:^3.1.8": - version: 3.1.10 - resolution: "ejs@npm:3.1.10" + version: 3.1.9 + resolution: "ejs@npm:3.1.9" dependencies: jake: "npm:^10.8.5" bin: ejs: bin/cli.js - checksum: 10/a9cb7d7cd13b7b1cd0be5c4788e44dd10d92f7285d2f65b942f33e127230c054f99a42db4d99f766d8dbc6c57e94799593ee66a14efd7c8dd70c4812bf6aa384 + checksum: 10/71f56d37540d2c2d71701f0116710c676f75314a3e997ef8b83515d5d4d2b111c5a72725377caeecb928671bacb84a0d38135f345904812e989847057d59f21a languageName: node linkType: hard @@ -18277,7 +18277,7 @@ __metadata: "@grafana/prometheus": "workspace:*" "@grafana/runtime": "workspace:*" "@grafana/saga-icons": "workspace:*" - "@grafana/scenes": "npm:^4.14.0" + "@grafana/scenes": "npm:^4.21.0" "@grafana/schema": "workspace:*" "@grafana/sql": "workspace:*" "@grafana/tsconfig": "npm:^1.3.0-rc1" @@ -19317,11 +19317,11 @@ __metadata: linkType: hard "i18next@npm:^23.0.0, i18next@npm:^23.5.1": - version: 23.11.4 - resolution: "i18next@npm:23.11.4" + version: 23.11.3 + resolution: "i18next@npm:23.11.3" dependencies: "@babel/runtime": "npm:^7.23.2" - checksum: 10/399314e2b53658d436801ce78a3c226e7a9fdc51db5320104c9337750dd84fdb6556601ec98c5114d81110c7026f845efea2086b4a972e4ece70da741e44581d + checksum: 10/9d562ade19d0beba16683ff94967a6dedc0a32ce335d203c5a160f075ac5a9a7a9adb164085a6b7b69328568bc932a65b92664834c2bf3e15d8f3bff90f15353 languageName: node linkType: hard @@ -21418,8 +21418,8 @@ __metadata: linkType: hard "knip@npm:^5.10.0": - version: 5.15.1 - resolution: "knip@npm:5.15.1" + version: 5.10.0 + resolution: "knip@npm:5.10.0" dependencies: "@ericcornelissen/bash-parser": "npm:0.5.2" "@nodelib/fs.walk": "npm:2.0.0" @@ -21445,7 +21445,7 @@ __metadata: bin: knip: bin/knip.js knip-bun: bin/knip-bun.js - checksum: 10/5f8286ec7e36f1cd359244ac4dce63bfbe4946f08150593e510e0ea4f4c30305a6b2167dc76ff116d603be63e5e8833521cf7990e73c6f07c1ddd8de83e664e1 + checksum: 10/eafc84cae08ddb249623c13814ebd1932abb1fb2e3d9c8061454cf2cb672ac478f6e1d63c4c80cb8af8d7ef05f0d03b40504894e4ed0d560c883c584d98d96ca languageName: node linkType: hard @@ -22681,7 +22681,7 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^4.2.4": +"minipass@npm:^4.0.0, minipass@npm:^4.2.4": version: 4.2.8 resolution: "minipass@npm:4.2.8" checksum: 10/e148eb6dcb85c980234cad889139ef8ddf9d5bdac534f4f0268446c8792dd4c74f4502479be48de3c1cce2f6450f6da4d0d4a86405a8a12be04c1c36b339569a @@ -29564,7 +29564,7 @@ __metadata: languageName: node linkType: hard -"tar@npm:6.2.1, tar@npm:^6.0.2, tar@npm:^6.1.11, tar@npm:^6.1.13, tar@npm:^6.1.2": +"tar@npm:6.2.1": version: 6.2.1 resolution: "tar@npm:6.2.1" dependencies: @@ -29578,6 +29578,20 @@ __metadata: languageName: node linkType: hard +"tar@npm:^6.0.2, tar@npm:^6.1.11, tar@npm:^6.1.13, tar@npm:^6.1.2": + version: 6.1.13 + resolution: "tar@npm:6.1.13" + dependencies: + chownr: "npm:^2.0.0" + fs-minipass: "npm:^2.0.0" + minipass: "npm:^4.0.0" + minizlib: "npm:^2.1.1" + mkdirp: "npm:^1.0.3" + yallist: "npm:^4.0.0" + checksum: 10/add2c3c6d0d71192186ec118d265b92d94be5cd57a0b8fdf0d29ee46dc846574925a5fc57170eefffd78201eda4c45d7604070b5a4b0648e4d6e1d65918b5a82 + languageName: node + linkType: hard + "teex@npm:^1.0.1": version: 1.0.1 resolution: "teex@npm:1.0.1"