mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
74da229cd6
commit
cd66149c65
@ -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"],
|
||||
|
@ -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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
@ -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({
|
||||
|
@ -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 };
|
||||
};
|
||||
|
@ -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!;
|
||||
}
|
||||
}
|
||||
|
@ -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": ""
|
||||
}
|
@ -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: '',
|
||||
};
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
@ -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() };
|
||||
|
@ -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: [],
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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]
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user