diff --git a/src/migrations.test.ts b/src/migrations.test.ts index c466451..4108752 100644 --- a/src/migrations.test.ts +++ b/src/migrations.test.ts @@ -1,5 +1,5 @@ import { PanelModel } from '@grafana/data'; - +import { cloneDeep } from 'lodash'; import { clockMigrationHandler } from './migrations'; describe('Clock migrations', () => { @@ -249,4 +249,33 @@ describe('Clock migrations', () => { expect(options).toMatchSnapshot(); expect(panel).toMatchSnapshot(); }); + + describe('support readonly targets in G12', () => { + it('should not try to mutate targets when migrating panel', () => { + const panel = createPanelWithReadonlyTargets({ + options: { + countdownSettings: { source: 'input' }, + }, + datasource: { type: 'test', uid: '123' }, + targets: [], + } as unknown as PanelModel); + + expect(() => clockMigrationHandler(panel)).not.toThrow(); + }); + }); }); + +function createPanelWithReadonlyTargets(panel: Partial): PanelModel { + // https://github.com/grafana/grafana/blob/2bbba880cd2a8269e262e4ea7138fcd43f4d5c66/public/app/features/dashboard-scene/serialization/angularMigration.ts#L18 + const targetClone = cloneDeep(panel.targets); + Object.defineProperty(panel, 'targets', { + get: function () { + console.warn( + 'Accessing the targets property when migrating a panel plugin is deprecated. Changes to this property will be ignored.' + ); + return targetClone; + }, + }); + + return panel as unknown as PanelModel; +} diff --git a/src/migrations.ts b/src/migrations.ts index 16b010f..8277627 100644 --- a/src/migrations.ts +++ b/src/migrations.ts @@ -10,8 +10,10 @@ export const clockMigrationHandler = (panel: PanelModel): Partial< options.refresh = ClockRefresh.dashboard; } - if (detectInputOnlyPluginConfig(panel)) { - migrateInputOnlyPluginConfig(panel); + if (!isReadonlyTarget(panel)) { + if (detectInputOnlyPluginConfig(panel)) { + migrateInputOnlyPluginConfig(panel); + } } // configuration options moved as the panel migrated, clean up if needed cleanupConfig(panel); @@ -159,3 +161,8 @@ const cleanupConfig = (panel: PanelModel) => { delete panel.timezoneSettings; } }; + +function isReadonlyTarget(panel: PanelModel) { + const description = Object.getOwnPropertyDescriptor(panel, 'targets'); + return typeof description?.set === 'undefined' && typeof description?.get === 'function'; +}