diff --git a/package.json b/package.json index b53e4ba533b..1f49cc96341 100644 --- a/package.json +++ b/package.json @@ -266,7 +266,7 @@ "@grafana/lezer-logql": "0.1.2", "@grafana/monaco-logql": "^0.0.7", "@grafana/runtime": "workspace:*", - "@grafana/scenes": "^0.2.0", + "@grafana/scenes": "^0.6.0", "@grafana/schema": "workspace:*", "@grafana/ui": "workspace:*", "@kusto/monaco-kusto": "5.3.6", diff --git a/public/app/features/scenes/ScenePage.tsx b/public/app/features/scenes/ScenePage.tsx index b755d11491b..eacdd2ae720 100644 --- a/public/app/features/scenes/ScenePage.tsx +++ b/public/app/features/scenes/ScenePage.tsx @@ -1,6 +1,7 @@ // Libraries import React, { useEffect, useState } from 'react'; +import { getUrlSyncManager } from '@grafana/scenes'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { getSceneByTitle } from './scenes'; @@ -13,7 +14,7 @@ export const ScenePage = (props: Props) => { useEffect(() => { if (scene && !isInitialized) { - scene.initUrlSync(); + getUrlSyncManager().initSync(scene); setInitialized(true); } }, [isInitialized, scene]); diff --git a/public/app/features/scenes/apps/SceneRadioToggle.tsx b/public/app/features/scenes/apps/SceneRadioToggle.tsx index 060b9327de2..b821a420f18 100644 --- a/public/app/features/scenes/apps/SceneRadioToggle.tsx +++ b/public/app/features/scenes/apps/SceneRadioToggle.tsx @@ -1,10 +1,10 @@ import React from 'react'; import { SelectableValue } from '@grafana/data'; -import { SceneComponentProps, SceneObjectBase, SceneObjectStatePlain } from '@grafana/scenes'; +import { SceneComponentProps, SceneObjectBase, SceneObjectState } from '@grafana/scenes'; import { RadioButtonGroup } from '@grafana/ui'; -export interface SceneRadioToggleState extends SceneObjectStatePlain { +export interface SceneRadioToggleState extends SceneObjectState { options: Array>; value: string; onChange: (value: string) => void; diff --git a/public/app/features/scenes/apps/SceneSearchBox.tsx b/public/app/features/scenes/apps/SceneSearchBox.tsx index 0a4a5dfb57c..1e5ebd6a24d 100644 --- a/public/app/features/scenes/apps/SceneSearchBox.tsx +++ b/public/app/features/scenes/apps/SceneSearchBox.tsx @@ -1,9 +1,9 @@ import React from 'react'; -import { SceneComponentProps, SceneObjectStatePlain, SceneObjectBase } from '@grafana/scenes'; +import { SceneComponentProps, SceneObjectState, SceneObjectBase } from '@grafana/scenes'; import { Input } from '@grafana/ui'; -export interface SceneSearchBoxState extends SceneObjectStatePlain { +export interface SceneSearchBoxState extends SceneObjectState { value: string; } diff --git a/public/app/features/scenes/dashboard/DashboardScene.tsx b/public/app/features/scenes/dashboard/DashboardScene.tsx index 433c7e65926..2f0d2967dc7 100644 --- a/public/app/features/scenes/dashboard/DashboardScene.tsx +++ b/public/app/features/scenes/dashboard/DashboardScene.tsx @@ -2,19 +2,13 @@ import { css } from '@emotion/css'; import React from 'react'; import { GrafanaTheme2, PageLayoutType } from '@grafana/data'; -import { config, locationService } from '@grafana/runtime'; -import { - UrlSyncManager, - SceneObjectBase, - SceneComponentProps, - SceneObject, - SceneObjectStatePlain, -} from '@grafana/scenes'; -import { PageToolbar, ToolbarButton, useStyles2 } from '@grafana/ui'; +import { locationService } from '@grafana/runtime'; +import { SceneObjectBase, SceneComponentProps, SceneObject, SceneObjectState } from '@grafana/scenes'; +import { ToolbarButton, useStyles2 } from '@grafana/ui'; import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate'; import { Page } from 'app/core/components/Page/Page'; -interface DashboardSceneState extends SceneObjectStatePlain { +interface DashboardSceneState extends SceneObjectState { title: string; uid?: string; body: SceneObject; @@ -24,18 +18,6 @@ interface DashboardSceneState extends SceneObjectStatePlain { export class DashboardScene extends SceneObjectBase { public static Component = DashboardSceneRenderer; - private urlSyncManager?: UrlSyncManager; - - /** - * It's better to do this before activate / mount to not trigger unnessary re-renders - */ - public initUrlSync() { - if (!this.urlSyncManager) { - this.urlSyncManager = new UrlSyncManager(this); - } - - this.urlSyncManager.initSync(); - } } function DashboardSceneRenderer({ model }: SceneComponentProps) { @@ -54,11 +36,7 @@ function DashboardSceneRenderer({ model }: SceneComponentProps) /> ); } - const pageToolbar = config.featureToggles.topnav ? ( - - ) : ( - {toolbarActions} - ); + const pageToolbar = ; return ( diff --git a/public/app/features/scenes/dashboard/DashboardsLoader.test.ts b/public/app/features/scenes/dashboard/DashboardsLoader.test.ts index 9daacdd517c..d934ce10db1 100644 --- a/public/app/features/scenes/dashboard/DashboardsLoader.test.ts +++ b/public/app/features/scenes/dashboard/DashboardsLoader.test.ts @@ -96,8 +96,6 @@ describe('DashboardLoader', () => { const loader = new DashboardLoader({}); await loader.load('fake-dash'); expect(loader.state.dashboard).toBeInstanceOf(DashboardScene); - // @ts-expect-error - private - expect(loader.state.dashboard?.urlSyncManager).toBeDefined(); expect(loader.state.isLoading).toBe(false); }); }); diff --git a/public/app/features/scenes/dashboard/DashboardsLoader.ts b/public/app/features/scenes/dashboard/DashboardsLoader.ts index 663ee91d789..ef173ca8595 100644 --- a/public/app/features/scenes/dashboard/DashboardsLoader.ts +++ b/public/app/features/scenes/dashboard/DashboardsLoader.ts @@ -19,15 +19,21 @@ import { DataSourceVariable, QueryVariable, ConstantVariable, + SceneRefreshPicker, SceneDataTransformer, SceneGridItem, + SceneDataProvider, + getUrlSyncManager, } from '@grafana/scenes'; import { StateManagerBase } from 'app/core/services/StateManagerBase'; import { dashboardLoaderSrv } from 'app/features/dashboard/services/DashboardLoaderSrv'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; +import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard/types'; import { DashboardDTO } from 'app/types'; import { DashboardScene } from './DashboardScene'; +import { ShareQueryDataProvider } from './ShareQueryDataProvider'; +import { getVizPanelKeyForPanelId } from './utils'; export interface DashboardLoaderState { dashboard?: DashboardScene; @@ -70,7 +76,7 @@ export class DashboardLoader extends StateManagerBase { // We initialize URL sync here as it better to do that before mounting and doing any rendering. // But would be nice to have a conditional around this so you can pre-load dashboards without url sync. - dashboard.initUrlSync(); + getUrlSyncManager().initSync(dashboard); this.cache[rsp.dashboard.uid] = dashboard; this.setState({ dashboard, isLoading: false }); @@ -178,7 +184,13 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel) children: createSceneObjectsForPanels(oldModel.panels), }), $timeRange: new SceneTimeRange(oldModel.time), - actions: [new SceneTimePicker({})], + actions: [ + new SceneTimePicker({}), + new SceneRefreshPicker({ + refresh: oldModel.refresh, + intervals: oldModel.timepicker.refresh_intervals, + }), + ], $variables: variables, ...(variables && { controls: [new VariableValueSelectors({})], @@ -252,11 +264,6 @@ export function createSceneVariableFromVariableModel(variable: VariableModel): S } export function createVizPanelFromPanelModel(panel: PanelModel) { - const queryRunner = new SceneQueryRunner({ - queries: panel.targets, - maxDataPoints: panel.maxDataPoints ?? undefined, - }); - return new SceneGridItem({ x: panel.gridPos.x, y: panel.gridPos.y, @@ -265,6 +272,7 @@ export function createVizPanelFromPanelModel(panel: PanelModel) { isDraggable: true, isResizable: true, body: new VizPanel({ + key: getVizPanelKeyForPanelId(panel.id), title: panel.title, pluginId: panel.type, options: panel.options ?? {}, @@ -273,16 +281,38 @@ export function createVizPanelFromPanelModel(panel: PanelModel) { displayMode: panel.transparent ? 'transparent' : undefined, // To be replaced with it's own option persited option instead derived hoverHeader: !panel.title && !panel.timeFrom && !panel.timeShift, - $data: panel.transformations?.length - ? new SceneDataTransformer({ - $data: queryRunner, - transformations: panel.transformations, - }) - : queryRunner, + $data: createPanelDataProvider(panel), }), }); } +export function createPanelDataProvider(panel: PanelModel): SceneDataProvider | undefined { + if (!panel.targets?.length) { + return undefined; + } + + let dataProvider: SceneDataProvider | undefined = undefined; + + if (panel.datasource?.uid === SHARED_DASHBOARD_QUERY) { + dataProvider = new ShareQueryDataProvider({ query: panel.targets[0] }); + } else { + dataProvider = new SceneQueryRunner({ + queries: panel.targets, + maxDataPoints: panel.maxDataPoints ?? undefined, + }); + } + + // Wrap inner data provider in a data transformer + if (panel.transformations?.length) { + dataProvider = new SceneDataTransformer({ + $data: dataProvider, + transformations: panel.transformations, + }); + } + + return dataProvider; +} + let loader: DashboardLoader | null = null; export function getDashboardLoader(): DashboardLoader { diff --git a/public/app/features/scenes/dashboard/ShareQueryDataProvider.test.ts b/public/app/features/scenes/dashboard/ShareQueryDataProvider.test.ts new file mode 100644 index 00000000000..04eb9fc3b17 --- /dev/null +++ b/public/app/features/scenes/dashboard/ShareQueryDataProvider.test.ts @@ -0,0 +1,54 @@ +import { getDefaultTimeRange, LoadingState } from '@grafana/data'; +import { + SceneDataNode, + SceneFlexItem, + SceneFlexLayout, + sceneGraph, + SceneObjectBase, + SceneObjectState, +} from '@grafana/scenes'; + +import { ShareQueryDataProvider } from './ShareQueryDataProvider'; +import { activateFullSceneTree, getVizPanelKeyForPanelId } from './utils'; + +export class SceneDummyPanel extends SceneObjectBase {} + +describe('ShareQueryDataProvider', () => { + it('Should find and subscribe to another VizPanels data provider', () => { + const panel = new SceneDummyPanel({ + key: getVizPanelKeyForPanelId(2), + $data: new ShareQueryDataProvider({ + query: { refId: 'A', panelId: 1 }, + }), + }); + + const sourceData = new SceneDataNode({ + data: { + series: [], + state: LoadingState.Done, + timeRange: getDefaultTimeRange(), + structureRev: 11, + }, + }); + + const scene = new SceneFlexLayout({ + children: [ + new SceneFlexItem({ + body: new SceneDummyPanel({ + key: getVizPanelKeyForPanelId(1), + $data: sourceData, + }), + }), + new SceneFlexItem({ body: panel }), + ], + }); + + activateFullSceneTree(scene); + + expect(sceneGraph.getData(panel).state.data?.structureRev).toBe(11); + + sourceData.setState({ data: { ...sourceData.state.data!, structureRev: 12 } }); + + expect(sceneGraph.getData(panel).state.data?.structureRev).toBe(12); + }); +}); diff --git a/public/app/features/scenes/dashboard/ShareQueryDataProvider.ts b/public/app/features/scenes/dashboard/ShareQueryDataProvider.ts new file mode 100644 index 00000000000..83d512617ee --- /dev/null +++ b/public/app/features/scenes/dashboard/ShareQueryDataProvider.ts @@ -0,0 +1,103 @@ +import { Unsubscribable } from 'rxjs'; + +import { + SceneDataProvider, + SceneDataState, + SceneDataTransformer, + SceneDeactivationHandler, + SceneObject, + SceneObjectBase, +} from '@grafana/scenes'; +import { DashboardQuery } from 'app/plugins/datasource/dashboard/types'; + +import { getVizPanelKeyForPanelId } from './utils'; + +export interface ShareQueryDataProviderState extends SceneDataState { + query: DashboardQuery; +} + +export class ShareQueryDataProvider extends SceneObjectBase implements SceneDataProvider { + private _querySub: Unsubscribable | undefined; + private _sourceDataDeactivationHandler?: SceneDeactivationHandler; + + public constructor(state: ShareQueryDataProviderState) { + super(state); + + this.addActivationHandler(() => { + // TODO handle changes to query model (changed panelId / withTransforms) + //this.subscribeToState(this._onStateChanged); + + this._subscribeToSource(); + + return () => { + if (this._querySub) { + this._querySub.unsubscribe(); + } + if (this._sourceDataDeactivationHandler) { + this._sourceDataDeactivationHandler(); + } + }; + }); + } + + private _subscribeToSource() { + const { query } = this.state; + + if (this._querySub) { + this._querySub.unsubscribe(); + } + + if (!query.panelId) { + return; + } + + const keyToFind = getVizPanelKeyForPanelId(query.panelId); + const source = findObjectInScene(this.getRoot(), (scene: SceneObject) => scene.state.key === keyToFind); + + if (!source) { + console.log('Shared dashboard query refers to a panel that does not exist in the scene'); + return; + } + + let sourceData = source.state.$data; + if (!sourceData) { + console.log('No source data found for shared dashboard query'); + return; + } + + // This will activate if sourceData is part of hidden panel + // Also make sure the sourceData is not deactivated if hidden later + this._sourceDataDeactivationHandler = sourceData.activate(); + + if (sourceData instanceof SceneDataTransformer) { + if (!query.withTransforms) { + if (!sourceData.state.$data) { + throw new Error('No source inner query runner found in data transformer'); + } + sourceData = sourceData.state.$data; + } + } + + this._querySub = sourceData.subscribeToState((state) => this.setState({ data: state.data })); + + // Copy the initial state + this.setState({ data: sourceData.state.data }); + } +} + +export function findObjectInScene(scene: SceneObject, check: (scene: SceneObject) => boolean): SceneObject | null { + if (check(scene)) { + return scene; + } + + let found: SceneObject | null = null; + + scene.forEachChild((child) => { + let maybe = findObjectInScene(child, check); + if (maybe) { + found = maybe; + } + }); + + return found; +} diff --git a/public/app/features/scenes/dashboard/utils.ts b/public/app/features/scenes/dashboard/utils.ts new file mode 100644 index 00000000000..418c793a3b0 --- /dev/null +++ b/public/app/features/scenes/dashboard/utils.ts @@ -0,0 +1,26 @@ +import { SceneDeactivationHandler, SceneObject } from '@grafana/scenes'; + +export function getVizPanelKeyForPanelId(panelId: number) { + return `panel-${panelId}`; +} + +/** + * Useful from tests to simulate mounting a full scene. Children are activated before parents to simulate the real order + * of React mount order and useEffect ordering. + * + */ +export function activateFullSceneTree(scene: SceneObject): SceneDeactivationHandler { + const deactivationHandlers: SceneDeactivationHandler[] = []; + + scene.forEachChild((child) => { + deactivationHandlers.push(activateFullSceneTree(child)); + }); + + deactivationHandlers.push(scene.activate()); + + return () => { + for (const handler of deactivationHandlers) { + handler(); + } + }; +} diff --git a/public/app/features/scenes/scenes/gridMultiTimeRange.tsx b/public/app/features/scenes/scenes/gridMultiTimeRange.tsx index 3969e858028..68381d27ac5 100644 --- a/public/app/features/scenes/scenes/gridMultiTimeRange.tsx +++ b/public/app/features/scenes/scenes/gridMultiTimeRange.tsx @@ -4,6 +4,7 @@ import { SceneTimePicker, SceneGridLayout, SceneTimeRange, + SceneRefreshPicker, SceneGridItem, } from '@grafana/scenes'; import { TestDataQueryType } from 'app/plugins/datasource/testdata/dataquery.gen'; @@ -77,6 +78,6 @@ export function getGridWithMultipleTimeRanges(): DashboardScene { }), $timeRange: globalTimeRange, $data: getQueryRunnerWithRandomWalkQuery(), - actions: [new SceneTimePicker({})], + actions: [new SceneTimePicker({}), new SceneRefreshPicker({})], }); } diff --git a/public/app/features/scenes/scenes/gridMultiple.tsx b/public/app/features/scenes/scenes/gridMultiple.tsx index 635b09c427f..beabe50e0c7 100644 --- a/public/app/features/scenes/scenes/gridMultiple.tsx +++ b/public/app/features/scenes/scenes/gridMultiple.tsx @@ -4,6 +4,7 @@ import { SceneFlexLayout, SceneGridLayout, SceneTimeRange, + SceneRefreshPicker, SceneGridItem, SceneFlexItem, } from '@grafana/scenes'; @@ -135,6 +136,6 @@ export function getMultipleGridLayoutTest(): DashboardScene { }), $timeRange: new SceneTimeRange(), $data: getQueryRunnerWithRandomWalkQuery(), - actions: [new SceneTimePicker({})], + actions: [new SceneTimePicker({}), new SceneRefreshPicker({})], }); } diff --git a/public/app/features/scenes/scenes/gridWithMultipleData.tsx b/public/app/features/scenes/scenes/gridWithMultipleData.tsx index 90999900208..b377b44a38c 100644 --- a/public/app/features/scenes/scenes/gridWithMultipleData.tsx +++ b/public/app/features/scenes/scenes/gridWithMultipleData.tsx @@ -4,6 +4,7 @@ import { SceneTimePicker, SceneGridLayout, SceneTimeRange, + SceneRefreshPicker, SceneGridItem, } from '@grafana/scenes'; import { TestDataQueryType } from 'app/plugins/datasource/testdata/dataquery.gen'; @@ -119,6 +120,6 @@ export function getGridWithMultipleData(): DashboardScene { }), $timeRange: new SceneTimeRange(), $data: getQueryRunnerWithRandomWalkQuery(), - actions: [new SceneTimePicker({})], + actions: [new SceneTimePicker({}), new SceneRefreshPicker({})], }); } diff --git a/public/app/features/scenes/scenes/queryVariableDemo.tsx b/public/app/features/scenes/scenes/queryVariableDemo.tsx index 303dc3490fa..809eea886ac 100644 --- a/public/app/features/scenes/scenes/queryVariableDemo.tsx +++ b/public/app/features/scenes/scenes/queryVariableDemo.tsx @@ -9,6 +9,7 @@ import { CustomVariable, DataSourceVariable, QueryVariable, + SceneRefreshPicker, SceneFlexItem, } from '@grafana/scenes'; @@ -61,7 +62,7 @@ export function getQueryVariableDemo(): DashboardScene { ], }), $timeRange: new SceneTimeRange(), - actions: [new SceneTimePicker({})], + actions: [new SceneTimePicker({}), new SceneRefreshPicker({})], controls: [new VariableValueSelectors({})], }); } diff --git a/public/app/features/scenes/scenes/sceneWithRows.tsx b/public/app/features/scenes/scenes/sceneWithRows.tsx index ef925e0da70..e4ffc455652 100644 --- a/public/app/features/scenes/scenes/sceneWithRows.tsx +++ b/public/app/features/scenes/scenes/sceneWithRows.tsx @@ -4,6 +4,7 @@ import { SceneTimePicker, SceneFlexLayout, SceneTimeRange, + SceneRefreshPicker, SceneFlexItem, } from '@grafana/scenes'; @@ -64,6 +65,6 @@ export function getSceneWithRows(): DashboardScene { }), $timeRange: new SceneTimeRange(), $data: getQueryRunnerWithRandomWalkQuery(), - actions: [new SceneTimePicker({})], + actions: [new SceneTimePicker({}), new SceneRefreshPicker({})], }); } diff --git a/public/app/features/scenes/scenes/transformations.tsx b/public/app/features/scenes/scenes/transformations.tsx index 43b8878ff70..4309c41971a 100644 --- a/public/app/features/scenes/scenes/transformations.tsx +++ b/public/app/features/scenes/scenes/transformations.tsx @@ -4,6 +4,7 @@ import { VizPanel, SceneDataTransformer, SceneTimeRange, + SceneRefreshPicker, SceneFlexItem, } from '@grafana/scenes'; @@ -75,6 +76,6 @@ export function getTransformationsDemo(): DashboardScene { }), $timeRange: new SceneTimeRange(), $data: getQueryRunnerWithRandomWalkQuery(), - actions: [new SceneTimePicker({})], + actions: [new SceneTimePicker({}), new SceneRefreshPicker({})], }); } diff --git a/public/app/features/scenes/scenes/variablesDemo.tsx b/public/app/features/scenes/scenes/variablesDemo.tsx index f1002e208e7..02ab81d9a92 100644 --- a/public/app/features/scenes/scenes/variablesDemo.tsx +++ b/public/app/features/scenes/scenes/variablesDemo.tsx @@ -10,6 +10,7 @@ import { DataSourceVariable, TestVariable, NestedScene, + SceneRefreshPicker, TextBoxVariable, SceneFlexItem, } from '@grafana/scenes'; @@ -126,7 +127,7 @@ export function getVariablesDemo(): DashboardScene { ], }), $timeRange: new SceneTimeRange(), - actions: [new SceneTimePicker({})], + actions: [new SceneTimePicker({}), new SceneRefreshPicker({})], controls: [new VariableValueSelectors({})], }); } @@ -193,7 +194,7 @@ export function getVariablesDemoWithAll(): DashboardScene { ], }), $timeRange: new SceneTimeRange(), - actions: [new SceneTimePicker({})], + actions: [new SceneTimePicker({}), new SceneRefreshPicker({})], controls: [new VariableValueSelectors({})], }); } diff --git a/yarn.lock b/yarn.lock index df9b937d755..09e37e5af0e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3088,7 +3088,7 @@ __metadata: languageName: unknown linkType: soft -"@grafana/e2e-selectors@9.5.0, @grafana/e2e-selectors@^9.4.3, @grafana/e2e-selectors@workspace:*, @grafana/e2e-selectors@workspace:packages/grafana-e2e-selectors": +"@grafana/e2e-selectors@9.5.0, @grafana/e2e-selectors@workspace:*, @grafana/e2e-selectors@workspace:packages/grafana-e2e-selectors": version: 0.0.0-use.local resolution: "@grafana/e2e-selectors@workspace:packages/grafana-e2e-selectors" dependencies: @@ -3107,6 +3107,17 @@ __metadata: languageName: unknown linkType: soft +"@grafana/e2e-selectors@npm:canary": + version: 10.0.0-112678pre + resolution: "@grafana/e2e-selectors@npm:10.0.0-112678pre" + dependencies: + "@grafana/tsconfig": ^1.2.0-rc1 + tslib: 2.5.0 + typescript: 4.8.4 + checksum: 0dc490899d63ffe82e7cfade68b36c90058a574390d64e87497c448b123fe1c9c46149049ff098b780355e49dc2d250cf41cc9cf29d3abd7103581c23ff0cbd6 + languageName: node + linkType: hard + "@grafana/e2e@workspace:*, @grafana/e2e@workspace:packages/grafana-e2e": version: 0.0.0-use.local resolution: "@grafana/e2e@workspace:packages/grafana-e2e" @@ -3315,17 +3326,17 @@ __metadata: languageName: unknown linkType: soft -"@grafana/scenes@npm:^0.2.0": - version: 0.2.0 - resolution: "@grafana/scenes@npm:0.2.0" +"@grafana/scenes@npm:^0.6.0": + version: 0.6.0 + resolution: "@grafana/scenes@npm:0.6.0" dependencies: - "@grafana/e2e-selectors": ^9.4.3 + "@grafana/e2e-selectors": canary "@grafana/experimental": 1.0.1 react-grid-layout: 1.3.4 react-use: 17.4.0 react-virtualized-auto-sizer: 1.0.7 uuid: ^9.0.0 - checksum: a04fe353eec8f048de7c51079e7f7bef461b372c3c086d5ff3be46fc0e70a21e907272f0ed05a41633847a3736c5e65e441ffa20b2c18eedea813decf3eef636 + checksum: 7197abac93ba84711900b526f0caa648b0b9f0c0e2edea2fbc125c1f192d6a3dae52389007cf82cbdf06a7b8019e5c03b5efa635f12ebc51eab5852cddf6427e languageName: node linkType: hard @@ -20099,7 +20110,7 @@ __metadata: "@grafana/lezer-logql": 0.1.2 "@grafana/monaco-logql": ^0.0.7 "@grafana/runtime": "workspace:*" - "@grafana/scenes": ^0.2.0 + "@grafana/scenes": ^0.6.0 "@grafana/schema": "workspace:*" "@grafana/toolkit": "workspace:*" "@grafana/tsconfig": ^1.2.0-rc1