Dashboard Schema V2: ResponseTransformers: Transform layout, panels, annotations and variables when ensuring v1 (#99050)
* wip * Fix annotationV1 * variables * tests * Fix query variable * Don't need extra chaining arg in maxPerRow * Logic for libary panels; tests for variables and library panels * lint
This commit is contained in:
+2
-1
@@ -3775,7 +3775,8 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Do not use any type assertions.", "2"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "3"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "4"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "5"]
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "5"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "6"]
|
||||
],
|
||||
"public/app/features/dashboard/api/v0.ts:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||
|
||||
@@ -175,8 +175,6 @@ export function sceneVariablesSetToVariables(set: SceneVariables, keepQueryOptio
|
||||
} else if (sceneUtils.isAdHocVariable(variable)) {
|
||||
variables.push({
|
||||
...commonProperties,
|
||||
name: variable.state.name,
|
||||
type: 'adhoc',
|
||||
datasource: variable.state.datasource,
|
||||
allowCustomValue: variable.state.allowCustomValue,
|
||||
// @ts-expect-error
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { DataQuery, VariableModel, VariableRefresh } from '@grafana/schema';
|
||||
import { DashboardV2Spec, VariableKind } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0';
|
||||
import { AnnotationQuery, DataQuery, VariableModel, VariableRefresh, Panel } from '@grafana/schema';
|
||||
import { DashboardV2Spec, PanelKind, VariableKind } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0';
|
||||
import { handyTestingSchema } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0/examples';
|
||||
import {
|
||||
AnnoKeyCreatedBy,
|
||||
AnnoKeyDashboardGnetId,
|
||||
@@ -16,7 +17,12 @@ import {
|
||||
} from 'app/features/dashboard-scene/serialization/transformToV2TypesUtils';
|
||||
import { DashboardDataDTO, DashboardDTO } from 'app/types';
|
||||
|
||||
import { getDefaultDatasource, getPanelQueries, ResponseTransformers } from './ResponseTransformers';
|
||||
import {
|
||||
getDefaultDatasource,
|
||||
getPanelQueries,
|
||||
ResponseTransformers,
|
||||
transformMappingsToV1,
|
||||
} from './ResponseTransformers';
|
||||
import { DashboardWithAccessInfo } from './types';
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
@@ -546,15 +552,10 @@ describe('ResponseTransformers', () => {
|
||||
tooltip: 'Link 1 Tooltip',
|
||||
},
|
||||
],
|
||||
annotations: [],
|
||||
variables: [],
|
||||
elements: {},
|
||||
layout: {
|
||||
kind: 'GridLayout',
|
||||
spec: {
|
||||
items: [],
|
||||
},
|
||||
},
|
||||
annotations: handyTestingSchema.annotations,
|
||||
variables: handyTestingSchema.variables,
|
||||
elements: handyTestingSchema.elements,
|
||||
layout: handyTestingSchema.layout,
|
||||
},
|
||||
access: {
|
||||
url: '/d/dashboard-slug',
|
||||
@@ -612,153 +613,237 @@ describe('ResponseTransformers', () => {
|
||||
expect(dashboard.fiscalYearStartMonth).toBe(dashboardV2.spec.timeSettings.fiscalYearStartMonth);
|
||||
expect(dashboard.weekStart).toBe(dashboardV2.spec.timeSettings.weekStart);
|
||||
expect(dashboard.links).toEqual(dashboardV2.spec.links);
|
||||
expect(dashboard.annotations).toEqual({ list: [] });
|
||||
// variables
|
||||
validateVariablesV1ToV2(dashboardV2.spec.variables[0], dashboard.templating?.list?.[0]);
|
||||
validateVariablesV1ToV2(dashboardV2.spec.variables[1], dashboard.templating?.list?.[1]);
|
||||
validateVariablesV1ToV2(dashboardV2.spec.variables[2], dashboard.templating?.list?.[2]);
|
||||
validateVariablesV1ToV2(dashboardV2.spec.variables[3], dashboard.templating?.list?.[3]);
|
||||
validateVariablesV1ToV2(dashboardV2.spec.variables[4], dashboard.templating?.list?.[4]);
|
||||
validateVariablesV1ToV2(dashboardV2.spec.variables[5], dashboard.templating?.list?.[5]);
|
||||
validateVariablesV1ToV2(dashboardV2.spec.variables[6], dashboard.templating?.list?.[6]);
|
||||
validateVariablesV1ToV2(dashboardV2.spec.variables[7], dashboard.templating?.list?.[7]);
|
||||
// annotations
|
||||
validateAnnotation(dashboard.annotations!.list![0], dashboardV2.spec.annotations[0]);
|
||||
validateAnnotation(dashboard.annotations!.list![1], dashboardV2.spec.annotations[1]);
|
||||
validateAnnotation(dashboard.annotations!.list![2], dashboardV2.spec.annotations[2]);
|
||||
validateAnnotation(dashboard.annotations!.list![3], dashboardV2.spec.annotations[3]);
|
||||
// panel
|
||||
const panelKey = 'panel-1';
|
||||
const panelV2 = dashboardV2.spec.elements[panelKey] as PanelKind;
|
||||
expect(panelV2.kind).toBe('Panel');
|
||||
validatePanel(dashboard.panels![0], panelV2, dashboardV2.spec.layout, panelKey);
|
||||
// library panel
|
||||
expect(dashboard.panels![1].libraryPanel).toEqual(dashboardV2.spec.elements['library-panel-1'].spec);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPanelQueries', () => {
|
||||
it('respects targets data source', () => {
|
||||
const panelDs = {
|
||||
type: 'theoretical-ds',
|
||||
uid: 'theoretical-uid',
|
||||
};
|
||||
const targets: DataQuery[] = [
|
||||
{
|
||||
refId: 'A',
|
||||
datasource: {
|
||||
type: 'theoretical-ds',
|
||||
uid: 'theoretical-uid',
|
||||
},
|
||||
},
|
||||
{
|
||||
refId: 'B',
|
||||
datasource: {
|
||||
type: 'theoretical-ds',
|
||||
uid: 'theoretical-uid',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const result = getPanelQueries(targets, panelDs);
|
||||
|
||||
expect(result).toHaveLength(targets.length);
|
||||
expect(result[0].spec.refId).toBe('A');
|
||||
expect(result[1].spec.refId).toBe('B');
|
||||
|
||||
result.forEach((query) => {
|
||||
expect(query.kind).toBe('PanelQuery');
|
||||
expect(query.spec.datasource).toEqual({
|
||||
describe('getPanelQueries', () => {
|
||||
it('respects targets data source', () => {
|
||||
const panelDs = {
|
||||
type: 'theoretical-ds',
|
||||
uid: 'theoretical-uid',
|
||||
};
|
||||
const targets: DataQuery[] = [
|
||||
{
|
||||
refId: 'A',
|
||||
datasource: {
|
||||
type: 'theoretical-ds',
|
||||
uid: 'theoretical-uid',
|
||||
},
|
||||
},
|
||||
{
|
||||
refId: 'B',
|
||||
datasource: {
|
||||
type: 'theoretical-ds',
|
||||
uid: 'theoretical-uid',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const result = getPanelQueries(targets, panelDs);
|
||||
|
||||
expect(result).toHaveLength(targets.length);
|
||||
expect(result[0].spec.refId).toBe('A');
|
||||
expect(result[1].spec.refId).toBe('B');
|
||||
|
||||
result.forEach((query) => {
|
||||
expect(query.kind).toBe('PanelQuery');
|
||||
expect(query.spec.datasource).toEqual({
|
||||
type: 'theoretical-ds',
|
||||
uid: 'theoretical-uid',
|
||||
});
|
||||
expect(query.spec.query.kind).toBe('theoretical-ds');
|
||||
});
|
||||
expect(query.spec.query.kind).toBe('theoretical-ds');
|
||||
});
|
||||
});
|
||||
|
||||
it('respects panel data source', () => {
|
||||
const panelDs = {
|
||||
type: 'theoretical-ds',
|
||||
uid: 'theoretical-uid',
|
||||
};
|
||||
const targets: DataQuery[] = [
|
||||
{
|
||||
refId: 'A',
|
||||
},
|
||||
{
|
||||
refId: 'B',
|
||||
},
|
||||
];
|
||||
|
||||
const result = getPanelQueries(targets, panelDs);
|
||||
|
||||
expect(result).toHaveLength(targets.length);
|
||||
expect(result[0].spec.refId).toBe('A');
|
||||
expect(result[1].spec.refId).toBe('B');
|
||||
|
||||
result.forEach((query) => {
|
||||
expect(query.kind).toBe('PanelQuery');
|
||||
expect(query.spec.datasource).toEqual({
|
||||
it('respects panel data source', () => {
|
||||
const panelDs = {
|
||||
type: 'theoretical-ds',
|
||||
uid: 'theoretical-uid',
|
||||
};
|
||||
const targets: DataQuery[] = [
|
||||
{
|
||||
refId: 'A',
|
||||
},
|
||||
{
|
||||
refId: 'B',
|
||||
},
|
||||
];
|
||||
|
||||
const result = getPanelQueries(targets, panelDs);
|
||||
|
||||
expect(result).toHaveLength(targets.length);
|
||||
expect(result[0].spec.refId).toBe('A');
|
||||
expect(result[1].spec.refId).toBe('B');
|
||||
|
||||
result.forEach((query) => {
|
||||
expect(query.kind).toBe('PanelQuery');
|
||||
expect(query.spec.datasource).toEqual({
|
||||
type: 'theoretical-ds',
|
||||
uid: 'theoretical-uid',
|
||||
});
|
||||
expect(query.spec.query.kind).toBe('theoretical-ds');
|
||||
});
|
||||
expect(query.spec.query.kind).toBe('theoretical-ds');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function validateAnnotation(v1: AnnotationQuery, v2: DashboardV2Spec['annotations'][0]) {
|
||||
const { spec: v2Spec } = v2;
|
||||
|
||||
expect(v1.name).toBe(v2Spec.name);
|
||||
expect(v1.datasource).toBe(v2Spec.datasource);
|
||||
expect(v1.enable).toBe(v2Spec.enable);
|
||||
expect(v1.hide).toBe(v2Spec.hide);
|
||||
expect(v1.iconColor).toBe(v2Spec.iconColor);
|
||||
expect(v1.builtIn).toBe(v2Spec.builtIn ? 1 : 0);
|
||||
expect(v1.target).toBe(v2Spec.query?.spec);
|
||||
expect(v1.filter).toEqual(v2Spec.filter);
|
||||
}
|
||||
|
||||
function validatePanel(v1: Panel, v2: PanelKind, layoutV2: DashboardV2Spec['layout'], panelKey: string) {
|
||||
const { spec: v2Spec } = v2;
|
||||
|
||||
expect(v1.id).toBe(v2Spec.id);
|
||||
expect(v1.id).toBe(v2Spec.id);
|
||||
expect(v1.type).toBe(v2Spec.vizConfig.kind);
|
||||
expect(v1.title).toBe(v2Spec.title);
|
||||
expect(v1.description).toBe(v2Spec.description);
|
||||
expect(v1.fieldConfig).toEqual(transformMappingsToV1(v2Spec.vizConfig.spec.fieldConfig));
|
||||
expect(v1.options).toBe(v2Spec.vizConfig.spec.options);
|
||||
expect(v1.pluginVersion).toBe(v2Spec.vizConfig.spec.pluginVersion);
|
||||
expect(v1.links).toEqual(v2Spec.links);
|
||||
expect(v1.targets).toEqual(
|
||||
v2Spec.data.spec.queries.map((q) => {
|
||||
return {
|
||||
refId: q.spec.refId,
|
||||
hide: q.spec.hidden,
|
||||
datasource: q.spec.datasource,
|
||||
...q.spec.query.spec,
|
||||
};
|
||||
})
|
||||
);
|
||||
expect(v1.transformations).toEqual(v2Spec.data.spec.transformations.map((t) => t.spec));
|
||||
const layoutElement = layoutV2.spec.items.find(
|
||||
(item) => item.kind === 'GridLayoutItem' && item.spec.element.name === panelKey
|
||||
);
|
||||
expect(v1.gridPos?.x).toEqual(layoutElement?.spec.x);
|
||||
expect(v1.gridPos?.y).toEqual(layoutElement?.spec.y);
|
||||
expect(v1.gridPos?.w).toEqual(layoutElement?.spec.width);
|
||||
expect(v1.gridPos?.h).toEqual(layoutElement?.spec.height);
|
||||
|
||||
expect(v1.repeat).toEqual(layoutElement?.spec.repeat?.value);
|
||||
expect(v1.repeatDirection).toEqual(layoutElement?.spec.repeat?.direction);
|
||||
expect(v1.maxPerRow).toEqual(layoutElement?.spec.repeat?.maxPerRow);
|
||||
|
||||
expect(v1.cacheTimeout).toBe(v2Spec.data.spec.queryOptions.cacheTimeout);
|
||||
expect(v1.maxDataPoints).toBe(v2Spec.data.spec.queryOptions.maxDataPoints);
|
||||
expect(v1.interval).toBe(v2Spec.data.spec.queryOptions.interval);
|
||||
expect(v1.hideTimeOverride).toBe(v2Spec.data.spec.queryOptions.hideTimeOverride);
|
||||
expect(v1.queryCachingTTL).toBe(v2Spec.data.spec.queryOptions.queryCachingTTL);
|
||||
expect(v1.timeFrom).toBe(v2Spec.data.spec.queryOptions.timeFrom);
|
||||
expect(v1.timeShift).toBe(v2Spec.data.spec.queryOptions.timeShift);
|
||||
expect(v1.transparent).toBe(v2Spec.transparent);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (typeof v1.query === 'string') {
|
||||
expect(v2.spec.query).toEqual(v1.query);
|
||||
} else {
|
||||
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);
|
||||
// @ts-expect-error
|
||||
expect(v2.spec.filters).toEqual(v1.filters);
|
||||
// @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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
import { TypedVariableModel } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { AnnotationQuery, DataQuery, DataSourceRef, Panel } from '@grafana/schema';
|
||||
import {
|
||||
AnnotationQuery,
|
||||
DataQuery,
|
||||
DataSourceRef,
|
||||
Panel,
|
||||
VariableModel,
|
||||
VariableType,
|
||||
FieldConfigSource as FieldConfigSourceV1,
|
||||
FieldColorModeId as FieldColorModeIdV1,
|
||||
ThresholdsMode as ThresholdsModeV1,
|
||||
MappingType as MappingTypeV1,
|
||||
SpecialValueMatch as SpecialValueMatchV1,
|
||||
} from '@grafana/schema';
|
||||
import {
|
||||
AnnotationQueryKind,
|
||||
DashboardV2Spec,
|
||||
@@ -12,6 +24,10 @@ import {
|
||||
PanelQueryKind,
|
||||
QueryVariableKind,
|
||||
TransformationKind,
|
||||
FieldColorModeId,
|
||||
FieldConfigSource,
|
||||
ThresholdsMode,
|
||||
SpecialValueMatch,
|
||||
AdhocVariableKind,
|
||||
CustomVariableKind,
|
||||
ConstantVariableKind,
|
||||
@@ -19,7 +35,7 @@ import {
|
||||
TextVariableKind,
|
||||
GroupByVariableKind,
|
||||
} from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0';
|
||||
import { DataTransformerConfig } from '@grafana/schema/src/raw/dashboard/x/dashboard_types.gen';
|
||||
import { DashboardLink, DataTransformerConfig } from '@grafana/schema/src/raw/dashboard/x/dashboard_types.gen';
|
||||
import {
|
||||
AnnoKeyCreatedBy,
|
||||
AnnoKeyDashboardGnetId,
|
||||
@@ -31,8 +47,14 @@ import {
|
||||
AnnoKeyUpdatedBy,
|
||||
AnnoKeyUpdatedTimestamp,
|
||||
} from 'app/features/apiserver/types';
|
||||
import { TypedVariableModelV2 } from 'app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene';
|
||||
import { getDefaultDataSourceRef } from 'app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2';
|
||||
import { transformCursorSyncV2ToV1 } from 'app/features/dashboard-scene/serialization/transformToV1TypesUtils';
|
||||
import {
|
||||
transformCursorSyncV2ToV1,
|
||||
transformSortVariableToEnumV1,
|
||||
transformVariableHideToEnumV1,
|
||||
transformVariableRefreshToEnumV1,
|
||||
} from 'app/features/dashboard-scene/serialization/transformToV1TypesUtils';
|
||||
import {
|
||||
transformCursorSynctoEnum,
|
||||
transformDataTopic,
|
||||
@@ -179,6 +201,9 @@ export function ensureV1Response(
|
||||
};
|
||||
} else {
|
||||
// if dashboard is on v2 schema convert to v1 schema
|
||||
const annotations = getAnnotationsV1(spec.annotations);
|
||||
const variables = getVariablesV1(spec.variables);
|
||||
const panels = getPanelsV1(spec.elements, spec.layout);
|
||||
return {
|
||||
meta: {
|
||||
created: dashboard.metadata.creationTimestamp,
|
||||
@@ -224,9 +249,9 @@ export function ensureV1Response(
|
||||
weekStart: spec.timeSettings.weekStart,
|
||||
version: parseInt(dashboard.metadata.resourceVersion, 10),
|
||||
links: spec.links,
|
||||
annotations: { list: [] }, // TODO
|
||||
panels: [], // TODO
|
||||
templating: { list: [] }, // TODO
|
||||
annotations: { list: annotations },
|
||||
panels,
|
||||
templating: { list: variables },
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -593,3 +618,372 @@ function getAnnotations(annotations: AnnotationQuery[]): DashboardV2Spec['annota
|
||||
return aq;
|
||||
});
|
||||
}
|
||||
|
||||
function getVariablesV1(vars: DashboardV2Spec['variables']): VariableModel[] {
|
||||
const variables: VariableModel[] = [];
|
||||
|
||||
for (const v of vars) {
|
||||
const commonProperties = {
|
||||
name: v.spec.name,
|
||||
label: v.spec.label,
|
||||
...(v.spec.description && { description: v.spec.description }),
|
||||
skipUrlSync: v.spec.skipUrlSync,
|
||||
hide: transformVariableHideToEnumV1(v.spec.hide),
|
||||
type: transformToV1VariableTypes(v),
|
||||
};
|
||||
|
||||
switch (v.kind) {
|
||||
case 'QueryVariable':
|
||||
const qv: VariableModel = {
|
||||
...commonProperties,
|
||||
current: v.spec.current,
|
||||
options: v.spec.options,
|
||||
query: typeof v.spec.query === 'string' ? v.spec.query : v.spec.query.spec,
|
||||
datasource: v.spec.datasource,
|
||||
sort: transformSortVariableToEnumV1(v.spec.sort),
|
||||
refresh: transformVariableRefreshToEnumV1(v.spec.refresh),
|
||||
regex: v.spec.regex,
|
||||
allValue: v.spec.allValue,
|
||||
includeAll: v.spec.includeAll,
|
||||
multi: v.spec.multi,
|
||||
// @ts-expect-error - definition is not part of v1 VariableModel
|
||||
definition: v.spec.definition,
|
||||
};
|
||||
variables.push(qv);
|
||||
break;
|
||||
case 'DatasourceVariable':
|
||||
const dv: VariableModel = {
|
||||
...commonProperties,
|
||||
current: v.spec.current,
|
||||
options: [],
|
||||
regex: v.spec.regex,
|
||||
refresh: transformVariableRefreshToEnumV1(v.spec.refresh),
|
||||
query: v.spec.pluginId,
|
||||
multi: v.spec.multi,
|
||||
allValue: v.spec.allValue,
|
||||
includeAll: v.spec.includeAll,
|
||||
};
|
||||
variables.push(dv);
|
||||
break;
|
||||
case 'CustomVariable':
|
||||
const cv: VariableModel = {
|
||||
...commonProperties,
|
||||
current: {
|
||||
text: v.spec.current.value,
|
||||
value: v.spec.current.value,
|
||||
},
|
||||
options: v.spec.options,
|
||||
query: v.spec.query,
|
||||
multi: v.spec.multi,
|
||||
allValue: v.spec.allValue,
|
||||
includeAll: v.spec.includeAll,
|
||||
};
|
||||
variables.push(cv);
|
||||
break;
|
||||
case 'ConstantVariable':
|
||||
const constant: VariableModel = {
|
||||
...commonProperties,
|
||||
current: {
|
||||
text: v.spec.current.value,
|
||||
value: v.spec.current.value,
|
||||
},
|
||||
hide: transformVariableHideToEnumV1(v.spec.hide),
|
||||
// @ts-expect-error
|
||||
query: v.spec.current.value,
|
||||
};
|
||||
variables.push(constant);
|
||||
break;
|
||||
case 'IntervalVariable':
|
||||
const iv: VariableModel = {
|
||||
...commonProperties,
|
||||
current: {
|
||||
text: v.spec.current.value,
|
||||
value: v.spec.current.value,
|
||||
},
|
||||
hide: transformVariableHideToEnumV1(v.spec.hide),
|
||||
query: v.spec.query,
|
||||
refresh: transformVariableRefreshToEnumV1(v.spec.refresh),
|
||||
options: v.spec.options,
|
||||
// @ts-expect-error
|
||||
auto: v.spec.auto,
|
||||
auto_min: v.spec.auto_min,
|
||||
auto_count: v.spec.auto_count,
|
||||
};
|
||||
variables.push(iv);
|
||||
break;
|
||||
case 'TextVariable':
|
||||
const current = {
|
||||
text: v.spec.current.value,
|
||||
value: v.spec.current.value,
|
||||
};
|
||||
|
||||
const tv: VariableModel = {
|
||||
...commonProperties,
|
||||
current: {
|
||||
text: v.spec.current.value,
|
||||
value: v.spec.current.value,
|
||||
},
|
||||
options: [{ ...current, selected: true }],
|
||||
query: v.spec.query,
|
||||
};
|
||||
variables.push(tv);
|
||||
break;
|
||||
case 'GroupByVariable':
|
||||
const gv: VariableModel = {
|
||||
...commonProperties,
|
||||
datasource: v.spec.datasource,
|
||||
current: v.spec.current,
|
||||
options: v.spec.options,
|
||||
};
|
||||
variables.push(gv);
|
||||
break;
|
||||
case 'AdhocVariable':
|
||||
const av: VariableModel = {
|
||||
...commonProperties,
|
||||
datasource: v.spec.datasource,
|
||||
// @ts-expect-error
|
||||
baseFilters: v.spec.baseFilters,
|
||||
filters: v.spec.filters,
|
||||
defaultKeys: v.spec.defaultKeys,
|
||||
};
|
||||
variables.push(av);
|
||||
break;
|
||||
default:
|
||||
// do not throw error, just log it
|
||||
console.error(`Variable transformation not implemented: ${v}`);
|
||||
}
|
||||
}
|
||||
return variables;
|
||||
}
|
||||
|
||||
function getAnnotationsV1(annotations: DashboardV2Spec['annotations']): AnnotationQuery[] {
|
||||
// @ts-expect-error - target v2 query is not compatible with v1 target
|
||||
return annotations.map((a) => {
|
||||
return {
|
||||
name: a.spec.name,
|
||||
datasource: a.spec.datasource,
|
||||
enable: a.spec.enable,
|
||||
hide: a.spec.hide,
|
||||
iconColor: a.spec.iconColor,
|
||||
builtIn: a.spec.builtIn ? 1 : 0,
|
||||
target: a.spec.query?.spec,
|
||||
filter: a.spec.filter,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
interface LibraryPanelDTO extends Pick<Panel, 'libraryPanel' | 'id' | 'title' | 'gridPos'> {}
|
||||
|
||||
function getPanelsV1(
|
||||
panels: DashboardV2Spec['elements'],
|
||||
layout: DashboardV2Spec['layout']
|
||||
): Array<Panel | LibraryPanelDTO> {
|
||||
return Object.entries(panels).map(([key, p]) => {
|
||||
const layoutElement = layout.spec.items.find(
|
||||
(item) => item.kind === 'GridLayoutItem' && item.spec.element.name === key
|
||||
);
|
||||
const { x, y, width, height, repeat } = layoutElement?.spec || { x: 0, y: 0, width: 0, height: 0 };
|
||||
const gridPos = { x, y, w: width, h: height };
|
||||
if (p.kind === 'Panel') {
|
||||
const panel = p.spec;
|
||||
return {
|
||||
id: panel.id,
|
||||
type: panel.vizConfig.kind,
|
||||
title: panel.title,
|
||||
description: panel.description,
|
||||
fieldConfig: transformMappingsToV1(panel.vizConfig.spec.fieldConfig),
|
||||
options: panel.vizConfig.spec.options,
|
||||
pluginVersion: panel.vizConfig.spec.pluginVersion,
|
||||
links:
|
||||
// @ts-expect-error - Panel link is wrongly typed as DashboardLink
|
||||
panel.links?.map<DashboardLink>((l) => ({
|
||||
title: l.title,
|
||||
url: l.url,
|
||||
...(l.targetBlank && { targetBlank: l.targetBlank }),
|
||||
})) || [],
|
||||
targets: panel.data.spec.queries.map((q) => {
|
||||
return {
|
||||
refId: q.spec.refId,
|
||||
hide: q.spec.hidden,
|
||||
datasource: q.spec.datasource,
|
||||
...q.spec.query.spec,
|
||||
};
|
||||
}),
|
||||
transformations: panel.data.spec.transformations.map((t) => t.spec),
|
||||
gridPos,
|
||||
cacheTimeout: panel.data.spec.queryOptions.cacheTimeout,
|
||||
maxDataPoints: panel.data.spec.queryOptions.maxDataPoints,
|
||||
interval: panel.data.spec.queryOptions.interval,
|
||||
hideTimeOverride: panel.data.spec.queryOptions.hideTimeOverride,
|
||||
queryCachingTTL: panel.data.spec.queryOptions.queryCachingTTL,
|
||||
timeFrom: panel.data.spec.queryOptions.timeFrom,
|
||||
timeShift: panel.data.spec.queryOptions.timeShift,
|
||||
transparent: panel.transparent,
|
||||
...(repeat?.value && { repeat: repeat.value }),
|
||||
...(repeat?.direction && { repeatDirection: repeat.direction }),
|
||||
...(repeat?.maxPerRow && { maxPerRow: repeat.maxPerRow }),
|
||||
};
|
||||
} else if (p.kind === 'LibraryPanel') {
|
||||
const panel = p.spec;
|
||||
return {
|
||||
id: 0, //TODO: LibraryPanelSpec.id will be available after https://github.com/grafana/grafana/pull/99281/ is merged
|
||||
title: panel.name,
|
||||
gridPos,
|
||||
libraryPanel: {
|
||||
uid: panel.uid,
|
||||
name: panel.name,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
throw new Error(`Unknown element kind: ${p}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function transformMappingsToV1(fieldConfig: FieldConfigSource): FieldConfigSourceV1 {
|
||||
const getThresholdsMode = (mode: ThresholdsMode): ThresholdsModeV1 => {
|
||||
switch (mode) {
|
||||
case 'absolute':
|
||||
return ThresholdsModeV1.Absolute;
|
||||
case 'percentage':
|
||||
return ThresholdsModeV1.Percentage;
|
||||
default:
|
||||
return ThresholdsModeV1.Absolute;
|
||||
}
|
||||
};
|
||||
|
||||
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),
|
||||
};
|
||||
}
|
||||
|
||||
if (fieldConfig.defaults.color?.mode) {
|
||||
transformedDefaults.color = {
|
||||
...fieldConfig.defaults.color,
|
||||
mode: colorIdToEnumv1(fieldConfig.defaults.color.mode),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...fieldConfig,
|
||||
defaults: transformedDefaults,
|
||||
};
|
||||
}
|
||||
|
||||
function colorIdToEnumv1(colorId: FieldColorModeId): FieldColorModeIdV1 {
|
||||
switch (colorId) {
|
||||
case 'thresholds':
|
||||
return FieldColorModeIdV1.Thresholds;
|
||||
case 'palette-classic':
|
||||
return FieldColorModeIdV1.PaletteClassic;
|
||||
case 'palette-classic-by-name':
|
||||
return FieldColorModeIdV1.PaletteClassicByName;
|
||||
case 'continuous-GrYlRd':
|
||||
return FieldColorModeIdV1.ContinuousGrYlRd;
|
||||
case 'continuous-RdYlGr':
|
||||
return FieldColorModeIdV1.ContinuousRdYlGr;
|
||||
case 'continuous-BlYlRd':
|
||||
return FieldColorModeIdV1.ContinuousBlYlRd;
|
||||
case 'continuous-YlRd':
|
||||
return FieldColorModeIdV1.ContinuousYlRd;
|
||||
case 'continuous-BlPu':
|
||||
return FieldColorModeIdV1.ContinuousBlPu;
|
||||
case 'continuous-YlBl':
|
||||
return FieldColorModeIdV1.ContinuousYlBl;
|
||||
case 'continuous-blues':
|
||||
return FieldColorModeIdV1.ContinuousBlues;
|
||||
case 'continuous-reds':
|
||||
return FieldColorModeIdV1.ContinuousReds;
|
||||
case 'continuous-greens':
|
||||
return FieldColorModeIdV1.ContinuousGreens;
|
||||
case 'continuous-purples':
|
||||
return FieldColorModeIdV1.ContinuousPurples;
|
||||
case 'fixed':
|
||||
return FieldColorModeIdV1.Fixed;
|
||||
case 'shades':
|
||||
return FieldColorModeIdV1.Shades;
|
||||
default:
|
||||
return FieldColorModeIdV1.Thresholds;
|
||||
}
|
||||
}
|
||||
|
||||
function transformSpecialValueMatchToV1(match: SpecialValueMatch): SpecialValueMatchV1 {
|
||||
switch (match) {
|
||||
case 'true':
|
||||
return SpecialValueMatchV1.True;
|
||||
case 'false':
|
||||
return SpecialValueMatchV1.False;
|
||||
case 'null':
|
||||
return SpecialValueMatchV1.Null;
|
||||
case 'nan':
|
||||
return SpecialValueMatchV1.NaN;
|
||||
case 'null+nan':
|
||||
return SpecialValueMatchV1.NullAndNan;
|
||||
case 'empty':
|
||||
return SpecialValueMatchV1.Empty;
|
||||
default:
|
||||
throw new Error(`Unknown match type: ${match}`);
|
||||
}
|
||||
}
|
||||
|
||||
function transformToV1VariableTypes(variable: TypedVariableModelV2): VariableType {
|
||||
switch (variable.kind) {
|
||||
case 'QueryVariable':
|
||||
return 'query';
|
||||
case 'DatasourceVariable':
|
||||
return 'datasource';
|
||||
case 'CustomVariable':
|
||||
return 'custom';
|
||||
case 'ConstantVariable':
|
||||
return 'constant';
|
||||
case 'IntervalVariable':
|
||||
return 'interval';
|
||||
case 'TextVariable':
|
||||
return 'textbox';
|
||||
case 'GroupByVariable':
|
||||
return 'groupby';
|
||||
case 'AdhocVariable':
|
||||
return 'adhoc';
|
||||
default:
|
||||
throw new Error(`Unknown variable type: ${variable}`);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user