From 42218fbdbb0a77e3f55e3ba243270b183fa2cbfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 10 Oct 2023 14:33:58 +0200 Subject: [PATCH] DashboardScene: Support auto migration for angular panels (#76100) * DashboardScene: Support auto migration for angular panels * minor tweak * Update scenes * Review fix * Update --- .betterer.results | 23 +++++++----- package.json | 2 +- .../serialization/angularMigration.test.ts | 35 +++++++++++++++++++ .../serialization/angularMigration.ts | 22 ++++++++++++ .../sceneVariablesSetToVariables.test.ts | 1 - .../transformSaveModelToScene.ts | 3 ++ .../features/dashboard/state/PanelModel.ts | 2 +- public/app/features/scenes/apps/utils.ts | 2 +- .../scenes/scenes/queryVariableDemo.tsx | 6 ++-- .../scenes/scenes/repeatingPanels.tsx | 2 +- yarn.lock | 10 +++--- 11 files changed, 86 insertions(+), 22 deletions(-) create mode 100644 public/app/features/dashboard-scene/serialization/angularMigration.test.ts create mode 100644 public/app/features/dashboard-scene/serialization/angularMigration.ts diff --git a/.betterer.results b/.betterer.results index 3fb02eb3288..8f8c6ee10c8 100644 --- a/.betterer.results +++ b/.betterer.results @@ -3007,6 +3007,10 @@ exports[`better eslint`] = { "public/app/features/dashboard-scene/inspect/InspectJsonTab.tsx:5381": [ [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"] ], + "public/app/features/dashboard-scene/serialization/angularMigration.test.ts:5381": [ + [0, 0, 0, "Unexpected any. Specify a different type.", "0"], + [0, 0, 0, "Unexpected any. Specify a different type.", "1"] + ], "public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] @@ -3579,18 +3583,19 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "9"], [0, 0, 0, "Unexpected any. Specify a different type.", "10"], [0, 0, 0, "Unexpected any. Specify a different type.", "11"], - [0, 0, 0, "Do not use any type assertions.", "12"], - [0, 0, 0, "Unexpected any. Specify a different type.", "13"], - [0, 0, 0, "Do not use any type assertions.", "14"], - [0, 0, 0, "Unexpected any. Specify a different type.", "15"], + [0, 0, 0, "Unexpected any. Specify a different type.", "12"], + [0, 0, 0, "Do not use any type assertions.", "13"], + [0, 0, 0, "Unexpected any. Specify a different type.", "14"], + [0, 0, 0, "Do not use any type assertions.", "15"], [0, 0, 0, "Unexpected any. Specify a different type.", "16"], [0, 0, 0, "Unexpected any. Specify a different type.", "17"], - [0, 0, 0, "Do not use any type assertions.", "18"], - [0, 0, 0, "Unexpected any. Specify a different type.", "19"], - [0, 0, 0, "Do not use any type assertions.", "20"], - [0, 0, 0, "Unexpected any. Specify a different type.", "21"], + [0, 0, 0, "Unexpected any. Specify a different type.", "18"], + [0, 0, 0, "Do not use any type assertions.", "19"], + [0, 0, 0, "Unexpected any. Specify a different type.", "20"], + [0, 0, 0, "Do not use any type assertions.", "21"], [0, 0, 0, "Unexpected any. Specify a different type.", "22"], - [0, 0, 0, "Unexpected any. Specify a different type.", "23"] + [0, 0, 0, "Unexpected any. Specify a different type.", "23"], + [0, 0, 0, "Unexpected any. Specify a different type.", "24"] ], "public/app/features/dashboard/state/TimeModel.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], diff --git a/package.json b/package.json index 82e096ab989..07bd5cf092d 100644 --- a/package.json +++ b/package.json @@ -250,7 +250,7 @@ "@grafana/lezer-traceql": "0.0.6", "@grafana/monaco-logql": "^0.0.7", "@grafana/runtime": "workspace:*", - "@grafana/scenes": "^1.13.0", + "@grafana/scenes": "^1.15.0", "@grafana/schema": "workspace:*", "@grafana/ui": "workspace:*", "@kusto/monaco-kusto": "^7.4.0", diff --git a/public/app/features/dashboard-scene/serialization/angularMigration.test.ts b/public/app/features/dashboard-scene/serialization/angularMigration.test.ts new file mode 100644 index 00000000000..c8a8d890889 --- /dev/null +++ b/public/app/features/dashboard-scene/serialization/angularMigration.test.ts @@ -0,0 +1,35 @@ +import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks'; +import { PanelModel } from 'app/features/dashboard/state'; + +import { getAngularPanelMigrationHandler } from './angularMigration'; + +describe('getAngularPanelMigrationHandler', () => { + describe('Given an old angular panel', () => { + it('Should call migration handler', () => { + const onPanelTypeChanged = (panel: PanelModel, prevPluginId: string, prevOptions: Record) => { + panel.fieldConfig = { defaults: { unit: 'bytes' }, overrides: [] }; + return { name: prevOptions.angular.oldOptionProp }; + }; + + const reactPlugin = getPanelPlugin({ id: 'timeseries' }).setPanelChangeHandler(onPanelTypeChanged as any); + + const oldModel = new PanelModel({ + autoMigrateFrom: 'graph', + oldOptionProp: 'old name', + type: 'timeseries', + }); + + const mutatedModel = { + id: 1, + type: 'timeseries', + options: {}, + fieldConfig: { defaults: {}, overrides: [] }, + }; + + getAngularPanelMigrationHandler(oldModel)(mutatedModel, reactPlugin); + + expect(mutatedModel.options).toEqual({ name: 'old name' }); + expect(mutatedModel.fieldConfig).toEqual({ defaults: { unit: 'bytes' }, overrides: [] }); + }); + }); +}); diff --git a/public/app/features/dashboard-scene/serialization/angularMigration.ts b/public/app/features/dashboard-scene/serialization/angularMigration.ts new file mode 100644 index 00000000000..0eb88a32886 --- /dev/null +++ b/public/app/features/dashboard-scene/serialization/angularMigration.ts @@ -0,0 +1,22 @@ +import { PanelModel as PanelModelFromData, PanelPlugin } from '@grafana/data'; +import { autoMigrateAngular, PanelModel } from 'app/features/dashboard/state/PanelModel'; + +export function getAngularPanelMigrationHandler(oldModel: PanelModel) { + return function handleAngularPanelMigrations(panel: PanelModelFromData, plugin: PanelPlugin) { + if (plugin.angularPanelCtrl) { + panel.options = { angularOptions: oldModel.getOptionsToRemember() }; + return; + } + + if (oldModel.autoMigrateFrom) { + const wasAngular = autoMigrateAngular[oldModel.autoMigrateFrom] != null; + const oldOptions = oldModel.getOptionsToRemember(); + const prevPluginId = oldModel.autoMigrateFrom; + + if (plugin.onPanelTypeChanged) { + const prevOptions = wasAngular ? { angular: oldOptions } : oldOptions.options; + Object.assign(panel.options, plugin.onPanelTypeChanged(panel, prevPluginId, prevOptions, panel.fieldConfig)); + } + } + }; +} diff --git a/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.test.ts b/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.test.ts index 12af487dc7d..fb528d04bca 100644 --- a/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.test.ts +++ b/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.test.ts @@ -122,7 +122,6 @@ describe('sceneVariablesSetToVariables', () => { "query": "query", "refresh": 1, "regex": "", - "sort": 1, } `); }); diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts index 3ee6e03618e..690a7bc735d 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts @@ -46,6 +46,8 @@ import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior'; import { createPanelDataProvider } from '../utils/createPanelDataProvider'; import { getVizPanelKeyForPanelId } from '../utils/utils'; +import { getAngularPanelMigrationHandler } from './angularMigration'; + export interface DashboardLoaderState { dashboard?: DashboardScene; isLoading?: boolean; @@ -338,6 +340,7 @@ export function buildGridItemForPanel(panel: PanelModel): SceneGridItemLike { menu: new VizPanelMenu({ $behaviors: [panelMenuBehavior], }), + _UNSAFE_customMigrationHandler: getAngularPanelMigrationHandler(panel), }; if (panel.timeFrom || panel.timeShift) { diff --git a/public/app/features/dashboard/state/PanelModel.ts b/public/app/features/dashboard/state/PanelModel.ts index ab336f1eafb..d053d2c756d 100644 --- a/public/app/features/dashboard/state/PanelModel.ts +++ b/public/app/features/dashboard/state/PanelModel.ts @@ -394,7 +394,7 @@ export class PanelModel implements DataConfigSource, IPanelModel { } } - private getOptionsToRemember() { + public getOptionsToRemember(): any { return Object.keys(this).reduce((acc, property) => { if (notPersistedProperties[property] || mustKeepProps[property]) { return acc; diff --git a/public/app/features/scenes/apps/utils.ts b/public/app/features/scenes/apps/utils.ts index eb37d5dc16e..48afdf1efee 100644 --- a/public/app/features/scenes/apps/utils.ts +++ b/public/app/features/scenes/apps/utils.ts @@ -50,7 +50,7 @@ export function getVariablesDefinitions() { new QueryVariable({ name: 'instance', datasource: { uid: 'gdev-prometheus' }, - query: { query: 'label_values(grafana_http_request_duration_seconds_sum, instance)' }, + query: { query: 'label_values(grafana_http_request_duration_seconds_sum, instance)', refId: 'A' }, }), ], }); diff --git a/public/app/features/scenes/scenes/queryVariableDemo.tsx b/public/app/features/scenes/scenes/queryVariableDemo.tsx index d51143cf783..d1f1e025426 100644 --- a/public/app/features/scenes/scenes/queryVariableDemo.tsx +++ b/public/app/features/scenes/scenes/queryVariableDemo.tsx @@ -31,19 +31,19 @@ export function getQueryVariableDemo(): DashboardScene { new QueryVariable({ name: 'instance (using datasource variable)', refresh: VariableRefresh.onTimeRangeChanged, - query: { query: 'label_values(go_gc_duration_seconds, ${metric})' }, + query: { query: 'label_values(go_gc_duration_seconds, ${metric})', refId: 'A' }, datasource: { uid: '${datasource}' }, }), new QueryVariable({ name: 'label values (on time range refresh)', refresh: VariableRefresh.onTimeRangeChanged, - query: { query: 'label_values(go_gc_duration_seconds, ${metric})' }, + query: { query: 'label_values(go_gc_duration_seconds, ${metric})', refId: 'B' }, datasource: { uid: 'gdev-prometheus', type: 'prometheus' }, }), new QueryVariable({ name: 'legacy (graphite)', refresh: VariableRefresh.onTimeRangeChanged, - query: { queryType: 'Default', target: 'stats.response.*' }, + query: { queryType: 'Default', target: 'stats.response.*', refId: 'C' }, datasource: { uid: 'gdev-graphite', type: 'graphite' }, }), ], diff --git a/public/app/features/scenes/scenes/repeatingPanels.tsx b/public/app/features/scenes/scenes/repeatingPanels.tsx index b2440e6f3bd..8286f8cfc71 100644 --- a/public/app/features/scenes/scenes/repeatingPanels.tsx +++ b/public/app/features/scenes/scenes/repeatingPanels.tsx @@ -79,7 +79,7 @@ export function getRepeatingPanelsDemo(): DashboardScene { function changeVariable(variable: TestVariable) { const sub = variable.subscribeToState((state, old) => { if (!state.loading && old.loading) { - if (variable.state.optionsToReturn.length === 2) { + if (variable.state.optionsToReturn?.length === 2) { variable.setState({ query: 'ABC', optionsToReturn: [ diff --git a/yarn.lock b/yarn.lock index eaa1fe156ec..2d0ae3143a9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3343,9 +3343,9 @@ __metadata: languageName: unknown linkType: soft -"@grafana/scenes@npm:^1.13.0": - version: 1.13.0 - resolution: "@grafana/scenes@npm:1.13.0" +"@grafana/scenes@npm:^1.15.0": + version: 1.15.0 + resolution: "@grafana/scenes@npm:1.15.0" dependencies: "@grafana/e2e-selectors": 10.0.2 react-grid-layout: 1.3.4 @@ -3357,7 +3357,7 @@ __metadata: "@grafana/runtime": 10.0.3 "@grafana/schema": 10.0.3 "@grafana/ui": 10.0.3 - checksum: 3a6c4419ee324d49f41b9569dc86ca7ffa926438eedd762aa79c3327c39cd16b9671047ad02f5de3c52190a81b4139cdb75653a4b3741753f851ca0c9155b9bf + checksum: 8416327ad9a0e3be35d5a106788248d5886f6aa220777cf0772ef73a3f64ecac889f58a8e804beea0de339acd571d6e49a10278180cf1c50a2a51f44e03bf93e languageName: node linkType: hard @@ -17654,7 +17654,7 @@ __metadata: "@grafana/lezer-traceql": 0.0.6 "@grafana/monaco-logql": ^0.0.7 "@grafana/runtime": "workspace:*" - "@grafana/scenes": ^1.13.0 + "@grafana/scenes": ^1.15.0 "@grafana/schema": "workspace:*" "@grafana/tsconfig": ^1.3.0-rc1 "@grafana/ui": "workspace:*"