diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataPane.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataPane.tsx index ba1188f4166..1fb5e244a5c 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataPane.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataPane.tsx @@ -9,10 +9,12 @@ import { SceneObjectState, SceneObjectUrlSyncConfig, SceneObjectUrlValues, - VizPanel, } from '@grafana/scenes'; import { Container, CustomScrollbar, TabContent, TabsBar, useStyles2 } from '@grafana/ui'; -import { shouldShowAlertingTab } from 'app/features/dashboard/components/PanelEditor/state/selectors'; +import { config, getConfig } from 'app/core/config'; +import { contextSrv } from 'app/core/core'; +import { getRulesPermissions } from 'app/features/alerting/unified/utils/access-control'; +import { GRAFANA_RULES_SOURCE_NAME } from 'app/features/alerting/unified/utils/datasource'; import { VizPanelManager } from '../VizPanelManager'; @@ -29,7 +31,6 @@ export interface PanelDataPaneState extends SceneObjectState { export class PanelDataPane extends SceneObjectBase { static Component = PanelDataPaneRendered; protected _urlSync = new SceneObjectUrlSyncConfig(this, { keys: ['tab'] }); - private _initialTabsBuilt = false; private panelSubscription: Unsubscribable | undefined; public panelManager: VizPanelManager; @@ -59,16 +60,13 @@ export class PanelDataPane extends SceneObjectBase { } private onActivate() { - const panel = this.panelManager.state.panel; - this.setupPanelSubscription(panel); this.buildTabs(); this._subs.add( // Setup subscription for the case when panel type changed this.panelManager.subscribeToState((n, p) => { - if (n.panel !== p.panel) { + if (n.pluginId !== p.pluginId) { this.buildTabs(); - this.setupPanelSubscription(n.panel); } }) ); @@ -81,35 +79,16 @@ export class PanelDataPane extends SceneObjectBase { }; } - private setupPanelSubscription(panel: VizPanel) { - if (this.panelSubscription) { - this._initialTabsBuilt = false; - this.panelSubscription.unsubscribe(); - } - - this.panelSubscription = panel.subscribeToState(() => { - if (panel.getPlugin() && !this._initialTabsBuilt) { - this.buildTabs(); - this._initialTabsBuilt = true; - } - }); - } - private buildTabs() { const panelManager = this.panelManager; const panel = panelManager.state.panel; + const pluginId = panelManager.state.pluginId; const runner = this.panelManager.queryRunner; const tabs: PanelDataPaneTab[] = []; if (panel) { - const plugin = panel.getPlugin(); - - if (!plugin) { - return; - } - - if (plugin.meta.skipDataQuery) { + if (config.panels[pluginId]?.skipDataQuery) { this.setState({ tabs }); return; } else { @@ -119,7 +98,7 @@ export class PanelDataPane extends SceneObjectBase { tabs.push(new PanelDataTransformationsTab(this.panelManager)); - if (shouldShowAlertingTab(plugin)) { + if (shouldShowAlertingTab(panelManager.state.pluginId)) { tabs.push(new PanelDataAlertingTab(this.panelManager)); } } @@ -137,7 +116,7 @@ function PanelDataPaneRendered({ model }: SceneComponentProps) { const { tab, tabs } = model.useState(); const styles = useStyles2(getStyles); - if (!tabs) { + if (!tabs || !tabs.length) { return; } @@ -165,6 +144,20 @@ function PanelDataPaneRendered({ model }: SceneComponentProps) { ); } +export function shouldShowAlertingTab(pluginId: string) { + const { unifiedAlertingEnabled = false } = getConfig(); + const hasRuleReadPermissions = contextSrv.hasPermission(getRulesPermissions(GRAFANA_RULES_SOURCE_NAME).read); + const isAlertingAvailable = unifiedAlertingEnabled && hasRuleReadPermissions; + if (!isAlertingAvailable) { + return false; + } + + const isGraph = pluginId === 'graph'; + const isTimeseries = pluginId === 'timeseries'; + + return isGraph || isTimeseries; +} + function getStyles(theme: GrafanaTheme2) { return { dataPane: css({ diff --git a/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx b/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx index 0147ccd37c9..616acfc8238 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx @@ -49,8 +49,8 @@ export class PanelEditor extends SceneObjectBase { this._subs.add( panelManager.subscribeToState((n, p) => { - if (n.panel.state.pluginId !== p.panel.state.pluginId) { - this._initDataPane(n.panel.state.pluginId); + if (n.pluginId !== p.pluginId) { + this._initDataPane(n.pluginId); } }) ); diff --git a/public/app/features/dashboard-scene/panel-edit/PanelOptionsPane.tsx b/public/app/features/dashboard-scene/panel-edit/PanelOptionsPane.tsx index 8caf65b2cd4..5effa56b8f8 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelOptionsPane.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelOptionsPane.tsx @@ -43,7 +43,7 @@ export class PanelOptionsPane extends SceneObjectBase { static Component = ({ model }: SceneComponentProps) => { const { isVizPickerOpen, searchQuery, listMode } = model.useState(); const vizManager = sceneGraph.getAncestor(model, PanelEditor).state.vizManager; - const { pluginId } = vizManager.state.panel.useState(); + const { pluginId } = vizManager.useState(); const { data } = sceneGraph.getData(vizManager.state.panel).useState(); const styles = useStyles2(getStyles); diff --git a/public/app/features/dashboard-scene/panel-edit/VizPanelManager.test.tsx b/public/app/features/dashboard-scene/panel-edit/VizPanelManager.test.tsx index 844da6d0e23..1b9eeded9e4 100644 --- a/public/app/features/dashboard-scene/panel-edit/VizPanelManager.test.tsx +++ b/public/app/features/dashboard-scene/panel-edit/VizPanelManager.test.tsx @@ -4,14 +4,7 @@ import { DataQueryRequest, DataSourceApi, DataSourceInstanceSettings, LoadingSta import { calculateFieldTransformer } from '@grafana/data/src/transformations/transformers/calculateField'; import { mockTransformationsRegistry } from '@grafana/data/src/utils/tests/mockTransformationsRegistry'; import { config, locationService } from '@grafana/runtime'; -import { - LocalValueVariable, - SceneGridRow, - SceneQueryRunner, - SceneVariableSet, - VizPanel, - sceneGraph, -} from '@grafana/scenes'; +import { LocalValueVariable, SceneGridRow, SceneVariableSet, VizPanel, sceneGraph } from '@grafana/scenes'; import { DataQuery, DataSourceJsonData, DataSourceRef } from '@grafana/schema'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; import { InspectTab } from 'app/features/inspector/types'; @@ -193,93 +186,80 @@ jest.useFakeTimers(); describe('VizPanelManager', () => { describe('When changing plugin', () => { - it('Should successfully change from one viz type to another', () => { + it('Should set the cache', () => { const { vizPanelManager } = setupTest('panel-1'); + vizPanelManager.state.panel.changePluginType = jest.fn(); + expect(vizPanelManager.state.panel.state.pluginId).toBe('timeseries'); + vizPanelManager.changePluginType('table'); - expect(vizPanelManager.state.panel.state.pluginId).toBe('table'); + expect(vizPanelManager['_cachedPluginOptions']['timeseries']?.options).toBe( + vizPanelManager.state.panel.state.options + ); + expect(vizPanelManager['_cachedPluginOptions']['timeseries']?.fieldConfig).toBe( + vizPanelManager.state.panel.state.fieldConfig + ); }); - it('Should clear custom options', () => { - const overrides = [ + it('Should preserve correct field config', () => { + const { vizPanelManager } = setupTest('panel-1'); + const mockFn = jest.fn(); + vizPanelManager.state.panel.changePluginType = mockFn; + const fieldConfig = vizPanelManager.state.panel.state.fieldConfig; + fieldConfig.defaults = { + ...fieldConfig.defaults, + unit: 'flop', + decimals: 2, + }; + fieldConfig.overrides = [ { - matcher: { id: 'matcherOne' }, - properties: [{ id: 'custom.propertyOne' }, { id: 'custom.propertyTwo' }, { id: 'standardProperty' }], + matcher: { + id: 'byName', + options: 'A-series', + }, + properties: [ + { + id: 'displayName', + value: 'test', + }, + ], + }, + { + matcher: { id: 'byName', options: 'D-series' }, + //should be removed because it's custom + properties: [ + { + id: 'custom.customPropNoExist', + value: 'google', + }, + ], }, ]; - const vizPanel = new VizPanel({ - title: 'Panel A', - key: 'panel-1', - pluginId: 'table', - $data: new SceneQueryRunner({ - key: 'data-query-runner', - datasource: { - type: 'grafana-testdata-datasource', - uid: 'gdev-testdata', - }, - queries: [{ refId: 'A' }], - }), - options: undefined, - fieldConfig: { - defaults: { - custom: 'Custom', - }, - overrides, - }, + vizPanelManager.state.panel.setState({ + fieldConfig: fieldConfig, }); - new DashboardGridItem({ - body: vizPanel, - }); - - const vizPanelManager = VizPanelManager.createFor(vizPanel); - - expect(vizPanelManager.state.panel.state.fieldConfig.defaults.custom).toBe('Custom'); - expect(vizPanelManager.state.panel.state.fieldConfig.overrides).toBe(overrides); - - vizPanelManager.changePluginType('timeseries'); - - expect(vizPanelManager.state.panel.state.fieldConfig.defaults.custom).toStrictEqual({}); - expect(vizPanelManager.state.panel.state.fieldConfig.overrides[0].properties).toHaveLength(1); - expect(vizPanelManager.state.panel.state.fieldConfig.overrides[0].properties[0].id).toBe('standardProperty'); - }); - - it('Should restore cached options/fieldConfig if they exist', () => { - const vizPanel = new VizPanel({ - title: 'Panel A', - key: 'panel-1', - pluginId: 'table', - $data: new SceneQueryRunner({ - key: 'data-query-runner', - datasource: { - type: 'grafana-testdata-datasource', - uid: 'gdev-testdata', - }, - queries: [{ refId: 'A' }], - }), - options: { - customOption: 'A', - }, - fieldConfig: { defaults: { custom: 'Custom' }, overrides: [] }, - }); - - new DashboardGridItem({ - body: vizPanel, - }); - - const vizPanelManager = VizPanelManager.createFor(vizPanel); - - vizPanelManager.changePluginType('timeseries'); - //@ts-ignore - expect(vizPanelManager.state.panel.state.options['customOption']).toBeUndefined(); - expect(vizPanelManager.state.panel.state.fieldConfig.defaults.custom).toStrictEqual({}); + expect(vizPanelManager.state.panel.state.fieldConfig.defaults.color?.mode).toBe('palette-classic'); + expect(vizPanelManager.state.panel.state.fieldConfig.defaults.thresholds?.mode).toBe('absolute'); + expect(vizPanelManager.state.panel.state.fieldConfig.defaults.unit).toBe('flop'); + expect(vizPanelManager.state.panel.state.fieldConfig.defaults.decimals).toBe(2); + expect(vizPanelManager.state.panel.state.fieldConfig.overrides).toHaveLength(2); + expect(vizPanelManager.state.panel.state.fieldConfig.overrides[1].properties).toHaveLength(1); + expect(vizPanelManager.state.panel.state.fieldConfig.defaults.custom).toHaveProperty('axisBorderShow'); vizPanelManager.changePluginType('table'); - //@ts-ignore - expect(vizPanelManager.state.panel.state.options['customOption']).toBe('A'); - expect(vizPanelManager.state.panel.state.fieldConfig.defaults.custom).toBe('Custom'); + expect(mockFn).toHaveBeenCalled(); + expect(mockFn.mock.calls[0][2].defaults.color?.mode).toBe('palette-classic'); + expect(mockFn.mock.calls[0][2].defaults.thresholds?.mode).toBe('absolute'); + expect(mockFn.mock.calls[0][2].defaults.unit).toBe('flop'); + expect(mockFn.mock.calls[0][2].defaults.decimals).toBe(2); + expect(mockFn.mock.calls[0][2].overrides).toHaveLength(2); + //removed custom property + expect(mockFn.mock.calls[0][2].overrides[1].properties).toHaveLength(0); + //removed fieldConfig custom values as well + expect(mockFn.mock.calls[0][2].defaults.custom).toStrictEqual({}); }); }); diff --git a/public/app/features/dashboard-scene/panel-edit/VizPanelManager.tsx b/public/app/features/dashboard-scene/panel-edit/VizPanelManager.tsx index 33e36101985..3e8721302ca 100644 --- a/public/app/features/dashboard-scene/panel-edit/VizPanelManager.tsx +++ b/public/app/features/dashboard-scene/panel-edit/VizPanelManager.tsx @@ -7,7 +7,6 @@ import { DataSourceInstanceSettings, FieldConfigSource, GrafanaTheme2, - PanelModel, filterFieldConfigOverrides, getDataSourceRef, isStandardFieldProp, @@ -26,11 +25,9 @@ import { SceneQueryRunner, SceneVariables, VizPanel, - sceneUtils, } from '@grafana/scenes'; import { DataQuery, DataTransformerConfig, Panel } from '@grafana/schema'; import { useStyles2 } from '@grafana/ui'; -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 { updateLibraryVizPanel } from 'app/features/library-panels/state/api'; @@ -49,6 +46,7 @@ import { getDashboardSceneFor, getPanelIdForVizPanel, getQueryRunnerFor } from ' export interface VizPanelManagerState extends SceneObjectState { panel: VizPanel; sourcePanel: SceneObjectRef; + pluginId: string; datasource?: DataSourceApi; dsSettings?: DataSourceInstanceSettings; tableView?: VizPanel; @@ -103,6 +101,7 @@ export class VizPanelManager extends SceneObjectBase { $variables: variables, panel: sourcePanel.clone(), sourcePanel: sourcePanel.getRef(), + pluginId: sourcePanel.state.pluginId, ...repeatOptions, }); } @@ -197,12 +196,7 @@ export class VizPanelManager extends SceneObjectBase { } public changePluginType(pluginId: string) { - const { - options: prevOptions, - fieldConfig: prevFieldConfig, - pluginId: prevPluginId, - ...restOfOldState - } = sceneUtils.cloneSceneObjectState(this.state.panel.state); + const { options: prevOptions, fieldConfig: prevFieldConfig, pluginId: prevPluginId } = this.state.panel.state; // clear custom options let newFieldConfig: FieldConfigSource = { @@ -222,13 +216,6 @@ export class VizPanelManager extends SceneObjectBase { newFieldConfig = restoreCustomOverrideRules(newFieldConfig, cachedFieldConfig); } - const newPanel = new VizPanel({ - options: cachedOptions ?? {}, - fieldConfig: newFieldConfig, - pluginId: pluginId, - ...restOfOldState, - }); - // When changing from non-data to data panel, we need to add a new data provider if (!this.state.panel.state.$data && !config.panels[pluginId].skipDataQuery) { let ds = getLastUsedDatasourceFromStorage(getDashboardSceneFor(this).state.uid!)?.datasourceUid; @@ -237,7 +224,7 @@ export class VizPanelManager extends SceneObjectBase { ds = config.defaultDatasource; } - newPanel.setState({ + this.state.panel.setState({ $data: new SceneDataTransformer({ $data: new SceneQueryRunner({ datasource: { @@ -250,26 +237,12 @@ export class VizPanelManager extends SceneObjectBase { }); } - const newPlugin = newPanel.getPlugin(); - const panel: PanelModel = { - title: newPanel.state.title, - options: newPanel.state.options, - fieldConfig: newPanel.state.fieldConfig, - id: 1, - type: pluginId, - }; + this.setState({ + pluginId, + }); - const newOptions = newPlugin?.onPanelTypeChanged?.(panel, prevPluginId, prevOptions, prevFieldConfig); + this.state.panel.changePluginType(pluginId, cachedOptions, newFieldConfig); - if (newOptions) { - newPanel.onOptionsChange(newOptions, true, true); - } - - if (newPlugin?.onPanelMigration) { - newPanel.setState({ pluginVersion: getPluginVersion(newPlugin) }); - } - - this.setState({ panel: newPanel }); this.loadDataSource(); } diff --git a/public/app/features/dashboard-scene/scene/DashboardDatasourceBehaviour.test.tsx b/public/app/features/dashboard-scene/scene/DashboardDatasourceBehaviour.test.tsx index de5e655eae2..259702e9bf4 100644 --- a/public/app/features/dashboard-scene/scene/DashboardDatasourceBehaviour.test.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardDatasourceBehaviour.test.tsx @@ -279,6 +279,7 @@ describe('DashboardDatasourceBehaviour', () => { panel: dashboardDSPanel.clone(), $data: dashboardDSPanel.state.$data?.clone(), sourcePanel: dashboardDSPanel.getRef(), + pluginId: dashboardDSPanel.state.pluginId, }); vizPanelManager.activate();