diff --git a/.betterer.results b/.betterer.results index 2137d9c9884..c69a0a8a2dc 100644 --- a/.betterer.results +++ b/.betterer.results @@ -2418,12 +2418,14 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "3"], [0, 0, 0, "Unexpected any. Specify a different type.", "4"], [0, 0, 0, "Unexpected any. Specify a different type.", "5"], - [0, 0, 0, "Unexpected any. Specify a different type.", "6"] + [0, 0, 0, "Unexpected any. Specify a different type.", "6"], + [0, 0, 0, "Unexpected any. Specify a different type.", "7"] ], "public/app/features/dashboard-scene/panel-edit/VizPanelManager.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"], - [0, 0, 0, "Do not use any type assertions.", "2"] + [0, 0, 0, "Do not use any type assertions.", "2"], + [0, 0, 0, "Do not use any type assertions.", "3"] ], "public/app/features/dashboard-scene/scene/DashboardScene.test.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] @@ -2464,6 +2466,12 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "4"], [0, 0, 0, "Do not use any type assertions.", "5"] ], + "public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"], + [0, 0, 0, "Do not use any type assertions.", "2"], + [0, 0, 0, "Do not use any type assertions.", "3"] + ], "public/app/features/dashboard-scene/utils/test-utils.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"], diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx index 6a8f082824c..50066071d17 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx @@ -5,6 +5,7 @@ import { SceneObjectBase, SceneComponentProps, SceneQueryRunner, sceneGraph } fr import { DataQuery } from '@grafana/schema'; import { QueryEditorRows } from 'app/features/query/components/QueryEditorRows'; import { QueryGroupTopSection } from 'app/features/query/components/QueryGroup'; +import { DashboardQueryEditor } from 'app/plugins/datasource/dashboard'; import { GrafanaQuery } from 'app/plugins/datasource/grafana/types'; import { QueryGroupOptions } from 'app/types'; @@ -15,7 +16,6 @@ import { VizPanelManager } from '../VizPanelManager'; import { PanelDataPaneTabState, PanelDataPaneTab } from './types'; interface PanelDataQueriesTabState extends PanelDataPaneTabState { - // dataRef: SceneObjectRef; datasource?: DataSourceApi; dsSettings?: DataSourceInstanceSettings; } @@ -115,6 +115,10 @@ export class PanelDataQueriesTab extends SceneObjectBase { + this._panelManager.queryRunner.runQueries(); + }; + getQueries() { const dataObj = this._panelManager.state.panel.state.$data!; @@ -132,11 +136,6 @@ export class PanelDataQueriesTab extends SceneObjectBase) { const { panel, datasource, dsSettings } = model.panelManager.useState(); const { $data: dataObj } = panel.useState(); - - if (!dataObj) { - return; - } - const { data } = dataObj!.useState(); if (!datasource || !dsSettings || !data) { @@ -156,7 +155,7 @@ function PanelDataQueriesTabRendered({ model }: SceneComponentProps {dataObj instanceof ShareQueryDataProvider ? ( -

TODO: DashboardQueryEditor

