Dashboard Schema v2: Add tests for variables, annotations, and dataTransformer (#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
sbom-fix
Haris Rozajac 6 months ago committed by GitHub
parent 6bdd8f7fca
commit 9e885cab7c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      .betterer.results
  2. 86
      packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts
  3. 149
      public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.test.ts
  4. 6
      public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts
  5. 88
      public/app/features/dashboard-scene/v2schema/test-helpers.ts

@ -2279,6 +2279,9 @@ exports[`better eslint`] = {
"public/app/features/dashboard-scene/utils/PanelModelCompatibilityWrapper.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],
"public/app/features/dashboard-scene/v2schema/test-helpers.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],
"public/app/features/dashboard/api/dashboard_api.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],

@ -22,7 +22,91 @@ export const handyTestingSchema: DashboardV2Spec = {
to: 'now',
weekStart: 'monday',
},
annotations: [],
annotations: [
{
kind: 'AnnotationQuery',
spec: {
builtIn: true,
query: {
kind: 'prometheus',
spec: {
expr: 'test-query',
},
},
datasource: {
type: 'prometheus',
uid: 'uid',
},
filter: { ids: [] },
enable: true,
hide: false,
iconColor: 'rgba(0, 211, 255, 1)',
name: 'Annotations & Alerts',
},
},
{
kind: 'AnnotationQuery',
spec: {
datasource: {
type: 'grafana-testdata-datasource',
uid: 'uid',
},
enable: true,
iconColor: 'red',
name: 'Enabled',
query: {
kind: 'grafana-testdata-datasource',
spec: {
lines: 4,
refId: 'Anno',
scenarioId: 'annotations',
},
},
filter: { ids: [] },
hide: true,
},
},
{
kind: 'AnnotationQuery',
spec: {
datasource: {
type: 'grafana-testdata-datasource',
uid: 'uid',
},
filter: { ids: [] },
enable: false,
iconColor: 'yellow',
name: 'Disabled',
query: {
kind: 'grafana-testdata-datasource',
spec: { lines: 5, refId: 'Anno', scenarioId: 'annotations' },
},
hide: false,
},
},
{
kind: 'AnnotationQuery',
spec: {
datasource: {
type: 'grafana-testdata-datasource',
uid: 'uid',
},
filter: { ids: [] },
enable: true,
hide: true,
iconColor: 'dark-purple',
name: 'Hidden',
query: {
kind: 'grafana-testdata-datasource',
spec: {
lines: 6,
refId: 'Anno',
scenarioId: 'annotations',
},
},
},
},
],
elements: {
'test-panel-uid': {
kind: 'Panel',

@ -1,15 +1,38 @@
import { cloneDeep } from 'lodash';
import { config } from '@grafana/runtime';
import { behaviors, sceneGraph, SceneQueryRunner } from '@grafana/scenes';
import { DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0/dashboard.gen';
import {
behaviors,
ConstantVariable,
CustomVariable,
DataSourceVariable,
IntervalVariable,
QueryVariable,
TextBoxVariable,
sceneGraph,
GroupByVariable,
AdHocFiltersVariable,
} from '@grafana/scenes';
import {
AdhocVariableKind,
ConstantVariableKind,
CustomVariableKind,
DashboardV2Spec,
DatasourceVariableKind,
GroupByVariableKind,
IntervalVariableKind,
QueryVariableKind,
TextVariableKind,
} 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/dashboard_api';
import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource';
import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet';
import { DashboardLayoutManager } from '../scene/types';
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
import { getQueryRunnerFor } from '../utils/utils';
import { validateVariable, validateVizPanel } from '../v2schema/test-helpers';
import { transformSaveModelSchemaV2ToScene } from './transformSaveModelSchemaV2ToScene';
import { transformCursorSynctoEnum } from './transformToV2TypesUtils';
@ -85,14 +108,96 @@ describe('transformSaveModelSchemaV2ToScene', () => {
expect(dashboardControls.state.refreshPicker.state.intervals).toEqual(time.autoRefreshIntervals);
expect(dashboardControls.state.hideTimeControls).toBe(time.hideTimepicker);
// TODO: Variables
// expect(scene.state?.$variables?.state.variables).toHaveLength(dash.variables.length);
// expect(scene.state?.$variables?.getByName(dash.variables[0].spec.name)).toBeInstanceOf(QueryVariable);
// expect(scene.state?.$variables?.getByName(dash.variables[1].spec.name)).toBeInstanceOf(TextBoxVariable); ...
// Variables
const variables = scene.state?.$variables;
expect(variables?.state.variables).toHaveLength(dash.variables.length);
validateVariable({
sceneVariable: variables?.state.variables[0],
variableKind: dash.variables[0] as QueryVariableKind,
scene: scene,
dashSpec: dash,
sceneVariableClass: QueryVariable,
index: 0,
});
validateVariable({
sceneVariable: variables?.state.variables[1],
variableKind: dash.variables[1] as CustomVariableKind,
scene: scene,
dashSpec: dash,
sceneVariableClass: CustomVariable,
index: 1,
});
validateVariable({
sceneVariable: variables?.state.variables[2],
variableKind: dash.variables[2] as DatasourceVariableKind,
scene: scene,
dashSpec: dash,
sceneVariableClass: DataSourceVariable,
index: 2,
});
validateVariable({
sceneVariable: variables?.state.variables[3],
variableKind: dash.variables[3] as ConstantVariableKind,
scene: scene,
dashSpec: dash,
sceneVariableClass: ConstantVariable,
index: 3,
});
validateVariable({
sceneVariable: variables?.state.variables[4],
variableKind: dash.variables[4] as IntervalVariableKind,
scene: scene,
dashSpec: dash,
sceneVariableClass: IntervalVariable,
index: 4,
});
validateVariable({
sceneVariable: variables?.state.variables[5],
variableKind: dash.variables[5] as TextVariableKind,
scene: scene,
dashSpec: dash,
sceneVariableClass: TextBoxVariable,
index: 5,
});
validateVariable({
sceneVariable: variables?.state.variables[6],
variableKind: dash.variables[6] as GroupByVariableKind,
scene: scene,
dashSpec: dash,
sceneVariableClass: GroupByVariable,
index: 6,
});
validateVariable({
sceneVariable: variables?.state.variables[7],
variableKind: dash.variables[7] as AdhocVariableKind,
scene: scene,
dashSpec: dash,
sceneVariableClass: AdHocFiltersVariable,
index: 7,
});
// TODO: Annotations
// expect(scene.state.annotations).toHaveLength(dash.annotations.length);
// expect(scene.state.annotations[0].text).toBe(dash.annotations[0].text); ...
// Annotations
expect(scene.state.$data).toBeInstanceOf(DashboardDataLayerSet);
const dataLayers = scene.state.$data as DashboardDataLayerSet;
expect(dataLayers.state.annotationLayers).toHaveLength(dash.annotations.length);
expect(dataLayers.state.annotationLayers[0].state.name).toBe(dash.annotations[0].spec.name);
expect(dataLayers.state.annotationLayers[0].state.isEnabled).toBe(dash.annotations[0].spec.enable);
expect(dataLayers.state.annotationLayers[0].state.isHidden).toBe(dash.annotations[0].spec.hide);
// Enabled
expect(dataLayers.state.annotationLayers[1].state.name).toBe(dash.annotations[1].spec.name);
expect(dataLayers.state.annotationLayers[1].state.isEnabled).toBe(dash.annotations[1].spec.enable);
expect(dataLayers.state.annotationLayers[1].state.isHidden).toBe(dash.annotations[1].spec.hide);
// Disabled
expect(dataLayers.state.annotationLayers[2].state.name).toBe(dash.annotations[2].spec.name);
expect(dataLayers.state.annotationLayers[2].state.isEnabled).toBe(dash.annotations[2].spec.enable);
expect(dataLayers.state.annotationLayers[2].state.isHidden).toBe(dash.annotations[2].spec.hide);
// Hidden
expect(dataLayers.state.annotationLayers[3].state.name).toBe(dash.annotations[3].spec.name);
expect(dataLayers.state.annotationLayers[3].state.isEnabled).toBe(dash.annotations[3].spec.enable);
expect(dataLayers.state.annotationLayers[3].state.isHidden).toBe(dash.annotations[3].spec.hide);
// To be implemented
// expect(timePicker.state.ranges).toEqual(dash.timeSettings.quickRanges);
@ -101,31 +206,7 @@ describe('transformSaveModelSchemaV2ToScene', () => {
const vizPanels = (scene.state.body as DashboardLayoutManager).getVizPanels();
expect(vizPanels).toHaveLength(1);
const vizPanel = vizPanels[0];
expect(vizPanel.state.title).toBe(dash.elements['test-panel-uid'].spec.title);
expect(vizPanel.state.description).toBe(dash.elements['test-panel-uid'].spec.description);
expect(vizPanel.state.pluginId).toBe(dash.elements['test-panel-uid'].spec.vizConfig.kind);
expect(vizPanel.state.pluginVersion).toBe(dash.elements['test-panel-uid'].spec.vizConfig.spec.pluginVersion);
expect(vizPanel.state.options).toEqual(dash.elements['test-panel-uid'].spec.vizConfig.spec.options);
expect(vizPanel.state.fieldConfig).toEqual(dash.elements['test-panel-uid'].spec.vizConfig.spec.fieldConfig);
// FIXME: There is an error of data being undefined
// expect(vizPanel.state.$data).toBeInstanceOf(SceneDataTransformer);
// const dataTransformer = vizPanel.state.$data as SceneDataTransformer;
// expect(dataTransformer.state.transformations).toEqual([{ id: 'transform1', options: {} }]);
// expect(dataTransformer.state.$data).toBeInstanceOf(SceneQueryRunner);
const queryRunner = getQueryRunnerFor(vizPanel);
expect(queryRunner).toBeInstanceOf(SceneQueryRunner);
expect(queryRunner?.state.datasource).toBeUndefined();
// expect(queryRunner.state.queries).toEqual([{ query: 'test-query', datasource: { uid: 'datasource1', type: 'prometheus' } }]);
// expect(queryRunner.state.maxDataPoints).toBe(100);
// expect(queryRunner.state.cacheTimeout).toBe('1m');
// expect(queryRunner.state.queryCachingTTL).toBe(60);
// expect(queryRunner.state.minInterval).toBe('1m');
// expect(queryRunner.state.dataLayerFilter?.panelId).toBe(1);
// FIXME: Fix the key incompatibility since panel is not numeric anymore
// expect(vizPanel.state.key).toBe(dash.elements['test-panel-uid'].spec.uid);
validateVizPanel(vizPanel, dash);
// FIXME: Tests for layout
});

@ -85,7 +85,7 @@ import {
const DEFAULT_DATASOURCE = 'default';
type TypedVariableModelv2 =
export type TypedVariableModelV2 =
| QueryVariableKind
| TextVariableKind
| ConstantVariableKind
@ -396,7 +396,7 @@ function createVariablesForDashboard(dashboard: DashboardV2Spec) {
});
}
function createSceneVariableFromVariableModel(variable: TypedVariableModelv2): SceneVariable {
function createSceneVariableFromVariableModel(variable: TypedVariableModelV2): SceneVariable {
const commonProperties = {
name: variable.spec.name,
label: variable.spec.label,
@ -593,7 +593,7 @@ export function createVariablesForSnapshot(dashboard: DashboardV2Spec): SceneVar
}
/** Snapshots variables are read-only and should not be updated */
export function createSnapshotVariable(variable: TypedVariableModelv2): SceneVariable {
export function createSnapshotVariable(variable: TypedVariableModelV2): SceneVariable {
let snapshotVariable: SnapshotVariable;
let current: { value: string | string[]; text: string | string[] };
if (variable.kind === 'IntervalVariable') {

@ -0,0 +1,88 @@
import {
AdHocFiltersVariable,
DataSourceVariable,
GroupByVariable,
QueryVariable,
SceneDataTransformer,
SceneQueryRunner,
SceneVariable,
SceneVariableState,
VizPanel,
} from '@grafana/scenes';
import { DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0/dashboard.gen';
import { DashboardScene } from '../scene/DashboardScene';
import { TypedVariableModelV2 } from '../serialization/transformSaveModelSchemaV2ToScene';
import { getQueryRunnerFor } from '../utils/utils';
type SceneVariableConstructor<T extends SceneVariableState, V extends SceneVariable<T>> = new (
initialState: Partial<T>
) => V;
interface VariableValidation<T extends TypedVariableModelV2, S extends SceneVariableState, V extends SceneVariable<S>> {
sceneVariable: SceneVariable<SceneVariableState> | undefined;
variableKind: T;
scene: DashboardScene;
dashSpec: DashboardV2Spec;
sceneVariableClass: SceneVariableConstructor<S, V>;
index: number;
}
export function validateVariable<
T extends TypedVariableModelV2,
S extends SceneVariableState,
V extends SceneVariable<S>,
>({ sceneVariable, variableKind, scene, dashSpec, sceneVariableClass, index }: VariableValidation<T, S, V>) {
if (variableKind.kind === 'AdhocVariable' && sceneVariable instanceof AdHocFiltersVariable) {
expect(sceneVariable).toBeInstanceOf(AdHocFiltersVariable);
expect(scene.state?.$variables?.getByName(dashSpec.variables[index].spec.name)?.getValue()).toBe(
`${variableKind.spec.filters[0].key}="${variableKind.spec.filters[0].value}"`
);
expect(sceneVariable?.state.datasource).toEqual(variableKind.spec.datasource);
} else if (variableKind.kind !== 'AdhocVariable') {
expect(sceneVariable).toBeInstanceOf(sceneVariableClass);
expect(scene.state?.$variables?.getByName(dashSpec.variables[index].spec.name)?.getValue()).toBe(
variableKind.spec.current.value
);
}
if (sceneVariable instanceof DataSourceVariable && variableKind.kind === 'DatasourceVariable') {
expect(sceneVariable?.state.pluginId).toBe(variableKind.spec.pluginId);
}
if (sceneVariable instanceof QueryVariable && variableKind.kind === 'QueryVariable') {
expect(sceneVariable?.state.datasource).toBe(variableKind.spec.datasource);
expect(sceneVariable?.state.query).toBe(variableKind.spec.query);
}
if (sceneVariable instanceof GroupByVariable && variableKind.kind === 'CustomVariable') {
expect(sceneVariable?.state.datasource).toBe(variableKind.spec.query);
}
}
export function validateVizPanel(vizPanel: VizPanel, dash: DashboardV2Spec) {
expect(vizPanel.state.title).toBe(dash.elements['test-panel-uid'].spec.title);
expect(vizPanel.state.description).toBe(dash.elements['test-panel-uid'].spec.description);
expect(vizPanel.state.pluginId).toBe(dash.elements['test-panel-uid'].spec.vizConfig.kind);
expect(vizPanel.state.pluginVersion).toBe(dash.elements['test-panel-uid'].spec.vizConfig.spec.pluginVersion);
expect(vizPanel.state.options).toEqual(dash.elements['test-panel-uid'].spec.vizConfig.spec.options);
expect(vizPanel.state.fieldConfig).toEqual(dash.elements['test-panel-uid'].spec.vizConfig.spec.fieldConfig);
expect(vizPanel.state.key).toBe(dash.elements['test-panel-uid'].spec.uid);
expect(vizPanel.state.$data).toBeInstanceOf(SceneDataTransformer);
const dataTransformer = vizPanel.state.$data as SceneDataTransformer;
expect(dataTransformer.state.transformations[0]).toEqual(
dash.elements['test-panel-uid'].spec.data.spec.transformations[0].spec
);
expect(dataTransformer.state.$data).toBeInstanceOf(SceneQueryRunner);
const queryRunner = getQueryRunnerFor(vizPanel)!;
expect(queryRunner).toBeInstanceOf(SceneQueryRunner);
expect(queryRunner.state.queries).toEqual([
{ datasource: { type: 'prometheus', uid: 'datasource1' }, expr: 'test-query', hide: false, refId: 'A' },
]);
expect(queryRunner.state.maxDataPoints).toBe(100);
expect(queryRunner.state.cacheTimeout).toBe('1m');
expect(queryRunner.state.queryCachingTTL).toBe(60);
expect(queryRunner.state.minInterval).toBe('1m');
// FIXME: This is asking for a number as panel ID but here the uid of a panel is string
// will be fixed once scenes package is updated to support string panel ID
// expect(queryRunner.state.dataLayerFilter?.panelId).toBe(0);
}
Loading…
Cancel
Save