Plugins: Allow async panel migrations (#73782)

* Plugins: Allow async panel migrations

* comment
pull/73909/head
Josh Hunt 2 years ago committed by GitHub
parent 7543a28ae2
commit f95405d3c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      packages/grafana-data/src/types/panel.ts
  2. 4
      public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.test.tsx
  3. 42
      public/app/features/dashboard/state/PanelModel.test.ts
  4. 5
      public/app/features/dashboard/state/PanelModel.ts
  5. 4
      public/app/features/dashboard/utils/panel.test.ts
  6. 4
      public/app/features/panel/state/actions.ts

@ -135,9 +135,12 @@ export interface PanelEditorProps<T = any> {
} }
/** /**
* 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<TOptions = any> = (panel: PanelModel<TOptions>) => Partial<TOptions>; export type PanelMigrationHandler<TOptions = any> = (
panel: PanelModel<TOptions>
) => Partial<TOptions> | Promise<Partial<TOptions>>;
/** /**
* Called before a panel is initialized. Allows panel inspection for any updates before changing the panel type. * Called before a panel is initialized. Allows panel inspection for any updates before changing the panel type.

@ -132,14 +132,14 @@ describe('DashboardPrompt', () => {
}); });
}); });
it('Should ignore panel schema migrations', () => { it('Should ignore panel schema migrations', async () => {
const { original, dash } = getTestContext(); const { original, dash } = getTestContext();
const plugin = getPanelPlugin({}).setMigrationHandler((panel) => { const plugin = getPanelPlugin({}).setMigrationHandler((panel) => {
delete (panel as any).legend; delete (panel as any).legend;
return { option1: 'Aasd' }; return { option1: 'Aasd' };
}); });
dash.panels[0].pluginLoaded(plugin); await dash.panels[0].pluginLoaded(plugin);
expect(hasChanges(dash, original)).toBe(false); expect(hasChanges(dash, original)).toBe(false);
}); });

@ -8,6 +8,7 @@ import {
standardFieldConfigEditorRegistry, standardFieldConfigEditorRegistry,
dateTime, dateTime,
TimeRange, TimeRange,
PanelMigrationHandler,
} from '@grafana/data'; } from '@grafana/data';
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks'; import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
import { mockStandardFieldConfigOptions } from '@grafana/data/test/helpers/fieldConfig'; import { mockStandardFieldConfigOptions } from '@grafana/data/test/helpers/fieldConfig';
@ -80,7 +81,7 @@ describe('PanelModel', () => {
}, },
}); });
beforeEach(() => { beforeEach(async () => {
persistedOptionsMock = { persistedOptionsMock = {
fieldOptions: { fieldOptions: {
thresholds: [ thresholds: [
@ -141,7 +142,44 @@ describe('PanelModel', () => {
}; };
model = new PanelModel(modelJson); 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', () => { it('should apply defaults', () => {

@ -429,7 +429,7 @@ export class PanelModel implements DataConfigSource, IPanelModel {
this.options = options.options; this.options = options.options;
} }
pluginLoaded(plugin: PanelPlugin) { async pluginLoaded(plugin: PanelPlugin) {
this.plugin = plugin; this.plugin = plugin;
const version = getPluginVersion(plugin); const version = getPluginVersion(plugin);
@ -451,7 +451,8 @@ export class PanelModel implements DataConfigSource, IPanelModel {
if (plugin.onPanelMigration) { if (plugin.onPanelMigration) {
if (version !== this.pluginVersion) { if (version !== this.pluginVersion) {
this.options = plugin.onPanelMigration(this); const newPanelOptions = plugin.onPanelMigration(this);
this.options = await newPanelOptions;
this.pluginVersion = version; this.pluginVersion = version;
} }
} }

@ -83,9 +83,9 @@ describe('applyPanelTimeOverrides', () => {
expect(height).toBe(82); 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({}); const panelModel = new PanelModel({});
panelModel.pluginLoaded( await panelModel.pluginLoaded(
getPanelPlugin({ id: 'table' }, null as unknown as ComponentClass<PanelProps>, null).setNoPadding() getPanelPlugin({ id: 'table' }, null as unknown as ComponentClass<PanelProps>, null).setNoPadding()
); );

@ -30,7 +30,7 @@ export function initPanelState(panel: PanelModel): ThunkResult<Promise<void>> {
} }
if (!panel.plugin) { if (!panel.plugin) {
panel.pluginLoaded(plugin); await panel.pluginLoaded(plugin);
} }
dispatch(panelModelAndPluginReady({ key: panel.key, plugin })); dispatch(panelModelAndPluginReady({ key: panel.key, plugin }));
@ -120,7 +120,7 @@ export function changeToLibraryPanel(panel: PanelModel, libraryPanel: LibraryEle
plugin = await dispatch(loadPanelPlugin(newPluginId)); plugin = await dispatch(loadPanelPlugin(newPluginId));
} }
panel.pluginLoaded(plugin); await panel.pluginLoaded(plugin);
panel.generateNewKey(); panel.generateNewKey();
await dispatch(panelModelAndPluginReady({ key: panel.key, plugin })); await dispatch(panelModelAndPluginReady({ key: panel.key, plugin }));

Loading…
Cancel
Save