+ ) : ( {}} onQueriesChange={model.onQueriesChange} - onRunQueries={() => {}} + onRunQueries={model.onRunQueries} /> )} diff --git a/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx b/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx index de217bf914b..f915a914302 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx @@ -17,6 +17,7 @@ import { import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; import { DashboardScene } from '../scene/DashboardScene'; +import { ShareQueryDataProvider } from '../scene/ShareQueryDataProvider'; import { DashboardModelCompatibilityWrapper } from '../utils/DashboardModelCompatibilityWrapper'; import { getDashboardUrl } from '../utils/urlBuilders'; @@ -106,6 +107,13 @@ export class PanelEditor extends SceneObjectBase { const newState = sceneUtils.cloneSceneObjectState(panelMngr.state.panel.state); + // Remove data provider if it's a share query. For editing purposes the data provider is cloned and attached to the + // ShareQueryDataProvider when panel is in edit mode. + // TODO: Handle transformations when we get on transformations edit. + if (newState.$data instanceof ShareQueryDataProvider) { + newState.$data.setState({ $data: undefined }); + } + sourcePanel.setState(newState); // preserve time range and variables state @@ -129,7 +137,7 @@ export class PanelEditor extends SceneObjectBase { export function buildPanelEditScene(dashboard: DashboardScene, panel: VizPanel): PanelEditor { const panelClone = panel.clone(); - const vizPanelMgr = new VizPanelManager(panelClone, dashboard.getRef()); + const vizPanelMgr = new VizPanelManager(panelClone); const dashboardStateCloned = sceneUtils.cloneSceneObjectState(dashboard.state); return new PanelEditor({ 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 d0a7c50bc3c..b7bc6ce459a 100644 --- a/public/app/features/dashboard-scene/panel-edit/VizPanelManager.test.tsx +++ b/public/app/features/dashboard-scene/panel-edit/VizPanelManager.test.tsx @@ -9,15 +9,15 @@ import { InspectTab } from 'app/features/inspector/types'; import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard'; import { DASHBOARD_DATASOURCE_PLUGIN_ID } from 'app/plugins/datasource/dashboard/types'; -import { DashboardScene } from '../scene/DashboardScene'; import { PanelTimeRange, PanelTimeRangeState } from '../scene/PanelTimeRange'; import { ShareQueryDataProvider } from '../scene/ShareQueryDataProvider'; import { transformSaveModelToScene } from '../serialization/transformSaveModelToScene'; import { DashboardModelCompatibilityWrapper } from '../utils/DashboardModelCompatibilityWrapper'; import { findVizPanelByKey } from '../utils/utils'; +import { buildPanelEditScene } from './PanelEditor'; import { VizPanelManager } from './VizPanelManager'; -import testDashboard from './testfiles/testDashboard.json'; +import { panelWithQueriesOnly, panelWithTransformations, testDashboard } from './testfiles/testDashboard'; const runRequestMock = jest.fn().mockImplementation((ds: DataSourceApi, request: DataQueryRequest) => { const result: PanelData = { @@ -143,14 +143,13 @@ jest.mock('@grafana/runtime', () => ({ describe('VizPanelManager', () => { describe('changePluginType', () => { it('Should successfully change from one viz type to another', () => { - const vizPanelManager = setupTest('panel-1'); + const { vizPanelManager } = setupTest('panel-1'); expect(vizPanelManager.state.panel.state.pluginId).toBe('timeseries'); vizPanelManager.changePluginType('table'); expect(vizPanelManager.state.panel.state.pluginId).toBe('table'); }); it('Should clear custom options', () => { - const dashboardSceneMock = new DashboardScene({}); const overrides = [ { matcher: { id: 'matcherOne' }, @@ -171,7 +170,7 @@ describe('VizPanelManager', () => { }, }); - const vizPanelManager = new VizPanelManager(vizPanel, dashboardSceneMock.getRef()); + const vizPanelManager = new VizPanelManager(vizPanel); expect(vizPanelManager.state.panel.state.fieldConfig.defaults.custom).toBe('Custom'); expect(vizPanelManager.state.panel.state.fieldConfig.overrides).toBe(overrides); @@ -184,7 +183,6 @@ describe('VizPanelManager', () => { }); it('Should restore cached options/fieldConfig if they exist', () => { - const dashboardSceneMock = new DashboardScene({}); const vizPanel = new VizPanel({ title: 'Panel A', key: 'panel-1', @@ -196,9 +194,9 @@ describe('VizPanelManager', () => { fieldConfig: { defaults: { custom: 'Custom' }, overrides: [] }, }); - const vizPanelManager = new VizPanelManager(vizPanel, dashboardSceneMock.getRef()); + const vizPanelManager = new VizPanelManager(vizPanel); - vizPanelManager.changePluginType('timeseties'); + vizPanelManager.changePluginType('timeseries'); //@ts-ignore expect(vizPanelManager.state.panel.state.options['customOption']).toBeUndefined(); expect(vizPanelManager.state.panel.state.fieldConfig.defaults.custom).toStrictEqual({}); @@ -218,7 +216,7 @@ describe('VizPanelManager', () => { describe('activation', () => { it('should load data source', async () => { - const vizPanelManager = setupTest('panel-1'); + const { vizPanelManager } = setupTest('panel-1'); vizPanelManager.activate(); await Promise.resolve(); @@ -227,7 +225,7 @@ describe('VizPanelManager', () => { }); it('should store loaded data source in local storage', async () => { - const vizPanelManager = setupTest('panel-1'); + const { vizPanelManager } = setupTest('panel-1'); vizPanelManager.activate(); await Promise.resolve(); @@ -240,20 +238,17 @@ describe('VizPanelManager', () => { describe('data source change', () => { it('should load new data source', async () => { - const vizPanelManager = setupTest('panel-1'); + const { vizPanelManager } = setupTest('panel-1'); vizPanelManager.activate(); - await Promise.resolve(); - - const dataObj = vizPanelManager.queryRunner; - dataObj.setState({ - datasource: { - type: 'grafana-prometheus-datasource', - uid: 'gdev-prometheus', - }, - }); + vizPanelManager.state.panel.state.$data?.activate(); await Promise.resolve(); + await vizPanelManager.changePanelDataSource( + { type: 'grafana-prometheus-datasource', uid: 'gdev-prometheus' } as any, + [] + ); + expect(store.setObject).toHaveBeenCalledTimes(2); expect(store.setObject).toHaveBeenLastCalledWith('grafana.dashboards.panelEdit.lastUsedDatasource', { dashboardUid: 'ffbe00e2-803c-4d49-adb7-41aad336234f', @@ -268,8 +263,9 @@ describe('VizPanelManager', () => { describe('query options change', () => { describe('time overrides', () => { it('should create PanelTimeRange object', async () => { - const vizPanelManager = setupTest('panel-1'); + const { vizPanelManager } = setupTest('panel-1'); vizPanelManager.activate(); + vizPanelManager.state.panel.state.$data?.activate(); await Promise.resolve(); const panel = vizPanelManager.state.panel; @@ -291,7 +287,7 @@ describe('VizPanelManager', () => { expect(panel.state.$timeRange).toBeInstanceOf(PanelTimeRange); }); it('should update PanelTimeRange object on time options update', async () => { - const vizPanelManager = setupTest('panel-1'); + const { vizPanelManager } = setupTest('panel-1'); vizPanelManager.activate(); await Promise.resolve(); @@ -330,7 +326,7 @@ describe('VizPanelManager', () => { }); it('should remove PanelTimeRange object on time options cleared', async () => { - const vizPanelManager = setupTest('panel-1'); + const { vizPanelManager } = setupTest('panel-1'); vizPanelManager.activate(); await Promise.resolve(); @@ -370,7 +366,7 @@ describe('VizPanelManager', () => { describe('max data points and interval', () => { it('max data points', async () => { - const vizPanelManager = setupTest('panel-1'); + const { vizPanelManager } = setupTest('panel-1'); vizPanelManager.activate(); await Promise.resolve(); @@ -392,7 +388,7 @@ describe('VizPanelManager', () => { }); it('max data points', async () => { - const vizPanelManager = setupTest('panel-1'); + const { vizPanelManager } = setupTest('panel-1'); vizPanelManager.activate(); await Promise.resolve(); @@ -417,7 +413,7 @@ describe('VizPanelManager', () => { describe('query inspection', () => { it('allows query inspection from the tab', async () => { - const vizPanelManager = setupTest('panel-1'); + const { vizPanelManager } = setupTest('panel-1'); vizPanelManager.inspectPanel(); expect(locationService.partial).toHaveBeenCalledWith({ inspect: 1, inspectTab: InspectTab.Query }); @@ -426,7 +422,7 @@ describe('VizPanelManager', () => { describe('data source change', () => { it('changing from one plugin to another', async () => { - const vizPanelManager = setupTest('panel-1'); + const { vizPanelManager } = setupTest('panel-1'); vizPanelManager.activate(); await Promise.resolve(); @@ -457,7 +453,7 @@ describe('VizPanelManager', () => { }); it('changing from a plugin to a dashboard data source', async () => { - const vizPanelManager = setupTest('panel-1'); + const { vizPanelManager } = setupTest('panel-1'); vizPanelManager.activate(); await Promise.resolve(); @@ -485,7 +481,7 @@ describe('VizPanelManager', () => { }); it('changing from dashboard data source to a plugin', async () => { - const vizPanelManager = setupTest('panel-3'); + const { vizPanelManager } = setupTest('panel-3'); vizPanelManager.activate(); await Promise.resolve(); @@ -513,7 +509,7 @@ describe('VizPanelManager', () => { describe('with transformations', () => { it('changing from one plugin to another', async () => { - const vizPanelManager = setupTest('panel-2'); + const { vizPanelManager } = setupTest('panel-2'); vizPanelManager.activate(); await Promise.resolve(); @@ -546,7 +542,7 @@ describe('VizPanelManager', () => { }); it('changing from a plugin to dashboard data source', async () => { - const vizPanelManager = setupTest('panel-2'); + const { vizPanelManager } = setupTest('panel-2'); vizPanelManager.activate(); await Promise.resolve(); @@ -575,7 +571,7 @@ describe('VizPanelManager', () => { }); it('changing from a dashboard data source to a plugin', async () => { - const vizPanelManager = setupTest('panel-4'); + const { vizPanelManager } = setupTest('panel-4'); vizPanelManager.activate(); await Promise.resolve(); @@ -604,18 +600,99 @@ describe('VizPanelManager', () => { }); }); }); + + describe('change queries', () => { + describe('plugin queries', () => { + it('should update queries', () => { + const { vizPanelManager } = setupTest('panel-1'); + + vizPanelManager.activate(); + vizPanelManager.state.panel.state.$data?.activate(); + + vizPanelManager.changeQueries([ + { + datasource: { + type: 'grafana-testdata-datasource', + uid: 'gdev-testdata', + }, + refId: 'A', + scenarioId: 'random_walk', + seriesCount: 5, + }, + ]); + + expect(vizPanelManager.queryRunner.state.queries).toEqual([ + { + datasource: { + type: 'grafana-testdata-datasource', + uid: 'gdev-testdata', + }, + refId: 'A', + scenarioId: 'random_walk', + seriesCount: 5, + }, + ]); + }); + }); + + describe('dashboard queries', () => { + it('should update queries', () => { + const { scene, panel } = setupTest('panel-3'); + + const panelEditScene = buildPanelEditScene(scene, panel); + + const vizPanelManager = panelEditScene.state.panelRef.resolve(); + vizPanelManager.activate(); + vizPanelManager.state.panel.state.$data?.activate(); + + // Changing dashboard query to a panel with transformations + vizPanelManager.changeQueries([ + { + refId: 'A', + datasource: { + type: DASHBOARD_DATASOURCE_PLUGIN_ID, + }, + panelId: panelWithTransformations.id, + }, + ]); + expect(vizPanelManager.panelData).toBeInstanceOf(ShareQueryDataProvider); + expect((vizPanelManager.panelData as ShareQueryDataProvider).state.query.panelId).toBe( + panelWithTransformations.id + ); + expect(vizPanelManager.panelData.state.$data).toBeInstanceOf(SceneDataTransformer); + expect(vizPanelManager.panelData.state.$data?.state.$data).toBeInstanceOf(SceneQueryRunner); + expect((vizPanelManager.panelData.state.$data?.state.$data as SceneQueryRunner).state.queries).toEqual( + panelWithTransformations.targets + ); + + // Changing dashboard query to a panel with queries only + vizPanelManager.changeQueries([ + { + refId: 'A', + datasource: { + type: DASHBOARD_DATASOURCE_PLUGIN_ID, + }, + panelId: panelWithQueriesOnly.id, + }, + ]); + + expect(vizPanelManager.panelData).toBeInstanceOf(ShareQueryDataProvider); + expect((vizPanelManager.panelData as ShareQueryDataProvider).state.query.panelId).toBe(panelWithQueriesOnly.id); + expect(vizPanelManager.queryRunner.state.queries).toEqual(panelWithQueriesOnly.targets); + }); + }); + }); }); const setupTest = (panelId: string) => { const scene = transformSaveModelToScene({ dashboard: testDashboard as any, meta: {} }); + const panel = findVizPanelByKey(scene, panelId)!; + + const vizPanelManager = new VizPanelManager(panel.clone()); // The following happens on DahsboardScene activation. For the needs of this test this activation aint needed hence we hand-call it // @ts-expect-error getDashboardSrv().setCurrent(new DashboardModelCompatibilityWrapper(scene)); - const panel = findVizPanelByKey(scene, panelId)!; - - const vizPanelManager = new VizPanelManager(panel.clone(), scene.getRef()); - - return vizPanelManager; + return { vizPanelManager, scene, panel }; }; diff --git a/public/app/features/dashboard-scene/panel-edit/VizPanelManager.tsx b/public/app/features/dashboard-scene/panel-edit/VizPanelManager.tsx index e3ce3700aa9..dcec8ea7ec6 100644 --- a/public/app/features/dashboard-scene/panel-edit/VizPanelManager.tsx +++ b/public/app/features/dashboard-scene/panel-edit/VizPanelManager.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import { Unsubscribable } from 'rxjs'; import { DataSourceApi, @@ -20,8 +19,6 @@ import { SceneComponentProps, sceneUtils, DeepPartial, - SceneObjectRef, - SceneObject, SceneQueryRunner, sceneGraph, SceneDataTransformer, @@ -32,14 +29,13 @@ import { getPluginVersion } from 'app/features/dashboard/state/PanelModel'; import { storeLastUsedDataSourceInLocalStorage } from 'app/features/datasources/components/picker/utils'; import { updateQueries } from 'app/features/query/state/updateQueries'; import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard'; -import { DASHBOARD_DATASOURCE_PLUGIN_ID } from 'app/plugins/datasource/dashboard/types'; +import { DASHBOARD_DATASOURCE_PLUGIN_ID, DashboardQuery } from 'app/plugins/datasource/dashboard/types'; import { GrafanaQuery } from 'app/plugins/datasource/grafana/types'; import { QueryGroupOptions } from 'app/types'; -import { DashboardScene } from '../scene/DashboardScene'; import { PanelTimeRange, PanelTimeRangeState } from '../scene/PanelTimeRange'; -import { ShareQueryDataProvider, findObjectInScene } from '../scene/ShareQueryDataProvider'; -import { getPanelIdForVizPanel, getVizPanelKeyForPanelId } from '../utils/utils'; +import { ShareQueryDataProvider } from '../scene/ShareQueryDataProvider'; +import { getPanelIdForVizPanel } from '../utils/utils'; interface VizPanelManagerState extends SceneObjectState { panel: VizPanel; @@ -60,57 +56,14 @@ export class VizPanelManager extends SceneObjectBase { { options: DeepPartial<{}>; fieldConfig: FieldConfigSource> } | undefined > = {}; - private _dataObjectSubscription: Unsubscribable | undefined; - - public constructor(panel: VizPanel, dashboardRef: SceneObjectRef) { + public constructor(panel: VizPanel) { super({ panel }); - /** - * If the panel uses a shared query, we clone the source runner and attach it as a data provider for the shared one. - * This way the source panel does not to be present in the edit scene hierarchy. - */ - if (panel.state.$data instanceof ShareQueryDataProvider) { - const sharedProvider = panel.state.$data; - if (sharedProvider.state.query.panelId) { - const keyToFind = getVizPanelKeyForPanelId(sharedProvider.state.query.panelId); - const source = findObjectInScene(dashboardRef.resolve(), (scene: SceneObject) => scene.state.key === keyToFind); - if (source) { - sharedProvider.setState({ - $data: source.state.$data!.clone(), - }); - } - } - } - this.addActivationHandler(() => this._onActivate()); } private _onActivate() { - this.setupDataObjectSubscription(); - this.loadDataSource(); - - return () => { - this._dataObjectSubscription?.unsubscribe(); - }; - } - - /** - * The subscription is updated whenever the data source type is changed so that we can update manager's stored - * data source and data source instance settings, which are needed for the query options and editors - */ - private setupDataObjectSubscription() { - const runner = this.queryRunner; - - if (this._dataObjectSubscription) { - this._dataObjectSubscription.unsubscribe(); - } - - this._dataObjectSubscription = runner.subscribeToState((n, p) => { - if (n.datasource !== p.datasource) { - this.loadDataSource(); - } - }); } private async loadDataSource() { @@ -207,7 +160,6 @@ export class VizPanelManager extends SceneObjectBase { } this.setState({ panel: newPanel }); - this.setupDataObjectSubscription(); } public async changePanelDataSource( @@ -249,8 +201,6 @@ export class VizPanelManager extends SceneObjectBase { }, }); panel.setState({ $data: sharedProvider }); - this.setupDataObjectSubscription(); - this.loadDataSource(); } else { dataObj.setState({ datasource: { @@ -272,8 +222,6 @@ export class VizPanelManager extends SceneObjectBase { queries, }); panel.setState({ $data: dataProvider }); - this.setupDataObjectSubscription(); - this.loadDataSource(); } else if (dataObj instanceof SceneDataTransformer) { const data = dataObj.clone(); @@ -304,10 +252,8 @@ export class VizPanelManager extends SceneObjectBase { }); panel.setState({ $data: data }); - - this.setupDataObjectSubscription(); - this.loadDataSource(); } + this.loadDataSource(); } public changeQueryOptions(options: QueryGroupOptions) { @@ -346,10 +292,20 @@ export class VizPanelManager extends SceneObjectBase { dataObj.runQueries(); } - public changeQueries(queries: DataQuery[]) { - const dataObj = this.queryRunner; - dataObj.setState({ queries }); - // TODO: Handle dashboard query + public changeQueries(queries: T[]) { + const dataObj = this.state.panel.state.$data; + const runner = this.queryRunner; + + if (dataObj instanceof ShareQueryDataProvider && queries.length > 0) { + const dashboardQuery = queries[0] as DashboardQuery; + if (dashboardQuery.panelId) { + dataObj.setState({ + query: dashboardQuery, + }); + } + } else { + runner.setState({ queries }); + } } public inspectPanel() { @@ -375,4 +331,8 @@ export class VizPanelManager extends SceneObjectBase { return dataObj as SceneQueryRunner; } + + get panelData(): SceneDataProvider { + return this.state.panel.state.$data!; + } } diff --git a/public/app/features/dashboard-scene/panel-edit/testfiles/testDashboard.json b/public/app/features/dashboard-scene/panel-edit/testfiles/testDashboard.json deleted file mode 100644 index 09b3a39febd..00000000000 --- a/public/app/features/dashboard-scene/panel-edit/testfiles/testDashboard.json +++ /dev/null @@ -1,368 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": { - "type": "grafana", - "uid": "-- Grafana --" - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "fiscalYearStartMonth": 0, - "graphTooltip": 0, - "id": 2378, - "links": [], - "liveNow": false, - "panels": [ - { - "datasource": { - "type": "grafana-testdata-datasource", - "uid": "gdev-testdata" - }, - "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", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 0 - }, - "id": 1, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "grafana-testdata-datasource", - "uid": "gdev-testdata" - }, - "refId": "A", - "scenarioId": "random_walk", - "seriesCount": 1 - } - ], - "title": "Source panel", - "type": "timeseries" - }, - { - "datasource": { - "type": "grafana-testdata-datasource", - "uid": "gdev-testdata" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "custom": { - "align": "auto", - "cellOptions": { - "type": "auto" - }, - "inspect": false - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 0 - }, - "id": 2, - "options": { - "cellHeight": "sm", - "footer": { - "countRows": false, - "fields": "", - "reducer": ["sum"], - "show": false - }, - "showHeader": true - }, - "pluginVersion": "10.3.0-pre", - "targets": [ - { - "datasource": { - "type": "grafana-testdata-datasource", - "uid": "gdev-testdata" - }, - "refId": "A", - "scenarioId": "random_walk", - "seriesCount": 1 - } - ], - "title": "Panel with transforms", - "transformations": [ - { - "id": "reduce", - "options": {} - } - ], - "type": "table" - }, - { - "datasource": { - "type": "datasource", - "uid": "-- Dashboard --" - }, - "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", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 8 - }, - "id": 3, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "datasource", - "uid": "-- Dashboard --" - }, - "panelId": 1, - "refId": "A" - } - ], - "title": "Dashboard query", - "type": "timeseries" - }, - { - "datasource": { - "type": "datasource", - "uid": "-- Dashboard --" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "custom": { - "align": "auto", - "cellOptions": { - "type": "auto" - }, - "inspect": false - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 8 - }, - "id": 4, - "options": { - "cellHeight": "sm", - "footer": { - "countRows": false, - "fields": "", - "reducer": ["sum"], - "show": false - }, - "showHeader": true - }, - "pluginVersion": "10.3.0-pre", - "targets": [ - { - "datasource": { - "type": "datasource", - "uid": "-- Dashboard --" - }, - "panelId": 1, - "refId": "A" - } - ], - "title": "Dashboard query with transformations", - "transformations": [ - { - "id": "reduce", - "options": {} - } - ], - "type": "table" - } - ], - "refresh": "", - "schemaVersion": 39, - "tags": [], - "templating": { - "list": [] - }, - "time": { - "from": "now-6h", - "to": "now" - }, - "timepicker": {}, - "timezone": "", - "title": "Scenes/PanelEdit/Queries: Edit", - "uid": "ffbe00e2-803c-4d49-adb7-41aad336234f", - "version": 6, - "weekStart": "" -} diff --git a/public/app/features/dashboard-scene/panel-edit/testfiles/testDashboard.ts b/public/app/features/dashboard-scene/panel-edit/testfiles/testDashboard.ts new file mode 100644 index 00000000000..e5ab4038444 --- /dev/null +++ b/public/app/features/dashboard-scene/panel-edit/testfiles/testDashboard.ts @@ -0,0 +1,375 @@ +export const panelWithQueriesOnly = { + datasource: { + type: 'grafana-testdata-datasource', + uid: 'gdev-testdata', + }, + 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', + value: null, + }, + { + color: 'red', + value: 80, + }, + ], + }, + }, + overrides: [], + }, + gridPos: { + h: 8, + w: 12, + x: 0, + y: 0, + }, + id: 1, + options: { + legend: { + calcs: [], + displayMode: 'list', + placement: 'bottom', + showLegend: true, + }, + tooltip: { + mode: 'single', + sort: 'none', + }, + }, + targets: [ + { + datasource: { + type: 'grafana-testdata-datasource', + uid: 'gdev-testdata', + }, + refId: 'A', + scenarioId: 'random_walk', + seriesCount: 1, + }, + ], + title: 'Panel with just queries', + type: 'timeseries', +}; + +export const panelWithTransformations = { + datasource: { + type: 'grafana-testdata-datasource', + uid: 'gdev-testdata', + }, + fieldConfig: { + defaults: { + color: { + mode: 'thresholds', + }, + custom: { + align: 'auto', + cellOptions: { + type: 'auto', + }, + inspect: false, + }, + mappings: [], + thresholds: { + mode: 'absolute', + steps: [ + { + color: 'green', + value: null, + }, + { + color: 'red', + value: 80, + }, + ], + }, + }, + overrides: [], + }, + gridPos: { + h: 8, + w: 12, + x: 12, + y: 0, + }, + id: 2, + options: { + cellHeight: 'sm', + footer: { + countRows: false, + fields: '', + reducer: ['sum'], + show: false, + }, + showHeader: true, + }, + pluginVersion: '10.3.0-pre', + targets: [ + { + datasource: { + type: 'grafana-testdata-datasource', + uid: 'gdev-testdata', + }, + refId: 'A', + scenarioId: 'random_walk', + seriesCount: 1, + }, + ], + title: 'Panel with transforms', + transformations: [ + { + id: 'reduce', + options: {}, + }, + ], + type: 'table', +}; + +export const panelWithDashboardQuery = { + datasource: { + type: 'datasource', + uid: '-- Dashboard --', + }, + 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', + value: null, + }, + { + color: 'red', + value: 80, + }, + ], + }, + }, + overrides: [], + }, + gridPos: { + h: 8, + w: 12, + x: 0, + y: 8, + }, + id: 3, + options: { + legend: { + calcs: [], + displayMode: 'list', + placement: 'bottom', + showLegend: true, + }, + tooltip: { + mode: 'single', + sort: 'none', + }, + }, + targets: [ + { + datasource: { + type: 'datasource', + uid: '-- Dashboard --', + }, + panelId: 1, + refId: 'A', + }, + ], + title: 'Panel with a Dashboard query', + type: 'timeseries', +}; + +export const panelWithDashboardQueryAndTransformations = { + datasource: { + type: 'datasource', + uid: '-- Dashboard --', + }, + fieldConfig: { + defaults: { + color: { + mode: 'thresholds', + }, + custom: { + align: 'auto', + cellOptions: { + type: 'auto', + }, + inspect: false, + }, + mappings: [], + thresholds: { + mode: 'absolute', + steps: [ + { + color: 'green', + value: null, + }, + { + color: 'red', + value: 80, + }, + ], + }, + }, + overrides: [], + }, + gridPos: { + h: 8, + w: 12, + x: 12, + y: 8, + }, + id: 4, + options: { + cellHeight: 'sm', + footer: { + countRows: false, + fields: '', + reducer: ['sum'], + show: false, + }, + showHeader: true, + }, + pluginVersion: '10.3.0-pre', + targets: [ + { + datasource: { + type: 'datasource', + uid: '-- Dashboard --', + }, + panelId: 1, + refId: 'A', + }, + ], + title: 'Panel with a dashboard query with transformations', + transformations: [ + { + id: 'reduce', + options: {}, + }, + ], + type: 'table', +}; +export const testDashboard = { + annotations: { + list: [ + { + builtIn: 1, + datasource: { + type: 'grafana', + uid: '-- Grafana --', + }, + enable: true, + hide: true, + iconColor: 'rgba(0, 211, 255, 1)', + name: 'Annotations & Alerts', + type: 'dashboard', + }, + ], + }, + editable: true, + fiscalYearStartMonth: 0, + graphTooltip: 0, + id: 2378, + links: [], + liveNow: false, + panels: [ + panelWithQueriesOnly, + panelWithTransformations, + panelWithDashboardQuery, + panelWithDashboardQueryAndTransformations, + ], + refresh: '', + schemaVersion: 39, + tags: [], + templating: { + list: [], + }, + time: { + from: 'now-6h', + to: 'now', + }, + timepicker: {}, + timezone: '', + title: 'Scenes/PanelEdit/Queries: Edit', + uid: 'ffbe00e2-803c-4d49-adb7-41aad336234f', + version: 6, + weekStart: '', +}; diff --git a/public/app/features/dashboard-scene/scene/ShareQueryDataProvider.test.ts b/public/app/features/dashboard-scene/scene/ShareQueryDataProvider.test.ts index b1b4bbab832..14c7e570505 100644 --- a/public/app/features/dashboard-scene/scene/ShareQueryDataProvider.test.ts +++ b/public/app/features/dashboard-scene/scene/ShareQueryDataProvider.test.ts @@ -53,4 +53,69 @@ describe('ShareQueryDataProvider', () => { expect(sceneGraph.getData(panel).state.data?.structureRev).toBe(12); }); + + it('should find and update to another VizPanels data provider when query changed', () => { + const sharedQuery = new ShareQueryDataProvider({ + query: { refId: 'A', panelId: 1 }, + }); + const panelWithSharedQuery = new SceneDummyPanel({ + key: getVizPanelKeyForPanelId(3), + $data: sharedQuery, + }); + + const sourceData1 = new SceneDataNode({ + data: { + series: [], + state: LoadingState.Done, + timeRange: getDefaultTimeRange(), + structureRev: 1, + }, + }); + + const sourceData2 = new SceneDataNode({ + data: { + series: [], + state: LoadingState.Done, + timeRange: getDefaultTimeRange(), + structureRev: 100, + }, + }); + + const sourcePanel1 = new SceneDummyPanel({ + key: getVizPanelKeyForPanelId(1), + $data: sourceData1, + }); + + const sourcePanel2 = new SceneDummyPanel({ + key: getVizPanelKeyForPanelId(2), + $data: sourceData2, + }); + + const scene = new SceneFlexLayout({ + children: [ + new SceneFlexItem({ + body: sourcePanel1, + }), + new SceneFlexItem({ + body: sourcePanel2, + }), + new SceneFlexItem({ body: panelWithSharedQuery }), + ], + }); + + activateFullSceneTree(scene); + + expect(sceneGraph.getData(panelWithSharedQuery).state.data?.structureRev).toBe(1); + + sharedQuery.setState({ + query: { + refId: 'A', + panelId: 2, + }, + }); + + expect(sceneGraph.getData(panelWithSharedQuery).state.data?.structureRev).toBe(100); + sourceData2.setState({ data: { ...sourceData2.state.data!, structureRev: 101 } }); + expect(sceneGraph.getData(panelWithSharedQuery).state.data?.structureRev).toBe(101); + }); }); diff --git a/public/app/features/dashboard-scene/scene/ShareQueryDataProvider.ts b/public/app/features/dashboard-scene/scene/ShareQueryDataProvider.ts index 854f616c795..8564df69c53 100644 --- a/public/app/features/dashboard-scene/scene/ShareQueryDataProvider.ts +++ b/public/app/features/dashboard-scene/scene/ShareQueryDataProvider.ts @@ -1,6 +1,6 @@ import { Observable, ReplaySubject, Unsubscribable } from 'rxjs'; -import { getDefaultTimeRange, LoadingState } from '@grafana/data'; +import { getDefaultTimeRange, LoadingState, PanelData } from '@grafana/data'; import { SceneDataProvider, SceneDataProviderResult, @@ -12,6 +12,7 @@ import { } from '@grafana/scenes'; import { DashboardQuery } from 'app/plugins/datasource/dashboard/types'; +import { PanelEditor } from '../panel-edit/PanelEditor'; import { getVizPanelKeyForPanelId } from '../utils/utils'; export interface ShareQueryDataProviderState extends SceneDataState { @@ -26,25 +27,57 @@ export class ShareQueryDataProvider extends SceneObjectBase { - // 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(); - } - }; + super({ + data: emptyPanelData, + ...state, }); + + this.addActivationHandler(this._onActivate); } + // Will detect the root of the scene and if it's a PanelEditor, it will clone the data from the source panel. + // We are doing it because the source panel's original scene (dashboard) does not exist in the edit mode. + private _setupEditMode(panelId: number, root: PanelEditor) { + const keyToFind = getVizPanelKeyForPanelId(panelId); + const source = findObjectInScene( + root.state.dashboardRef.resolve(), + (scene: SceneObject) => scene.state.key === keyToFind + ); + + if (source) { + // Indicate that when de-activated, i.e. navigating back to dashboard, the cloned$data state needs to be removed + // so that the panel on the dashboar can use data from the actual source panel. + this.setState({ + $data: source.state.$data!.clone(), + }); + } + } + + private _onActivate = () => { + const root = this.getRoot(); + if (root instanceof PanelEditor && this.state.query.panelId) { + this._setupEditMode(this.state.query.panelId, root); + } + // TODO handle changes to query model (changed panelId / withTransforms) + this.subscribeToState(this._onStateChanged); + /** + * If the panel uses a shared query, we clone the source runner and attach it as a data provider for the shared one. + * This way the source panel does not to be present in the edit scene hierarchy. + */ + + this._subscribeToSource(); + + return () => { + if (this._querySub) { + this._querySub.unsubscribe(); + } + if (this._sourceDataDeactivationHandler) { + this._sourceDataDeactivationHandler(); + this._sourceDataDeactivationHandler = undefined; + } + }; + }; + public getResultsStream(): Observable { return this._results; } @@ -56,6 +89,11 @@ export class ShareQueryDataProvider extends SceneObjectBase { + const root = this.getRoot(); + // If the query changed, we need to find the new source panel and subscribe to it + if (n.query !== p.query && n.query.panelId) { + if (root instanceof PanelEditor) { + this._setupEditMode(n.query.panelId, root); + } + this._subscribeToSource(); + } + }; + public setContainerWidth(width: number) { if (this._passContainerWidth && this._sourceProvider) { this._sourceProvider.setContainerWidth?.(width); @@ -143,3 +192,5 @@ export function findObjectInScene(scene: SceneObject, check: (scene: SceneObject return found; } + +const emptyPanelData: PanelData = { state: LoadingState.Done, series: [], timeRange: getDefaultTimeRange() }; diff --git a/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.test.ts b/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.test.ts index 2cf0bf38ddf..51adfd75f09 100644 --- a/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.test.ts +++ b/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.test.ts @@ -8,12 +8,15 @@ import { SceneTimeRange, VizPanel, SceneTimePicker, + SceneDataTransformer, } from '@grafana/scenes'; import { DashboardCursorSync } from '@grafana/schema'; +import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard'; import { DashboardControls } from '../scene/DashboardControls'; import { DashboardLinksControls } from '../scene/DashboardLinksControls'; import { DashboardScene } from '../scene/DashboardScene'; +import { ShareQueryDataProvider } from '../scene/ShareQueryDataProvider'; import { DashboardModelCompatibilityWrapper } from './DashboardModelCompatibilityWrapper'; @@ -32,6 +35,27 @@ describe('DashboardModelCompatibilityWrapper', () => { expect(wrapper.weekStart).toBe('friday'); expect(wrapper.timepicker.refresh_intervals).toEqual(['1s']); expect(wrapper.timepicker.hidden).toEqual(true); + expect(wrapper.panels).toHaveLength(5); + + expect(wrapper.panels[0].targets).toHaveLength(1); + expect(wrapper.panels[0].targets[0]).toEqual({ refId: 'A' }); + expect(wrapper.panels[1].targets).toHaveLength(0); + expect(wrapper.panels[2].targets).toHaveLength(1); + expect(wrapper.panels[2].targets).toEqual([ + { datasource: { uid: SHARED_DASHBOARD_QUERY, type: 'datasource' }, refId: 'A', panelId: 1 }, + ]); + expect(wrapper.panels[3].targets).toHaveLength(1); + expect(wrapper.panels[3].targets[0]).toEqual({ refId: 'A' }); + expect(wrapper.panels[4].targets).toHaveLength(1); + expect(wrapper.panels[4].targets).toEqual([ + { datasource: { uid: SHARED_DASHBOARD_QUERY, type: 'datasource' }, refId: 'A', panelId: 1 }, + ]); + + expect(wrapper.panels[0].datasource).toEqual({ uid: 'gdev-testdata', type: 'grafana-testdata-datasource' }); + expect(wrapper.panels[1].datasource).toEqual(null); + expect(wrapper.panels[2].datasource).toEqual({ uid: SHARED_DASHBOARD_QUERY, type: 'datasource' }); + expect(wrapper.panels[3].datasource).toEqual({ uid: 'gdev-testdata', type: 'grafana-testdata-datasource' }); + expect(wrapper.panels[4].datasource).toEqual({ uid: SHARED_DASHBOARD_QUERY, type: 'datasource' }); (scene.state.controls![0] as DashboardControls).setState({ hideTimeControls: false, @@ -70,16 +94,18 @@ describe('DashboardModelCompatibilityWrapper', () => { it('Can get fake panel with getPanelById', () => { const { wrapper } = setup(); - expect(wrapper.getPanelById(1)!.title).toBe('Panel A'); - expect(wrapper.getPanelById(2)!.title).toBe('Panel B'); + expect(wrapper.getPanelById(1)!.title).toBe('Panel with a regular data source query'); + expect(wrapper.getPanelById(2)!.title).toBe('Panel with no queries'); }); it('Can remove panel', () => { const { wrapper, scene } = setup(); + expect((scene.state.body as SceneGridLayout).state.children.length).toBe(5); + wrapper.removePanel(wrapper.getPanelById(1)!); - expect((scene.state.body as SceneGridLayout).state.children.length).toBe(1); + expect((scene.state.body as SceneGridLayout).state.children.length).toBe(4); }); }); @@ -113,19 +139,59 @@ function setup() { key: 'griditem-1', x: 0, body: new VizPanel({ - title: 'Panel A', + title: 'Panel with a regular data source query', key: 'panel-1', pluginId: 'table', - $data: new SceneQueryRunner({ key: 'data-query-runner', queries: [{ refId: 'A' }] }), + $data: new SceneQueryRunner({ + key: 'data-query-runner', + queries: [{ refId: 'A' }], + datasource: { uid: 'gdev-testdata', type: 'grafana-testdata-datasource' }, + }), }), }), new SceneGridItem({ body: new VizPanel({ - title: 'Panel B', + title: 'Panel with no queries', key: 'panel-2', pluginId: 'table', }), }), + + new SceneGridItem({ + body: new VizPanel({ + title: 'Panel with a shared query', + key: 'panel-3', + pluginId: 'table', + $data: new ShareQueryDataProvider({ query: { refId: 'A', panelId: 1 } }), + }), + }), + + new SceneGridItem({ + body: new VizPanel({ + title: 'Panel with a regular data source query and transformations', + key: 'panel-4', + pluginId: 'table', + $data: new SceneDataTransformer({ + $data: new SceneQueryRunner({ + key: 'data-query-runner', + queries: [{ refId: 'A' }], + datasource: { uid: 'gdev-testdata', type: 'grafana-testdata-datasource' }, + }), + transformations: [], + }), + }), + }), + new SceneGridItem({ + body: new VizPanel({ + title: 'Panel with a shared query and transformations', + key: 'panel-4', + pluginId: 'table', + $data: new SceneDataTransformer({ + $data: new ShareQueryDataProvider({ query: { refId: 'A', panelId: 1 } }), + transformations: [], + }), + }), + }), ], }), }); diff --git a/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts b/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts index eb17a92a148..5a5d96d57ef 100644 --- a/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts +++ b/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts @@ -9,10 +9,16 @@ import { SceneGridItem, SceneGridLayout, SceneGridRow, + SceneObject, + SceneQueryRunner, VizPanel, } from '@grafana/scenes'; +import { DataSourceRef } from '@grafana/schema'; +import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard'; import { DashboardScene } from '../scene/DashboardScene'; +import { LibraryVizPanel } from '../scene/LibraryVizPanel'; +import { ShareQueryDataProvider } from '../scene/ShareQueryDataProvider'; import { dashboardSceneGraph } from './dashboardSceneGraph'; import { findVizPanelByKey, getPanelIdForVizPanel, getVizPanelKeyForPanelId } from './utils'; @@ -91,6 +97,13 @@ export class DashboardModelCompatibilityWrapper { }; } + public get panels() { + const panels = findAllObjects(this._scene, (o) => { + return Boolean(o instanceof VizPanel); + }); + return panels.map((p) => new PanelCompatibilityWrapper(p as VizPanel)); + } + /** * Used from from timeseries migration handler to migrate time regions to dashboard annotations */ @@ -206,7 +219,9 @@ class PanelCompatibilityWrapper { constructor(private _vizPanel: VizPanel) {} public get id() { - const id = getPanelIdForVizPanel(this._vizPanel); + const id = getPanelIdForVizPanel( + this._vizPanel.parent instanceof LibraryVizPanel ? this._vizPanel.parent : this._vizPanel + ); if (isNaN(id)) { console.error('VizPanel key could not be translated to a legacy numeric panel id', this._vizPanel); @@ -232,6 +247,63 @@ class PanelCompatibilityWrapper { return []; } + public get targets() { + if (this._vizPanel.state.$data instanceof SceneQueryRunner) { + return this._vizPanel.state.$data.state.queries; + } + + if (this._vizPanel.state.$data instanceof ShareQueryDataProvider) { + return [ + { + datasource: { + uid: SHARED_DASHBOARD_QUERY, + type: 'datasource', + }, + ...this._vizPanel.state.$data.state.query, + }, + ]; + } + + if (this._vizPanel.state.$data instanceof SceneDataTransformer) { + if (this._vizPanel.state.$data.state.$data instanceof ShareQueryDataProvider) { + return [ + { + datasource: { + uid: SHARED_DASHBOARD_QUERY, + type: 'datasource', + }, + ...(this._vizPanel.state.$data.state.$data as ShareQueryDataProvider).state.query, + }, + ]; + } + if (this._vizPanel.state.$data.state.$data instanceof SceneQueryRunner) { + return (this._vizPanel.state.$data.state.$data as SceneQueryRunner).state.queries; + } + } + + return []; + } + + public get datasource(): DataSourceRef | null { + if (this._vizPanel.state.$data instanceof SceneQueryRunner) { + return this._vizPanel.state.$data.state.datasource ?? null; + } + + if (this._vizPanel.state.$data instanceof ShareQueryDataProvider) { + return { uid: SHARED_DASHBOARD_QUERY, type: 'datasource' }; + } + + if (this._vizPanel.state.$data instanceof SceneDataTransformer) { + if (this._vizPanel.state.$data.state.$data instanceof ShareQueryDataProvider) { + return { uid: SHARED_DASHBOARD_QUERY, type: 'datasource' }; + } + + return (this._vizPanel.state.$data.state.$data as SceneQueryRunner).state.datasource ?? null; + } + + return null; + } + public refresh() { console.error('Scenes PanelCompatibilityWrapper.refresh no implemented (yet)'); } @@ -244,3 +316,16 @@ class PanelCompatibilityWrapper { console.error('Scenes PanelCompatibilityWrapper.getQueryRunner no implemented (yet)'); } } + +function findAllObjects(root: SceneObject, check: (o: SceneObject) => boolean) { + let result: SceneObject[] = []; + root.forEachChild((child) => { + if (check(child)) { + result.push(child); + } else { + result = result.concat(findAllObjects(child, check)); + } + }); + + return result; +} diff --git a/public/app/plugins/datasource/dashboard/DashboardQueryEditor.tsx b/public/app/plugins/datasource/dashboard/DashboardQueryEditor.tsx index e2c8360519b..8b2f0c435ee 100644 --- a/public/app/plugins/datasource/dashboard/DashboardQueryEditor.tsx +++ b/public/app/plugins/datasource/dashboard/DashboardQueryEditor.tsx @@ -32,7 +32,7 @@ interface Props { queries: DataQuery[]; panelData: PanelData; onChange: (queries: DataQuery[]) => void; - onRunQueries: () => void; + onRunQueries?: () => void; } const topics = [ @@ -74,7 +74,9 @@ export function DashboardQueryEditor({ panelData, queries, onChange, onRunQuerie const onUpdateQuery = useCallback( (query: DashboardQuery) => { onChange([query]); - onRunQueries(); + if (onRunQueries) { + onRunQueries(); + } }, [onChange, onRunQueries] );