Dashboard Schema V2: Introduce __legacyStringValue and deprecate string type for query prop in QueryVariableSpec (#99716)

* Introduce __legacyStringValue and deprecate string type for query

* Fix tests

* Fix tests

* remove default

* kind should default to default ds if variable doesn't have ds field

* lint

* getDefaultDataSourceRef should not return undefined
This commit is contained in:
Haris Rozajac 2025-02-06 07:33:06 -07:00 committed by GitHub
parent f1eac34b54
commit 4c52abb6b4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 66 additions and 29 deletions

View File

@ -651,7 +651,7 @@ QueryVariableSpec: {
skipUrlSync: bool | *false skipUrlSync: bool | *false
description?: string description?: string
datasource?: DataSourceRef datasource?: DataSourceRef
query: string | DataQueryKind | *"" query: DataQueryKind
regex: string | *"" regex: string | *""
sort: VariableSort sort: VariableSort
definition?: string definition?: string

View File

@ -344,7 +344,13 @@ export const handyTestingSchema: DashboardV2Spec = {
multi: true, multi: true,
name: 'queryVar', name: 'queryVar',
options: [], options: [],
query: 'query1', query: {
kind: 'prometheus',
spec: {
expr: 'test-query',
refId: 'A',
},
},
refresh: 'onDashboardLoad', refresh: 'onDashboardLoad',
regex: 'regex1', regex: 'regex1',
skipUrlSync: false, skipUrlSync: false,

View File

@ -960,7 +960,7 @@ export interface QueryVariableSpec {
skipUrlSync: boolean; skipUrlSync: boolean;
description?: string; description?: string;
datasource?: DataSourceRef; datasource?: DataSourceRef;
query: string | DataQueryKind; query: DataQueryKind;
regex: string; regex: string;
sort: VariableSort; sort: VariableSort;
definition?: string; definition?: string;
@ -977,7 +977,7 @@ export const defaultQueryVariableSpec = (): QueryVariableSpec => ({
hide: "dontHide", hide: "dontHide",
refresh: "never", refresh: "never",
skipUrlSync: false, skipUrlSync: false,
query: "", query: defaultDataQueryKind(),
regex: "", regex: "",
sort: "disabled", sort: "disabled",
options: [], options: [],

View File

@ -222,7 +222,13 @@ exports[`transformSceneToSaveModelSchemaV2 should transform scene to save model
"multi": true, "multi": true,
"name": "queryVar", "name": "queryVar",
"options": [], "options": [],
"query": "query1", "query": {
"kind": "prometheus",
"spec": {
"expr": "label_values(node_boot_time_seconds)",
"refId": "A",
},
},
"refresh": "onDashboardLoad", "refresh": "onDashboardLoad",
"regex": "regex1", "regex": "regex1",
"skipUrlSync": false, "skipUrlSync": false,

View File

@ -775,7 +775,12 @@ describe('sceneVariablesSetToVariables', () => {
"multi": true, "multi": true,
"name": "test", "name": "test",
"options": [], "options": [],
"query": "query", "query": {
"kind": "fake-std",
"spec": {
"__legacyStringValue": "query",
},
},
"refresh": "onDashboardLoad", "refresh": "onDashboardLoad",
"regex": "", "regex": "",
"skipUrlSync": false, "skipUrlSync": false,

View File

@ -27,6 +27,7 @@ import {
transformVariableRefreshToEnum, transformVariableRefreshToEnum,
transformVariableHideToEnum, transformVariableHideToEnum,
transformSortVariableToEnum, transformSortVariableToEnum,
LEGACY_STRING_VALUE_KEY,
} from './transformToV2TypesUtils'; } from './transformToV2TypesUtils';
/** /**
* Converts a SceneVariables object into an array of VariableModel objects. * Converts a SceneVariables object into an array of VariableModel objects.
@ -269,16 +270,20 @@ export function sceneVariablesSetToSchemaV2Variables(
if (transformVariableRefreshToEnum(variable.state.refresh) === 'never' || keepQueryOptions) { if (transformVariableRefreshToEnum(variable.state.refresh) === 'never' || keepQueryOptions) {
options = variableValueOptionsToVariableOptions(variable.state); options = variableValueOptionsToVariableOptions(variable.state);
} }
//query: DataQueryKind | string;
const query = variable.state.query; const query = variable.state.query;
let dataQuery: DataQueryKind | string; let dataQuery: DataQueryKind | string;
if (typeof query !== 'string') { if (typeof query !== 'string') {
dataQuery = { dataQuery = {
kind: getDataQueryKind(query), kind: variable.state.datasource?.type ?? getDataQueryKind(query),
spec: getDataQuerySpec(query), spec: getDataQuerySpec(query),
}; };
} else { } else {
dataQuery = query; dataQuery = {
kind: variable.state.datasource?.type ?? getDataQueryKind(query),
spec: {
[LEGACY_STRING_VALUE_KEY]: query,
},
};
} }
const queryVariable: QueryVariableKind = { const queryVariable: QueryVariableKind = {
kind: 'QueryVariable', kind: 'QueryVariable',

View File

@ -100,6 +100,7 @@ import {
transformVariableHideToEnumV1, transformVariableHideToEnumV1,
transformVariableRefreshToEnumV1, transformVariableRefreshToEnumV1,
} from './transformToV1TypesUtils'; } from './transformToV1TypesUtils';
import { LEGACY_STRING_VALUE_KEY } from './transformToV2TypesUtils';
const DEFAULT_DATASOURCE = 'default'; const DEFAULT_DATASOURCE = 'default';
@ -635,12 +636,12 @@ function createSceneVariableFromVariableModel(variable: TypedVariableModelV2): S
} }
function getDataQueryForVariable(variable: QueryVariableKind) { function getDataQueryForVariable(variable: QueryVariableKind) {
return typeof variable.spec.query !== 'string' return LEGACY_STRING_VALUE_KEY in variable.spec.query.spec
? { ? (variable.spec.query.spec[LEGACY_STRING_VALUE_KEY] ?? '')
: {
...variable.spec.query.spec, ...variable.spec.query.spec,
refId: variable.spec.query.spec.refId ?? 'A', refId: variable.spec.query.spec.refId ?? 'A',
} };
: (variable.spec.query ?? '');
} }
export function getCurrentValueForOldIntervalModel(variable: IntervalVariableKind, intervals: string[]): string { export function getCurrentValueForOldIntervalModel(variable: IntervalVariableKind, intervals: string[]): string {

View File

@ -225,7 +225,10 @@ describe('transformSceneToSaveModelSchemaV2', () => {
hide: VariableHideV1.hideLabel, hide: VariableHideV1.hideLabel,
value: 'value1', value: 'value1',
text: 'text1', text: 'text1',
query: 'query1', query: {
expr: 'label_values(node_boot_time_seconds)',
refId: 'A',
},
definition: 'definition1', definition: 'definition1',
datasource: { uid: 'datasource1', type: 'prometheus' }, datasource: { uid: 'datasource1', type: 'prometheus' },
sort: VariableSortV1.alphabeticalDesc, sort: VariableSortV1.alphabeticalDesc,

View File

@ -403,8 +403,11 @@ function getVizPanelQueries(vizPanel: VizPanel): PanelQueryKind[] {
return queries; return queries;
} }
export function getDataQueryKind(query: SceneDataQuery): string { export function getDataQueryKind(query: SceneDataQuery | string): string {
// If the query has a datasource, use the datasource type, otherwise return empty kind if (typeof query === 'string') {
return getDefaultDataSourceRef()?.type ?? '';
}
return query.datasource?.type ?? getDefaultDataSourceRef()?.type ?? ''; return query.datasource?.type ?? getDefaultDataSourceRef()?.type ?? '';
} }
@ -616,19 +619,15 @@ export function getAnnotationQueryKind(annotationQuery: AnnotationQuery): string
} }
} }
export function getDefaultDataSourceRef(): DataSourceRef | undefined { export function getDefaultDataSourceRef(): DataSourceRef {
// we need to return the default datasource configured in the BootConfig // we need to return the default datasource configured in the BootConfig
const defaultDatasource = config.bootData.settings.defaultDatasource; const defaultDatasource = config.bootData.settings.defaultDatasource;
// get default datasource type // get default datasource type
const dsList = config.bootData.settings.datasources ?? {}; const dsList = config.bootData.settings.datasources;
const ds = dsList[defaultDatasource]; const ds = dsList[defaultDatasource];
if (ds) { return { type: ds.meta.id, uid: ds.name }; // in the datasource list from bootData "id" is the type
return { type: ds.meta.id, uid: ds.name }; // in the datasource list from bootData "id" is the type
}
return undefined;
} }
// Function to know if the dashboard transformed is a valid DashboardV2Spec // Function to know if the dashboard transformed is a valid DashboardV2Spec

View File

@ -19,6 +19,9 @@ import {
FieldColorModeId as FieldColorModeIdV2, FieldColorModeId as FieldColorModeIdV2,
} from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0'; } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0';
// used for QueryVariableKind's query prop - in schema V2 we've deprecated string type and support only DataQuery
export const LEGACY_STRING_VALUE_KEY = '__legacyStringValue';
export function transformCursorSynctoEnum(cursorSync?: DashboardCursorSyncV1): DashboardCursorSync { export function transformCursorSynctoEnum(cursorSync?: DashboardCursorSyncV1): DashboardCursorSync {
switch (cursorSync) { switch (cursorSync) {
case 0: case 0:

View File

@ -53,7 +53,7 @@ export function validateVariable<
} }
if (sceneVariable instanceof QueryVariable && variableKind.kind === 'QueryVariable') { if (sceneVariable instanceof QueryVariable && variableKind.kind === 'QueryVariable') {
expect(sceneVariable?.state.datasource).toBe(variableKind.spec.datasource); expect(sceneVariable?.state.datasource).toBe(variableKind.spec.datasource);
expect(sceneVariable?.state.query).toBe(variableKind.spec.query); expect(sceneVariable?.state.query).toEqual(variableKind.spec.query.spec);
} }
if (sceneVariable instanceof CustomVariable && variableKind.kind === 'CustomVariable') { if (sceneVariable instanceof CustomVariable && variableKind.kind === 'CustomVariable') {
expect(sceneVariable?.state.query).toBe(variableKind.spec.query); expect(sceneVariable?.state.query).toBe(variableKind.spec.query);

View File

@ -19,6 +19,7 @@ import {
} from 'app/features/apiserver/types'; } from 'app/features/apiserver/types';
import { getDefaultDataSourceRef } from 'app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2'; import { getDefaultDataSourceRef } from 'app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2';
import { import {
LEGACY_STRING_VALUE_KEY,
transformVariableHideToEnum, transformVariableHideToEnum,
transformVariableRefreshToEnum, transformVariableRefreshToEnum,
} from 'app/features/dashboard-scene/serialization/transformToV2TypesUtils'; } from 'app/features/dashboard-scene/serialization/transformToV2TypesUtils';
@ -915,7 +916,7 @@ describe('ResponseTransformers', () => {
expect(v2.spec.datasource).toEqual(v1.datasource); expect(v2.spec.datasource).toEqual(v1.datasource);
if (typeof v1.query === 'string') { if (typeof v1.query === 'string') {
expect(v2.spec.query).toEqual(v1.query); expect(v2.spec.query.spec[LEGACY_STRING_VALUE_KEY]).toEqual(v1.query);
} else { } else {
expect(v2.spec.query).toEqual({ expect(v2.spec.query).toEqual({
kind: v1.datasource?.type, kind: v1.datasource?.type,

View File

@ -62,6 +62,7 @@ import {
transformVariableRefreshToEnumV1, transformVariableRefreshToEnumV1,
} from 'app/features/dashboard-scene/serialization/transformToV1TypesUtils'; } from 'app/features/dashboard-scene/serialization/transformToV1TypesUtils';
import { import {
LEGACY_STRING_VALUE_KEY,
transformCursorSynctoEnum, transformCursorSynctoEnum,
transformDataTopic, transformDataTopic,
transformSortVariableToEnum, transformSortVariableToEnum,
@ -504,8 +505,12 @@ function getVariables(vars: TypedVariableModel[]): DashboardV2Spec['variables']
let query = v.query || {}; let query = v.query || {};
if (typeof query === 'string') { if (typeof query === 'string') {
console.error('Query variable query is a string. It needs to extend DataQuery.'); console.warn(
query = {}; 'Query variable query is a string which is deprecated in the schema v2. It should extend DataQuery'
);
query = {
[LEGACY_STRING_VALUE_KEY]: query,
};
} }
const qv: QueryVariableKind = { const qv: QueryVariableKind = {
@ -527,7 +532,7 @@ function getVariables(vars: TypedVariableModel[]): DashboardV2Spec['variables']
query: { query: {
kind: v.datasource?.type || getDefaultDatasourceType(), kind: v.datasource?.type || getDefaultDatasourceType(),
spec: { spec: {
...query, ...v.query,
}, },
}, },
}, },
@ -708,7 +713,10 @@ function getVariablesV1(vars: DashboardV2Spec['variables']): VariableModel[] {
...commonProperties, ...commonProperties,
current: v.spec.current, current: v.spec.current,
options: v.spec.options, options: v.spec.options,
query: typeof v.spec.query === 'string' ? v.spec.query : v.spec.query.spec, query:
LEGACY_STRING_VALUE_KEY in v.spec.query.spec
? v.spec.query.spec[LEGACY_STRING_VALUE_KEY]
: v.spec.query.spec,
datasource: v.spec.datasource, datasource: v.spec.datasource,
sort: transformSortVariableToEnumV1(v.spec.sort), sort: transformSortVariableToEnumV1(v.spec.sort),
refresh: transformVariableRefreshToEnumV1(v.spec.refresh), refresh: transformVariableRefreshToEnumV1(v.spec.refresh),