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);
+ });
+});