From 1a7faed56a53f48a3e7379c2c405790d0c44bf1d Mon Sep 17 00:00:00 2001 From: Ivan Ortega Alba Date: Wed, 20 Nov 2024 13:53:58 +0100 Subject: [PATCH] Add tests to SceneToSaveModelV2 transformer (#96700) * Add tests to SceneToSaveModelV2 transformer * Add text properties always --- .../dashboard/v2alpha0/dashboard.gen.ts | 7 +- .../dashboard/v2alpha0/dashboard.schema.cue | 6 +- .../src/schema/dashboard/v2alpha0/examples.ts | 1 + ...sformSceneToSaveModelSchemaV2.test.ts.snap | 319 ++++++++++++++++++ .../sceneVariablesSetToVariables.test.ts | 4 +- .../sceneVariablesSetToVariables.ts | 14 +- .../transformSceneToSaveModelSchemaV2.test.ts | 285 ++++++++++++++++ .../transformSceneToSaveModelSchemaV2.ts | 6 +- 8 files changed, 633 insertions(+), 9 deletions(-) create mode 100644 public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModelSchemaV2.test.ts.snap create mode 100644 public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.gen.ts b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.gen.ts index beffe5eb4be..23dfe83a255 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.gen.ts +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.gen.ts @@ -31,9 +31,13 @@ export interface DashboardV2Spec { // |* more element types in the future elements: Record; annotations: AnnotationQueryKind[]; + layout: GridLayoutKind; + // Version of the JSON schema, incremented each time a Grafana update brings + // changes to said schema. + // version: will rely on k8s resource versioning, via metadata.resorceVersion // revision?: int // for plugins only // gnetId?: string // ??? Wat is this used for? - layout: GridLayoutKind; + schemaVersion: number; } export const defaultDashboardV2Spec = (): DashboardV2Spec => ({ @@ -47,6 +51,7 @@ export const defaultDashboardV2Spec = (): DashboardV2Spec => ({ elements: {}, annotations: [], layout: defaultGridLayoutKind(), + schemaVersion: 39, }); export interface AnnotationPanelFilter { diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue index 4808ff61031..e7922f46eae 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue @@ -45,8 +45,12 @@ DashboardV2Spec: { layout: GridLayoutKind - // version: will rely on k8s resource versioning, via metadata.resorceVersion + // Version of the JSON schema, incremented each time a Grafana update brings + // changes to said schema. + schemaVersion: uint16 | *39 + + // version: will rely on k8s resource versioning, via metadata.resorceVersion // revision?: int // for plugins only // gnetId?: string // ??? Wat is this used for? } diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts b/packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts index 3ca6973097f..5b98aaf0854 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts @@ -10,6 +10,7 @@ export const handyTestingSchema: DashboardV2Spec = { editable: true, links: [], tags: [], + schemaVersion: 39, timeSettings: { timezone: 'browser', from: 'now-6h', diff --git a/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModelSchemaV2.test.ts.snap b/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModelSchemaV2.test.ts.snap new file mode 100644 index 00000000000..e89b5470781 --- /dev/null +++ b/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModelSchemaV2.test.ts.snap @@ -0,0 +1,319 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`transformSceneToSaveModelSchemaV2 should transform scene to save model schema v2 1`] = ` +{ + "annotations": [], + "cursorSync": "Crosshair", + "description": "Test Description", + "editable": true, + "elements": { + "test-panel-uid": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [], + "queryOptions": {}, + "transformations": [], + }, + }, + "description": "Test Description", + "links": [], + "title": "Test Panel", + "uid": "test-panel-uid", + "vizConfig": { + "kind": "timeseries", + "spec": { + "fieldConfig": { + "defaults": {}, + "overrides": [], + }, + "options": {}, + "pluginVersion": "7.0.0", + }, + }, + }, + }, + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "test-panel-uid", + }, + "height": 0, + "width": 0, + "x": 0, + "y": 0, + }, + }, + ], + }, + }, + "links": [ + { + "asDropdown": false, + "icon": "", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": false, + "title": "Test Link", + "tooltip": "", + "type": "link", + "url": "http://test.com", + }, + ], + "liveNow": true, + "preload": true, + "schemaVersion": 40, + "tags": [ + "tag1", + "tag2", + ], + "timeSettings": { + "autoRefresh": "5s", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + ], + "fiscalYearStartMonth": 1, + "from": "now-1h", + "hideTimepicker": false, + "nowDelay": "1m", + "quickRanges": [], + "timezone": "UTC", + "to": "now", + "weekStart": "monday", + }, + "title": "Test Dashboard", + "variables": [ + { + "kind": "QueryVariable", + "spec": { + "allValue": "*", + "current": { + "text": "text1", + "value": "value1", + }, + "datasource": { + "type": "prometheus", + "uid": "datasource1", + }, + "definition": "definition1", + "description": "A query variable", + "hide": "hideLabel", + "includeAll": true, + "label": "Query Variable", + "multi": true, + "name": "queryVar", + "options": [], + "query": "query1", + "refresh": "onDashboardLoad", + "regex": "regex1", + "skipUrlSync": false, + "sort": "alphabeticalDesc", + }, + }, + { + "kind": "CustomVariable", + "spec": { + "allValue": "All", + "current": { + "text": "option1", + "value": "option1", + }, + "description": "A custom variable", + "hide": "dontHide", + "includeAll": true, + "label": "Custom Variable", + "multi": true, + "name": "customVar", + "options": [ + { + "selected": true, + "text": "option1", + "value": "option1", + }, + { + "selected": false, + "text": "option2", + "value": "option2", + }, + ], + "query": "option1, option2", + "skipUrlSync": false, + }, + }, + { + "kind": "DatasourceVariable", + "spec": { + "allValue": undefined, + "current": { + "text": "text1", + "value": "value1", + }, + "defaultOptionEnabled": true, + "description": "A datasource variable", + "hide": "dontHide", + "includeAll": false, + "label": "Datasource Variable", + "multi": false, + "name": "datasourceVar", + "options": [], + "pluginId": "datasource1", + "refresh": "onDashboardLoad", + "regex": "regex1", + "skipUrlSync": false, + }, + }, + { + "kind": "ConstantVariable", + "spec": { + "current": { + "text": "value4", + "value": "value4", + }, + "description": "A constant variable", + "hide": "dontHide", + "label": "Constant Variable", + "name": "constantVar", + "query": "value4", + "skipUrlSync": true, + }, + }, + { + "kind": "IntervalVariable", + "spec": { + "auto": false, + "auto_count": 10, + "auto_min": "1m", + "current": { + "text": "1m", + "value": "1m", + }, + "description": "An interval variable", + "hide": "dontHide", + "label": "Interval Variable", + "name": "intervalVar", + "options": [ + { + "selected": true, + "text": "1m", + "value": "1m", + }, + { + "selected": false, + "text": "5m", + "value": "5m", + }, + { + "selected": false, + "text": "10m", + "value": "10m", + }, + ], + "query": "1m,5m,10m", + "refresh": "onTimeRangeChanged", + "skipUrlSync": false, + }, + }, + { + "kind": "TextVariable", + "spec": { + "current": { + "text": "value6", + "value": "value6", + }, + "description": "A text variable", + "hide": "dontHide", + "label": "Text Variable", + "name": "textVar", + "query": "value6", + "skipUrlSync": false, + }, + }, + { + "kind": "GroupByVariable", + "spec": { + "current": { + "text": "text7", + "value": "value7", + }, + "datasource": { + "type": "prometheus", + "uid": "datasource2", + }, + "description": "A group by variable", + "hide": "dontHide", + "includeAll": false, + "label": "Group By Variable", + "multi": false, + "name": "groupByVar", + "options": [ + { + "text": "option1", + "value": "option1", + }, + { + "text": "option2", + "value": "option2", + }, + ], + "skipUrlSync": false, + }, + }, + { + "kind": "AdhocVariable", + "spec": { + "baseFilters": [ + { + "condition": "AND", + "key": "key1", + "operator": "=", + "value": "value1", + }, + { + "condition": "OR", + "key": "key2", + "operator": "=", + "value": "value2", + }, + ], + "datasource": { + "type": "prometheus", + "uid": "datasource3", + }, + "defaultKeys": [ + { + "expandable": true, + "group": "defaultGroup1", + "text": "defaultKey1", + "value": "defaultKey1", + }, + ], + "description": "An adhoc variable", + "filters": [ + { + "condition": "AND", + "key": "key3", + "operator": "=", + "value": "value3", + }, + ], + "hide": "dontHide", + "label": "Adhoc Variable", + "name": "adhocVar", + "skipUrlSync": false, + }, + }, + ], +} +`; diff --git a/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.test.ts b/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.test.ts index 48c3bee5c81..293b17a1143 100644 --- a/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.test.ts +++ b/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.test.ts @@ -911,7 +911,7 @@ describe('sceneVariablesSetToVariables', () => { "kind": "ConstantVariable", "spec": { "current": { - "text": undefined, + "text": "constant value", "value": "constant value", }, "description": "test-desc", @@ -979,7 +979,7 @@ describe('sceneVariablesSetToVariables', () => { "auto_count": 30, "auto_min": "10s", "current": { - "text": undefined, + "text": "1m", "value": "1m", }, "description": undefined, diff --git a/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.ts b/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.ts index af522af8ee7..4aff03af4ec 100644 --- a/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.ts +++ b/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.ts @@ -334,7 +334,11 @@ export function sceneVariablesSetToSchemaV2Variables( kind: 'ConstantVariable', spec: { ...commonProperties, - current: currentVariableOption, + current: { + ...currentVariableOption, + // Constant variable doesn't use text state + text: String(variable.state.value), + }, // @ts-expect-error query: variable.state.value, }, @@ -346,7 +350,11 @@ export function sceneVariablesSetToSchemaV2Variables( kind: 'IntervalVariable', spec: { ...commonProperties, - current: currentVariableOption, + current: { + ...currentVariableOption, + // Interval variable doesn't use text state + text: variable.state.value, + }, query: intervals, refresh: VariableRefresh.OnTimeRangeChanged, options: variable.state.intervals.map((interval) => ({ @@ -410,7 +418,7 @@ export function sceneVariablesSetToSchemaV2Variables( }; variables.push(adhocVariable); } else { - throw new Error('Unsupported variable type'); + throw new Error('Unsupported variable type: ' + variable.state.type); } } diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts new file mode 100644 index 00000000000..2d466858c3d --- /dev/null +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts @@ -0,0 +1,285 @@ +import { VariableRefresh } from '@grafana/data'; +import { config } from '@grafana/runtime'; +import { + AdHocFiltersVariable, + behaviors, + ConstantVariable, + CustomVariable, + DataSourceVariable, + GroupByVariable, + IntervalVariable, + QueryVariable, + SceneGridLayout, + SceneRefreshPicker, + SceneTimePicker, + SceneTimeRange, + SceneVariableSet, + TextBoxVariable, + VizPanel, +} from '@grafana/scenes'; +import { + DashboardCursorSync as DashboardCursorSyncV1, + VariableHide as VariableHideV1, + VariableSort as VariableSortV1, +} from '@grafana/schema/dist/esm/index.gen'; + +import { DashboardControls } from '../scene/DashboardControls'; +import { DashboardScene, DashboardSceneState } from '../scene/DashboardScene'; +import { DashboardGridItem } from '../scene/layout-default/DashboardGridItem'; +import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLayoutManager'; + +import { transformSceneToSaveModelSchemaV2 } from './transformSceneToSaveModelSchemaV2'; + +function setupDashboardScene(state: DashboardSceneState): DashboardScene { + return new DashboardScene(state); +} + +describe('transformSceneToSaveModelSchemaV2', () => { + let dashboardScene: DashboardScene; + let prevFeatureToggleValue: boolean; + + beforeAll(() => { + prevFeatureToggleValue = !!config.featureToggles.groupByVariable; + config.featureToggles.groupByVariable = true; + }); + + afterAll(() => { + config.featureToggles.groupByVariable = prevFeatureToggleValue; + }); + + beforeEach(() => { + // The intention is to have a complete dashboard scene + // with all the possible properties set + dashboardScene = setupDashboardScene({ + title: 'Test Dashboard', + description: 'Test Description', + preload: true, + tags: ['tag1', 'tag2'], + uid: 'test-uid', + version: 1, + $timeRange: new SceneTimeRange({ + timeZone: 'UTC', + from: 'now-1h', + to: 'now', + weekStart: 'monday', + fiscalYearStartMonth: 1, + UNSAFE_nowDelay: '1m', + refreshOnActivate: { + afterMs: 10, + percent: 0.1, + }, + }), + controls: new DashboardControls({ + refreshPicker: new SceneRefreshPicker({ + refresh: '5s', + intervals: ['5s', '10s', '30s'], + autoEnabled: true, + autoMinInterval: '5s', + autoValue: '5s', + isOnCanvas: true, + primary: true, + withText: true, + minRefreshInterval: '5s', + }), + timePicker: new SceneTimePicker({ + isOnCanvas: true, + hidePicker: true, + }), + }), + links: [ + { + title: 'Test Link', + url: 'http://test.com', + asDropdown: false, + icon: '', + includeVars: false, + keepTime: false, + tags: [], + targetBlank: false, + tooltip: '', + type: 'link', + }, + ], + body: new DefaultGridLayoutManager({ + grid: new SceneGridLayout({ + isLazy: false, + children: [ + new DashboardGridItem({ + body: new VizPanel({ + key: 'test-panel-uid', + pluginId: 'timeseries', + title: 'Test Panel', + description: 'Test Description', + hoverHeader: true, + hoverHeaderOffset: 10, + fieldConfig: { defaults: {}, overrides: [] }, + displayMode: 'transparent', + pluginVersion: '7.0.0', + $timeRange: new SceneTimeRange({ + timeZone: 'UTC', + from: 'now-3h', + to: 'now', + }), + }), + // Props related to repeatable panels + // repeatedPanels?: VizPanel[], + // variableName?: string, + // itemHeight?: number, + // repeatDirection?: RepeatDirection, + // maxPerRow?: number, + }), + ], + }), + }), + meta: {}, + $behaviors: [ + new behaviors.CursorSync({ + sync: DashboardCursorSyncV1.Crosshair, + }), + new behaviors.LiveNowTimer({ + enabled: true, + }), + ], + $variables: new SceneVariableSet({ + // Test each of the variables + variables: [ + new QueryVariable({ + name: 'queryVar', + label: 'Query Variable', + description: 'A query variable', + skipUrlSync: false, + hide: VariableHideV1.hideLabel, + value: 'value1', + text: 'text1', + query: 'query1', + definition: 'definition1', + datasource: { uid: 'datasource1', type: 'prometheus' }, + sort: VariableSortV1.alphabeticalDesc, + refresh: VariableRefresh.onDashboardLoad, + regex: 'regex1', + allValue: '*', + includeAll: true, + isMulti: true, + }), + new CustomVariable({ + name: 'customVar', + label: 'Custom Variable', + description: 'A custom variable', + skipUrlSync: false, + hide: VariableHideV1.dontHide, + value: 'option1', + text: 'option1', + query: 'option1, option2', + options: [ + { label: 'option1', value: 'option1' }, + { label: 'option2', value: 'option2' }, + ], + isMulti: true, + allValue: 'All', + includeAll: true, + }), + new DataSourceVariable({ + name: 'datasourceVar', + label: 'Datasource Variable', + description: 'A datasource variable', + skipUrlSync: false, + hide: VariableHideV1.dontHide, + value: 'value1', + text: 'text1', + regex: 'regex1', + pluginId: 'datasource1', + defaultOptionEnabled: true, + }), + new ConstantVariable({ + name: 'constantVar', + label: 'Constant Variable', + description: 'A constant variable', + skipUrlSync: false, + hide: VariableHideV1.dontHide, + value: 'value4', + }), + new IntervalVariable({ + name: 'intervalVar', + label: 'Interval Variable', + description: 'An interval variable', + skipUrlSync: false, + hide: VariableHideV1.dontHide, + value: '1m', + intervals: ['1m', '5m', '10m'], + autoEnabled: false, + autoMinInterval: '1m', + autoStepCount: 10, + }), + new TextBoxVariable({ + name: 'textVar', + label: 'Text Variable', + description: 'A text variable', + skipUrlSync: false, + hide: VariableHideV1.dontHide, + value: 'value6', + }), + new GroupByVariable({ + name: 'groupByVar', + label: 'Group By Variable', + description: 'A group by variable', + skipUrlSync: false, + hide: VariableHideV1.dontHide, + value: 'value7', + text: 'text7', + datasource: { uid: 'datasource2', type: 'prometheus' }, + defaultOptions: [ + { text: 'option1', value: 'option1' }, + { text: 'option2', value: 'option2' }, + ], + isMulti: false, + includeAll: false, + }), + new AdHocFiltersVariable({ + name: 'adhocVar', + label: 'Adhoc Variable', + description: 'An adhoc variable', + skipUrlSync: false, + hide: VariableHideV1.dontHide, + datasource: { uid: 'datasource3', type: 'prometheus' }, + baseFilters: [ + { + key: 'key1', + operator: '=', + value: 'value1', + condition: 'AND', + }, + { + key: 'key2', + operator: '=', + value: 'value2', + condition: 'OR', + }, + ], + filters: [ + { + key: 'key3', + operator: '=', + value: 'value3', + condition: 'AND', + }, + ], + defaultKeys: [ + { + text: 'defaultKey1', + value: 'defaultKey1', + group: 'defaultGroup1', + expandable: true, + }, + ], + }), + ], + }), + }); + }); + + it('should transform scene to save model schema v2', () => { + const result = transformSceneToSaveModelSchemaV2(dashboardScene); + + expect(result).toMatchSnapshot(); + }); +}); diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts index 2ef5ee34285..ec8ef3d1940 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts @@ -1,5 +1,4 @@ import { behaviors, SceneDataQuery, SceneDataTransformer, SceneVariableSet, VizPanel } from '@grafana/scenes'; - import { DashboardV2Spec, defaultDashboardV2Spec, @@ -24,7 +23,9 @@ import { ConstantVariableKind, GroupByVariableKind, AdhocVariableKind, -} from '../../../../../packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.gen'; +} from '@grafana/schema/src/schema/dashboard/v2alpha0/dashboard.gen'; +import { DASHBOARD_SCHEMA_VERSION } from 'app/features/dashboard/state/DashboardMigrator'; + import { DashboardScene, DashboardSceneState } from '../scene/DashboardScene'; import { PanelTimeRange } from '../scene/PanelTimeRange'; import { DashboardGridItem } from '../scene/layout-default/DashboardGridItem'; @@ -59,6 +60,7 @@ export function transformSceneToSaveModelSchemaV2(scene: DashboardScene, isSnaps editable: oldDash.editable, links: transformDashboardLinksToEnums(oldDash.links), tags: oldDash.tags, + schemaVersion: DASHBOARD_SCHEMA_VERSION, // EOF dashboard settings // time settings