From 3af8f3246a316793fa1666d1dc897ef438242445 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 5 Apr 2023 10:19:54 +0200 Subject: [PATCH] Scenes: Add support for shared query results of other panel (#65413) * Scene: Add support for shared query results of other panel * Update * Fixing dashboard --- .../panel-common/shared_queries.json | 494 +++++++++++------- .../scenes/dashboard/DashboardsLoader.test.ts | 18 + .../scenes/dashboard/DashboardsLoader.ts | 44 +- .../dashboard/ShareQueryDataProvider.test.ts | 54 ++ .../dashboard/ShareQueryDataProvider.ts | 103 ++++ public/app/features/scenes/dashboard/utils.ts | 26 + .../app/plugins/datasource/dashboard/types.ts | 1 + yarn.lock | 4 +- 8 files changed, 551 insertions(+), 193 deletions(-) create mode 100644 public/app/features/scenes/dashboard/ShareQueryDataProvider.test.ts create mode 100644 public/app/features/scenes/dashboard/ShareQueryDataProvider.ts create mode 100644 public/app/features/scenes/dashboard/utils.ts diff --git a/devenv/dev-dashboards/panel-common/shared_queries.json b/devenv/dev-dashboards/panel-common/shared_queries.json index b29485f4c3f..a4804559089 100644 --- a/devenv/dev-dashboards/panel-common/shared_queries.json +++ b/devenv/dev-dashboards/panel-common/shared_queries.json @@ -3,7 +3,10 @@ "list": [ { "builtIn": 1, - "datasource": "-- Grafana --", + "datasource": { + "type": "datasource", + "uid": "grafana" + }, "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", @@ -13,17 +16,69 @@ ] }, "editable": true, - "gnetId": null, + "fiscalYearStartMonth": 0, "graphTooltip": 0, "links": [], + "liveNow": false, "panels": [ { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "fill": 0, - "fillGradient": 6, + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 60, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 6, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "always", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "links": [], + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, "gridPos": { "h": 15, "w": 12, @@ -31,89 +86,79 @@ "y": 0 }, "id": 2, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", "options": { - "dataLinks": [] + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } }, - "percentage": false, - "pointradius": 2, - "points": true, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, + "pluginVersion": "9.5.0-pre", "targets": [ { + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A" + }, "refId": "A", "scenarioId": "csv_metric_values", "stringInput": "1,20,90,30,5,0,100" }, { + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A" + }, "refId": "B", "scenarioId": "csv_metric_values", "stringInput": "1,20,90,30,5,-100,200" }, { + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A" + }, "refId": "C", "scenarioId": "csv_metric_values", "stringInput": "2.5,3.5,4.5,10.5,20.5,21.5,19.5" } ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, "title": "Raw Data Graph", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } + "type": "timeseries" }, { - "datasource": "-- Dashboard --", + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, "gridPos": { "h": 5, "w": 12, @@ -122,44 +167,59 @@ }, "id": 4, "options": { - "fieldOptions": { - "calcs": ["lastNotNull"], - "defaults": { - "mappings": [], - "max": 100, - "min": 0, - "thresholds": [ + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "9.5.0-pre", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 2, + "refId": "A" + } + ], + "title": "Last non-null", + "type": "gauge" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", "value": 80 } ] - }, - "override": {}, - "values": false + } }, - "orientation": "auto", - "showThresholdLabels": false, - "showThresholdMarkers": true + "overrides": [] }, - "pluginVersion": "6.4.0-pre", - "targets": [ - { - "panelId": 2, - "refId": "A" - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Last non-null", - "type": "gauge" - }, - { - "datasource": "-- Dashboard --", "gridPos": { "h": 5, "w": 12, @@ -168,63 +228,49 @@ }, "id": 6, "options": { - "fieldOptions": { - "calcs": ["min"], - "defaults": { - "mappings": [], - "max": 100, - "min": 0, - "thresholds": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "override": {}, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "min" + ], + "fields": "", "values": false }, - "orientation": "auto", "showThresholdLabels": false, "showThresholdMarkers": true }, - "pluginVersion": "6.4.0-pre", + "pluginVersion": "9.5.0-pre", "targets": [ { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, "panelId": 2, "refId": "A" } ], - "timeFrom": null, - "timeShift": null, "title": "min", "type": "gauge" }, { - "datasource": "-- Dashboard --", - "gridPos": { - "h": 5, - "w": 12, - "x": 12, - "y": 10 + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" }, - "id": 5, - "options": { - "displayMode": "basic", - "fieldOptions": { - "calcs": ["max"], - "defaults": { - "mappings": [], - "max": 200, - "min": 0, - "thresholds": [ + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 200, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "blue", @@ -235,28 +281,102 @@ "value": 120 } ] - }, - "override": {}, + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 12, + "x": 12, + "y": 10 + }, + "id": 5, + "options": { + "displayMode": "basic", + "minVizHeight": 10, + "minVizWidth": 0, + "orientation": "vertical", + "reduceOptions": { + "calcs": [ + "max" + ], + "fields": "", "values": false }, - "orientation": "vertical" + "showUnfilled": true, + "valueMode": "color" }, - "pluginVersion": "6.4.0-pre", + "pluginVersion": "9.5.0-pre", "targets": [ { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, "panelId": 2, "refId": "A" } ], - "timeFrom": null, - "timeShift": null, "title": "Max", "type": "bargauge" }, { - "columns": [], - "datasource": "-- Dashboard --", - "fontSize": "100%", + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "decimals": 2, + "displayName": "", + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Time" + }, + "properties": [ + { + "id": "displayName", + "value": "Time" + }, + { + "id": "unit", + "value": "time: YYYY-MM-DD HH:mm:ss" + }, + { + "id": "custom.align" + } + ] + } + ] + }, "gridPos": { "h": 10, "w": 24, @@ -264,47 +384,49 @@ "y": 15 }, "id": 8, - "options": {}, - "pageSize": null, - "showHeader": true, - "sort": { - "col": 0, - "desc": true - }, - "styles": [ - { - "alias": "Time", - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "pattern": "Time", - "type": "date" + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false }, - { - "alias": "", - "colorMode": null, - "colors": ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"], - "decimals": 2, - "pattern": "/.*/", - "thresholds": [], - "type": "number", - "unit": "short" - } - ], + "showHeader": true, + "showRowNums": false + }, + "pluginVersion": "9.5.0-pre", "targets": [ { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, "panelId": 2, "refId": "A" } ], - "timeFrom": null, - "timeShift": null, - "title": "Panel Title", - "transform": "timeseries_to_columns", + "title": "The data from graph above with seriesToColumns transform", + "transformations": [ + { + "id": "seriesToColumns", + "options": { + "reducers": [] + } + } + ], "type": "table" } ], - "schemaVersion": 19, + "refresh": "", + "schemaVersion": 38, "style": "dark", - "tags": ["gdev", "datasource-test"], + "tags": [ + "gdev", + "datasource-test" + ], "templating": { "list": [] }, @@ -313,10 +435,22 @@ "to": "now" }, "timepicker": { - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] }, "timezone": "", "title": "Datasource tests - Shared Queries", "uid": "ZqZnVvFZz", - "version": 10 -} + "version": 8, + "weekStart": "" +} \ No newline at end of file diff --git a/public/app/features/scenes/dashboard/DashboardsLoader.test.ts b/public/app/features/scenes/dashboard/DashboardsLoader.test.ts index 9daacdd517c..8d758d979c2 100644 --- a/public/app/features/scenes/dashboard/DashboardsLoader.test.ts +++ b/public/app/features/scenes/dashboard/DashboardsLoader.test.ts @@ -13,6 +13,8 @@ import { defaultDashboard, LoadingState, Panel, RowPanel, VariableType } from '@ import { DashboardLoaderSrv, setDashboardLoaderSrv } from 'app/features/dashboard/services/DashboardLoaderSrv'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { createPanelJSONFixture } from 'app/features/dashboard/state/__fixtures__/dashboardFixtures'; +import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard'; +import { DASHBOARD_DATASOURCE_PLUGIN_ID } from 'app/plugins/datasource/dashboard/types'; import { DashboardScene } from './DashboardScene'; import { @@ -21,6 +23,7 @@ import { createSceneVariableFromVariableModel, DashboardLoader, } from './DashboardsLoader'; +import { ShareQueryDataProvider } from './ShareQueryDataProvider'; describe('DashboardLoader', () => { describe('when fetching/loading a dashboard', () => { @@ -328,6 +331,21 @@ describe('DashboardLoader', () => { expect((vizPanelSceneObject.state.body as VizPanel)?.state.displayMode).toEqual('transparent'); expect((vizPanelSceneObject.state.body as VizPanel)?.state.hoverHeader).toEqual(true); }); + + it('should handle a dashboard query data source', () => { + const panel = { + title: '', + type: 'test-plugin', + datasource: { uid: SHARED_DASHBOARD_QUERY, type: DASHBOARD_DATASOURCE_PLUGIN_ID }, + gridPos: { x: 0, y: 0, w: 12, h: 8 }, + transparent: true, + targets: [{ refId: 'A', panelId: 10 }], + }; + + const vizPanel = createVizPanelFromPanelModel(new PanelModel(panel)).state.body as VizPanel; + + expect(vizPanel.state.$data).toBeInstanceOf(ShareQueryDataProvider); + }); }); describe('when creating variables objects', () => { diff --git a/public/app/features/scenes/dashboard/DashboardsLoader.ts b/public/app/features/scenes/dashboard/DashboardsLoader.ts index 18632e11682..0d3c537e1dd 100644 --- a/public/app/features/scenes/dashboard/DashboardsLoader.ts +++ b/public/app/features/scenes/dashboard/DashboardsLoader.ts @@ -22,13 +22,17 @@ import { SceneRefreshPicker, SceneDataTransformer, SceneGridItem, + SceneDataProvider, } 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; @@ -259,11 +263,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, @@ -272,6 +271,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 ?? {}, @@ -280,16 +280,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/plugins/datasource/dashboard/types.ts b/public/app/plugins/datasource/dashboard/types.ts index 983c79260fc..c1d1d7b579c 100644 --- a/public/app/plugins/datasource/dashboard/types.ts +++ b/public/app/plugins/datasource/dashboard/types.ts @@ -1,6 +1,7 @@ import { DataFrame, DataQuery, DataQueryError, DataTopic } from '@grafana/data'; export const SHARED_DASHBOARD_QUERY = '-- Dashboard --'; +export const DASHBOARD_DATASOURCE_PLUGIN_ID = 'dashboard'; export interface DashboardQuery extends DataQuery { panelId?: number; diff --git a/yarn.lock b/yarn.lock index 3fe57d32bba..ace327afbb0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3327,7 +3327,7 @@ __metadata: linkType: soft "@grafana/scenes@npm:^0.3.0": - version: 0.3.0 + version: 0.0.0-use.local resolution: "@grafana/scenes@npm:0.3.0" dependencies: "@grafana/e2e-selectors": canary @@ -3338,7 +3338,7 @@ __metadata: uuid: ^9.0.0 checksum: 3610cedcc150b9d6e3d6948056bb1bbbfe58d7fa0ff6e762eec6619bb0940504db867ea87e56160f27e4d93772f6203493bf87d353a1b18d2664e74a03f03a05 languageName: node - linkType: hard + linkType: soft "@grafana/schema@10.0.0-pre, @grafana/schema@workspace:*, @grafana/schema@workspace:packages/grafana-schema": version: 0.0.0-use.local