From a110917577613da3b11104d181a4a4a575efe293 Mon Sep 17 00:00:00 2001 From: Haris Rozajac <58232930+harisrozajac@users.noreply.github.com> Date: Tue, 31 Dec 2024 11:43:04 -0700 Subject: [PATCH] Dashboard SchemaV2: V2 -> Scene -> V2 integration test (#98146) * Tests for layout, transformations; displayMode * wip * fix annotations * fix panel query and transformation topic * fix dash id, default options, fieldConfig * Complete integration test * fix annotation query * Fix test * Clarify * Have default value of builtIn * update import path --- .betterer.results | 3 + .../dashboard/v2alpha0/dashboard.gen.ts | 6 +- .../dashboard/v2alpha0/dashboard.schema.cue | 5 +- .../src/schema/dashboard/v2alpha0/examples.ts | 8 +- ...sformSceneToSaveModelSchemaV2.test.ts.snap | 26 +----- .../sceneVariablesSetToVariables.test.ts | 1 - .../sceneVariablesSetToVariables.ts | 1 - .../transformSaveModelSchemaV2ToScene.ts | 1 + .../transformSceneToSaveModelSchemaV2.test.ts | 3 +- .../transformSceneToSaveModelSchemaV2.ts | 31 ++++--- .../serialization/transformToV1TypesUtils.ts | 82 +++++++++-------- .../v2schema/transformers.test.ts | 91 +++++++++++++++++++ 12 files changed, 171 insertions(+), 87 deletions(-) create mode 100644 public/app/features/dashboard-scene/v2schema/transformers.test.ts diff --git a/.betterer.results b/.betterer.results index 0995d55b8cb..e1f60c1392c 100644 --- a/.betterer.results +++ b/.betterer.results @@ -3198,6 +3198,9 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "4"], [0, 0, 0, "Unexpected any. Specify a different type.", "5"] ], + "public/app/features/dashboard-scene/serialization/transformToV1TypesUtils.ts:5381": [ + [0, 0, 0, "Unexpected any. Specify a different type.", "0"] + ], "public/app/features/dashboard-scene/settings/DeleteDashboardButton.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], 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 7fbac4c909b..0dfb65702fa 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.gen.ts +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.gen.ts @@ -476,7 +476,7 @@ export const defaultVizConfigKind = (): VizConfigKind => ({ export interface AnnotationQuerySpec { datasource?: DataSourceRef; - query: DataQueryKind; + query?: DataQueryKind; builtIn?: boolean; enable: boolean; filter: AnnotationPanelFilter; @@ -486,7 +486,7 @@ export interface AnnotationQuerySpec { } export const defaultAnnotationQuerySpec = (): AnnotationQuerySpec => ({ - query: defaultDataQueryKind(), + builtIn: false, enable: false, filter: defaultAnnotationPanelFilter(), hide: false, @@ -976,7 +976,6 @@ export interface DatasourceVariableSpec { refresh: VariableRefresh; regex: string; current: VariableOption; - defaultOptionEnabled: boolean; options: VariableOption[]; multi: boolean; includeAll: boolean; @@ -993,7 +992,6 @@ export const defaultDatasourceVariableSpec = (): DatasourceVariableSpec => ({ refresh: "never", regex: "", current: { text: "", value: "", }, - defaultOptionEnabled: false, options: [], multi: false, includeAll: false, 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 d6a71bb211b..b2774bbd894 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue @@ -369,8 +369,8 @@ VizConfigKind: { AnnotationQuerySpec: { datasource?: DataSourceRef - query: DataQueryKind - builtIn?: bool + query?: DataQueryKind + builtIn?: bool | *false enable: bool filter: AnnotationPanelFilter hide: bool @@ -672,7 +672,6 @@ DatasourceVariableSpec: { text: "" value: "" } - defaultOptionEnabled: bool | *false options: [...VariableOption] | *[] multi: bool | *false includeAll: bool | *false diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts b/packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts index cb4b024e5ac..12ac19ebc3a 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts @@ -26,7 +26,7 @@ export const handyTestingSchema: DashboardV2Spec = { { kind: 'AnnotationQuery', spec: { - builtIn: true, + builtIn: false, query: { kind: 'prometheus', spec: { @@ -47,6 +47,7 @@ export const handyTestingSchema: DashboardV2Spec = { { kind: 'AnnotationQuery', spec: { + builtIn: false, datasource: { type: 'grafana-testdata-datasource', uid: 'uid', @@ -69,6 +70,7 @@ export const handyTestingSchema: DashboardV2Spec = { { kind: 'AnnotationQuery', spec: { + builtIn: false, datasource: { type: 'grafana-testdata-datasource', uid: 'uid', @@ -87,6 +89,7 @@ export const handyTestingSchema: DashboardV2Spec = { { kind: 'AnnotationQuery', spec: { + builtIn: false, datasource: { type: 'grafana-testdata-datasource', uid: 'uid', @@ -284,7 +287,6 @@ export const handyTestingSchema: DashboardV2Spec = { text: 'text1', value: 'value1', }, - defaultOptionEnabled: true, description: 'A datasource variable', hide: 'dontHide', includeAll: false, @@ -345,7 +347,7 @@ export const handyTestingSchema: DashboardV2Spec = { }, ], query: '1m,5m,10m', - refresh: 'onDashboardLoad', + refresh: 'onTimeRangeChanged', skipUrlSync: false, }, }, 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 index 86644958a4a..02f1b4b1c8e 100644 --- a/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModelSchemaV2.test.ts.snap +++ b/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModelSchemaV2.test.ts.snap @@ -19,14 +19,6 @@ exports[`transformSceneToSaveModelSchemaV2 should transform scene to save model "hide": false, "iconColor": "red", "name": "query1", - "query": { - "kind": "grafana", - "spec": { - "enable": true, - "iconColor": "red", - "name": "query1", - }, - }, }, }, { @@ -45,14 +37,6 @@ exports[`transformSceneToSaveModelSchemaV2 should transform scene to save model "hide": true, "iconColor": "blue", "name": "query2", - "query": { - "kind": "prometheus", - "spec": { - "enable": true, - "iconColor": "blue", - "name": "query2", - }, - }, }, }, { @@ -71,14 +55,6 @@ exports[`transformSceneToSaveModelSchemaV2 should transform scene to save model "hide": true, "iconColor": "green", "name": "query3", - "query": { - "kind": "loki", - "spec": { - "enable": true, - "iconColor": "green", - "name": "query3", - }, - }, }, }, ], @@ -125,6 +101,7 @@ exports[`transformSceneToSaveModelSchemaV2 should transform scene to save model }, }, }, + "id": 1, "layout": { "kind": "GridLayout", "spec": { @@ -249,7 +226,6 @@ exports[`transformSceneToSaveModelSchemaV2 should transform scene to save model "text": "text1", "value": "value1", }, - "defaultOptionEnabled": true, "description": "A datasource variable", "hide": "dontHide", "includeAll": false, diff --git a/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.test.ts b/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.test.ts index 838e01764bc..af6bc4d5618 100644 --- a/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.test.ts +++ b/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.test.ts @@ -888,7 +888,6 @@ describe('sceneVariablesSetToVariables', () => { "selected-ds-2", ], }, - "defaultOptionEnabled": false, "description": "test-desc", "hide": "dontHide", "includeAll": true, diff --git a/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.ts b/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.ts index d64a10ec40b..e0624887e3b 100644 --- a/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.ts +++ b/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.ts @@ -326,7 +326,6 @@ export function sceneVariablesSetToSchemaV2Variables( regex: variable.state.regex, refresh: 'onDashboardLoad', pluginId: variable.state.pluginId, - defaultOptionEnabled: !!variable.state.defaultOptionEnabled, multi: variable.state.isMulti || false, allValue: variable.state.allValue, includeAll: variable.state.includeAll || false, diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts index 65511180288..c0479b61946 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts @@ -516,6 +516,7 @@ function createSceneVariableFromVariableModel(variable: TypedVariableModelV2): S value: variable.spec.current?.value || [], text: variable.spec.current?.text || [], skipUrlSync: variable.spec.skipUrlSync, + isMulti: variable.spec.multi, hide: transformVariableHideToEnumV1(variable.spec.hide), // @ts-expect-error defaultOptions: variable.options, diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts index 7963f6883de..035c7298a8a 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts @@ -85,6 +85,7 @@ describe('transformSceneToSaveModelSchemaV2', () => { // with all the possible properties set dashboardScene = setupDashboardScene({ $data: new DashboardDataLayerSet({ annotationLayers }), + id: 1, title: 'Test Dashboard', description: 'Test Description', preload: true, @@ -328,7 +329,7 @@ describe('transformSceneToSaveModelSchemaV2', () => { // Check that the annotation layers are correctly transformed expect(result.annotations).toHaveLength(3); // check annotation layer 3 with no datasource has the default datasource defined as type - expect(result.annotations?.[2].spec.query.kind).toBe('loki'); + expect(result.annotations?.[2].spec.datasource?.type).toBe('loki'); }); }); diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts index cdd5c2a4505..bb46999d425 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts @@ -36,7 +36,6 @@ import { AdhocVariableKind, AnnotationQueryKind, defaultAnnotationPanelFilter, - defaultAnnotationQuerySpec, DataLink, } from '../../../../../packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.gen'; import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet'; @@ -66,6 +65,7 @@ export function transformSceneToSaveModelSchemaV2(scene: DashboardScene, isSnaps const dashboardSchemaV2: DeepPartial = { //dashboard settings + id: oldDash.id ? oldDash.id : undefined, title: oldDash.title, description: oldDash.description ?? '', cursorSync: getCursorSync(oldDash), @@ -265,13 +265,13 @@ function getVizPanelQueries(vizPanel: VizPanel): PanelQueryKind[] { vizPanelQueries.forEach((query) => { const dataQuery: DataQueryKind = { kind: getDataQueryKind(query), - spec: query, + spec: omit(query, 'datasource', 'refId', 'hide'), }; const querySpec: PanelQuerySpec = { datasource: datasource ?? getDefaultDataSourceRef(), query: dataQuery, refId: query.refId, - hidden: query.hidden, + hidden: Boolean(query.hide), }; queries.push({ kind: 'PanelQuery', @@ -312,10 +312,13 @@ function getVizPanelTransformations(vizPanel: VizPanel): TransformationKind[] { id: transformation.filter?.id ?? '', options: transformation.filter?.options ?? {}, }, - topic: transformation.topic, options: transformation.options, }; + if (transformation.topic !== undefined) { + transformationSpec.topic = transformation.topic; + } + transformations.push({ kind: transformation.id, spec: transformationSpec, @@ -349,6 +352,7 @@ function getVizPanelQueryOptions(vizPanel: VizPanel): QueryOptionsSpec { if (panelTime instanceof PanelTimeRange) { queryOptions.timeFrom = panelTime.state.timeFrom; queryOptions.timeShift = panelTime.state.timeShift; + queryOptions.hideTimeOverride = panelTime.state.hideTimeOverride; } return queryOptions; } @@ -399,22 +403,25 @@ function getAnnotations(state: DashboardSceneState): AnnotationQueryKind[] { const result: AnnotationQueryKind = { kind: 'AnnotationQuery', spec: { + builtIn: Boolean(layer.state.query.builtIn), name: layer.state.query.name, datasource: layer.state.query.datasource || getDefaultDataSourceRef(), - query: { - kind: getAnnotationQueryKind(layer.state.query), - spec: omit(layer.state.query, 'datasource'), - }, enable: Boolean(layer.state.isEnabled), hide: Boolean(layer.state.isHidden), filter: layer.state.query.filter ?? defaultAnnotationPanelFilter(), iconColor: layer.state.query.iconColor, - builtIn: - layer.state.query.builtIn === undefined - ? Boolean(layer.state.query.builtIn) - : defaultAnnotationQuerySpec().builtIn, }, }; + + // Check if DataQueryKind exists + const queryKind = getAnnotationQueryKind(layer.state.query); + if (layer.state.query.query?.kind === queryKind) { + result.spec.query = { + kind: queryKind, + spec: layer.state.query.query.spec, + }; + } + annotations.push(result); } return annotations; diff --git a/public/app/features/dashboard-scene/serialization/transformToV1TypesUtils.ts b/public/app/features/dashboard-scene/serialization/transformToV1TypesUtils.ts index b7b614e09aa..bcf57bd89db 100644 --- a/public/app/features/dashboard-scene/serialization/transformToV1TypesUtils.ts +++ b/public/app/features/dashboard-scene/serialization/transformToV1TypesUtils.ts @@ -113,44 +113,52 @@ export function transformMappingsToV1(fieldConfig: FieldConfigSource): FieldConf } }; + const transformedDefaults: any = { + ...fieldConfig.defaults, + }; + + if (fieldConfig.defaults.mappings) { + transformedDefaults.mappings = fieldConfig.defaults.mappings.map((mapping) => { + switch (mapping.type) { + case 'value': + return { + ...mapping, + type: MappingTypeV1.ValueToText, + }; + case 'range': + return { + ...mapping, + type: MappingTypeV1.RangeToText, + }; + case 'regex': + return { + ...mapping, + type: MappingTypeV1.RegexToText, + }; + case 'special': + return { + ...mapping, + options: { + ...mapping.options, + match: transformSpecialValueMatchToV1(mapping.options.match), + }, + type: MappingTypeV1.SpecialValue, + }; + default: + return mapping; + } + }); + } + + if (fieldConfig.defaults.thresholds) { + transformedDefaults.thresholds = { + ...fieldConfig.defaults.thresholds, + mode: getThresholdsMode(fieldConfig.defaults.thresholds.mode), + }; + } + return { ...fieldConfig, - defaults: { - ...fieldConfig.defaults, - mappings: fieldConfig.defaults.mappings?.map((mapping) => { - switch (mapping.type) { - case 'value': - return { - ...mapping, - type: MappingTypeV1.ValueToText, - }; - case 'range': - return { - ...mapping, - type: MappingTypeV1.RangeToText, - }; - case 'regex': - return { - ...mapping, - type: MappingTypeV1.RegexToText, - }; - case 'special': - return { - ...mapping, - options: { - ...mapping.options, - match: transformSpecialValueMatchToV1(mapping.options.match), - }, - type: MappingTypeV1.SpecialValue, - }; - default: - return mapping; - } - }), - thresholds: fieldConfig.defaults.thresholds && { - ...fieldConfig.defaults.thresholds, - mode: getThresholdsMode(fieldConfig.defaults.thresholds.mode), - }, - }, + defaults: transformedDefaults, }; } diff --git a/public/app/features/dashboard-scene/v2schema/transformers.test.ts b/public/app/features/dashboard-scene/v2schema/transformers.test.ts new file mode 100644 index 00000000000..e1fcfdc9e5f --- /dev/null +++ b/public/app/features/dashboard-scene/v2schema/transformers.test.ts @@ -0,0 +1,91 @@ +import { config } from '@grafana/runtime'; +import { CustomVariable, GroupByVariable } from '@grafana/scenes'; +import { DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0/dashboard.gen'; +import { handyTestingSchema } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0/examples'; +import { DashboardWithAccessInfo } from 'app/features/dashboard/api/types'; + +import { transformSaveModelSchemaV2ToScene } from '../serialization/transformSaveModelSchemaV2ToScene'; +import { transformSceneToSaveModelSchemaV2 } from '../serialization/transformSceneToSaveModelSchemaV2'; + +const defaultDashboard: DashboardWithAccessInfo = { + kind: 'DashboardWithAccessInfo', + metadata: { + name: 'dashboard-uid', + namespace: 'default', + labels: {}, + resourceVersion: '', + creationTimestamp: '', + }, + spec: handyTestingSchema, + access: {}, + apiVersion: 'v2', +}; + +jest.mock('@grafana/runtime', () => ({ + ...jest.requireActual('@grafana/runtime'), + getDataSourceSrv: () => ({ + getInstanceSettings: jest.fn(), + }), + config: { + ...jest.requireActual('@grafana/runtime').config, + bootData: { + settings: { + defaultDatasource: 'prometheus', + datasources: { + prometheus: { + name: 'datasource1', + meta: { id: 'prometheus' }, + type: 'prometheus', + }, + }, + }, + }, + }, +})); + +describe('V2 Transformers', () => { + beforeAll(() => { + config.featureToggles.groupByVariable = true; + }); + + afterAll(() => { + config.featureToggles.groupByVariable = false; + }); + it('should match original V2 Schema when transforming to scene and back to V2 Schema', async () => { + const dashV2Scene = transformSaveModelSchemaV2ToScene(defaultDashboard); + // Find and manually set options for CustomVariable + // Options are set based on query field using getValueOptions + const customVariable = dashV2Scene.state.$variables?.state.variables.find( + (v) => v instanceof CustomVariable + ) as CustomVariable; + expect(customVariable).toBeDefined(); + + const customOptions$ = customVariable.getValueOptions({}); + const customOptions = await customOptions$.toPromise(); + customVariable.setState({ options: customOptions }); + + // Find and manually set defaultOptions for GroupByVariable + // If defaultOptions are provided, getValueOptions will set options to defaultOptions + const groupByVariable = dashV2Scene.state.$variables?.state.variables.find( + (v) => v instanceof GroupByVariable + ) as GroupByVariable; + expect(groupByVariable).toBeDefined(); + + // Set default options directly in state + groupByVariable.setState({ + defaultOptions: [ + { text: 'option1', value: 'option1' }, + { text: 'option2', value: 'option2' }, + ], + }); + + const groupOptions$ = groupByVariable.getValueOptions({}); + const groupOptions = await groupOptions$.toPromise(); + groupByVariable.setState({ options: groupOptions }); + + // Transform back to dashboard V2 spec + const dashV2 = transformSceneToSaveModelSchemaV2(dashV2Scene); + + expect(dashV2).toEqual(defaultDashboard.spec); + }); +});