DashboardScene: Do not show data pane for panels with skipDataQuery (#81934)

* Bump scenes

* Don't show data pane for panels without data support

* Add tests

* Review
This commit is contained in:
Dominik Prokop 2024-02-07 00:51:25 -08:00 committed by GitHub
parent 8616f2e80a
commit a385ae4fa5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 169 additions and 10 deletions

View File

@ -51,6 +51,7 @@ export class PanelDataPane extends SceneObjectBase<PanelDataPaneState> {
constructor(panelMgr: VizPanelManager) {
super({
tab: TabId.Queries,
tabs: [],
});
this.panelManager = panelMgr;
@ -97,6 +98,7 @@ export class PanelDataPane extends SceneObjectBase<PanelDataPaneState> {
private buildTabs() {
const panelManager = this.panelManager;
const panel = panelManager.state.panel;
const runner = this.panelManager.queryRunner;
const tabs: PanelDataPaneTab[] = [];
@ -104,7 +106,6 @@ export class PanelDataPane extends SceneObjectBase<PanelDataPaneState> {
const plugin = panel.getPlugin();
if (!plugin) {
this.setState({ tabs });
return;
}

View File

@ -0,0 +1,95 @@
import { PanelPlugin, PanelPluginMeta, PluginType } from '@grafana/data';
import { SceneFlexItem, SplitLayout, VizPanel } from '@grafana/scenes';
import { DashboardScene } from '../scene/DashboardScene';
import { activateFullSceneTree } from '../utils/test-utils';
import { PanelDataPane } from './PanelDataPane/PanelDataPane';
import { buildPanelEditScene } from './PanelEditor';
let pluginToLoad: PanelPlugin | undefined;
jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'),
getPluginImportUtils: () => ({
getPanelPluginFromCache: jest.fn(() => pluginToLoad),
}),
config: {
panels: {
text: {
skipDataQuery: true,
},
timeseries: {
skipDataQuery: false,
},
},
},
}));
describe('PanelEditor', () => {
describe('PanelDataPane', () => {
it('should not exist if panel is skipDataQuery', () => {
pluginToLoad = getTestPanelPlugin({ id: 'text', skipDataQuery: true });
const panel = new VizPanel({
key: 'panel-1',
pluginId: 'text',
});
const editScene = buildPanelEditScene(panel);
const scene = new DashboardScene({
editPanel: editScene,
});
activateFullSceneTree(scene);
expect(((editScene.state.body as SplitLayout).state.primary as SplitLayout).state.secondary).toBeUndefined();
});
it('should exist if panel is supporting querying', () => {
pluginToLoad = getTestPanelPlugin({ id: 'timeseries' });
const panel = new VizPanel({
key: 'panel-1',
pluginId: 'timeseries',
});
const editScene = buildPanelEditScene(panel);
const scene = new DashboardScene({
editPanel: editScene,
});
activateFullSceneTree(scene);
const secondaryPane = ((editScene.state.body as SplitLayout).state.primary as SplitLayout).state.secondary;
expect(secondaryPane).toBeInstanceOf(SceneFlexItem);
expect((secondaryPane as SceneFlexItem).state.body).toBeInstanceOf(PanelDataPane);
});
});
});
export function getTestPanelPlugin(options: Partial<PanelPluginMeta>): PanelPlugin {
const plugin = new PanelPlugin(() => null);
plugin.meta = {
id: options.id!,
type: PluginType.panel,
name: options.id!,
sort: options.sort || 1,
info: {
author: {
name: options.id + 'name',
},
description: '',
links: [],
logos: {
large: '',
small: '',
},
screenshots: [],
updated: '',
version: '1.0.',
},
hideFromList: options.hideFromList === true,
module: options.module ?? '',
baseUrl: '',
skipDataQuery: options.skipDataQuery ?? false,
};
return plugin;
}

View File

@ -1,7 +1,7 @@
import * as H from 'history';
import { NavIndex } from '@grafana/data';
import { locationService } from '@grafana/runtime';
import { config, locationService } from '@grafana/runtime';
import {
SceneFlexItem,
SceneFlexLayout,
@ -118,14 +118,12 @@ export function buildPanelEditScene(panel: VizPanel): PanelEditor {
direction: 'row',
primary: new SplitLayout({
direction: 'column',
$behaviors: [conditionalDataPaneBehavior],
primary: new SceneFlexLayout({
direction: 'column',
minHeight: 200,
children: [vizPanelMgr],
}),
secondary: new SceneFlexItem({
body: new PanelDataPane(vizPanelMgr),
}),
primaryPaneStyles: {
minHeight: 0,
overflow: 'hidden',
@ -147,3 +145,45 @@ export function buildPanelEditScene(panel: VizPanel): PanelEditor {
}),
});
}
// This function is used to conditionally add the data pane to the panel editor,
// depending on the type of a panel being edited.
function conditionalDataPaneBehavior(scene: SplitLayout) {
const dashboard = getDashboardSceneFor(scene);
const editor = dashboard.state.editPanel;
if (!editor) {
return;
}
const panelManager = editor.state.panelRef.resolve();
const panel = panelManager.state.panel;
const getDataPane = () =>
new SceneFlexItem({
body: new PanelDataPane(panelManager),
});
if (!config.panels[panel.state.pluginId].skipDataQuery) {
scene.setState({
secondary: getDataPane(),
});
}
const sub = panelManager.subscribeToState((n, p) => {
const hadDataSupport = !config.panels[p.panel.state.pluginId].skipDataQuery;
const willHaveDataSupport = !config.panels[n.panel.state.pluginId].skipDataQuery;
if (hadDataSupport && !willHaveDataSupport) {
locationService.partial({ tab: null }, true);
scene.setState({ secondary: undefined });
} else if (!hadDataSupport && willHaveDataSupport) {
scene.setState({ secondary: getDataPane() });
}
});
return () => {
sub.unsubscribe();
};
}

View File

@ -9,7 +9,7 @@ import {
isStandardFieldProp,
restoreCustomOverrideRules,
} from '@grafana/data';
import { getDataSourceSrv, locationService } from '@grafana/runtime';
import { config, getDataSourceSrv, locationService } from '@grafana/runtime';
import {
SceneObjectState,
VizPanel,
@ -24,13 +24,14 @@ import {
} from '@grafana/scenes';
import { DataQuery, DataTransformerConfig } from '@grafana/schema';
import { getPluginVersion } from 'app/features/dashboard/state/PanelModel';
import { getLastUsedDatasourceFromStorage } from 'app/features/dashboard/utils/dashboard';
import { storeLastUsedDataSourceInLocalStorage } from 'app/features/datasources/components/picker/utils';
import { updateQueries } from 'app/features/query/state/updateQueries';
import { GrafanaQuery } from 'app/plugins/datasource/grafana/types';
import { QueryGroupOptions } from 'app/types';
import { PanelTimeRange, PanelTimeRangeState } from '../scene/PanelTimeRange';
import { getPanelIdForVizPanel, getQueryRunnerFor } from '../utils/utils';
import { getDashboardSceneFor, getPanelIdForVizPanel, getQueryRunnerFor } from '../utils/utils';
interface VizPanelManagerState extends SceneObjectState {
panel: VizPanel;
@ -128,6 +129,27 @@ export class VizPanelManager extends SceneObjectBase<VizPanelManagerState> {
...restOfOldState,
});
// When changing from non-data to data panel, we need to add a new data provider
if (!restOfOldState.$data && !config.panels[pluginType].skipDataQuery) {
let ds = getLastUsedDatasourceFromStorage(getDashboardSceneFor(this).state.uid!)?.datasourceUid;
if (!ds) {
ds = config.defaultDatasource;
}
newPanel.setState({
$data: new SceneDataTransformer({
$data: new SceneQueryRunner({
datasource: {
uid: ds,
},
queries: [{ refId: 'A' }],
}),
transformations: [],
}),
});
}
const newPlugin = newPanel.getPlugin();
const panel: PanelModel = {
title: newPanel.state.title,
@ -146,6 +168,7 @@ export class VizPanelManager extends SceneObjectBase<VizPanelManagerState> {
}
this.setState({ panel: newPanel });
this.loadDataSource();
}
public async changePanelDataSource(

View File

@ -25531,11 +25531,11 @@ __metadata:
linkType: hard
"react-hook-form@npm:^7.49.2":
version: 7.50.1
resolution: "react-hook-form@npm:7.50.1"
version: 7.50.0
resolution: "react-hook-form@npm:7.50.0"
peerDependencies:
react: ^16.8.0 || ^17 || ^18
checksum: 10/54a9daa2143c601a9867e96a2159a0bbe98707b5bbeb5953bfdf3342d2f04bfcaa6169907ed167c5c1f3a044630860d4f43685f4ac4e15b9cd892d1b00d54dde
checksum: 10/3b85cc179053af72a2734f2e77767de8f9b3ecbefeee282b73e81141c4b7bb97308ec00da61fdc25a28299a2defb74bff66417bb85a66357f5ceddba7b697ae7
languageName: node
linkType: hard