Skip to content

Commit 9e885ca

Browse files
authored
Dashboard Schema v2: Add tests for variables, annotations, and dataTransformer (grafana#97314)
* Add tests for variables, annotations, dataTransformer * Simplify variable validation; test additional variable fields * Move helpers to a separate file * remove unused param, rename helpers file * fix * Move remaining vizPanel test to helper * use ts
1 parent 6bdd8f7 commit 9e885ca

File tree

5 files changed

+294
-38
lines changed

5 files changed

+294
-38
lines changed

.betterer.results

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2279,6 +2279,9 @@ exports[`better eslint`] = {
22792279
"public/app/features/dashboard-scene/utils/PanelModelCompatibilityWrapper.ts:5381": [
22802280
[0, 0, 0, "Do not use any type assertions.", "0"]
22812281
],
2282+
"public/app/features/dashboard-scene/v2schema/test-helpers.ts:5381": [
2283+
[0, 0, 0, "Do not use any type assertions.", "0"]
2284+
],
22822285
"public/app/features/dashboard/api/dashboard_api.ts:5381": [
22832286
[0, 0, 0, "Do not use any type assertions.", "0"]
22842287
],

packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,91 @@ export const handyTestingSchema: DashboardV2Spec = {
2222
to: 'now',
2323
weekStart: 'monday',
2424
},
25-
annotations: [],
25+
annotations: [
26+
{
27+
kind: 'AnnotationQuery',
28+
spec: {
29+
builtIn: true,
30+
query: {
31+
kind: 'prometheus',
32+
spec: {
33+
expr: 'test-query',
34+
},
35+
},
36+
datasource: {
37+
type: 'prometheus',
38+
uid: 'uid',
39+
},
40+
filter: { ids: [] },
41+
enable: true,
42+
hide: false,
43+
iconColor: 'rgba(0, 211, 255, 1)',
44+
name: 'Annotations & Alerts',
45+
},
46+
},
47+
{
48+
kind: 'AnnotationQuery',
49+
spec: {
50+
datasource: {
51+
type: 'grafana-testdata-datasource',
52+
uid: 'uid',
53+
},
54+
enable: true,
55+
iconColor: 'red',
56+
name: 'Enabled',
57+
query: {
58+
kind: 'grafana-testdata-datasource',
59+
spec: {
60+
lines: 4,
61+
refId: 'Anno',
62+
scenarioId: 'annotations',
63+
},
64+
},
65+
filter: { ids: [] },
66+
hide: true,
67+
},
68+
},
69+
{
70+
kind: 'AnnotationQuery',
71+
spec: {
72+
datasource: {
73+
type: 'grafana-testdata-datasource',
74+
uid: 'uid',
75+
},
76+
filter: { ids: [] },
77+
enable: false,
78+
iconColor: 'yellow',
79+
name: 'Disabled',
80+
query: {
81+
kind: 'grafana-testdata-datasource',
82+
spec: { lines: 5, refId: 'Anno', scenarioId: 'annotations' },
83+
},
84+
hide: false,
85+
},
86+
},
87+
{
88+
kind: 'AnnotationQuery',
89+
spec: {
90+
datasource: {
91+
type: 'grafana-testdata-datasource',
92+
uid: 'uid',
93+
},
94+
filter: { ids: [] },
95+
enable: true,
96+
hide: true,
97+
iconColor: 'dark-purple',
98+
name: 'Hidden',
99+
query: {
100+
kind: 'grafana-testdata-datasource',
101+
spec: {
102+
lines: 6,
103+
refId: 'Anno',
104+
scenarioId: 'annotations',
105+
},
106+
},
107+
},
108+
},
109+
],
26110
elements: {
27111
'test-panel-uid': {
28112
kind: 'Panel',

public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.test.ts

Lines changed: 115 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,38 @@
11
import { cloneDeep } from 'lodash';
22

33
import { config } from '@grafana/runtime';
4-
import { behaviors, sceneGraph, SceneQueryRunner } from '@grafana/scenes';
5-
import { DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0/dashboard.gen';
4+
import {
5+
behaviors,
6+
ConstantVariable,
7+
CustomVariable,
8+
DataSourceVariable,
9+
IntervalVariable,
10+
QueryVariable,
11+
TextBoxVariable,
12+
sceneGraph,
13+
GroupByVariable,
14+
AdHocFiltersVariable,
15+
} from '@grafana/scenes';
16+
import {
17+
AdhocVariableKind,
18+
ConstantVariableKind,
19+
CustomVariableKind,
20+
DashboardV2Spec,
21+
DatasourceVariableKind,
22+
GroupByVariableKind,
23+
IntervalVariableKind,
24+
QueryVariableKind,
25+
TextVariableKind,
26+
} from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0/dashboard.gen';
627
import { handyTestingSchema } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0/examples';
728
import { DashboardWithAccessInfo } from 'app/features/dashboard/api/dashboard_api';
829
import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource';
930

31+
import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet';
1032
import { DashboardLayoutManager } from '../scene/types';
1133
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
1234
import { getQueryRunnerFor } from '../utils/utils';
35+
import { validateVariable, validateVizPanel } from '../v2schema/test-helpers';
1336

1437
import { transformSaveModelSchemaV2ToScene } from './transformSaveModelSchemaV2ToScene';
1538
import { transformCursorSynctoEnum } from './transformToV2TypesUtils';
@@ -85,14 +108,96 @@ describe('transformSaveModelSchemaV2ToScene', () => {
85108
expect(dashboardControls.state.refreshPicker.state.intervals).toEqual(time.autoRefreshIntervals);
86109
expect(dashboardControls.state.hideTimeControls).toBe(time.hideTimepicker);
87110

88-
// TODO: Variables
89-
// expect(scene.state?.$variables?.state.variables).toHaveLength(dash.variables.length);
90-
// expect(scene.state?.$variables?.getByName(dash.variables[0].spec.name)).toBeInstanceOf(QueryVariable);
91-
// expect(scene.state?.$variables?.getByName(dash.variables[1].spec.name)).toBeInstanceOf(TextBoxVariable); ...
111+
// Variables
112+
const variables = scene.state?.$variables;
113+
expect(variables?.state.variables).toHaveLength(dash.variables.length);
114+
validateVariable({
115+
sceneVariable: variables?.state.variables[0],
116+
variableKind: dash.variables[0] as QueryVariableKind,
117+
scene: scene,
118+
dashSpec: dash,
119+
sceneVariableClass: QueryVariable,
120+
index: 0,
121+
});
122+
validateVariable({
123+
sceneVariable: variables?.state.variables[1],
124+
variableKind: dash.variables[1] as CustomVariableKind,
125+
scene: scene,
126+
dashSpec: dash,
127+
sceneVariableClass: CustomVariable,
128+
index: 1,
129+
});
130+
validateVariable({
131+
sceneVariable: variables?.state.variables[2],
132+
variableKind: dash.variables[2] as DatasourceVariableKind,
133+
scene: scene,
134+
dashSpec: dash,
135+
sceneVariableClass: DataSourceVariable,
136+
index: 2,
137+
});
138+
validateVariable({
139+
sceneVariable: variables?.state.variables[3],
140+
variableKind: dash.variables[3] as ConstantVariableKind,
141+
scene: scene,
142+
dashSpec: dash,
143+
sceneVariableClass: ConstantVariable,
144+
index: 3,
145+
});
146+
validateVariable({
147+
sceneVariable: variables?.state.variables[4],
148+
variableKind: dash.variables[4] as IntervalVariableKind,
149+
scene: scene,
150+
dashSpec: dash,
151+
sceneVariableClass: IntervalVariable,
152+
index: 4,
153+
});
154+
validateVariable({
155+
sceneVariable: variables?.state.variables[5],
156+
variableKind: dash.variables[5] as TextVariableKind,
157+
scene: scene,
158+
dashSpec: dash,
159+
sceneVariableClass: TextBoxVariable,
160+
index: 5,
161+
});
162+
validateVariable({
163+
sceneVariable: variables?.state.variables[6],
164+
variableKind: dash.variables[6] as GroupByVariableKind,
165+
scene: scene,
166+
dashSpec: dash,
167+
sceneVariableClass: GroupByVariable,
168+
index: 6,
169+
});
170+
validateVariable({
171+
sceneVariable: variables?.state.variables[7],
172+
variableKind: dash.variables[7] as AdhocVariableKind,
173+
scene: scene,
174+
dashSpec: dash,
175+
sceneVariableClass: AdHocFiltersVariable,
176+
index: 7,
177+
});
92178

93-
// TODO: Annotations
94-
// expect(scene.state.annotations).toHaveLength(dash.annotations.length);
95-
// expect(scene.state.annotations[0].text).toBe(dash.annotations[0].text); ...
179+
// Annotations
180+
expect(scene.state.$data).toBeInstanceOf(DashboardDataLayerSet);
181+
const dataLayers = scene.state.$data as DashboardDataLayerSet;
182+
expect(dataLayers.state.annotationLayers).toHaveLength(dash.annotations.length);
183+
expect(dataLayers.state.annotationLayers[0].state.name).toBe(dash.annotations[0].spec.name);
184+
expect(dataLayers.state.annotationLayers[0].state.isEnabled).toBe(dash.annotations[0].spec.enable);
185+
expect(dataLayers.state.annotationLayers[0].state.isHidden).toBe(dash.annotations[0].spec.hide);
186+
187+
// Enabled
188+
expect(dataLayers.state.annotationLayers[1].state.name).toBe(dash.annotations[1].spec.name);
189+
expect(dataLayers.state.annotationLayers[1].state.isEnabled).toBe(dash.annotations[1].spec.enable);
190+
expect(dataLayers.state.annotationLayers[1].state.isHidden).toBe(dash.annotations[1].spec.hide);
191+
192+
// Disabled
193+
expect(dataLayers.state.annotationLayers[2].state.name).toBe(dash.annotations[2].spec.name);
194+
expect(dataLayers.state.annotationLayers[2].state.isEnabled).toBe(dash.annotations[2].spec.enable);
195+
expect(dataLayers.state.annotationLayers[2].state.isHidden).toBe(dash.annotations[2].spec.hide);
196+
197+
// Hidden
198+
expect(dataLayers.state.annotationLayers[3].state.name).toBe(dash.annotations[3].spec.name);
199+
expect(dataLayers.state.annotationLayers[3].state.isEnabled).toBe(dash.annotations[3].spec.enable);
200+
expect(dataLayers.state.annotationLayers[3].state.isHidden).toBe(dash.annotations[3].spec.hide);
96201

97202
// To be implemented
98203
// expect(timePicker.state.ranges).toEqual(dash.timeSettings.quickRanges);
@@ -101,31 +206,7 @@ describe('transformSaveModelSchemaV2ToScene', () => {
101206
const vizPanels = (scene.state.body as DashboardLayoutManager).getVizPanels();
102207
expect(vizPanels).toHaveLength(1);
103208
const vizPanel = vizPanels[0];
104-
expect(vizPanel.state.title).toBe(dash.elements['test-panel-uid'].spec.title);
105-
expect(vizPanel.state.description).toBe(dash.elements['test-panel-uid'].spec.description);
106-
expect(vizPanel.state.pluginId).toBe(dash.elements['test-panel-uid'].spec.vizConfig.kind);
107-
expect(vizPanel.state.pluginVersion).toBe(dash.elements['test-panel-uid'].spec.vizConfig.spec.pluginVersion);
108-
expect(vizPanel.state.options).toEqual(dash.elements['test-panel-uid'].spec.vizConfig.spec.options);
109-
expect(vizPanel.state.fieldConfig).toEqual(dash.elements['test-panel-uid'].spec.vizConfig.spec.fieldConfig);
110-
111-
// FIXME: There is an error of data being undefined
112-
// expect(vizPanel.state.$data).toBeInstanceOf(SceneDataTransformer);
113-
// const dataTransformer = vizPanel.state.$data as SceneDataTransformer;
114-
// expect(dataTransformer.state.transformations).toEqual([{ id: 'transform1', options: {} }]);
115-
116-
// expect(dataTransformer.state.$data).toBeInstanceOf(SceneQueryRunner);
117-
const queryRunner = getQueryRunnerFor(vizPanel);
118-
expect(queryRunner).toBeInstanceOf(SceneQueryRunner);
119-
expect(queryRunner?.state.datasource).toBeUndefined();
120-
// expect(queryRunner.state.queries).toEqual([{ query: 'test-query', datasource: { uid: 'datasource1', type: 'prometheus' } }]);
121-
// expect(queryRunner.state.maxDataPoints).toBe(100);
122-
// expect(queryRunner.state.cacheTimeout).toBe('1m');
123-
// expect(queryRunner.state.queryCachingTTL).toBe(60);
124-
// expect(queryRunner.state.minInterval).toBe('1m');
125-
// expect(queryRunner.state.dataLayerFilter?.panelId).toBe(1);
126-
127-
// FIXME: Fix the key incompatibility since panel is not numeric anymore
128-
// expect(vizPanel.state.key).toBe(dash.elements['test-panel-uid'].spec.uid);
209+
validateVizPanel(vizPanel, dash);
129210

130211
// FIXME: Tests for layout
131212
});

public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ import {
8585

8686
const DEFAULT_DATASOURCE = 'default';
8787

88-
type TypedVariableModelv2 =
88+
export type TypedVariableModelV2 =
8989
| QueryVariableKind
9090
| TextVariableKind
9191
| ConstantVariableKind
@@ -396,7 +396,7 @@ function createVariablesForDashboard(dashboard: DashboardV2Spec) {
396396
});
397397
}
398398

399-
function createSceneVariableFromVariableModel(variable: TypedVariableModelv2): SceneVariable {
399+
function createSceneVariableFromVariableModel(variable: TypedVariableModelV2): SceneVariable {
400400
const commonProperties = {
401401
name: variable.spec.name,
402402
label: variable.spec.label,
@@ -593,7 +593,7 @@ export function createVariablesForSnapshot(dashboard: DashboardV2Spec): SceneVar
593593
}
594594

595595
/** Snapshots variables are read-only and should not be updated */
596-
export function createSnapshotVariable(variable: TypedVariableModelv2): SceneVariable {
596+
export function createSnapshotVariable(variable: TypedVariableModelV2): SceneVariable {
597597
let snapshotVariable: SnapshotVariable;
598598
let current: { value: string | string[]; text: string | string[] };
599599
if (variable.kind === 'IntervalVariable') {
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import {
2+
AdHocFiltersVariable,
3+
DataSourceVariable,
4+
GroupByVariable,
5+
QueryVariable,
6+
SceneDataTransformer,
7+
SceneQueryRunner,
8+
SceneVariable,
9+
SceneVariableState,
10+
VizPanel,
11+
} from '@grafana/scenes';
12+
import { DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0/dashboard.gen';
13+
14+
import { DashboardScene } from '../scene/DashboardScene';
15+
import { TypedVariableModelV2 } from '../serialization/transformSaveModelSchemaV2ToScene';
16+
import { getQueryRunnerFor } from '../utils/utils';
17+
18+
type SceneVariableConstructor<T extends SceneVariableState, V extends SceneVariable<T>> = new (
19+
initialState: Partial<T>
20+
) => V;
21+
22+
interface VariableValidation<T extends TypedVariableModelV2, S extends SceneVariableState, V extends SceneVariable<S>> {
23+
sceneVariable: SceneVariable<SceneVariableState> | undefined;
24+
variableKind: T;
25+
scene: DashboardScene;
26+
dashSpec: DashboardV2Spec;
27+
sceneVariableClass: SceneVariableConstructor<S, V>;
28+
index: number;
29+
}
30+
31+
export function validateVariable<
32+
T extends TypedVariableModelV2,
33+
S extends SceneVariableState,
34+
V extends SceneVariable<S>,
35+
>({ sceneVariable, variableKind, scene, dashSpec, sceneVariableClass, index }: VariableValidation<T, S, V>) {
36+
if (variableKind.kind === 'AdhocVariable' && sceneVariable instanceof AdHocFiltersVariable) {
37+
expect(sceneVariable).toBeInstanceOf(AdHocFiltersVariable);
38+
expect(scene.state?.$variables?.getByName(dashSpec.variables[index].spec.name)?.getValue()).toBe(
39+
`${variableKind.spec.filters[0].key}="${variableKind.spec.filters[0].value}"`
40+
);
41+
expect(sceneVariable?.state.datasource).toEqual(variableKind.spec.datasource);
42+
} else if (variableKind.kind !== 'AdhocVariable') {
43+
expect(sceneVariable).toBeInstanceOf(sceneVariableClass);
44+
expect(scene.state?.$variables?.getByName(dashSpec.variables[index].spec.name)?.getValue()).toBe(
45+
variableKind.spec.current.value
46+
);
47+
}
48+
if (sceneVariable instanceof DataSourceVariable && variableKind.kind === 'DatasourceVariable') {
49+
expect(sceneVariable?.state.pluginId).toBe(variableKind.spec.pluginId);
50+
}
51+
if (sceneVariable instanceof QueryVariable && variableKind.kind === 'QueryVariable') {
52+
expect(sceneVariable?.state.datasource).toBe(variableKind.spec.datasource);
53+
expect(sceneVariable?.state.query).toBe(variableKind.spec.query);
54+
}
55+
if (sceneVariable instanceof GroupByVariable && variableKind.kind === 'CustomVariable') {
56+
expect(sceneVariable?.state.datasource).toBe(variableKind.spec.query);
57+
}
58+
}
59+
60+
export function validateVizPanel(vizPanel: VizPanel, dash: DashboardV2Spec) {
61+
expect(vizPanel.state.title).toBe(dash.elements['test-panel-uid'].spec.title);
62+
expect(vizPanel.state.description).toBe(dash.elements['test-panel-uid'].spec.description);
63+
expect(vizPanel.state.pluginId).toBe(dash.elements['test-panel-uid'].spec.vizConfig.kind);
64+
expect(vizPanel.state.pluginVersion).toBe(dash.elements['test-panel-uid'].spec.vizConfig.spec.pluginVersion);
65+
expect(vizPanel.state.options).toEqual(dash.elements['test-panel-uid'].spec.vizConfig.spec.options);
66+
expect(vizPanel.state.fieldConfig).toEqual(dash.elements['test-panel-uid'].spec.vizConfig.spec.fieldConfig);
67+
expect(vizPanel.state.key).toBe(dash.elements['test-panel-uid'].spec.uid);
68+
69+
expect(vizPanel.state.$data).toBeInstanceOf(SceneDataTransformer);
70+
const dataTransformer = vizPanel.state.$data as SceneDataTransformer;
71+
expect(dataTransformer.state.transformations[0]).toEqual(
72+
dash.elements['test-panel-uid'].spec.data.spec.transformations[0].spec
73+
);
74+
75+
expect(dataTransformer.state.$data).toBeInstanceOf(SceneQueryRunner);
76+
const queryRunner = getQueryRunnerFor(vizPanel)!;
77+
expect(queryRunner).toBeInstanceOf(SceneQueryRunner);
78+
expect(queryRunner.state.queries).toEqual([
79+
{ datasource: { type: 'prometheus', uid: 'datasource1' }, expr: 'test-query', hide: false, refId: 'A' },
80+
]);
81+
expect(queryRunner.state.maxDataPoints).toBe(100);
82+
expect(queryRunner.state.cacheTimeout).toBe('1m');
83+
expect(queryRunner.state.queryCachingTTL).toBe(60);
84+
expect(queryRunner.state.minInterval).toBe('1m');
85+
// FIXME: This is asking for a number as panel ID but here the uid of a panel is string
86+
// will be fixed once scenes package is updated to support string panel ID
87+
// expect(queryRunner.state.dataLayerFilter?.panelId).toBe(0);
88+
}

0 commit comments

Comments
 (0)