From f95405d3c3bb916c60a088f0a95eaff3b6da11dd Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Mon, 28 Aug 2023 10:06:55 +0000 Subject: [PATCH] Plugins: Allow async panel migrations (#73782) * Plugins: Allow async panel migrations * comment --- packages/grafana-data/src/types/panel.ts | 7 +++- .../DashboardPrompt/DashboardPrompt.test.tsx | 4 +- .../dashboard/state/PanelModel.test.ts | 42 ++++++++++++++++++- .../features/dashboard/state/PanelModel.ts | 5 ++- .../features/dashboard/utils/panel.test.ts | 4 +- public/app/features/panel/state/actions.ts | 4 +- 6 files changed, 54 insertions(+), 12 deletions(-) diff --git a/packages/grafana-data/src/types/panel.ts b/packages/grafana-data/src/types/panel.ts index 5f9bf8b54b4..0dd43826f6a 100644 --- a/packages/grafana-data/src/types/panel.ts +++ b/packages/grafana-data/src/types/panel.ts @@ -135,9 +135,12 @@ export interface PanelEditorProps { } /** - * Called when a panel is first loaded with current panel model + * Called when a panel is first loaded with current panel model to migrate panel options if needed. + * Can return panel options, or a Promise that resolves to panel options for async migrations */ -export type PanelMigrationHandler = (panel: PanelModel) => Partial; +export type PanelMigrationHandler = ( + panel: PanelModel +) => Partial | Promise>; /** * Called before a panel is initialized. Allows panel inspection for any updates before changing the panel type. diff --git a/public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.test.tsx b/public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.test.tsx index ecd7a47530f..cc768c3013d 100644 --- a/public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.test.tsx +++ b/public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.test.tsx @@ -132,14 +132,14 @@ describe('DashboardPrompt', () => { }); }); - it('Should ignore panel schema migrations', () => { + it('Should ignore panel schema migrations', async () => { const { original, dash } = getTestContext(); const plugin = getPanelPlugin({}).setMigrationHandler((panel) => { delete (panel as any).legend; return { option1: 'Aasd' }; }); - dash.panels[0].pluginLoaded(plugin); + await dash.panels[0].pluginLoaded(plugin); expect(hasChanges(dash, original)).toBe(false); }); diff --git a/public/app/features/dashboard/state/PanelModel.test.ts b/public/app/features/dashboard/state/PanelModel.test.ts index 67eed4ed7cf..22612a0951a 100644 --- a/public/app/features/dashboard/state/PanelModel.test.ts +++ b/public/app/features/dashboard/state/PanelModel.test.ts @@ -8,6 +8,7 @@ import { standardFieldConfigEditorRegistry, dateTime, TimeRange, + PanelMigrationHandler, } from '@grafana/data'; import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks'; import { mockStandardFieldConfigOptions } from '@grafana/data/test/helpers/fieldConfig'; @@ -80,7 +81,7 @@ describe('PanelModel', () => { }, }); - beforeEach(() => { + beforeEach(async () => { persistedOptionsMock = { fieldOptions: { thresholds: [ @@ -141,7 +142,44 @@ describe('PanelModel', () => { }; model = new PanelModel(modelJson); - model.pluginLoaded(tablePlugin); + await model.pluginLoaded(tablePlugin); + }); + + describe('migrations', () => { + let initialMigrator: PanelMigrationHandler<(typeof model)['options']> | undefined = undefined; + + beforeEach(() => { + initialMigrator = tablePlugin.onPanelMigration; + }); + afterEach(() => { + tablePlugin.onPanelMigration = initialMigrator; + }); + + it('should run sync migrations', async () => { + model.options.valueToMigrate = 'old-legacy'; + + tablePlugin.onPanelMigration = (p) => ({ ...p.options, valueToMigrate: 'new-version' }); + + tablePlugin.onPanelMigration = (p) => { + p.options.valueToMigrate = 'new-version'; + return p.options; + }; + + await model.pluginLoaded(tablePlugin); + expect(model.options).toMatchObject({ valueToMigrate: 'new-version' }); + }); + + it('should run async migrations', async () => { + model.options.valueToMigrate = 'old-legacy'; + + tablePlugin.onPanelMigration = async (p) => + new Promise((resolve) => { + setTimeout(() => resolve({ ...p.options, valueToMigrate: 'new-version' }), 10); + }); + + await model.pluginLoaded(tablePlugin); + expect(model.options).toMatchObject({ valueToMigrate: 'new-version' }); + }); }); it('should apply defaults', () => { diff --git a/public/app/features/dashboard/state/PanelModel.ts b/public/app/features/dashboard/state/PanelModel.ts index 0c05bc7ef23..8e51834629d 100644 --- a/public/app/features/dashboard/state/PanelModel.ts +++ b/public/app/features/dashboard/state/PanelModel.ts @@ -429,7 +429,7 @@ export class PanelModel implements DataConfigSource, IPanelModel { this.options = options.options; } - pluginLoaded(plugin: PanelPlugin) { + async pluginLoaded(plugin: PanelPlugin) { this.plugin = plugin; const version = getPluginVersion(plugin); @@ -451,7 +451,8 @@ export class PanelModel implements DataConfigSource, IPanelModel { if (plugin.onPanelMigration) { if (version !== this.pluginVersion) { - this.options = plugin.onPanelMigration(this); + const newPanelOptions = plugin.onPanelMigration(this); + this.options = await newPanelOptions; this.pluginVersion = version; } } diff --git a/public/app/features/dashboard/utils/panel.test.ts b/public/app/features/dashboard/utils/panel.test.ts index 16698ec3af4..0d82380b489 100644 --- a/public/app/features/dashboard/utils/panel.test.ts +++ b/public/app/features/dashboard/utils/panel.test.ts @@ -83,9 +83,9 @@ describe('applyPanelTimeOverrides', () => { expect(height).toBe(82); }); - it('Calculate panel height with panel plugin zeroChromePadding', () => { + it('Calculate panel height with panel plugin zeroChromePadding', async () => { const panelModel = new PanelModel({}); - panelModel.pluginLoaded( + await panelModel.pluginLoaded( getPanelPlugin({ id: 'table' }, null as unknown as ComponentClass, null).setNoPadding() ); diff --git a/public/app/features/panel/state/actions.ts b/public/app/features/panel/state/actions.ts index 9933ecfdc25..1bf3e1d549c 100644 --- a/public/app/features/panel/state/actions.ts +++ b/public/app/features/panel/state/actions.ts @@ -30,7 +30,7 @@ export function initPanelState(panel: PanelModel): ThunkResult> { } if (!panel.plugin) { - panel.pluginLoaded(plugin); + await panel.pluginLoaded(plugin); } dispatch(panelModelAndPluginReady({ key: panel.key, plugin })); @@ -120,7 +120,7 @@ export function changeToLibraryPanel(panel: PanelModel, libraryPanel: LibraryEle plugin = await dispatch(loadPanelPlugin(newPluginId)); } - panel.pluginLoaded(plugin); + await panel.pluginLoaded(plugin); panel.generateNewKey(); await dispatch(panelModelAndPluginReady({ key: panel.key, plugin }));