DashboardScene: Handle Dashboard data source when editing panel (#79991)

* Dashboard model compat wrapper update

* Handle changing data source type to and from Dashboard data source

* VizPanelManager tests

* Betterer

* DashboardModelCompatibilityWrapper tests

* Review: test updates

* Test updates

* Test fix

* Move the complexity of the dashboard data source edit directly to the ShareQueryDataProvider

* Make sure deactivation handler ain't called multiple times

* Make sure compat panel compat wrapper return queries for shared runner with transformations

* Betterer

* VizPanelManager: Remove data object subscription

* Remove unnecesary code
This commit is contained in:
Dominik Prokop 2024-01-12 01:21:32 -08:00 committed by GitHub
parent 74da229cd6
commit cd66149c65
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 834 additions and 506 deletions

View File

@ -2418,12 +2418,14 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
[0, 0, 0, "Unexpected any. Specify a different type.", "4"],
[0, 0, 0, "Unexpected any. Specify a different type.", "5"],
[0, 0, 0, "Unexpected any. Specify a different type.", "6"]
[0, 0, 0, "Unexpected any. Specify a different type.", "6"],
[0, 0, 0, "Unexpected any. Specify a different type.", "7"]
],
"public/app/features/dashboard-scene/panel-edit/VizPanelManager.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"],
[0, 0, 0, "Do not use any type assertions.", "2"]
[0, 0, 0, "Do not use any type assertions.", "2"],
[0, 0, 0, "Do not use any type assertions.", "3"]
],
"public/app/features/dashboard-scene/scene/DashboardScene.test.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
@ -2464,6 +2466,12 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not use any type assertions.", "4"],
[0, 0, 0, "Do not use any type assertions.", "5"]
],
"public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"],
[0, 0, 0, "Do not use any type assertions.", "2"],
[0, 0, 0, "Do not use any type assertions.", "3"]
],
"public/app/features/dashboard-scene/utils/test-utils.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"],

View File

