diff --git a/packages/grafana-data/src/panel/PanelPlugin.ts b/packages/grafana-data/src/panel/PanelPlugin.ts index b879066b469..61985074be9 100644 --- a/packages/grafana-data/src/panel/PanelPlugin.ts +++ b/packages/grafana-data/src/panel/PanelPlugin.ts @@ -3,6 +3,7 @@ import { ComponentClass, ComponentType } from 'react'; import { FieldConfigOptionsRegistry } from '../field/FieldConfigOptionsRegistry'; import { StandardEditorContext } from '../field/standardFieldConfigEditorRegistry'; +import { PanelModel } from '../types/dashboard'; import { FieldConfigProperty, FieldConfigSource } from '../types/fieldOverrides'; import { PanelPluginMeta, @@ -113,7 +114,7 @@ export class PanelPlugin< panel: ComponentType> | null; editor?: ComponentClass>; onPanelMigration?: PanelMigrationHandler; - shouldMigrate?: (panel: ComponentType> | null) => boolean; + shouldMigrate?: (panel: PanelModel) => boolean; onPanelTypeChanged?: PanelTypeChangedHandler; noPadding?: boolean; dataSupport: PanelPluginDataSupport = { @@ -208,10 +209,7 @@ export class PanelPlugin< * * This is a good place to support any changes to the options model */ - setMigrationHandler( - handler: PanelMigrationHandler, - shouldMigrate?: (panel: ComponentType> | null) => boolean - ) { + setMigrationHandler(handler: PanelMigrationHandler, shouldMigrate?: (panel: PanelModel) => boolean) { this.onPanelMigration = handler; this.shouldMigrate = shouldMigrate; return this; diff --git a/public/app/features/dashboard/state/PanelModel.test.ts b/public/app/features/dashboard/state/PanelModel.test.ts index 053b8b3aaaf..b8d11c01258 100644 --- a/public/app/features/dashboard/state/PanelModel.test.ts +++ b/public/app/features/dashboard/state/PanelModel.test.ts @@ -146,12 +146,16 @@ describe('PanelModel', () => { describe('migrations', () => { let initialMigrator: PanelMigrationHandler<(typeof model)['options']> | undefined = undefined; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let initialShouldMigrate: ((panel: any) => boolean) | undefined = undefined; beforeEach(() => { initialMigrator = tablePlugin.onPanelMigration; + initialShouldMigrate = tablePlugin.shouldMigrate; }); afterEach(() => { tablePlugin.onPanelMigration = initialMigrator; + tablePlugin.shouldMigrate = initialShouldMigrate; }); it('should run sync migrations', async () => { @@ -179,6 +183,58 @@ describe('PanelModel', () => { await model.pluginLoaded(tablePlugin); expect(model.options).toMatchObject({ valueToMigrate: 'new-version' }); }); + + it('should run migration when shouldMigrate=true and same version', async () => { + model.options.valueToMigrate = 'old-legacy'; + model.pluginVersion = '1.0.0'; + + tablePlugin.meta.info.version = '1.0.0'; + tablePlugin.onPanelMigration = (p) => ({ ...p.options, valueToMigrate: 'migrated-by-shouldMigrate' }); + tablePlugin.shouldMigrate = () => true; + + await model.pluginLoaded(tablePlugin); + + expect(model.options).toMatchObject({ valueToMigrate: 'migrated-by-shouldMigrate' }); + }); + + it('should run migration when shouldMigrate=false and versions are different', async () => { + model.options.valueToMigrate = 'old-legacy'; + model.pluginVersion = '1.0.0'; + + tablePlugin.meta.info.version = '2.0.0'; + tablePlugin.onPanelMigration = (p) => ({ ...p.options, valueToMigrate: 'migrated-by-version' }); + tablePlugin.shouldMigrate = () => false; + + await model.pluginLoaded(tablePlugin); + + expect(model.options).toMatchObject({ valueToMigrate: 'migrated-by-version' }); + }); + + it('should fallback to version comparison when shouldMigrate is false', async () => { + model.options.valueToMigrate = 'old-legacy'; + model.pluginVersion = '1.0.0'; + + tablePlugin.meta.info.version = '1.0.0'; + tablePlugin.onPanelMigration = (p) => ({ ...p.options, valueToMigrate: 'should-not-migrate' }); + tablePlugin.shouldMigrate = () => false; + + await model.pluginLoaded(tablePlugin); + + expect(model.options).toMatchObject({ valueToMigrate: 'old-legacy' }); + }); + + it('should fallback to version comparison when shouldMigrate is not defined', async () => { + model.options.valueToMigrate = 'old-legacy'; + model.pluginVersion = '1.0.0'; + + tablePlugin.meta.info.version = '2.0.0'; + tablePlugin.onPanelMigration = (p) => ({ ...p.options, valueToMigrate: 'migrated-by-version' }); + tablePlugin.shouldMigrate = undefined; + + await model.pluginLoaded(tablePlugin); + + expect(model.options).toMatchObject({ valueToMigrate: 'migrated-by-version' }); + }); }); it('should apply defaults', () => { diff --git a/public/app/features/dashboard/state/PanelModel.ts b/public/app/features/dashboard/state/PanelModel.ts index 8556006879d..a13208e15e1 100644 --- a/public/app/features/dashboard/state/PanelModel.ts +++ b/public/app/features/dashboard/state/PanelModel.ts @@ -458,7 +458,7 @@ export class PanelModel implements DataConfigSource, IPanelModel { } if (plugin.onPanelMigration) { - if (version !== this.pluginVersion) { + if (version !== this.pluginVersion || plugin.shouldMigrate?.(this)) { const newPanelOptions = plugin.onPanelMigration(this); this.options = await newPanelOptions; this.pluginVersion = version;