From 8157711893d0ebe4ace006f1e6d55eebb82cd775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 15 Jan 2024 16:43:30 +0100 Subject: [PATCH] DashboardDataSource: Implement sharing logic inside the data source (#80526) * Make dashboard data source query actually use DashboardDataSource * remove commented out bit * Always wrap SceneQueryRunner with SceneDataTransformer * Update Dashboard model compat wrapper tests * DashboardQueryEditor test * VizPanelManager tests update * transform save model to scene tests update * Betterer * PanelMenuBehavior test update * Few more bits * Prettier --------- Co-authored-by: Dominik Prokop --- .betterer.results | 26 ++- .../PanelDataPane/PanelDataPane.tsx | 27 +-- .../PanelDataPane/PanelDataQueriesTab.tsx | 57 ++--- .../panel-edit/PanelEditor.tsx | 9 +- .../panel-edit/VizPanelManager.test.tsx | 136 ++---------- .../panel-edit/VizPanelManager.tsx | 139 ++----------- .../scene/PanelMenuBehavior.tsx | 29 +-- .../scene/ShareQueryDataProvider.test.ts | 121 ----------- .../scene/ShareQueryDataProvider.ts | 196 ------------------ .../sceneVariablesSetToVariables.test.ts | 1 + .../transformSaveModelToScene.test.ts | 6 +- .../transformSceneToSaveModel.ts | 31 --- ...DashboardModelCompatibilityWrapper.test.ts | 23 +- .../DashboardModelCompatibilityWrapper.ts | 62 +----- .../utils/createPanelDataProvider.ts | 37 ++-- .../features/query/components/QueryGroup.tsx | 12 +- .../dashboard/DashboardQueryEditor.test.tsx | 15 +- .../dashboard/DashboardQueryEditor.tsx | 27 +-- .../datasource/dashboard/datasource.ts | 73 ++++++- .../plugins/datasource/dashboard/module.ts | 3 +- 20 files changed, 201 insertions(+), 829 deletions(-) delete mode 100644 public/app/features/dashboard-scene/scene/ShareQueryDataProvider.test.ts delete mode 100644 public/app/features/dashboard-scene/scene/ShareQueryDataProvider.ts diff --git a/.betterer.results b/.betterer.results index 8c7ff46d795..8862d56c298 100644 --- a/.betterer.results +++ b/.betterer.results @@ -2384,23 +2384,15 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "1"], [0, 0, 0, "Unexpected any. Specify a different type.", "2"], [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.", "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.", "3"] + [0, 0, 0, "Unexpected any. Specify a different type.", "4"] ], "public/app/features/dashboard-scene/scene/DashboardScene.test.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], "public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"] + [0, 0, 0, "Unexpected any. Specify a different type.", "1"], + [0, 0, 0, "Do not use any type assertions.", "2"] ], "public/app/features/dashboard-scene/scene/setDashboardPanelContext.test.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] @@ -2445,8 +2437,17 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "1"] ], "public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts:5381": [ +<<<<<<< HEAD [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"] +||||||| eb8dfe7933d + [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"] +======= + [0, 0, 0, "Do not use any type assertions.", "0"] +>>>>>>> 6073626683dd3e9f437a533ef55fd79baaceb982 ], "public/app/features/dashboard-scene/utils/test-utils.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], @@ -4819,9 +4820,6 @@ exports[`better eslint`] = { "public/app/plugins/datasource/cloudwatch/utils/logsRetry.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], - "public/app/plugins/datasource/dashboard/DashboardQueryEditor.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], "public/app/plugins/datasource/dashboard/runSharedRequest.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/PanelDataPane.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataPane.tsx index 24976dcbc0b..891ffe9e780 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataPane.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataPane.tsx @@ -3,19 +3,15 @@ import { Unsubscribable } from 'rxjs'; import { SceneComponentProps, - SceneDataTransformer, SceneObjectBase, SceneObjectState, SceneObjectUrlSyncConfig, SceneObjectUrlValues, - SceneQueryRunner, VizPanel, - sceneGraph, } from '@grafana/scenes'; import { Tab, TabContent, TabsBar } from '@grafana/ui'; import { shouldShowAlertingTab } from 'app/features/dashboard/components/PanelEditor/state/selectors'; -import { ShareQueryDataProvider } from '../../scene/ShareQueryDataProvider'; import { VizPanelManager } from '../VizPanelManager'; import { PanelDataAlertingTab } from './PanelDataAlertingTab'; @@ -96,30 +92,10 @@ export class PanelDataPane extends SceneObjectBase { }); } - private getDataObjects(): [SceneQueryRunner | ShareQueryDataProvider | undefined, SceneDataTransformer | undefined] { - const dataObj = sceneGraph.getData(this.panelManager.state.panel); - - let runner: SceneQueryRunner | ShareQueryDataProvider | undefined; - let transformer: SceneDataTransformer | undefined; - - if (dataObj instanceof SceneQueryRunner || dataObj instanceof ShareQueryDataProvider) { - runner = dataObj; - } - - if (dataObj instanceof SceneDataTransformer) { - transformer = dataObj; - if (transformer.state.$data instanceof SceneQueryRunner) { - runner = transformer.state.$data; - } - } - - return [runner, transformer]; - } - private buildTabs() { const panelManager = this.panelManager; const panel = panelManager.state.panel; - const [runner] = this.getDataObjects(); + const runner = this.panelManager.queryRunner; const tabs: PanelDataPaneTab[] = []; if (panel) { @@ -129,6 +105,7 @@ export class PanelDataPane extends SceneObjectBase { this.setState({ tabs }); return; } + if (plugin.meta.skipDataQuery) { this.setState({ tabs }); return; 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 50066071d17..54ec7264f33 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx @@ -1,16 +1,14 @@ import React from 'react'; import { DataSourceApi, DataSourceInstanceSettings, IconName } from '@grafana/data'; -import { SceneObjectBase, SceneComponentProps, SceneQueryRunner, sceneGraph } from '@grafana/scenes'; +import { SceneObjectBase, SceneComponentProps, sceneGraph } from '@grafana/scenes'; 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'; import { PanelTimeRange } from '../../scene/PanelTimeRange'; -import { ShareQueryDataProvider } from '../../scene/ShareQueryDataProvider'; import { VizPanelManager } from '../VizPanelManager'; import { PanelDataPaneTabState, PanelDataPaneTab } from './types'; @@ -30,17 +28,7 @@ export class PanelDataQueriesTab extends SceneObjectBase) { - const { panel, datasource, dsSettings } = model.panelManager.useState(); - const { $data: dataObj } = panel.useState(); - const { data } = dataObj!.useState(); + const { datasource, dsSettings } = model.panelManager.useState(); + const { data } = model.panelManager.queryRunner.useState(); if (!datasource || !dsSettings || !data) { return null; @@ -154,18 +127,14 @@ function PanelDataQueriesTabRendered({ model }: SceneComponentProps - {dataObj instanceof ShareQueryDataProvider ? ( - - ) : ( - {}} - onQueriesChange={model.onQueriesChange} - onRunQueries={model.onRunQueries} - /> - )} + {}} + onQueriesChange={model.onQueriesChange} + 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 0c5c4e49bc9..1f16cbdf45e 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx @@ -18,7 +18,6 @@ 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,13 +105,6 @@ export class PanelEditor extends SceneObjectBase { const panelMngr = this.state.panelRef.resolve(); - // 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 (panelMngr.state.panel.state.$data instanceof ShareQueryDataProvider) { - panelMngr.state.panel.state.$data.setState({ $data: undefined }); - } - if (sourcePanel.parent instanceof SceneGridItem) { sourcePanel.parent.setState({ body: panelMngr.state.panel.clone() }); } @@ -154,6 +146,7 @@ export function buildPanelEditScene(dashboard: DashboardScene, panel: VizPanel): direction: 'column', primary: new SceneFlexLayout({ direction: 'column', + minHeight: 200, children: [vizPanelMgr], }), secondary: new SceneFlexItem({ 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 b7bc6ce459a..418b3a8dd82 100644 --- a/public/app/features/dashboard-scene/panel-edit/VizPanelManager.test.tsx +++ b/public/app/features/dashboard-scene/panel-edit/VizPanelManager.test.tsx @@ -10,7 +10,6 @@ import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard'; import { DASHBOARD_DATASOURCE_PLUGIN_ID } from 'app/plugins/datasource/dashboard/types'; 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'; @@ -426,11 +425,7 @@ describe('VizPanelManager', () => { vizPanelManager.activate(); await Promise.resolve(); - const panel = vizPanelManager.state.panel; - - expect(panel.state.$data).toBeInstanceOf(SceneQueryRunner); - - expect((panel.state.$data as SceneQueryRunner).state.datasource).toEqual({ + expect(vizPanelManager.queryRunner.state.datasource).toEqual({ uid: 'gdev-testdata', type: 'grafana-testdata-datasource', }); @@ -446,7 +441,7 @@ describe('VizPanelManager', () => { }, } as any); - expect((panel.state.$data as SceneQueryRunner).state.datasource).toEqual({ + expect(vizPanelManager.queryRunner.state.datasource).toEqual({ uid: 'gdev-prometheus', type: 'grafana-prometheus-datasource', }); @@ -457,11 +452,7 @@ describe('VizPanelManager', () => { vizPanelManager.activate(); await Promise.resolve(); - const panel = vizPanelManager.state.panel; - - expect(panel.state.$data).toBeInstanceOf(SceneQueryRunner); - - expect((panel.state.$data as SceneQueryRunner).state.datasource).toEqual({ + expect(vizPanelManager.queryRunner.state.datasource).toEqual({ uid: 'gdev-testdata', type: 'grafana-testdata-datasource', }); @@ -477,7 +468,10 @@ describe('VizPanelManager', () => { }, } as any); - expect(panel.state.$data).toBeInstanceOf(ShareQueryDataProvider); + expect(vizPanelManager.queryRunner.state.datasource).toEqual({ + uid: SHARED_DASHBOARD_QUERY, + type: 'datasource', + }); }); it('changing from dashboard data source to a plugin', async () => { @@ -485,100 +479,10 @@ describe('VizPanelManager', () => { vizPanelManager.activate(); await Promise.resolve(); - const panel = vizPanelManager.state.panel; - - expect(panel.state.$data).toBeInstanceOf(ShareQueryDataProvider); - - await vizPanelManager.changePanelDataSource({ - name: 'grafana-prometheus', - type: 'grafana-prometheus-datasource', - uid: 'gdev-prometheus', - meta: { - name: 'Prometheus', - module: 'prometheus', - id: 'grafana-prometheus-datasource', - }, - } as any); - - expect(panel.state.$data).toBeInstanceOf(SceneQueryRunner); - expect((panel.state.$data as SceneQueryRunner).state.datasource).toEqual({ - uid: 'gdev-prometheus', - type: 'grafana-prometheus-datasource', - }); - }); - - describe('with transformations', () => { - it('changing from one plugin to another', async () => { - const { vizPanelManager } = setupTest('panel-2'); - vizPanelManager.activate(); - await Promise.resolve(); - - const panel = vizPanelManager.state.panel; - - expect(panel.state.$data).toBeInstanceOf(SceneDataTransformer); - - expect((panel.state.$data?.state.$data as SceneQueryRunner).state.datasource).toEqual({ - uid: 'gdev-testdata', - type: 'grafana-testdata-datasource', - }); - - await vizPanelManager.changePanelDataSource({ - name: 'grafana-prometheus', - type: 'grafana-prometheus-datasource', - uid: 'gdev-prometheus', - meta: { - name: 'Prometheus', - module: 'prometheus', - id: 'grafana-prometheus-datasource', - }, - } as any); - - expect(panel.state.$data).toBeInstanceOf(SceneDataTransformer); - expect((panel.state.$data?.state.$data as SceneQueryRunner).state.datasource).toEqual({ - uid: 'gdev-prometheus', - type: 'grafana-prometheus-datasource', - }); - }); - }); - - it('changing from a plugin to dashboard data source', async () => { - const { vizPanelManager } = setupTest('panel-2'); - vizPanelManager.activate(); - await Promise.resolve(); - - const panel = vizPanelManager.state.panel; - - expect(panel.state.$data).toBeInstanceOf(SceneDataTransformer); - - expect((panel.state.$data?.state.$data as SceneQueryRunner).state.datasource).toEqual({ - uid: 'gdev-testdata', - type: 'grafana-testdata-datasource', - }); - - await vizPanelManager.changePanelDataSource({ - name: SHARED_DASHBOARD_QUERY, - type: 'datasource', + expect(vizPanelManager.queryRunner.state.datasource).toEqual({ uid: SHARED_DASHBOARD_QUERY, - meta: { - name: 'Prometheus', - module: 'prometheus', - id: DASHBOARD_DATASOURCE_PLUGIN_ID, - }, - } as any); - - expect(panel.state.$data).toBeInstanceOf(SceneDataTransformer); - expect(panel.state.$data?.state.$data).toBeInstanceOf(ShareQueryDataProvider); - }); - - it('changing from a dashboard data source to a plugin', async () => { - const { vizPanelManager } = setupTest('panel-4'); - vizPanelManager.activate(); - await Promise.resolve(); - - const panel = vizPanelManager.state.panel; - - expect(panel.state.$data).toBeInstanceOf(SceneDataTransformer); - expect(panel.state.$data?.state.$data).toBeInstanceOf(ShareQueryDataProvider); + type: 'datasource', + }); await vizPanelManager.changePanelDataSource({ name: 'grafana-prometheus', @@ -591,9 +495,7 @@ describe('VizPanelManager', () => { }, } as any); - expect(panel.state.$data).toBeInstanceOf(SceneDataTransformer); - expect(panel.state.$data?.state.$data).toBeInstanceOf(SceneQueryRunner); - expect((panel.state.$data?.state.$data as SceneQueryRunner).state.datasource).toEqual({ + expect(vizPanelManager.queryRunner.state.datasource).toEqual({ uid: 'gdev-prometheus', type: 'grafana-prometheus-datasource', }); @@ -655,15 +557,8 @@ describe('VizPanelManager', () => { 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 - ); + expect(vizPanelManager.panelData).toBeInstanceOf(SceneDataTransformer); + expect(vizPanelManager.queryRunner.state.queries[0].panelId).toEqual(panelWithTransformations.id); // Changing dashboard query to a panel with queries only vizPanelManager.changeQueries([ @@ -676,9 +571,8 @@ describe('VizPanelManager', () => { }, ]); - expect(vizPanelManager.panelData).toBeInstanceOf(ShareQueryDataProvider); - expect((vizPanelManager.panelData as ShareQueryDataProvider).state.query.panelId).toBe(panelWithQueriesOnly.id); - expect(vizPanelManager.queryRunner.state.queries).toEqual(panelWithQueriesOnly.targets); + expect(vizPanelManager.panelData).toBeInstanceOf(SceneDataTransformer); + expect(vizPanelManager.queryRunner.state.queries[0].panelId).toBe(panelWithQueriesOnly.id); }); }); }); diff --git a/public/app/features/dashboard-scene/panel-edit/VizPanelManager.tsx b/public/app/features/dashboard-scene/panel-edit/VizPanelManager.tsx index dcec8ea7ec6..ba379d338b0 100644 --- a/public/app/features/dashboard-scene/panel-edit/VizPanelManager.tsx +++ b/public/app/features/dashboard-scene/panel-edit/VizPanelManager.tsx @@ -4,10 +4,8 @@ import { DataSourceApi, DataSourceInstanceSettings, FieldConfigSource, - LoadingState, PanelModel, filterFieldConfigOverrides, - getDefaultTimeRange, isStandardFieldProp, restoreCustomOverrideRules, } from '@grafana/data'; @@ -21,21 +19,17 @@ import { DeepPartial, SceneQueryRunner, sceneGraph, - SceneDataTransformer, SceneDataProvider, } from '@grafana/scenes'; -import { DataQuery, DataSourceRef } from '@grafana/schema'; +import { DataQuery } from '@grafana/schema'; 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, DashboardQuery } from 'app/plugins/datasource/dashboard/types'; import { GrafanaQuery } from 'app/plugins/datasource/grafana/types'; import { QueryGroupOptions } from 'app/types'; import { PanelTimeRange, PanelTimeRangeState } from '../scene/PanelTimeRange'; -import { ShareQueryDataProvider } from '../scene/ShareQueryDataProvider'; -import { getPanelIdForVizPanel } from '../utils/utils'; +import { getPanelIdForVizPanel, getQueryRunnerFor } from '../utils/utils'; interface VizPanelManagerState extends SceneObjectState { panel: VizPanel; @@ -73,16 +67,7 @@ export class VizPanelManager extends SceneObjectBase { return; } - let datasourceToLoad: DataSourceRef | undefined; - - if (dataObj instanceof ShareQueryDataProvider) { - datasourceToLoad = { - uid: SHARED_DASHBOARD_QUERY, - type: DASHBOARD_DATASOURCE_PLUGIN_ID, - }; - } else { - datasourceToLoad = this.queryRunner.state.datasource; - } + let datasourceToLoad = this.queryRunner.state.datasource; if (!datasourceToLoad) { return; @@ -166,93 +151,28 @@ export class VizPanelManager extends SceneObjectBase { newSettings: DataSourceInstanceSettings, defaultQueries?: DataQuery[] | GrafanaQuery[] ) { - const { panel, dsSettings } = this.state; - const dataObj = panel.state.$data; - if (!dataObj) { - return; - } + const { dsSettings } = this.state; + const queryRunner = this.queryRunner; const currentDS = dsSettings ? await getDataSourceSrv().get({ uid: dsSettings.uid }) : undefined; const nextDS = await getDataSourceSrv().get({ uid: newSettings.uid }); - const currentQueries = []; - if (dataObj instanceof SceneQueryRunner) { - currentQueries.push(...dataObj.state.queries); - } else if (dataObj instanceof ShareQueryDataProvider) { - currentQueries.push(dataObj.state.query); - } + const currentQueries = queryRunner.state.queries; // We need to pass in newSettings.uid as well here as that can be a variable expression and we want to store that in the query model not the current ds variable value const queries = defaultQueries || (await updateQueries(nextDS, newSettings.uid, currentQueries, currentDS)); - if (dataObj instanceof SceneQueryRunner) { - // Changing to Dashboard data source - if (newSettings.uid === SHARED_DASHBOARD_QUERY) { - // Changing from one plugin to another - const sharedProvider = new ShareQueryDataProvider({ - query: queries[0], - $data: new SceneQueryRunner({ - queries: [], - }), - data: { - series: [], - state: LoadingState.NotStarted, - timeRange: getDefaultTimeRange(), - }, - }); - panel.setState({ $data: sharedProvider }); - } else { - dataObj.setState({ - datasource: { - type: newSettings.type, - uid: newSettings.uid, - }, - queries, - }); - if (defaultQueries) { - dataObj.runQueries(); - } - } - } else if (dataObj instanceof ShareQueryDataProvider && newSettings.uid !== SHARED_DASHBOARD_QUERY) { - const dataProvider = new SceneQueryRunner({ - datasource: { - type: newSettings.type, - uid: newSettings.uid, - }, - queries, - }); - panel.setState({ $data: dataProvider }); - } else if (dataObj instanceof SceneDataTransformer) { - const data = dataObj.clone(); - - let provider: SceneDataProvider = new SceneQueryRunner({ - datasource: { - type: newSettings.type, - uid: newSettings.uid, - }, - queries, - }); - - if (newSettings.uid === SHARED_DASHBOARD_QUERY) { - provider = new ShareQueryDataProvider({ - query: queries[0], - $data: new SceneQueryRunner({ - queries: [], - }), - data: { - series: [], - state: LoadingState.NotStarted, - timeRange: getDefaultTimeRange(), - }, - }); - } - - data.setState({ - $data: provider, - }); - - panel.setState({ $data: data }); + queryRunner.setState({ + datasource: { + type: newSettings.type, + uid: newSettings.uid, + }, + queries, + }); + if (defaultQueries) { + queryRunner.runQueries(); } + this.loadDataSource(); } @@ -293,19 +213,8 @@ export class VizPanelManager extends SceneObjectBase { } 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 }); - } + runner.setState({ queries }); } public inspectPanel() { @@ -319,17 +228,13 @@ export class VizPanelManager extends SceneObjectBase { } get queryRunner(): SceneQueryRunner { - const dataObj = this.state.panel.state.$data; + // Panel data object is always SceneQueryRunner wrapped in a SceneDataTransformer + const runner = getQueryRunnerFor(this.state.panel); - if (dataObj instanceof ShareQueryDataProvider) { - return dataObj.state.$data as SceneQueryRunner; + if (!runner) { + throw new Error('Query runner not found'); } - - if (dataObj instanceof SceneDataTransformer) { - return dataObj.state.$data as SceneQueryRunner; - } - - return dataObj as SceneQueryRunner; + return runner; } get panelData(): SceneDataProvider { diff --git a/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx b/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx index f8e3407681c..a7346644071 100644 --- a/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx +++ b/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx @@ -6,15 +6,7 @@ import { getTimeZone, } from '@grafana/data'; import { config, getPluginLinkExtensions, locationService } from '@grafana/runtime'; -import { - LocalValueVariable, - SceneDataTransformer, - SceneGridRow, - SceneQueryRunner, - VizPanel, - VizPanelMenu, - sceneGraph, -} from '@grafana/scenes'; +import { LocalValueVariable, SceneGridRow, VizPanel, VizPanelMenu, sceneGraph } from '@grafana/scenes'; import { DataQuery } from '@grafana/schema'; import { t } from 'app/core/internationalization'; import { PanelModel } from 'app/features/dashboard/state'; @@ -26,12 +18,11 @@ import { addDataTrailPanelAction } from 'app/features/trails/dashboardIntegratio import { ShareModal } from '../sharing/ShareModal'; import { DashboardInteractions } from '../utils/interactions'; import { getDashboardUrl, getInspectUrl, getViewPanelUrl, tryGetExploreUrlForPanel } from '../utils/urlBuilders'; -import { getPanelIdForVizPanel } from '../utils/utils'; +import { getPanelIdForVizPanel, getQueryRunnerFor } from '../utils/utils'; import { DashboardScene } from './DashboardScene'; import { LibraryVizPanel } from './LibraryVizPanel'; import { VizPanelLinks } from './PanelLinks'; -import { ShareQueryDataProvider } from './ShareQueryDataProvider'; /** * Behavior is called when VizPanelMenu is activated (ie when it's opened). @@ -233,22 +224,10 @@ export function getPanelLinksBehavior(panel: PanelModel) { function createExtensionContext(panel: VizPanel, dashboard: DashboardScene): PluginExtensionPanelContext { const timeRange = sceneGraph.getTimeRange(panel); - let queryRunner = panel.state.$data; - let targets: DataQuery[] = []; + let queryRunner = getQueryRunnerFor(panel); + const targets: DataQuery[] = queryRunner?.state.queries as DataQuery[]; const id = getPanelIdForVizPanel(panel); - if (queryRunner instanceof SceneDataTransformer) { - queryRunner = queryRunner.state.$data; - } - - if (queryRunner instanceof SceneQueryRunner) { - targets = queryRunner.state.queries; - } - - if (queryRunner instanceof ShareQueryDataProvider) { - targets = [queryRunner.state.query]; - } - let scopedVars = {}; // Handle panel repeats scenario diff --git a/public/app/features/dashboard-scene/scene/ShareQueryDataProvider.test.ts b/public/app/features/dashboard-scene/scene/ShareQueryDataProvider.test.ts deleted file mode 100644 index 14c7e570505..00000000000 --- a/public/app/features/dashboard-scene/scene/ShareQueryDataProvider.test.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { getDefaultTimeRange, LoadingState } from '@grafana/data'; -import { - SceneDataNode, - SceneFlexItem, - SceneFlexLayout, - sceneGraph, - SceneObjectBase, - SceneObjectState, -} from '@grafana/scenes'; - -import { activateFullSceneTree } from '../utils/test-utils'; -import { getVizPanelKeyForPanelId } from '../utils/utils'; - -import { ShareQueryDataProvider } from './ShareQueryDataProvider'; - -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); - }); - - 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 deleted file mode 100644 index 8564df69c53..00000000000 --- a/public/app/features/dashboard-scene/scene/ShareQueryDataProvider.ts +++ /dev/null @@ -1,196 +0,0 @@ -import { Observable, ReplaySubject, Unsubscribable } from 'rxjs'; - -import { getDefaultTimeRange, LoadingState, PanelData } from '@grafana/data'; -import { - SceneDataProvider, - SceneDataProviderResult, - SceneDataState, - SceneDataTransformer, - SceneDeactivationHandler, - SceneObject, - SceneObjectBase, -} 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 { - query: DashboardQuery; -} - -export class ShareQueryDataProvider extends SceneObjectBase implements SceneDataProvider { - private _querySub: Unsubscribable | undefined; - private _sourceDataDeactivationHandler?: SceneDeactivationHandler; - private _results = new ReplaySubject(); - private _sourceProvider?: SceneDataProvider; - private _passContainerWidth = false; - - constructor(state: ShareQueryDataProviderState) { - 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; - } - - private _subscribeToSource() { - const { query } = this.state; - - if (this._querySub) { - this._querySub.unsubscribe(); - } - - if (this._sourceDataDeactivationHandler) { - this._sourceDataDeactivationHandler(); - this._sourceDataDeactivationHandler = undefined; - } - - if (this.state.$data) { - this._sourceProvider = this.state.$data; - this._passContainerWidth = true; - } else { - 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; - } - - this._sourceProvider = source.state.$data; - if (!this._sourceProvider) { - console.log('No source data found for shared dashboard query'); - return; - } - } - - // If the source is not active we need to pass the container width - if (!this._sourceProvider.isActive) { - this._passContainerWidth = true; - } - - // This will activate if sourceData is part of hidden panel - // Also make sure the sourceData is not deactivated if hidden later - this._sourceDataDeactivationHandler = this._sourceProvider.activate(); - - // If source is a data transformer we might need to get the inner query runner instead depending on withTransforms option - if (this._sourceProvider instanceof SceneDataTransformer && !query.withTransforms) { - if (!this._sourceProvider.state.$data) { - throw new Error('No source inner query runner found in data transformer'); - } - this._sourceProvider = this._sourceProvider.state.$data; - } - - this._querySub = this._sourceProvider.subscribeToState((state) => { - this._results.next({ - origin: this, - data: state.data || { - state: LoadingState.Done, - series: [], - timeRange: getDefaultTimeRange(), - }, - }); - - this.setState({ data: state.data }); - }); - - // Copy the initial state - this.setState({ data: this._sourceProvider.state.data || emptyPanelData }); - } - - private _onStateChanged = (n: ShareQueryDataProviderState, p: ShareQueryDataProviderState) => { - 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); - } - } - - public isDataReadyToDisplay() { - if (this._sourceProvider && this._sourceProvider.isDataReadyToDisplay) { - return this._sourceProvider.isDataReadyToDisplay(); - } - return false; - } -} - -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; -} - -const emptyPanelData: PanelData = { state: LoadingState.Done, series: [], timeRange: getDefaultTimeRange() }; diff --git a/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.test.ts b/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.test.ts index eba7dd50d8e..7dfab1318bf 100644 --- a/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.test.ts +++ b/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.test.ts @@ -98,6 +98,7 @@ describe('sceneVariablesSetToVariables', () => { allValue: 'test-all', isMulti: true, }); + const set = new SceneVariableSet({ variables: [variable], }); diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts index b20acc17ba7..0e2b7673340 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts @@ -22,6 +22,7 @@ import { SceneGridItem, SceneGridLayout, SceneGridRow, + SceneQueryRunner, SceneRefreshPicker, SceneTimePicker, VizPanel, @@ -44,7 +45,6 @@ import { DashboardControls } from '../scene/DashboardControls'; import { PanelRepeaterGridItem } from '../scene/PanelRepeaterGridItem'; import { PanelTimeRange } from '../scene/PanelTimeRange'; import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior'; -import { ShareQueryDataProvider } from '../scene/ShareQueryDataProvider'; import { getQueryRunnerFor } from '../utils/utils'; import dashboard_to_load1 from './testfiles/dashboard_to_load1.json'; @@ -382,7 +382,9 @@ describe('transformSaveModelToScene', () => { }; const { vizPanel } = buildGridItemForTest(panel); - expect(vizPanel.state.$data).toBeInstanceOf(ShareQueryDataProvider); + expect(vizPanel.state.$data).toBeInstanceOf(SceneDataTransformer); + expect(vizPanel.state.$data?.state.$data).toBeInstanceOf(SceneQueryRunner); + expect((vizPanel.state.$data?.state.$data as SceneQueryRunner).state.queries).toEqual(panel.targets); }); it('should not set SceneQueryRunner for plugins with skipDataQuery', () => { diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts index ccb5f53fc6d..e4b361bccae 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts @@ -28,7 +28,6 @@ import { } from '@grafana/schema'; import { sortedDeepCloneWithoutNulls } from 'app/core/utils/object'; import { getPanelDataFrames } from 'app/features/dashboard/components/HelpWizard/utils'; -import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard'; import { GrafanaQueryType } from 'app/plugins/datasource/grafana/types'; import { DashboardControls } from '../scene/DashboardControls'; @@ -37,7 +36,6 @@ import { LibraryVizPanel } from '../scene/LibraryVizPanel'; import { PanelRepeaterGridItem } from '../scene/PanelRepeaterGridItem'; import { PanelTimeRange } from '../scene/PanelTimeRange'; import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior'; -import { ShareQueryDataProvider } from '../scene/ShareQueryDataProvider'; import { getPanelIdForVizPanel } from '../utils/utils'; import { GRAFANA_DATASOURCE_REF } from './const'; @@ -231,21 +229,6 @@ function vizPanelDataToPanel( const dataProvider = vizPanel.state.$data; const panel: Pick = {}; - // Dashboard datasource handling - if (dataProvider instanceof ShareQueryDataProvider) { - panel.datasource = { - type: 'datasource', - uid: SHARED_DASHBOARD_QUERY, - }; - panel.targets = [ - { - datasource: { ...panel.datasource }, - refId: 'A', - panelId: dataProvider.state.query.panelId, - topic: dataProvider.state.query.topic, - }, - ]; - } // Regular queries handling if (dataProvider instanceof SceneQueryRunner) { @@ -257,20 +240,6 @@ function vizPanelDataToPanel( // Transformations handling if (dataProvider instanceof SceneDataTransformer) { const panelData = dataProvider.state.$data; - if (panelData instanceof ShareQueryDataProvider) { - panel.datasource = { - type: 'datasource', - uid: SHARED_DASHBOARD_QUERY, - }; - panel.targets = [ - { - datasource: { ...panel.datasource }, - refId: 'A', - panelId: panelData.state.query.panelId, - topic: panelData.state.query.topic, - }, - ]; - } if (panelData instanceof SceneQueryRunner) { panel.targets = panelData.state.queries; diff --git a/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.test.ts b/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.test.ts index 51adfd75f09..7e8fb941803 100644 --- a/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.test.ts +++ b/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.test.ts @@ -16,7 +16,6 @@ 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'; @@ -41,18 +40,14 @@ describe('DashboardModelCompatibilityWrapper', () => { 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[2].targets).toEqual([{ 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[4].targets).toEqual([{ 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[1].datasource).toEqual(undefined); 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' }); @@ -162,7 +157,11 @@ function setup() { title: 'Panel with a shared query', key: 'panel-3', pluginId: 'table', - $data: new ShareQueryDataProvider({ query: { refId: 'A', panelId: 1 } }), + $data: new SceneQueryRunner({ + key: 'data-query-runner', + queries: [{ refId: 'A', panelId: 1 }], + datasource: { uid: SHARED_DASHBOARD_QUERY, type: 'datasource' }, + }), }), }), @@ -187,7 +186,11 @@ function setup() { key: 'panel-4', pluginId: 'table', $data: new SceneDataTransformer({ - $data: new ShareQueryDataProvider({ query: { refId: 'A', panelId: 1 } }), + $data: new SceneQueryRunner({ + key: 'data-query-runner', + queries: [{ refId: 'A', panelId: 1 }], + datasource: { uid: SHARED_DASHBOARD_QUERY, type: 'datasource' }, + }), transformations: [], }), }), diff --git a/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts b/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts index e8dde484099..fb0dea65adc 100644 --- a/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts +++ b/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts @@ -10,18 +10,15 @@ import { 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'; +import { findVizPanelByKey, getPanelIdForVizPanel, getQueryRunnerFor, getVizPanelKeyForPanelId } from './utils'; /** * Will move this to make it the main way we remain somewhat compatible with getDashboardSrv().getCurrent @@ -248,60 +245,17 @@ class PanelCompatibilityWrapper { } public get targets() { - if (this._vizPanel.state.$data instanceof SceneQueryRunner) { - return this._vizPanel.state.$data.state.queries; + const queryRunner = getQueryRunnerFor(this._vizPanel); + if (!queryRunner) { + return []; } - 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.state.query, - }, - ]; - } - if (this._vizPanel.state.$data.state.$data instanceof SceneQueryRunner) { - return this._vizPanel.state.$data.state.$data.state.queries; - } - } - - return []; + return queryRunner.state.queries; } - 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 get datasource(): DataSourceRef | null | undefined { + const queryRunner = getQueryRunnerFor(this._vizPanel); + return queryRunner?.state.datasource; } public refresh() { diff --git a/public/app/features/dashboard-scene/utils/createPanelDataProvider.ts b/public/app/features/dashboard-scene/utils/createPanelDataProvider.ts index 12a6d721db2..6e58e8fa398 100644 --- a/public/app/features/dashboard-scene/utils/createPanelDataProvider.ts +++ b/public/app/features/dashboard-scene/utils/createPanelDataProvider.ts @@ -1,9 +1,6 @@ import { config } from '@grafana/runtime'; import { SceneDataProvider, SceneDataTransformer, SceneQueryRunner } from '@grafana/scenes'; import { PanelModel } from 'app/features/dashboard/state'; -import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard'; - -import { ShareQueryDataProvider } from '../scene/ShareQueryDataProvider'; export function createPanelDataProvider(panel: PanelModel): SceneDataProvider | undefined { // Skip setting query runner for panels without queries @@ -18,27 +15,19 @@ export function createPanelDataProvider(panel: PanelModel): SceneDataProvider | let dataProvider: SceneDataProvider | undefined = undefined; - if (panel.datasource?.uid === SHARED_DASHBOARD_QUERY) { - dataProvider = new ShareQueryDataProvider({ query: panel.targets[0] }); - } else { - dataProvider = new SceneQueryRunner({ - datasource: panel.datasource ?? undefined, - queries: panel.targets, - maxDataPoints: panel.maxDataPoints ?? undefined, - maxDataPointsFromWidth: true, - dataLayerFilter: { - panelId: panel.id, - }, - }); - } + dataProvider = new SceneQueryRunner({ + datasource: panel.datasource ?? undefined, + queries: panel.targets, + maxDataPoints: panel.maxDataPoints ?? undefined, + maxDataPointsFromWidth: true, + dataLayerFilter: { + panelId: panel.id, + }, + }); // Wrap inner data provider in a data transformer - if (panel.transformations?.length) { - dataProvider = new SceneDataTransformer({ - $data: dataProvider, - transformations: panel.transformations, - }); - } - - return dataProvider; + return new SceneDataTransformer({ + $data: dataProvider, + transformations: panel.transformations || [], + }); } diff --git a/public/app/features/query/components/QueryGroup.tsx b/public/app/features/query/components/QueryGroup.tsx index 8b8aab4222c..32c8d37b9c3 100644 --- a/public/app/features/query/components/QueryGroup.tsx +++ b/public/app/features/query/components/QueryGroup.tsx @@ -23,7 +23,7 @@ import { DataSourceModal } from 'app/features/datasources/components/picker/Data import { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker'; import { dataSource as expressionDatasource } from 'app/features/expressions/ExpressionDatasource'; import { AngularDeprecationPluginNotice } from 'app/features/plugins/angularDeprecation/AngularDeprecationPluginNotice'; -import { DashboardQueryEditor, isSharedDashboardQuery } from 'app/plugins/datasource/dashboard'; +import { isSharedDashboardQuery } from 'app/plugins/datasource/dashboard'; import { GrafanaQuery } from 'app/plugins/datasource/grafana/types'; import { QueryGroupOptions } from 'app/types'; @@ -252,16 +252,6 @@ export class QueryGroup extends PureComponent { renderQueries(dsSettings: DataSourceInstanceSettings) { const { onRunQueries } = this.props; const { data, queries } = this.state; - if (isSharedDashboardQuery(dsSettings.name)) { - return ( - - ); - } return (
diff --git a/public/app/plugins/datasource/dashboard/DashboardQueryEditor.test.tsx b/public/app/plugins/datasource/dashboard/DashboardQueryEditor.test.tsx index c165132ac63..ca9d18c71c1 100644 --- a/public/app/plugins/datasource/dashboard/DashboardQueryEditor.test.tsx +++ b/public/app/plugins/datasource/dashboard/DashboardQueryEditor.test.tsx @@ -14,6 +14,7 @@ import { } from '../../../features/dashboard/state/__fixtures__/dashboardFixtures'; import { DashboardQueryEditor } from './DashboardQueryEditor'; +import { DashboardDatasource } from './datasource'; import { SHARED_DASHBOARD_QUERY } from './types'; jest.mock('app/core/config', () => ({ @@ -78,10 +79,11 @@ describe('DashboardQueryEditor', () => { it('does not show a panel with the SHARED_DASHBOARD_QUERY datasource as an option in the dropdown', async () => { render( ); const select = screen.getByText('Choose panel'); @@ -101,10 +103,11 @@ describe('DashboardQueryEditor', () => { mockDashboard.initEditPanel(mockDashboard.panels[0]); render( ); const select = screen.getByText('Choose panel'); diff --git a/public/app/plugins/datasource/dashboard/DashboardQueryEditor.tsx b/public/app/plugins/datasource/dashboard/DashboardQueryEditor.tsx index 8b2f0c435ee..6fc9e91593d 100644 --- a/public/app/plugins/datasource/dashboard/DashboardQueryEditor.tsx +++ b/public/app/plugins/datasource/dashboard/DashboardQueryEditor.tsx @@ -4,7 +4,7 @@ import pluralize from 'pluralize'; import React, { useCallback, useMemo } from 'react'; import { useAsync } from 'react-use'; -import { DataQuery, GrafanaTheme2, PanelData, SelectableValue, DataTopic } from '@grafana/data'; +import { DataQuery, GrafanaTheme2, SelectableValue, DataTopic, QueryEditorProps } from '@grafana/data'; import { Card, Field, @@ -22,27 +22,22 @@ import { PanelModel } from 'app/features/dashboard/state'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { filterPanelDataToQuery } from 'app/features/query/components/QueryEditorRow'; +import { DashboardDatasource } from './datasource'; import { DashboardQuery, ResultInfo, SHARED_DASHBOARD_QUERY } from './types'; function getQueryDisplayText(query: DataQuery): string { return JSON.stringify(query); } -interface Props { - queries: DataQuery[]; - panelData: PanelData; - onChange: (queries: DataQuery[]) => void; - onRunQueries?: () => void; -} +interface Props extends QueryEditorProps {} const topics = [ { label: 'All data', value: false }, { label: 'Annotations', value: true, description: 'Include annotations as regular data' }, ]; -export function DashboardQueryEditor({ panelData, queries, onChange, onRunQueries }: Props) { +export function DashboardQueryEditor({ data, query, onChange, onRunQuery }: Props) { const { value: defaultDatasource } = useAsync(() => getDatasourceSrv().get()); - const query = queries[0] as DashboardQuery; const panel = useMemo(() => { const dashboard = getDashboardSrv().getCurrent(); @@ -50,7 +45,7 @@ export function DashboardQueryEditor({ panelData, queries, onChange, onRunQuerie }, [query.panelId]); const { value: results, loading: loadingResults } = useAsync(async (): Promise => { - if (!panel) { + if (!panel || !data) { return []; } const mainDS = await getDatasourceSrv().get(panel.datasource); @@ -58,7 +53,7 @@ export function DashboardQueryEditor({ panelData, queries, onChange, onRunQuerie panel.targets.map(async (query) => { const ds = query.datasource ? await getDatasourceSrv().get(query.datasource) : mainDS; const fmt = ds.getQueryDisplayText || getQueryDisplayText; - const queryData = filterPanelDataToQuery(panelData, query.refId) ?? panelData; + const queryData = filterPanelDataToQuery(data, query.refId) ?? data; return { refId: query.refId, query: fmt(query), @@ -69,16 +64,14 @@ export function DashboardQueryEditor({ panelData, queries, onChange, onRunQuerie }; }) ); - }, [panelData, panel]); + }, [data, panel]); const onUpdateQuery = useCallback( (query: DashboardQuery) => { - onChange([query]); - if (onRunQueries) { - onRunQueries(); - } + onChange(query); + onRunQuery(); }, - [onChange, onRunQueries] + [onChange, onRunQuery] ); const onPanelChanged = useCallback( diff --git a/public/app/plugins/datasource/dashboard/datasource.ts b/public/app/plugins/datasource/dashboard/datasource.ts index 96fcc13c6f6..9548f300b23 100644 --- a/public/app/plugins/datasource/dashboard/datasource.ts +++ b/public/app/plugins/datasource/dashboard/datasource.ts @@ -1,3 +1,5 @@ +import { Observable, map, of } from 'rxjs'; + import { DataSourceApi, DataQueryRequest, @@ -5,6 +7,13 @@ import { DataSourceInstanceSettings, TestDataSourceResponse, } from '@grafana/data'; +import { SceneDataProvider, SceneDataTransformer, SceneObject } from '@grafana/scenes'; +import { PanelEditor } from 'app/features/dashboard-scene/panel-edit/PanelEditor'; +import { + findVizPanelByKey, + getQueryRunnerFor, + getVizPanelKeyForPanelId, +} from 'app/features/dashboard-scene/utils/utils'; import { DashboardQuery } from './types'; @@ -20,8 +29,68 @@ export class DashboardDatasource extends DataSourceApi { return `Dashboard Reference: ${query.panelId}`; } - query(options: DataQueryRequest): Promise { - return Promise.reject('This should not be called directly'); + query(options: DataQueryRequest): Observable { + const scene: SceneObject | undefined = options.scopedVars?.__sceneObject?.value; + + if (!scene) { + throw new Error('Can only be called from a scene'); + } + + const query = options.targets[0]; + if (!query) { + return of({ data: [] }); + } + + const panelId = query.panelId; + + if (!panelId) { + return of({ data: [] }); + } + + let sourcePanel = this.findSourcePanel(scene, panelId); + + if (!sourcePanel) { + return of({ data: [], error: { message: 'Could not find source panel' } }); + } + + let sourceDataProvider: SceneDataProvider | undefined = getQueryRunnerFor(sourcePanel); + + if (!sourceDataProvider || !sourceDataProvider.getResultsStream) { + return of({ data: [] }); + } + + if (query.withTransforms && sourceDataProvider.parent) { + const transformer = sourceDataProvider.parent; + if (transformer && transformer instanceof SceneDataTransformer) { + sourceDataProvider = transformer; + } + } + + if (!sourceDataProvider?.isActive) { + sourceDataProvider?.activate(); + sourceDataProvider.setContainerWidth!(500); + } + + return sourceDataProvider.getResultsStream!().pipe( + map((result) => { + return { + data: result.data.series, + state: result.data.state, + errors: result.data.errors, + error: result.data.error, + }; + }) + ); + } + + private findSourcePanel(scene: SceneObject, panelId: number) { + let sceneToSearch = scene.getRoot(); + + if (sceneToSearch instanceof PanelEditor) { + sceneToSearch = sceneToSearch.state.dashboardRef.resolve(); + } + + return findVizPanelByKey(sceneToSearch, getVizPanelKeyForPanelId(panelId)); } testDatasource(): Promise { diff --git a/public/app/plugins/datasource/dashboard/module.ts b/public/app/plugins/datasource/dashboard/module.ts index 1fa8553046e..c9f0cc591ec 100644 --- a/public/app/plugins/datasource/dashboard/module.ts +++ b/public/app/plugins/datasource/dashboard/module.ts @@ -1,5 +1,6 @@ import { DataSourcePlugin } from '@grafana/data'; +import { DashboardQueryEditor } from './DashboardQueryEditor'; import { DashboardDatasource } from './datasource'; -export const plugin = new DataSourcePlugin(DashboardDatasource); +export const plugin = new DataSourcePlugin(DashboardDatasource).setQueryEditor(DashboardQueryEditor);