From 36e8ca7f1354594bdda1a976c46d871863f90ab8 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Mon, 27 Mar 2023 08:11:45 -0700 Subject: [PATCH] Dashboards: Unify angular auto-migration code (#63915) --- .../DashExportModal/DashboardExporter.test.ts | 16 +++++-- .../dashboard/state/DashboardModel.ts | 29 +++++++++++-- .../features/dashboard/state/PanelModel.ts | 36 ++++++++-------- .../state/__fixtures__/dashboardFixtures.ts | 2 +- .../scenes/dashboard/DashboardsLoader.ts | 13 ++---- .../plugins/panel/piechart/migrations.test.ts | 4 +- .../app/plugins/panel/piechart/migrations.ts | 4 -- public/test/helpers/getDashboardModel.ts | 2 +- yarn.lock | 43 ++----------------- 9 files changed, 68 insertions(+), 81 deletions(-) diff --git a/public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts b/public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts index e695b88a000..451e7150a53 100644 --- a/public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts +++ b/public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts @@ -85,7 +85,9 @@ it('handles a default datasource in a template variable', async () => { ], }, }; - const dashboardModel = new DashboardModel(dashboard, {}, () => dashboard.templating.list); + const dashboardModel = new DashboardModel(dashboard, undefined, { + getVariablesFromState: () => dashboard.templating.list, + }); const exporter = new DashboardExporter(); const exported: any = await exporter.makeExportable(dashboardModel); expect(exported.templating.list[0].datasource.uid).toBe('${DS_GFDB}'); @@ -105,7 +107,9 @@ it('If a panel queries has no datasource prop ignore it', async () => { }, ], }; - const dashboardModel = new DashboardModel(dashboard, {}, () => []); + const dashboardModel = new DashboardModel(dashboard, undefined, { + getVariablesFromState: () => [], + }); const exporter = new DashboardExporter(); const exported: any = await exporter.makeExportable(dashboardModel); expect(exported.panels[0].datasource).toEqual({ uid: '${DS_OTHER}', type: 'other' }); @@ -230,7 +234,13 @@ describe('given dashboard with repeated panels', () => { info: { version: '1.1.2' }, } as PanelPluginMeta; - dash = new DashboardModel(dash, {}, () => dash.templating.list); + dash = new DashboardModel( + dash, + {}, + { + getVariablesFromState: () => dash.templating.list, + } + ); // init library panels dash.getPanelById(17).initLibraryPanel({ diff --git a/public/app/features/dashboard/state/DashboardModel.ts b/public/app/features/dashboard/state/DashboardModel.ts index a88c02a66fe..daaa617bf06 100644 --- a/public/app/features/dashboard/state/DashboardModel.ts +++ b/public/app/features/dashboard/state/DashboardModel.ts @@ -16,7 +16,7 @@ import { TimeZone, UrlQueryValue, } from '@grafana/data'; -import { RefreshEvent, TimeRangeUpdatedEvent } from '@grafana/runtime'; +import { RefreshEvent, TimeRangeUpdatedEvent, config } from '@grafana/runtime'; import { Dashboard } from '@grafana/schema'; import { DEFAULT_ANNOTATION_COLOR } from '@grafana/ui'; import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT, REPEAT_DIR_VERTICAL } from 'app/core/constants'; @@ -41,7 +41,7 @@ import { getTimeSrv } from '../services/TimeSrv'; import { mergePanels, PanelMergeInfo } from '../utils/panelMerge'; import { DashboardMigrator } from './DashboardMigrator'; -import { GridPos, PanelModel } from './PanelModel'; +import { GridPos, PanelModel, autoMigrateAngular } from './PanelModel'; import { TimeModel } from './TimeModel'; import { deleteScopeVars, isOnTheSameGridRow } from './utils'; @@ -108,6 +108,7 @@ export class DashboardModel implements TimeModel { // repeat process cycles declare meta: DashboardMeta; events: EventBusExtended; + private getVariablesFromState: GetVariables; static nonPersistedProperties: { [str: string]: boolean } = { events: true, @@ -126,7 +127,18 @@ export class DashboardModel implements TimeModel { lastRefresh: true, }; - constructor(data: Dashboard, meta?: DashboardMeta, private getVariablesFromState: GetVariables = getVariablesByKey) { + constructor( + data: Dashboard, + meta?: DashboardMeta, + options?: { + // By default this uses variables from redux state + getVariablesFromState?: GetVariables; + + // Force the loader to migrate panels + autoMigrateOldPanels?: boolean; + } + ) { + this.getVariablesFromState = options?.getVariablesFromState ?? getVariablesByKey; this.events = new EventBusSrv(); this.id = data.id || null; // UID is not there for newly created dashboards @@ -162,6 +174,17 @@ export class DashboardModel implements TimeModel { this.initMeta(meta); this.updateSchema(data); + // Auto-migrate old angular panels + if (options?.autoMigrateOldPanels || !config.angularSupportEnabled || config.featureToggles.autoMigrateOldPanels) { + this.panels.forEach((p) => { + const newType = autoMigrateAngular[p.type]; + if (!p.autoMigrateFrom && newType) { + p.autoMigrateFrom = p.type; + p.type = newType; + } + }); + } + this.addBuiltInAnnotationQuery(); this.sortPanelsByGridPos(); this.panelsAffectedByVariableChange = null; diff --git a/public/app/features/dashboard/state/PanelModel.ts b/public/app/features/dashboard/state/PanelModel.ts index 81aa95b3031..dfdc8578c86 100644 --- a/public/app/features/dashboard/state/PanelModel.ts +++ b/public/app/features/dashboard/state/PanelModel.ts @@ -132,6 +132,19 @@ const defaults: any = { title: '', }; +export const autoMigrateAngular: Record = { + graph: 'timeseries', + 'table-old': 'table', + singlestat: 'stat', // also automigrated if dashboard schemaVerion < 27 + 'grafana-singlestat-panel': 'stat', + 'grafana-piechart-panel': 'piechart', + 'grafana-worldmap-panel': 'geomap', +}; + +const autoMigratePanelType: Record = { + 'heatmap-new': 'heatmap', // this was a temporary development panel that is now standard +}; + export class PanelModel implements DataConfigSource, IPanelModel { /* persisted id, used in URL to identify a panel */ id!: number; @@ -235,23 +248,10 @@ export class PanelModel implements DataConfigSource, IPanelModel { (this as any)[property] = model[property]; } - switch (this.type) { - case 'graph': - if (config.featureToggles?.autoMigrateOldPanels || !config.angularSupportEnabled) { - this.autoMigrateFrom = this.type; - this.type = 'timeseries'; - } - break; - case 'table-old': - if (config.featureToggles?.autoMigrateOldPanels || !config.angularSupportEnabled) { - this.autoMigrateFrom = this.type; - this.type = 'table'; - } - break; - case 'heatmap-new': - this.autoMigrateFrom = this.type; - this.type = 'heatmap'; - break; + const newType = autoMigratePanelType[this.type]; + if (newType) { + this.autoMigrateFrom = this.type; + this.type = newType; } // defaults @@ -427,7 +427,7 @@ export class PanelModel implements DataConfigSource, IPanelModel { const version = getPluginVersion(plugin); if (this.autoMigrateFrom) { - const wasAngular = this.autoMigrateFrom === 'graph' || this.autoMigrateFrom === 'table-old'; + const wasAngular = autoMigrateAngular[this.autoMigrateFrom] != null; this.callPanelTypeChangeHandler( plugin, this.autoMigrateFrom, diff --git a/public/app/features/dashboard/state/__fixtures__/dashboardFixtures.ts b/public/app/features/dashboard/state/__fixtures__/dashboardFixtures.ts index 4741059fa22..4565591d01a 100644 --- a/public/app/features/dashboard/state/__fixtures__/dashboardFixtures.ts +++ b/public/app/features/dashboard/state/__fixtures__/dashboardFixtures.ts @@ -27,7 +27,7 @@ export function createDashboardModelFixture( ...dashboardInput, }; - return new DashboardModel(dashboardJson, meta, getVariablesFromState); + return new DashboardModel(dashboardJson, meta, { getVariablesFromState }); } export function createPanelJSONFixture(panelInput: Partial = {}): Panel { diff --git a/public/app/features/scenes/dashboard/DashboardsLoader.ts b/public/app/features/scenes/dashboard/DashboardsLoader.ts index a5f4c6611c5..306a04cc7ef 100644 --- a/public/app/features/scenes/dashboard/DashboardsLoader.ts +++ b/public/app/features/scenes/dashboard/DashboardsLoader.ts @@ -25,7 +25,6 @@ import { import { StateManagerBase } from 'app/core/services/StateManagerBase'; import { dashboardLoaderSrv } from 'app/features/dashboard/services/DashboardLoaderSrv'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; -import { graphToTimeseriesOptions } from 'app/plugins/panel/timeseries/migrations'; import { DashboardDTO } from 'app/types'; import { DashboardScene } from './DashboardScene'; @@ -63,7 +62,9 @@ export class DashboardLoader extends StateManagerBase { private initDashboard(rsp: DashboardDTO) { // Just to have migrations run - const oldModel = new DashboardModel(rsp.dashboard, rsp.meta); + const oldModel = new DashboardModel(rsp.dashboard, rsp.meta, { + autoMigrateOldPanels: true, + }); const dashboard = createDashboardSceneFromDashboardModel(oldModel); @@ -262,14 +263,6 @@ export function createVizPanelFromPanelModel(panel: PanelModel) { maxDataPoints: panel.maxDataPoints ?? undefined, }); - // Migrate graph to timeseries - if (panel.type === 'graph') { - const { fieldConfig, options } = graphToTimeseriesOptions(panel); - panel.fieldConfig = fieldConfig; - panel.options = options; - panel.type = 'timeseries'; - } - return new VizPanel({ title: panel.title, pluginId: panel.type, diff --git a/public/app/plugins/panel/piechart/migrations.test.ts b/public/app/plugins/panel/piechart/migrations.test.ts index 033b95167ac..633ed4f4f95 100644 --- a/public/app/plugins/panel/piechart/migrations.test.ts +++ b/public/app/plugins/panel/piechart/migrations.test.ts @@ -60,13 +60,13 @@ describe('PieChart -> PieChartV2 migrations', () => { expect(options).toMatchObject({ displayLabels: [PieChartLabels.Name, PieChartLabels.Value] }); }); - it('hides the legend when no legend values are selected', () => { + it('hides the legend when show is false', () => { const panel = { options: {} } as PanelModel; const oldPieChartOptions = { angular: { legendType: 'On graph', - legend: {}, + legend: { show: false }, }, }; const options = PieChartPanelChangedHandler(panel, 'grafana-piechart-panel', oldPieChartOptions); diff --git a/public/app/plugins/panel/piechart/migrations.ts b/public/app/plugins/panel/piechart/migrations.ts index 04cbe4fae3d..46cd1dba31e 100644 --- a/public/app/plugins/panel/piechart/migrations.ts +++ b/public/app/plugins/panel/piechart/migrations.ts @@ -102,10 +102,6 @@ export const PieChartPanelChangedHandler = ( if (angular.legend.percentage) { options.legend.values.push(PieChartLegendValues.Percent); } - if (!angular.legend.percentage && !angular.legend.values) { - // If you deselect both value and percentage in the old pie chart plugin, the legend is hidden. - options.legend.showLegend = false; - } } // Set up labels when the old piechart is using 'on graph', for the legend option. diff --git a/public/test/helpers/getDashboardModel.ts b/public/test/helpers/getDashboardModel.ts index f341d80943c..2ad11a3e6e0 100644 --- a/public/test/helpers/getDashboardModel.ts +++ b/public/test/helpers/getDashboardModel.ts @@ -3,5 +3,5 @@ import { DashboardMeta } from '../../app/types/dashboard'; export const getDashboardModel = (json: any, meta: DashboardMeta = {}) => { const getVariablesFromState = () => json.templating.list; - return new DashboardModel(json, meta, getVariablesFromState); + return new DashboardModel(json, meta, { getVariablesFromState }); }; diff --git a/yarn.lock b/yarn.lock index 61f0284e0dd..da95550061a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15644,45 +15644,10 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001271, caniuse-lite@npm:^1.0.30001286, caniuse-lite@npm:^1.0.30001317": - version: 1.0.30001332 - resolution: "caniuse-lite@npm:1.0.30001332" - checksum: e54182ea42ab3d2ff1440f9a6480292f7ab23c00c188df7ad65586312e4da567e8bedd5cb5fb8f0ff4193dc027a54e17e0b3c0b6db5d5a3fb61c7726ff9c45b3 - languageName: node - linkType: hard - -"caniuse-lite@npm:^1.0.30001109, caniuse-lite@npm:^1.0.30001449": - version: 1.0.30001467 - resolution: "caniuse-lite@npm:1.0.30001467" - checksum: c7df36ddb8050fb366a4bedd278f4b639c1dde94b6ba62bacf960f26d488395632a0630b7932ebc52d3d04b57940d12dcb4a7d3bb744ff64c249b61fb3e0c238 - languageName: node - linkType: hard - -"caniuse-lite@npm:^1.0.30001332": - version: 1.0.30001335 - resolution: "caniuse-lite@npm:1.0.30001335" - checksum: fe08b49ec6cb76cc69958ff001cf89d0a8ef9f35e0c8028b65981585046384f76e007d64dea372a34ca56d91caa83cc614c00779fe2b4d378aa0e68696374f67 - languageName: node - linkType: hard - -"caniuse-lite@npm:^1.0.30001335": - version: 1.0.30001341 - resolution: "caniuse-lite@npm:1.0.30001341" - checksum: 7262b093fb0bf49dbc5328418f5ce4e3dbb0b13e39c015f986ba1807634c123ac214efc94df7d095a336f57f86852b4b63ee61838f18dcc3a4a35f87b390c8f5 - languageName: node - linkType: hard - -"caniuse-lite@npm:^1.0.30001400": - version: 1.0.30001402 - resolution: "caniuse-lite@npm:1.0.30001402" - checksum: 6068ccccd64b357f75388cb2303cf351b686b20800571d0a845bff5c0e0d24f83df0133afbbdd8177a33eb087c93d39ecf359035a52b2feac5f182c946f706ee - languageName: node - linkType: hard - -"caniuse-lite@npm:^1.0.30001426": - version: 1.0.30001429 - resolution: "caniuse-lite@npm:1.0.30001429" - checksum: d1658080248ef5ef0f5157423b2766026e6aa45642ce3b2cc74859b6a54e39881dd902397a2368324ed30ed0cd40250f11a4a4f3773453cd57b88db5e5e5c76a +"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001109, caniuse-lite@npm:^1.0.30001271, caniuse-lite@npm:^1.0.30001286, caniuse-lite@npm:^1.0.30001317, caniuse-lite@npm:^1.0.30001332, caniuse-lite@npm:^1.0.30001335, caniuse-lite@npm:^1.0.30001400, caniuse-lite@npm:^1.0.30001426, caniuse-lite@npm:^1.0.30001449": + version: 1.0.30001469 + resolution: "caniuse-lite@npm:1.0.30001469" + checksum: 8e496509d7e9ff189c72205675b5db0c5f1b6a09917027441e835efae0848a468a8c4e7d2b409ffc202438fcd23ae53e017f976a03c22c04d12d3c0e1e33e5de languageName: node linkType: hard