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 <dominik.prokop@grafana.com>
This commit is contained in:
Torkel Ödegaard 2024-01-15 16:43:30 +01:00 committed by GitHub
parent bffb28c177
commit 8157711893
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 201 additions and 829 deletions

View File

@ -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"]

View File

@ -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<PanelDataPaneState> {
});
}
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<PanelDataPaneState> {
this.setState({ tabs });
return;
}
if (plugin.meta.skipDataQuery) {
this.setState({ tabs });
return;

View File

@ -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<PanelDataQueriesTabStat
}
getItemsCount() {
const dataObj = this._panelManager.state.panel.state.$data!;
if (dataObj instanceof ShareQueryDataProvider) {
return 1;
}
if (dataObj instanceof SceneQueryRunner) {
return dataObj.state.queries.length;
}
return null;
return this.getQueries().length;
}
constructor(panelManager: VizPanelManager) {
@ -52,9 +40,7 @@ export class PanelDataQueriesTab extends SceneObjectBase<PanelDataQueriesTabStat
buildQueryOptions(): QueryGroupOptions {
const panelManager = this._panelManager;
const panelObj = this._panelManager.state.panel;
const dataObj = panelObj.state.$data!;
const queryRunner = this._panelManager.queryRunner;
const timeRangeObj = sceneGraph.getTimeRange(panelObj);
let timeRangeOpts: QueryGroupOptions['timeRange'] = {
@ -71,14 +57,7 @@ export class PanelDataQueriesTab extends SceneObjectBase<PanelDataQueriesTabStat
};
}
let queries: QueryGroupOptions['queries'] = [];
if (dataObj instanceof ShareQueryDataProvider) {
queries = [dataObj.state.query];
}
if (dataObj instanceof SceneQueryRunner) {
queries = dataObj.state.queries;
}
let queries: QueryGroupOptions['queries'] = queryRunner.state.queries;
return {
// TODO
@ -120,11 +99,6 @@ export class PanelDataQueriesTab extends SceneObjectBase<PanelDataQueriesTabStat
};
getQueries() {
const dataObj = this._panelManager.state.panel.state.$data!;
if (dataObj instanceof ShareQueryDataProvider) {
return [dataObj.state.query];
}
return this._panelManager.queryRunner.state.queries;
}
@ -134,9 +108,8 @@ export class PanelDataQueriesTab extends SceneObjectBase<PanelDataQueriesTabStat
}
function PanelDataQueriesTabRendered({ model }: SceneComponentProps<PanelDataQueriesTab>) {
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<PanelDataQue
onOpenQueryInspector={model.onOpenInspector}
/>
{dataObj instanceof ShareQueryDataProvider ? (
<DashboardQueryEditor queries={model.getQueries()} panelData={data} onChange={model.onQueriesChange} />
) : (
<QueryEditorRows
data={data}
queries={model.getQueries()}
dsSettings={dsSettings}
onAddQuery={() => {}}
onQueriesChange={model.onQueriesChange}
onRunQueries={model.onRunQueries}
/>
)}
<QueryEditorRows
data={data}
queries={model.getQueries()}
dsSettings={dsSettings}
onAddQuery={() => {}}
onQueriesChange={model.onQueriesChange}
onRunQueries={model.onRunQueries}
/>
</>
);
}

View File

@ -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<PanelEditorState> {
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({

View File

@ -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);
});
});
});

View File

@ -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<VizPanelManagerState> {
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<VizPanelManagerState> {
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<VizPanelManagerState> {
}
public changeQueries<T extends DataQuery>(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<VizPanelManagerState> {
}
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 {

View File

@ -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

View File

@ -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<SceneObjectState> {}
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);
});
});

View File

@ -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<ShareQueryDataProviderState> implements SceneDataProvider {
private _querySub: Unsubscribable | undefined;
private _sourceDataDeactivationHandler?: SceneDeactivationHandler;
private _results = new ReplaySubject<SceneDataProviderResult>();
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<SceneDataProviderResult> {
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() };

View File

@ -98,6 +98,7 @@ describe('sceneVariablesSetToVariables', () => {
allValue: 'test-all',
isMulti: true,
});
const set = new SceneVariableSet({
variables: [variable],
});

View File

@ -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', () => {

View File

@ -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<Panel, 'datasource' | 'targets' | 'maxDataPoints' | 'transformations'> = {};
// 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;

View File

@ -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: [],
}),
}),

View File

@ -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() {

View File

@ -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 || [],
});
}

View File

@ -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<Props, State> {
renderQueries(dsSettings: DataSourceInstanceSettings) {
const { onRunQueries } = this.props;
const { data, queries } = this.state;
if (isSharedDashboardQuery(dsSettings.name)) {
return (
<DashboardQueryEditor
queries={queries}
panelData={data}
onChange={this.onQueriesChange}
onRunQueries={onRunQueries}
/>
);
}
return (
<div aria-label={selectors.components.QueryTab.content}>

View File

@ -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(
<DashboardQueryEditor
queries={mockQueries}
panelData={mockPanelData}
datasource={{} as DashboardDatasource}
query={mockQueries[0]}
data={mockPanelData}
onChange={mockOnChange}
onRunQueries={mockOnRunQueries}
onRunQuery={mockOnRunQueries}
/>
);
const select = screen.getByText('Choose panel');
@ -101,10 +103,11 @@ describe('DashboardQueryEditor', () => {
mockDashboard.initEditPanel(mockDashboard.panels[0]);
render(
<DashboardQueryEditor
queries={mockQueries}
panelData={mockPanelData}
datasource={{} as DashboardDatasource}
query={mockQueries[0]}
data={mockPanelData}
onChange={mockOnChange}
onRunQueries={mockOnRunQueries}
onRunQuery={mockOnRunQueries}
/>
);
const select = screen.getByText('Choose panel');

View File

@ -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<DashboardDatasource, DashboardQuery> {}
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<ResultInfo[]> => {
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(

View File

@ -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<DashboardQuery> {
return `Dashboard Reference: ${query.panelId}`;
}
query(options: DataQueryRequest<DashboardQuery>): Promise<DataQueryResponse> {
return Promise.reject('This should not be called directly');
query(options: DataQueryRequest<DashboardQuery>): Observable<DataQueryResponse> {
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<TestDataSourceResponse> {

View File

@ -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);