@ -5,6 +5,7 @@ import { SceneObjectBase, SceneComponentProps, SceneQueryRunner, sceneGraph } fr
import { DataQuery } from '@grafana/schema';
import { QueryEditorRows } from 'app/features/query/components/QueryEditorRows';
import { QueryGroupTopSection } from 'app/features/query/components/QueryGroup';
import { DashboardQueryEditor } from 'app/plugins/datasource/dashboard';
import { GrafanaQuery } from 'app/plugins/datasource/grafana/types';
import { QueryGroupOptions } from 'app/types';
@ -15,7 +16,6 @@ import { VizPanelManager } from '../VizPanelManager';
import { PanelDataPaneTabState, PanelDataPaneTab } from './types';
interface PanelDataQueriesTabState extends PanelDataPaneTabState {
// dataRef: SceneObjectRef<SceneQueryRunner | ShareQueryDataProvider>;
datasource?: DataSourceApi;
dsSettings?: DataSourceInstanceSettings;
}
@ -115,6 +115,10 @@ export class PanelDataQueriesTab extends SceneObjectBase<PanelDataQueriesTabStat
this._panelManager.changeQueries(queries);
};
onRunQueries = () => {
this._panelManager.queryRunner.runQueries();
};
getQueries() {
const dataObj = this._panelManager.state.panel.state.$data!;
@ -132,11 +136,6 @@ export class PanelDataQueriesTab extends SceneObjectBase<PanelDataQueriesTabStat
function PanelDataQueriesTabRendered({ model }: SceneComponentProps<PanelDataQueriesTab>) {
const { panel, datasource, dsSettings } = model.panelManager.useState();
const { $data: dataObj } = panel.useState();
if (!dataObj) {
return;
}
const { data } = dataObj!.useState();
if (!datasource || !dsSettings || !data) {
@ -156,7 +155,7 @@ function PanelDataQueriesTabRendered({ model }: SceneComponentProps<PanelDataQue
/>
{dataObj instanceof ShareQueryDataProvider ? (
<h1>TODO: DashboardQueryEditor</h1>
<DashboardQueryEditor queries={model.getQueries()} panelData={data} onChange={model.onQueriesChange} />
) : (
<QueryEditorRows
data={data}
@ -164,7 +163,7 @@ function PanelDataQueriesTabRendered({ model }: SceneComponentProps<PanelDataQue
dsSettings={dsSettings}
onAddQuery={() => {}}
onQueriesChange={model.onQueriesChange}
onRunQueries={() => {}}
onRunQueries={model.onRunQueries}
/>
)}
</>

View File

@ -17,6 +17,7 @@ import {
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
import { DashboardScene } from '../scene/DashboardScene';
import { ShareQueryDataProvider } from '../scene/ShareQueryDataProvider';
import { DashboardModelCompatibilityWrapper } from '../utils/DashboardModelCompatibilityWrapper';
import { getDashboardUrl } from '../utils/urlBuilders';
@ -106,6 +107,13 @@ export class PanelEditor extends SceneObjectBase<PanelEditorState> {
const newState = sceneUtils.cloneSceneObjectState(panelMngr.state.panel.state);
// Remove data provider if it's a share query. For editing purposes the data provider is cloned and attached to the
// ShareQueryDataProvider when panel is in edit mode.
// TODO: Handle transformations when we get on transformations edit.
if (newState.$data instanceof ShareQueryDataProvider) {
newState.$data.setState({ $data: undefined });
}
sourcePanel.setState(newState);
// preserve time range and variables state
@ -129,7 +137,7 @@ export class PanelEditor extends SceneObjectBase<PanelEditorState> {
export function buildPanelEditScene(dashboard: DashboardScene, panel: VizPanel): PanelEditor {
const panelClone = panel.clone();
const vizPanelMgr = new VizPanelManager(panelClone, dashboard.getRef());
const vizPanelMgr = new VizPanelManager(panelClone);
const dashboardStateCloned = sceneUtils.cloneSceneObjectState(dashboard.state);
return new PanelEditor({

View File

@ -9,15 +9,15 @@ import { InspectTab } from 'app/features/inspector/types';
import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard';
import { DASHBOARD_DATASOURCE_PLUGIN_ID } from 'app/plugins/datasource/dashboard/types';
import { DashboardScene } from '../scene/DashboardScene';
import { PanelTimeRange, PanelTimeRangeState } from '../scene/PanelTimeRange';
import { ShareQueryDataProvider } from '../scene/ShareQueryDataProvider';
import { transformSaveModelToScene } from '../serialization/transformSaveModelToScene';
import { DashboardModelCompatibilityWrapper } from '../utils/DashboardModelCompatibilityWrapper';
import { findVizPanelByKey } from '../utils/utils';
import { buildPanelEditScene } from './PanelEditor';
import { VizPanelManager } from './VizPanelManager';
import testDashboard from './testfiles/testDashboard.json';
import { panelWithQueriesOnly, panelWithTransformations, testDashboard } from './testfiles/testDashboard';
const runRequestMock = jest.fn().mockImplementation((ds: DataSourceApi, request: DataQueryRequest) => {
const result: PanelData = {
@ -143,14 +143,13 @@ jest.mock('@grafana/runtime', () => ({
describe('VizPanelManager', () => {
describe('changePluginType', () => {
it('Should successfully change from one viz type to another', () => {
const vizPanelManager = setupTest('panel-1');
const { vizPanelManager } = setupTest('panel-1');
expect(vizPanelManager.state.panel.state.pluginId).toBe('timeseries');
vizPanelManager.changePluginType('table');
expect(vizPanelManager.state.panel.state.pluginId).toBe('table');
});
it('Should clear custom options', () => {
const dashboardSceneMock = new DashboardScene({});
const overrides = [
{
matcher: { id: 'matcherOne' },
@ -171,7 +170,7 @@ describe('VizPanelManager', () => {
},
});
const vizPanelManager = new VizPanelManager(vizPanel, dashboardSceneMock.getRef());
const vizPanelManager = new VizPanelManager(vizPanel);
expect(vizPanelManager.state.panel.state.fieldConfig.defaults.custom).toBe('Custom');
expect(vizPanelManager.state.panel.state.fieldConfig.overrides).toBe(overrides);
@ -184,7 +183,6 @@ describe('VizPanelManager', () => {
});
it('Should restore cached options/fieldConfig if they exist', () => {
const dashboardSceneMock = new DashboardScene({});
const vizPanel = new VizPanel({
title: 'Panel A',
key: 'panel-1',
@ -196,9 +194,9 @@ describe('VizPanelManager', () => {
fieldConfig: { defaults: { custom: 'Custom' }, overrides: [] },
});
const vizPanelManager = new VizPanelManager(vizPanel, dashboardSceneMock.getRef());
const vizPanelManager = new VizPanelManager(vizPanel);
vizPanelManager.changePluginType('timeseties');
vizPanelManager.changePluginType('timeseries');
//@ts-ignore
expect(vizPanelManager.state.panel.state.options['customOption']).toBeUndefined();
expect(vizPanelManager.state.panel.state.fieldConfig.defaults.custom).toStrictEqual({});
@ -218,7 +216,7 @@ describe('VizPanelManager', () => {
describe('activation', () => {
it('should load data source', async () => {
const vizPanelManager = setupTest('panel-1');
const { vizPanelManager } = setupTest('panel-1');
vizPanelManager.activate();
await Promise.resolve();
@ -227,7 +225,7 @@ describe('VizPanelManager', () => {
});
it('should store loaded data source in local storage', async () => {
const vizPanelManager = setupTest('panel-1');
const { vizPanelManager } = setupTest('panel-1');
vizPanelManager.activate();
await Promise.resolve();
@ -240,20 +238,17 @@ describe('VizPanelManager', () => {
describe('data source change', () => {
it('should load new data source', async () => {
const vizPanelManager = setupTest('panel-1');
const { vizPanelManager } = setupTest('panel-1');
vizPanelManager.activate();
await Promise.resolve();
const dataObj = vizPanelManager.queryRunner;
dataObj.setState({
datasource: {
type: 'grafana-prometheus-datasource',
uid: 'gdev-prometheus',
},
});
vizPanelManager.state.panel.state.$data?.activate();
await Promise.resolve();
await vizPanelManager.changePanelDataSource(
{ type: 'grafana-prometheus-datasource', uid: 'gdev-prometheus' } as any,
[]
);
expect(store.setObject).toHaveBeenCalledTimes(2);
expect(store.setObject).toHaveBeenLastCalledWith('grafana.dashboards.panelEdit.lastUsedDatasource', {
dashboardUid: 'ffbe00e2-803c-4d49-adb7-41aad336234f',
@ -268,8 +263,9 @@ describe('VizPanelManager', () => {
describe('query options change', () => {
describe('time overrides', () => {
it('should create PanelTimeRange object', async () => {
const vizPanelManager = setupTest('panel-1');
const { vizPanelManager } = setupTest('panel-1');
vizPanelManager.activate();
vizPanelManager.state.panel.state.$data?.activate();
await Promise.resolve();
const panel = vizPanelManager.state.panel;
@ -291,7 +287,7 @@ describe('VizPanelManager', () => {
expect(panel.state.$timeRange).toBeInstanceOf(PanelTimeRange);
});
it('should update PanelTimeRange object on time options update', async () => {
const vizPanelManager = setupTest('panel-1');
const { vizPanelManager } = setupTest('panel-1');
vizPanelManager.activate();
await Promise.resolve();
@ -330,7 +326,7 @@ describe('VizPanelManager', () => {
});
it('should remove PanelTimeRange object on time options cleared', async () => {
const vizPanelManager = setupTest('panel-1');
const { vizPanelManager } = setupTest('panel-1');
vizPanelManager.activate();
await Promise.resolve();
@ -370,7 +366,7 @@ describe('VizPanelManager', () => {
describe('max data points and interval', () => {
it('max data points', async () => {
const vizPanelManager = setupTest('panel-1');
const { vizPanelManager } = setupTest('panel-1');
vizPanelManager.activate();
await Promise.resolve();
@ -392,7 +388,7 @@ describe('VizPanelManager', () => {
});
it('max data points', async () => {
const vizPanelManager = setupTest('panel-1');
const { vizPanelManager } = setupTest('panel-1');
vizPanelManager.activate();
await Promise.resolve();
@ -417,7 +413,7 @@ describe('VizPanelManager', () => {
describe('query inspection', () => {
it('allows query inspection from the tab', async () => {
const vizPanelManager = setupTest('panel-1');
const { vizPanelManager } = setupTest('panel-1');
vizPanelManager.inspectPanel();
expect(locationService.partial).toHaveBeenCalledWith({ inspect: 1, inspectTab: InspectTab.Query });
@ -426,7 +422,7 @@ describe('VizPanelManager', () => {
describe('data source change', () => {
it('changing from one plugin to another', async () => {
const vizPanelManager = setupTest('panel-1');
const { vizPanelManager } = setupTest('panel-1');
vizPanelManager.activate();
await Promise.resolve();
@ -457,7 +453,7 @@ describe('VizPanelManager', () => {
});
it('changing from a plugin to a dashboard data source', async () => {
const vizPanelManager = setupTest('panel-1');
const { vizPanelManager } = setupTest('panel-1');
vizPanelManager.activate();
await Promise.resolve();
@ -485,7 +481,7 @@ describe('VizPanelManager', () => {
});
it('changing from dashboard data source to a plugin', async () => {
const vizPanelManager = setupTest('panel-3');
const { vizPanelManager } = setupTest('panel-3');
vizPanelManager.activate();
await Promise.resolve();
@ -513,7 +509,7 @@ describe('VizPanelManager', () => {
describe('with transformations', () => {
it('changing from one plugin to another', async () => {
const vizPanelManager = setupTest('panel-2');
const { vizPanelManager } = setupTest('panel-2');
vizPanelManager.activate();
await Promise.resolve();
@ -546,7 +542,7 @@ describe('VizPanelManager', () => {
});
it('changing from a plugin to dashboard data source', async () => {
const vizPanelManager = setupTest('panel-2');
const { vizPanelManager } = setupTest('panel-2');
vizPanelManager.activate();
await Promise.resolve();
@ -575,7 +571,7 @@ describe('VizPanelManager', () => {
});
it('changing from a dashboard data source to a plugin', async () => {
const vizPanelManager = setupTest('panel-4');
const { vizPanelManager } = setupTest('panel-4');
vizPanelManager.activate();
await Promise.resolve();
@ -604,18 +600,99 @@ describe('VizPanelManager', () => {
});
});
});
describe('change queries', () => {
describe('plugin queries', () => {
it('should update queries', () => {
const { vizPanelManager } = setupTest('panel-1');
vizPanelManager.activate();
vizPanelManager.state.panel.state.$data?.activate();
vizPanelManager.changeQueries([
{
datasource: {
type: 'grafana-testdata-datasource',
uid: 'gdev-testdata',
},
refId: 'A',
scenarioId: 'random_walk',
seriesCount: 5,
},
]);
expect(vizPanelManager.queryRunner.state.queries).toEqual([
{
datasource: {
type: 'grafana-testdata-datasource',
uid: 'gdev-testdata',
},
refId: 'A',
scenarioId: 'random_walk',
seriesCount: 5,
},
]);
});
});
describe('dashboard queries', () => {
it('should update queries', () => {
const { scene, panel } = setupTest('panel-3');
const panelEditScene = buildPanelEditScene(scene, panel);
const vizPanelManager = panelEditScene.state.panelRef.resolve();
vizPanelManager.activate();
vizPanelManager.state.panel.state.$data?.activate();
// Changing dashboard query to a panel with transformations
vizPanelManager.changeQueries([
{
refId: 'A',
datasource: {
type: DASHBOARD_DATASOURCE_PLUGIN_ID,
},
panelId: panelWithTransformations.id,
},
]);
expect(vizPanelManager.panelData).toBeInstanceOf(ShareQueryDataProvider);
expect((vizPanelManager.panelData as ShareQueryDataProvider).state.query.panelId).toBe(
panelWithTransformations.id
);
expect(vizPanelManager.panelData.state.$data).toBeInstanceOf(SceneDataTransformer);
expect(vizPanelManager.panelData.state.$data?.state.$data).toBeInstanceOf(SceneQueryRunner);
expect((vizPanelManager.panelData.state.$data?.state.$data as SceneQueryRunner).state.queries).toEqual(
panelWithTransformations.targets
);
// Changing dashboard query to a panel with queries only
vizPanelManager.changeQueries([
{
refId: 'A',
datasource: {
type: DASHBOARD_DATASOURCE_PLUGIN_ID,
},
panelId: panelWithQueriesOnly.id,
},
]);
expect(vizPanelManager.panelData).toBeInstanceOf(ShareQueryDataProvider);
expect((vizPanelManager.panelData as ShareQueryDataProvider).state.query.panelId).toBe(panelWithQueriesOnly.id);
expect(vizPanelManager.queryRunner.state.queries).toEqual(panelWithQueriesOnly.targets);
});
});
});
});
const setupTest = (panelId: string) => {
const scene = transformSaveModelToScene({ dashboard: testDashboard as any, meta: {} });
const panel = findVizPanelByKey(scene, panelId)!;
const vizPanelManager = new VizPanelManager(panel.clone());
// The following happens on DahsboardScene activation. For the needs of this test this activation aint needed hence we hand-call it
// @ts-expect-error
getDashboardSrv().setCurrent(new DashboardModelCompatibilityWrapper(scene));
const panel = findVizPanelByKey(scene, panelId)!;
const vizPanelManager = new VizPanelManager(panel.clone(), scene.getRef());
return vizPanelManager;
return { vizPanelManager, scene, panel };
};

View File

@ -1,5 +1,4 @@
import React from 'react';
import { Unsubscribable } from 'rxjs';
import {
DataSourceApi,
@ -20,8 +19,6 @@ import {
SceneComponentProps,
sceneUtils,
DeepPartial,
SceneObjectRef,
SceneObject,
SceneQueryRunner,
sceneGraph,
SceneDataTransformer,
@ -32,14 +29,13 @@ import { getPluginVersion } from 'app/features/dashboard/state/PanelModel';
import { storeLastUsedDataSourceInLocalStorage } from 'app/features/datasources/components/picker/utils';
import { updateQueries } from 'app/features/query/state/updateQueries';
import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard';
import { DASHBOARD_DATASOURCE_PLUGIN_ID } from 'app/plugins/datasource/dashboard/types';
import { DASHBOARD_DATASOURCE_PLUGIN_ID, DashboardQuery } from 'app/plugins/datasource/dashboard/types';
import { GrafanaQuery } from 'app/plugins/datasource/grafana/types';
import { QueryGroupOptions } from 'app/types';
import { DashboardScene } from '../scene/DashboardScene';
import { PanelTimeRange, PanelTimeRangeState } from '../scene/PanelTimeRange';
import { ShareQueryDataProvider, findObjectInScene } from '../scene/ShareQueryDataProvider';
import { getPanelIdForVizPanel, getVizPanelKeyForPanelId } from '../utils/utils';
import { ShareQueryDataProvider } from '../scene/ShareQueryDataProvider';
import { getPanelIdForVizPanel } from '../utils/utils';
interface VizPanelManagerState extends SceneObjectState {
panel: VizPanel;
@ -60,57 +56,14 @@ export class VizPanelManager extends SceneObjectBase<VizPanelManagerState> {
{ options: DeepPartial<{}>; fieldConfig: FieldConfigSource<DeepPartial<{}>> } | undefined
> = {};
private _dataObjectSubscription: Unsubscribable | undefined;
public constructor(panel: VizPanel, dashboardRef: SceneObjectRef<DashboardScene>) {
public constructor(panel: VizPanel) {
super({ panel });
/**
* If the panel uses a shared query, we clone the source runner and attach it as a data provider for the shared one.
* This way the source panel does not to be present in the edit scene hierarchy.
*/
if (panel.state.$data instanceof ShareQueryDataProvider) {
const sharedProvider = panel.state.$data;
if (sharedProvider.state.query.panelId) {
const keyToFind = getVizPanelKeyForPanelId(sharedProvider.state.query.panelId);
const source = findObjectInScene(dashboardRef.resolve(), (scene: SceneObject) => scene.state.key === keyToFind);
if (source) {
sharedProvider.setState({
$data: source.state.$data!.clone(),
});
}
}
}
this.addActivationHandler(() => this._onActivate());
}
private _onActivate() {
this.setupDataObjectSubscription();
this.loadDataSource();
return () => {
this._dataObjectSubscription?.unsubscribe();
};
}
/**
* The subscription is updated whenever the data source type is changed so that we can update manager's stored
* data source and data source instance settings, which are needed for the query options and editors
*/
private setupDataObjectSubscription() {
const runner = this.queryRunner;
if (this._dataObjectSubscription) {
this._dataObjectSubscription.unsubscribe();
}
this._dataObjectSubscription = runner.subscribeToState((n, p) => {
if (n.datasource !== p.datasource) {
this.loadDataSource();
}
});
}
private async loadDataSource() {
@ -207,7 +160,6 @@ export class VizPanelManager extends SceneObjectBase<VizPanelManagerState> {
}
this.setState({ panel: newPanel });
this.setupDataObjectSubscription();
}
public async changePanelDataSource(
@ -249,8 +201,6 @@ export class VizPanelManager extends SceneObjectBase<VizPanelManagerState> {
},
});
panel.setState({ $data: sharedProvider });
this.setupDataObjectSubscription();
this.loadDataSource();
} else {
dataObj.setState({
datasource: {
@ -272,8 +222,6 @@ export class VizPanelManager extends SceneObjectBase<VizPanelManagerState> {
queries,
});
panel.setState({ $data: dataProvider });
this.setupDataObjectSubscription();
this.loadDataSource();
} else if (dataObj instanceof SceneDataTransformer) {
const data = dataObj.clone();
@ -304,10 +252,8 @@ export class VizPanelManager extends SceneObjectBase<VizPanelManagerState> {
});
panel.setState({ $data: data });
this.setupDataObjectSubscription();
this.loadDataSource();
}
this.loadDataSource();
}
public changeQueryOptions(options: QueryGroupOptions) {
@ -346,10 +292,20 @@ export class VizPanelManager extends SceneObjectBase<VizPanelManagerState> {
dataObj.runQueries();
}
public changeQueries(queries: DataQuery[]) {
const dataObj = this.queryRunner;
dataObj.setState({ queries });
// TODO: Handle dashboard query
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 });
}
}
public inspectPanel() {
@ -375,4 +331,8 @@ export class VizPanelManager extends SceneObjectBase<VizPanelManagerState> {
return dataObj as SceneQueryRunner;
}
get panelData(): SceneDataProvider {
return this.state.panel.state.$data!;
}
}

View File

@ -1,368 +0,0 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 2378,
"links": [],
"liveNow": false,
"panels": [
{
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "gdev-testdata"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 0
},
"id": 1,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "gdev-testdata"
},
"refId": "A",
"scenarioId": "random_walk",
"seriesCount": 1
}
],
"title": "Source panel",
"type": "timeseries"
},
{
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "gdev-testdata"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"custom": {
"align": "auto",
"cellOptions": {
"type": "auto"
},
"inspect": false
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 0
},
"id": 2,
"options": {
"cellHeight": "sm",
"footer": {
"countRows": false,
"fields": "",
"reducer": ["sum"],
"show": false
},
"showHeader": true
},
"pluginVersion": "10.3.0-pre",
"targets": [
{
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "gdev-testdata"
},
"refId": "A",
"scenarioId": "random_walk",
"seriesCount": 1
}
],
"title": "Panel with transforms",
"transformations": [
{
"id": "reduce",
"options": {}
}
],
"type": "table"
},
{
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 8
},
"id": 3,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"panelId": 1,
"refId": "A"
}
],
"title": "Dashboard query",
"type": "timeseries"
},
{
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"custom": {
"align": "auto",
"cellOptions": {
"type": "auto"
},
"inspect": false
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 8
},
"id": 4,
"options": {
"cellHeight": "sm",
"footer": {
"countRows": false,
"fields": "",
"reducer": ["sum"],
"show": false
},
"showHeader": true
},
"pluginVersion": "10.3.0-pre",
"targets": [
{
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"panelId": 1,
"refId": "A"
}
],
"title": "Dashboard query with transformations",
"transformations": [
{
"id": "reduce",
"options": {}
}
],
"type": "table"
}
],
"refresh": "",
"schemaVersion": 39,
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "Scenes/PanelEdit/Queries: Edit",
"uid": "ffbe00e2-803c-4d49-adb7-41aad336234f",
"version": 6,
"weekStart": ""
}

View File

@ -0,0 +1,375 @@
export const panelWithQueriesOnly = {
datasource: {
type: 'grafana-testdata-datasource',
uid: 'gdev-testdata',
},
fieldConfig: {
defaults: {
color: {
mode: 'palette-classic',
},
custom: {
axisBorderShow: false,
axisCenteredZero: false,
axisColorMode: 'text',
axisLabel: '',
axisPlacement: 'auto',
barAlignment: 0,
drawStyle: 'line',
fillOpacity: 0,
gradientMode: 'none',
hideFrom: {
legend: false,
tooltip: false,
viz: false,
},
insertNulls: false,
lineInterpolation: 'linear',
lineWidth: 1,
pointSize: 5,
scaleDistribution: {
type: 'linear',
},
showPoints: 'auto',
spanNulls: false,
stacking: {
group: 'A',
mode: 'none',
},
thresholdsStyle: {
mode: 'off',
},
},
mappings: [],
thresholds: {
mode: 'absolute',
steps: [
{
color: 'green',
value: null,
},
{
color: 'red',
value: 80,
},
],
},
},
overrides: [],
},
gridPos: {
h: 8,
w: 12,
x: 0,
y: 0,
},
id: 1,
options: {
legend: {
calcs: [],
displayMode: 'list',
placement: 'bottom',
showLegend: true,
},
tooltip: {
mode: 'single',
sort: 'none',
},
},
targets: [
{
datasource: {
type: 'grafana-testdata-datasource',
uid: 'gdev-testdata',
},
refId: 'A',
scenarioId: 'random_walk',
seriesCount: 1,
},
],
title: 'Panel with just queries',
type: 'timeseries',
};
export const panelWithTransformations = {
datasource: {
type: 'grafana-testdata-datasource',
uid: 'gdev-testdata',
},
fieldConfig: {
defaults: {
color: {
mode: 'thresholds',
},
custom: {
align: 'auto',
cellOptions: {
type: 'auto',
},
inspect: false,
},
mappings: [],
thresholds: {
mode: 'absolute',
steps: [
{
color: 'green',
value: null,
},
{
color: 'red',
value: 80,
},
],
},
},
overrides: [],
},
gridPos: {
h: 8,
w: 12,
x: 12,
y: 0,
},
id: 2,
options: {
cellHeight: 'sm',
footer: {
countRows: false,
fields: '',
reducer: ['sum'],
show: false,
},
showHeader: true,
},
pluginVersion: '10.3.0-pre',
targets: [
{
datasource: {
type: 'grafana-testdata-datasource',
uid: 'gdev-testdata',
},
refId: 'A',
scenarioId: 'random_walk',
seriesCount: 1,
},
],
title: 'Panel with transforms',
transformations: [
{
id: 'reduce',
options: {},
},
],
type: 'table',
};
export const panelWithDashboardQuery = {
datasource: {
type: 'datasource',
uid: '-- Dashboard --',
},
fieldConfig: {
defaults: {
color: {
mode: 'palette-classic',
},
custom: {
axisBorderShow: false,
axisCenteredZero: false,
axisColorMode: 'text',
axisLabel: '',
axisPlacement: 'auto',
barAlignment: 0,
drawStyle: 'line',
fillOpacity: 0,
gradientMode: 'none',
hideFrom: {
legend: false,
tooltip: false,
viz: false,
},
insertNulls: false,
lineInterpolation: 'linear',
lineWidth: 1,
pointSize: 5,
scaleDistribution: {
type: 'linear',
},
showPoints: 'auto',
spanNulls: false,
stacking: {
group: 'A',
mode: 'none',
},
thresholdsStyle: {
mode: 'off',
},
},
mappings: [],
thresholds: {
mode: 'absolute',
steps: [
{
color: 'green',
value: null,
},
{
color: 'red',
value: 80,
},
],
},
},
overrides: [],
},
gridPos: {
h: 8,
w: 12,
x: 0,
y: 8,
},
id: 3,
options: {
legend: {
calcs: [],
displayMode: 'list',
placement: 'bottom',
showLegend: true,
},
tooltip: {
mode: 'single',
sort: 'none',
},
},
targets: [
{
datasource: {
type: 'datasource',
uid: '-- Dashboard --',
},
panelId: 1,
refId: 'A',
},
],
title: 'Panel with a Dashboard query',
type: 'timeseries',
};
export const panelWithDashboardQueryAndTransformations = {
datasource: {
type: 'datasource',
uid: '-- Dashboard --',
},
fieldConfig: {
defaults: {
color: {
mode: 'thresholds',
},
custom: {
align: 'auto',
cellOptions: {
type: 'auto',
},
inspect: false,
},
mappings: [],
thresholds: {
mode: 'absolute',
steps: [
{
color: 'green',
value: null,
},
{
color: 'red',
value: 80,
},
],
},
},
overrides: [],
},
gridPos: {
h: 8,
w: 12,
x: 12,
y: 8,
},
id: 4,
options: {
cellHeight: 'sm',
footer: {
countRows: false,
fields: '',
reducer: ['sum'],
show: false,
},
showHeader: true,
},
pluginVersion: '10.3.0-pre',
targets: [
{
datasource: {
type: 'datasource',
uid: '-- Dashboard --',
},
panelId: 1,
refId: 'A',
},
],
title: 'Panel with a dashboard query with transformations',
transformations: [
{
id: 'reduce',
options: {},
},
],
type: 'table',
};
export const testDashboard = {
annotations: {
list: [
{
builtIn: 1,
datasource: {
type: 'grafana',
uid: '-- Grafana --',
},
enable: true,
hide: true,
iconColor: 'rgba(0, 211, 255, 1)',
name: 'Annotations & Alerts',
type: 'dashboard',
},
],
},
editable: true,
fiscalYearStartMonth: 0,
graphTooltip: 0,
id: 2378,
links: [],
liveNow: false,
panels: [
panelWithQueriesOnly,
panelWithTransformations,
panelWithDashboardQuery,
panelWithDashboardQueryAndTransformations,
],
refresh: '',
schemaVersion: 39,
tags: [],
templating: {
list: [],
},
time: {
from: 'now-6h',
to: 'now',
},
timepicker: {},
timezone: '',
title: 'Scenes/PanelEdit/Queries: Edit',
uid: 'ffbe00e2-803c-4d49-adb7-41aad336234f',
version: 6,
weekStart: '',
};

View File

@ -53,4 +53,69 @@ describe('ShareQueryDataProvider', () => {
expect(sceneGraph.getData(panel).state.data?.structureRev).toBe(12);
});
it('should find and update to another VizPanels data provider when query changed', () => {
const sharedQuery = new ShareQueryDataProvider({
query: { refId: 'A', panelId: 1 },
});
const panelWithSharedQuery = new SceneDummyPanel({
key: getVizPanelKeyForPanelId(3),
$data: sharedQuery,
});
const sourceData1 = new SceneDataNode({
data: {
series: [],
state: LoadingState.Done,
timeRange: getDefaultTimeRange(),
structureRev: 1,
},
});
const sourceData2 = new SceneDataNode({
data: {
series: [],
state: LoadingState.Done,
timeRange: getDefaultTimeRange(),
structureRev: 100,
},
});
const sourcePanel1 = new SceneDummyPanel({
key: getVizPanelKeyForPanelId(1),
$data: sourceData1,
});
const sourcePanel2 = new SceneDummyPanel({
key: getVizPanelKeyForPanelId(2),
$data: sourceData2,
});
const scene = new SceneFlexLayout({
children: [
new SceneFlexItem({
body: sourcePanel1,
}),
new SceneFlexItem({
body: sourcePanel2,
}),
new SceneFlexItem({ body: panelWithSharedQuery }),
],
});
activateFullSceneTree(scene);
expect(sceneGraph.getData(panelWithSharedQuery).state.data?.structureRev).toBe(1);
sharedQuery.setState({
query: {
refId: 'A',
panelId: 2,
},
});
expect(sceneGraph.getData(panelWithSharedQuery).state.data?.structureRev).toBe(100);
sourceData2.setState({ data: { ...sourceData2.state.data!, structureRev: 101 } });
expect(sceneGraph.getData(panelWithSharedQuery).state.data?.structureRev).toBe(101);
});
});

View File

@ -1,6 +1,6 @@
import { Observable, ReplaySubject, Unsubscribable } from 'rxjs';
import { getDefaultTimeRange, LoadingState } from '@grafana/data';
import { getDefaultTimeRange, LoadingState, PanelData } from '@grafana/data';
import {
SceneDataProvider,
SceneDataProviderResult,
@ -12,6 +12,7 @@ import {
} from '@grafana/scenes';
import { DashboardQuery } from 'app/plugins/datasource/dashboard/types';
import { PanelEditor } from '../panel-edit/PanelEditor';
import { getVizPanelKeyForPanelId } from '../utils/utils';
export interface ShareQueryDataProviderState extends SceneDataState {
@ -26,25 +27,57 @@ export class ShareQueryDataProvider extends SceneObjectBase<ShareQueryDataProvid
private _passContainerWidth = false;
constructor(state: ShareQueryDataProviderState) {
super(state);
this.addActivationHandler(() => {
// TODO handle changes to query model (changed panelId / withTransforms)
//this.subscribeToState(this._onStateChanged);
this._subscribeToSource();
return () => {
if (this._querySub) {
this._querySub.unsubscribe();
}
if (this._sourceDataDeactivationHandler) {
this._sourceDataDeactivationHandler();
}
};
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;
}
@ -56,6 +89,11 @@ export class ShareQueryDataProvider extends SceneObjectBase<ShareQueryDataProvid
this._querySub.unsubscribe();
}
if (this._sourceDataDeactivationHandler) {
this._sourceDataDeactivationHandler();
this._sourceDataDeactivationHandler = undefined;
}
if (this.state.$data) {
this._sourceProvider = this.state.$data;
this._passContainerWidth = true;
@ -110,9 +148,20 @@ export class ShareQueryDataProvider extends SceneObjectBase<ShareQueryDataProvid
});
// Copy the initial state
this.setState({ data: this._sourceProvider.state.data });
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);
@ -143,3 +192,5 @@ export function findObjectInScene(scene: SceneObject, check: (scene: SceneObject
return found;
}
const emptyPanelData: PanelData = { state: LoadingState.Done, series: [], timeRange: getDefaultTimeRange() };

View File

@ -8,12 +8,15 @@ import {
SceneTimeRange,
VizPanel,
SceneTimePicker,
SceneDataTransformer,
} from '@grafana/scenes';
import { DashboardCursorSync } from '@grafana/schema';
import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard';
import { DashboardControls } from '../scene/DashboardControls';
import { DashboardLinksControls } from '../scene/DashboardLinksControls';
import { DashboardScene } from '../scene/DashboardScene';
import { ShareQueryDataProvider } from '../scene/ShareQueryDataProvider';
import { DashboardModelCompatibilityWrapper } from './DashboardModelCompatibilityWrapper';
@ -32,6 +35,27 @@ describe('DashboardModelCompatibilityWrapper', () => {
expect(wrapper.weekStart).toBe('friday');
expect(wrapper.timepicker.refresh_intervals).toEqual(['1s']);
expect(wrapper.timepicker.hidden).toEqual(true);
expect(wrapper.panels).toHaveLength(5);
expect(wrapper.panels[0].targets).toHaveLength(1);
expect(wrapper.panels[0].targets[0]).toEqual({ refId: 'A' });
expect(wrapper.panels[1].targets).toHaveLength(0);
expect(wrapper.panels[2].targets).toHaveLength(1);
expect(wrapper.panels[2].targets).toEqual([
{ datasource: { uid: SHARED_DASHBOARD_QUERY, type: 'datasource' }, refId: 'A', panelId: 1 },
]);
expect(wrapper.panels[3].targets).toHaveLength(1);
expect(wrapper.panels[3].targets[0]).toEqual({ refId: 'A' });
expect(wrapper.panels[4].targets).toHaveLength(1);
expect(wrapper.panels[4].targets).toEqual([
{ datasource: { uid: SHARED_DASHBOARD_QUERY, type: 'datasource' }, refId: 'A', panelId: 1 },
]);
expect(wrapper.panels[0].datasource).toEqual({ uid: 'gdev-testdata', type: 'grafana-testdata-datasource' });
expect(wrapper.panels[1].datasource).toEqual(null);
expect(wrapper.panels[2].datasource).toEqual({ uid: SHARED_DASHBOARD_QUERY, type: 'datasource' });
expect(wrapper.panels[3].datasource).toEqual({ uid: 'gdev-testdata', type: 'grafana-testdata-datasource' });
expect(wrapper.panels[4].datasource).toEqual({ uid: SHARED_DASHBOARD_QUERY, type: 'datasource' });
(scene.state.controls![0] as DashboardControls).setState({
hideTimeControls: false,
@ -70,16 +94,18 @@ describe('DashboardModelCompatibilityWrapper', () => {
it('Can get fake panel with getPanelById', () => {
const { wrapper } = setup();
expect(wrapper.getPanelById(1)!.title).toBe('Panel A');
expect(wrapper.getPanelById(2)!.title).toBe('Panel B');
expect(wrapper.getPanelById(1)!.title).toBe('Panel with a regular data source query');
expect(wrapper.getPanelById(2)!.title).toBe('Panel with no queries');
});
it('Can remove panel', () => {
const { wrapper, scene } = setup();
expect((scene.state.body as SceneGridLayout).state.children.length).toBe(5);
wrapper.removePanel(wrapper.getPanelById(1)!);
expect((scene.state.body as SceneGridLayout).state.children.length).toBe(1);
expect((scene.state.body as SceneGridLayout).state.children.length).toBe(4);
});
});
@ -113,19 +139,59 @@ function setup() {
key: 'griditem-1',
x: 0,
body: new VizPanel({
title: 'Panel A',
title: 'Panel with a regular data source query',
key: 'panel-1',
pluginId: 'table',
$data: new SceneQueryRunner({ key: 'data-query-runner', queries: [{ refId: 'A' }] }),
$data: new SceneQueryRunner({
key: 'data-query-runner',
queries: [{ refId: 'A' }],
datasource: { uid: 'gdev-testdata', type: 'grafana-testdata-datasource' },
}),
}),
}),
new SceneGridItem({
body: new VizPanel({
title: 'Panel B',
title: 'Panel with no queries',
key: 'panel-2',
pluginId: 'table',
}),
}),
new SceneGridItem({
body: new VizPanel({
title: 'Panel with a shared query',
key: 'panel-3',
pluginId: 'table',
$data: new ShareQueryDataProvider({ query: { refId: 'A', panelId: 1 } }),
}),
}),
new SceneGridItem({
body: new VizPanel({
title: 'Panel with a regular data source query and transformations',
key: 'panel-4',
pluginId: 'table',
$data: new SceneDataTransformer({
$data: new SceneQueryRunner({
key: 'data-query-runner',
queries: [{ refId: 'A' }],
datasource: { uid: 'gdev-testdata', type: 'grafana-testdata-datasource' },
}),
transformations: [],
}),
}),
}),
new SceneGridItem({
body: new VizPanel({
title: 'Panel with a shared query and transformations',
key: 'panel-4',
pluginId: 'table',
$data: new SceneDataTransformer({
$data: new ShareQueryDataProvider({ query: { refId: 'A', panelId: 1 } }),
transformations: [],
}),
}),
}),
],
}),
});

View File

@ -9,10 +9,16 @@ import {
SceneGridItem,
SceneGridLayout,
SceneGridRow,
SceneObject,
SceneQueryRunner,
VizPanel,
} from '@grafana/scenes';
import { DataSourceRef } from '@grafana/schema';
import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard';
import { DashboardScene } from '../scene/DashboardScene';
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
import { ShareQueryDataProvider } from '../scene/ShareQueryDataProvider';
import { dashboardSceneGraph } from './dashboardSceneGraph';
import { findVizPanelByKey, getPanelIdForVizPanel, getVizPanelKeyForPanelId } from './utils';
@ -91,6 +97,13 @@ export class DashboardModelCompatibilityWrapper {
};
}
public get panels() {
const panels = findAllObjects(this._scene, (o) => {
return Boolean(o instanceof VizPanel);
});
return panels.map((p) => new PanelCompatibilityWrapper(p as VizPanel));
}
/**
* Used from from timeseries migration handler to migrate time regions to dashboard annotations
*/
@ -206,7 +219,9 @@ class PanelCompatibilityWrapper {
constructor(private _vizPanel: VizPanel) {}
public get id() {
const id = getPanelIdForVizPanel(this._vizPanel);
const id = getPanelIdForVizPanel(
this._vizPanel.parent instanceof LibraryVizPanel ? this._vizPanel.parent : this._vizPanel
);
if (isNaN(id)) {
console.error('VizPanel key could not be translated to a legacy numeric panel id', this._vizPanel);
@ -232,6 +247,63 @@ class PanelCompatibilityWrapper {
return [];
}
public get targets() {
if (this._vizPanel.state.$data instanceof SceneQueryRunner) {
return this._vizPanel.state.$data.state.queries;
}
if (this._vizPanel.state.$data instanceof ShareQueryDataProvider) {
return [
{
datasource: {
uid: SHARED_DASHBOARD_QUERY,
type: 'datasource',
},
...this._vizPanel.state.$data.state.query,
},
];
}
if (this._vizPanel.state.$data instanceof SceneDataTransformer) {
if (this._vizPanel.state.$data.state.$data instanceof ShareQueryDataProvider) {
return [
{
datasource: {
uid: SHARED_DASHBOARD_QUERY,
type: 'datasource',
},
...(this._vizPanel.state.$data.state.$data as ShareQueryDataProvider).state.query,
},
];
}
if (this._vizPanel.state.$data.state.$data instanceof SceneQueryRunner) {
return (this._vizPanel.state.$data.state.$data as SceneQueryRunner).state.queries;
}
}
return [];
}
public get datasource(): DataSourceRef | null {
if (this._vizPanel.state.$data instanceof SceneQueryRunner) {
return this._vizPanel.state.$data.state.datasource ?? null;
}
if (this._vizPanel.state.$data instanceof ShareQueryDataProvider) {
return { uid: SHARED_DASHBOARD_QUERY, type: 'datasource' };
}
if (this._vizPanel.state.$data instanceof SceneDataTransformer) {
if (this._vizPanel.state.$data.state.$data instanceof ShareQueryDataProvider) {
return { uid: SHARED_DASHBOARD_QUERY, type: 'datasource' };
}
return (this._vizPanel.state.$data.state.$data as SceneQueryRunner).state.datasource ?? null;
}
return null;
}
public refresh() {
console.error('Scenes PanelCompatibilityWrapper.refresh no implemented (yet)');
}
@ -244,3 +316,16 @@ class PanelCompatibilityWrapper {
console.error('Scenes PanelCompatibilityWrapper.getQueryRunner no implemented (yet)');
}
}
function findAllObjects(root: SceneObject, check: (o: SceneObject) => boolean) {
let result: SceneObject[] = [];
root.forEachChild((child) => {
if (check(child)) {
result.push(child);
} else {
result = result.concat(findAllObjects(child, check));
}
});
return result;
}

View File

@ -32,7 +32,7 @@ interface Props {
queries: DataQuery[];
panelData: PanelData;
onChange: (queries: DataQuery[]) => void;
onRunQueries: () => void;
onRunQueries?: () => void;
}
const topics = [
@ -74,7 +74,9 @@ export function DashboardQueryEditor({ panelData, queries, onChange, onRunQuerie
const onUpdateQuery = useCallback(
(query: DashboardQuery) => {
onChange([query]);
onRunQueries();
if (onRunQueries) {
onRunQueries();
}
},
[onChange, onRunQueries]
);