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 f324bfbcca8..00274bef07a 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue @@ -753,8 +753,6 @@ GroupByVariableSpec: { } options: [...VariableOption] | *[] multi: bool | *false - includeAll: bool | *false - allValue?: string label?: string hide: VariableHide skipUrlSync: 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 bcf69326667..c65b2a375f5 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts @@ -380,7 +380,6 @@ export const handyTestingSchema: DashboardV2Spec = { }, description: 'A group by variable', hide: 'dontHide', - includeAll: false, label: 'Group By Variable', multi: false, name: 'groupByVar', diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha0/types.gen.ts b/packages/grafana-schema/src/schema/dashboard/v2alpha0/types.gen.ts index 85bd30efe59..848bf39138b 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha0/types.gen.ts +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/types.gen.ts @@ -1109,8 +1109,6 @@ export interface GroupByVariableSpec { current: VariableOption; options: VariableOption[]; multi: boolean; - includeAll: boolean; - allValue?: string; label?: string; hide: VariableHide; skipUrlSync: boolean; @@ -1122,7 +1120,6 @@ export const defaultGroupByVariableSpec = (): GroupByVariableSpec => ({ current: { text: "", value: "", }, options: [], multi: false, - includeAll: false, hide: "dontHide", skipUrlSync: false, }); diff --git a/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.test.ts b/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.test.ts index 6f80d06d7c9..29eab57f88f 100644 --- a/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.test.ts +++ b/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.test.ts @@ -488,7 +488,6 @@ describe('DashboardSceneSerializer', () => { }, ], multi: false, - includeAll: false, hide: 'dontHide', 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 df2ee920da5..e03f5c91ab8 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 @@ -305,7 +305,6 @@ exports[`transformSceneToSaveModelSchemaV2 should transform scene to save model }, "description": "A group by variable", "hide": "dontHide", - "includeAll": false, "label": "Group By Variable", "multi": false, "name": "groupByVar", diff --git a/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.test.ts b/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.test.ts index af6bc4d5618..2cbb9a03550 100644 --- a/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.test.ts +++ b/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.test.ts @@ -1230,7 +1230,6 @@ describe('sceneVariablesSetToVariables', () => { }, "description": "test-desc", "hide": "dontHide", - "includeAll": false, "label": "test-label", "multi": true, "name": "test", diff --git a/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.ts b/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.ts index 4035c994b2f..eb35c143333 100644 --- a/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.ts +++ b/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.ts @@ -407,7 +407,6 @@ export function sceneVariablesSetToSchemaV2Variables( })) || [], current: currentVariableOption, multi: variable.state.isMulti || false, - includeAll: variable.state.includeAll || false, }, }; variables.push(groupVariable); diff --git a/public/app/features/dashboard/api/ResponseTransformers.test.ts b/public/app/features/dashboard/api/ResponseTransformers.test.ts index eec31339d32..a8c6e3fd8fb 100644 --- a/public/app/features/dashboard/api/ResponseTransformers.test.ts +++ b/public/app/features/dashboard/api/ResponseTransformers.test.ts @@ -1,5 +1,5 @@ -import { DataQuery } from '@grafana/schema'; -import { DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0'; +import { DataQuery, VariableModel, VariableRefresh } from '@grafana/schema'; +import { DashboardV2Spec, VariableKind } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0'; import { AnnoKeyCreatedBy, AnnoKeyDashboardGnetId, @@ -10,6 +10,10 @@ import { AnnoKeyUpdatedTimestamp, } from 'app/features/apiserver/types'; import { getDefaultDataSourceRef } from 'app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2'; +import { + transformVariableHideToEnum, + transformVariableRefreshToEnum, +} from 'app/features/dashboard-scene/serialization/transformToV2TypesUtils'; import { DashboardDataDTO, DashboardDTO } from 'app/types'; import { getDefaultDatasource, getPanelQueries, ResponseTransformers } from './ResponseTransformers'; @@ -119,6 +123,164 @@ describe('ResponseTransformers', () => { annotations: { list: [], }, + templating: { + list: [ + { + type: 'query', + name: 'var1', + label: 'query var', + description: 'query var description', + skipUrlSync: false, + hide: 0, + multi: true, + includeAll: true, + current: { value: '1', text: '1' }, + options: [ + { selected: true, text: '1', value: '1' }, + { selected: false, text: '2', value: '2' }, + ], + refresh: VariableRefresh.onTimeRangeChanged, + datasource: { + type: 'prometheus', + uid: 'abc', + }, + regex: '.*', + sort: 1, + query: { + expr: 'sum(query)', + }, + }, + { + type: 'datasource', + name: 'var2', + label: 'datasource var', + description: 'datasource var description', + skipUrlSync: false, + hide: 0, + multi: true, + includeAll: true, + current: { value: 'PromTest', text: 'PromTest' }, + options: [ + { selected: true, text: 'PromTest', value: 'PromTest' }, + { selected: false, text: 'Grafana', value: 'Grafana' }, + ], + refresh: VariableRefresh.onTimeRangeChanged, + regex: '.*', + sort: 1, + query: 'sum(query)', + }, + { + type: 'custom', + name: 'var3', + label: 'custom var', + description: 'custom var description', + skipUrlSync: false, + hide: 0, + multi: true, + includeAll: true, + current: { value: '1', text: '1' }, + query: '1,2,3', + options: [ + { selected: true, text: '1', value: '1' }, + { selected: false, text: '2', value: '2' }, + ], + allValue: '1,2,3', + }, + { + type: 'adhoc', + name: 'var4', + label: 'adhoc var', + description: 'adhoc var description', + skipUrlSync: false, + hide: 0, + datasource: { + type: 'prometheus', + uid: 'abc', + }, + // @ts-expect-error + baseFilters: [{ key: 'key1', operator: 'AND' }], + filters: [], + defaultKeys: [], + }, + { + type: 'constant', + name: 'var5', + label: 'constant var', + description: 'constant var description', + skipUrlSync: false, + hide: 0, + current: { value: '1', text: '0' }, + query: '1', + }, + { + type: 'interval', + name: 'var6', + label: 'interval var', + description: 'interval var description', + skipUrlSync: false, + query: '1m,10m,30m,1h', + hide: 0, + current: { + value: 'auto', + text: 'auto', + }, + refresh: VariableRefresh.onTimeRangeChanged, + options: [ + { + selected: true, + text: '1m', + value: '1m', + }, + { + selected: false, + text: '10m', + value: '10m', + }, + { + selected: false, + text: '30m', + value: '30m', + }, + { + selected: false, + text: '1h', + value: '1h', + }, + ], + // @ts-expect-error + auto: false, + auto_min: '1s', + auto_count: 1, + }, + { + type: 'textbox', + name: 'var7', + label: 'textbox var', + description: 'textbox var description', + skipUrlSync: false, + hide: 0, + current: { value: '1', text: '1' }, + query: '1', + }, + { + type: 'groupby', + name: 'var8', + label: 'groupby var', + description: 'groupby var description', + skipUrlSync: false, + hide: 0, + datasource: { + type: 'prometheus', + uid: 'abc', + }, + options: [ + { selected: true, text: '1', value: '1' }, + { selected: false, text: '2', value: '2' }, + ], + current: { value: ['1'], text: ['1'] }, + }, + ], + }, }; const dto: DashboardWithAccessInfo = { @@ -142,7 +304,6 @@ describe('ResponseTransformers', () => { metadata: { name: 'dashboard-uid', resourceVersion: '1', - creationTimestamp: '2023-01-01T00:00:00Z', annotations: { [AnnoKeyCreatedBy]: 'user1', @@ -188,6 +349,14 @@ describe('ResponseTransformers', () => { expect(spec.timeSettings.weekStart).toBe(dashboardV1.weekStart); expect(spec.links).toEqual(dashboardV1.links); expect(spec.annotations).toEqual([]); + validateVariablesV1ToV2(spec.variables[0], dashboardV1.templating?.list?.[0]); + validateVariablesV1ToV2(spec.variables[1], dashboardV1.templating?.list?.[1]); + validateVariablesV1ToV2(spec.variables[2], dashboardV1.templating?.list?.[2]); + validateVariablesV1ToV2(spec.variables[3], dashboardV1.templating?.list?.[3]); + validateVariablesV1ToV2(spec.variables[4], dashboardV1.templating?.list?.[4]); + validateVariablesV1ToV2(spec.variables[5], dashboardV1.templating?.list?.[5]); + validateVariablesV1ToV2(spec.variables[6], dashboardV1.templating?.list?.[6]); + validateVariablesV1ToV2(spec.variables[7], dashboardV1.templating?.list?.[7]); }); }); @@ -399,3 +568,79 @@ describe('ResponseTransformers', () => { }); }); }); + +function validateVariablesV1ToV2(v2: VariableKind, v1: VariableModel | undefined) { + if (!v1) { + return expect(v1).toBeDefined(); + } + + const v1Common = { + name: v1.name, + label: v1.label, + description: v1.description, + hide: transformVariableHideToEnum(v1.hide), + skipUrlSync: v1.skipUrlSync, + }; + const v2Common = { + name: v2.spec.name, + label: v2.spec.label, + description: v2.spec.description, + hide: v2.spec.hide, + skipUrlSync: v2.spec.skipUrlSync, + }; + + expect(v2Common).toEqual(v1Common); + + if (v2.kind === 'QueryVariable') { + expect(v2.spec.datasource).toEqual(v1.datasource); + expect(v2.spec.query).toEqual({ + kind: v1.datasource?.type, + spec: { + ...(typeof v1.query === 'object' ? v1.query : {}), + }, + }); + } + + if (v2.kind === 'DatasourceVariable') { + expect(v2.spec.pluginId).toBe(v1.query); + expect(v2.spec.refresh).toBe(transformVariableRefreshToEnum(v1.refresh)); + } + + if (v2.kind === 'CustomVariable') { + expect(v2.spec.query).toBe(v1.query); + expect(v2.spec.options).toEqual(v1.options); + } + + if (v2.kind === 'AdhocVariable') { + expect(v2.spec.datasource).toEqual(v1.datasource); + expect(v2.spec.filters).toEqual([]); + // @ts-expect-error + expect(v2.spec.baseFilters).toEqual(v1.baseFilters); + } + + if (v2.kind === 'ConstantVariable') { + expect(v2.spec.query).toBe(v1.query); + } + + if (v2.kind === 'IntervalVariable') { + expect(v2.spec.query).toBe(v1.query); + expect(v2.spec.options).toEqual(v1.options); + expect(v2.spec.current).toEqual(v1.current); + // @ts-expect-error + expect(v2.spec.auto).toBe(v1.auto); + // @ts-expect-error + expect(v2.spec.auto_min).toBe(v1.auto_min); + // @ts-expect-error + expect(v2.spec.auto_count).toBe(v1.auto_count); + } + + if (v2.kind === 'TextVariable') { + expect(v2.spec.query).toBe(v1.query); + expect(v2.spec.current).toEqual(v1.current); + } + + if (v2.kind === 'GroupByVariable') { + expect(v2.spec.datasource).toEqual(v1.datasource); + expect(v2.spec.options).toEqual(v1.options); + } +} diff --git a/public/app/features/dashboard/api/ResponseTransformers.ts b/public/app/features/dashboard/api/ResponseTransformers.ts index 4b651cffb02..4b91d1e4d16 100644 --- a/public/app/features/dashboard/api/ResponseTransformers.ts +++ b/public/app/features/dashboard/api/ResponseTransformers.ts @@ -1,5 +1,6 @@ +import { TypedVariableModel } from '@grafana/data'; import { config } from '@grafana/runtime'; -import { AnnotationQuery, DataQuery, DataSourceRef, Panel, VariableModel } from '@grafana/schema'; +import { AnnotationQuery, DataQuery, DataSourceRef, Panel } from '@grafana/schema'; import { AnnotationQueryKind, DashboardV2Spec, @@ -11,6 +12,12 @@ import { PanelQueryKind, QueryVariableKind, TransformationKind, + AdhocVariableKind, + CustomVariableKind, + ConstantVariableKind, + IntervalVariableKind, + TextVariableKind, + GroupByVariableKind, } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0'; import { DataTransformerConfig } from '@grafana/schema/src/raw/dashboard/x/dashboard_types.gen'; import { @@ -55,6 +62,8 @@ export function ensureV2Response( const timeSettingsDefaults = defaultTimeSettingsSpec(); const dashboardDefaults = defaultDashboardV2Spec(); const [elements, layout] = getElementsFromPanels(dashboard.panels || []); + // @ts-expect-error - dashboard.templating.list is VariableModel[] and we need TypedVariableModel[] here + // that would allow accessing unique properties for each variable type that the API returns const variables = getVariables(dashboard.templating?.list || []); const annotations = getAnnotations(dashboard.annotations?.list || []); @@ -368,9 +377,17 @@ function getPanelTransformations(transformations: DataTransformerConfig[]): Tran }); } -function getVariables(vars: VariableModel[]): DashboardV2Spec['variables'] { +function getVariables(vars: TypedVariableModel[]): DashboardV2Spec['variables'] { const variables: DashboardV2Spec['variables'] = []; for (const v of vars) { + const commonProperties = { + name: v.name, + label: v.label, + ...(v.description && { description: v.description }), + skipUrlSync: Boolean(v.skipUrlSync), + hide: transformVariableHideToEnum(v.hide), + }; + switch (v.type) { case 'query': let query = v.query || {}; @@ -383,17 +400,17 @@ function getVariables(vars: VariableModel[]): DashboardV2Spec['variables'] { const qv: QueryVariableKind = { kind: 'QueryVariable', spec: { - name: v.name, - label: v.label, - hide: transformVariableHideToEnum(v.hide), - skipUrlSync: Boolean(v.skipUrlSync), + ...commonProperties, multi: Boolean(v.multi), includeAll: Boolean(v.includeAll), - allValue: v.allValue, - current: v.current || { text: '', value: '' }, + ...(v.allValue && { allValue: v.allValue }), + current: { + value: v.current.value, + text: v.current.text, + }, options: v.options || [], refresh: transformVariableRefreshToEnum(v.refresh), - datasource: v.datasource ?? undefined, + ...(v.datasource && { datasource: v.datasource }), regex: v.regex || '', sort: transformSortVariableToEnum(v.sort), query: { @@ -416,23 +433,119 @@ function getVariables(vars: VariableModel[]): DashboardV2Spec['variables'] { const dv: DatasourceVariableKind = { kind: 'DatasourceVariable', spec: { - name: v.name, - label: v.label, - hide: transformVariableHideToEnum(v.hide), - skipUrlSync: Boolean(v.skipUrlSync), + ...commonProperties, multi: Boolean(v.multi), includeAll: Boolean(v.includeAll), - allValue: v.allValue, - current: v.current || { text: '', value: '' }, + ...(v.allValue && { allValue: v.allValue }), + current: { + value: v.current.value, + text: v.current.text, + }, options: v.options || [], refresh: transformVariableRefreshToEnum(v.refresh), pluginId, regex: v.regex || '', - description: v.description || '', }, }; variables.push(dv); break; + case 'custom': + const cv: CustomVariableKind = { + kind: 'CustomVariable', + spec: { + ...commonProperties, + query: v.query, + current: { + value: v.current.value, + text: v.current.text, + }, + options: v.options, + multi: v.multi, + includeAll: v.includeAll, + ...(v.allValue && { allValue: v.allValue }), + }, + }; + variables.push(cv); + break; + case 'adhoc': + const av: AdhocVariableKind = { + kind: 'AdhocVariable', + spec: { + ...commonProperties, + datasource: v.datasource || getDefaultDatasource(), + baseFilters: v.baseFilters || [], + filters: v.filters || [], + defaultKeys: v.defaultKeys || [], + }, + }; + variables.push(av); + break; + case 'constant': + const cnts: ConstantVariableKind = { + kind: 'ConstantVariable', + spec: { + ...commonProperties, + current: { + value: v.current.value, + // Constant variable doesn't use text state + text: v.current.value, + }, + query: v.query, + }, + }; + variables.push(cnts); + break; + case 'interval': + const intrv: IntervalVariableKind = { + kind: 'IntervalVariable', + spec: { + ...commonProperties, + current: { + value: v.current.value, + // Interval variable doesn't use text state + text: v.current.value, + }, + query: v.query, + refresh: 'onTimeRangeChanged', + options: v.options, + auto: v.auto, + auto_min: v.auto_min, + auto_count: v.auto_count, + }, + }; + variables.push(intrv); + break; + case 'textbox': + const tx: TextVariableKind = { + kind: 'TextVariable', + spec: { + ...commonProperties, + current: { + value: v.current.value, + // Text variable doesn't use text state + text: v.current.value, + }, + query: v.query, + }, + }; + variables.push(tx); + break; + case 'groupby': + const gb: GroupByVariableKind = { + kind: 'GroupByVariable', + spec: { + ...commonProperties, + datasource: v.datasource || getDefaultDatasource(), + options: v.options, + current: { + value: v.current.value, + text: v.current.text, + }, + multi: v.multi, + }, + }; + variables.push(gb); + break; default: // do not throw error, just log it console.error(`Variable transformation not implemented: ${v.type}`); @@ -447,7 +560,7 @@ function getAnnotations(annotations: AnnotationQuery[]): DashboardV2Spec['annota kind: 'AnnotationQuery', spec: { name: a.name, - datasource: a.datasource ?? undefined, + ...(a.datasource && { datasource: a.datasource }), enable: a.enable, hide: Boolean(a.hide), iconColor: a.iconColor,