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
pull/97953/head
Haris Rozajac 5 months ago committed by GitHub
parent e43e86376e
commit a110917577
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      .betterer.results
  2. 6
      packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.gen.ts
  3. 5
      packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue
  4. 8
      packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts
  5. 26
      public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModelSchemaV2.test.ts.snap
  6. 1
      public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.test.ts
  7. 1
      public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.ts
  8. 1
      public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts
  9. 3
      public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts
  10. 31
      public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts
  11. 82
      public/app/features/dashboard-scene/serialization/transformToV1TypesUtils.ts
  12. 91
      public/app/features/dashboard-scene/v2schema/transformers.test.ts

@ -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 <Trans /> or use t()", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],

@ -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,

@ -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

@ -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,
},
},

@ -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,

@ -888,7 +888,6 @@ describe('sceneVariablesSetToVariables', () => {
"selected-ds-2",
],
},
"defaultOptionEnabled": false,
"description": "test-desc",
"hide": "dontHide",
"includeAll": true,

@ -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,

@ -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,

@ -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');
});
});

@ -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<DashboardV2Spec> = {
//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;

@ -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,
};
}

@ -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<DashboardV2Spec> = {
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);
});
});
Loading…
Cancel
Save