2024-11-28 18:36:09 +08:00
|
|
|
import { omit } from 'lodash';
|
|
|
|
|
|
|
|
import { AnnotationQuery } from '@grafana/data';
|
|
|
|
import { config } from '@grafana/runtime';
|
|
|
|
import {
|
|
|
|
behaviors,
|
|
|
|
dataLayers,
|
|
|
|
SceneDataQuery,
|
|
|
|
SceneDataTransformer,
|
|
|
|
SceneVariableSet,
|
|
|
|
VizPanel,
|
|
|
|
} from '@grafana/scenes';
|
|
|
|
import { DataSourceRef } from '@grafana/schema';
|
|
|
|
import { DASHBOARD_SCHEMA_VERSION } from 'app/features/dashboard/state/DashboardMigrator';
|
|
|
|
|
2024-11-19 20:01:40 +08:00
|
|
|
import {
|
|
|
|
DashboardV2Spec,
|
|
|
|
defaultDashboardV2Spec,
|
|
|
|
defaultFieldConfigSource,
|
|
|
|
PanelKind,
|
|
|
|
PanelQueryKind,
|
|
|
|
TransformationKind,
|
|
|
|
FieldConfigSource,
|
|
|
|
DataTransformerConfig,
|
|
|
|
PanelQuerySpec,
|
|
|
|
DataQueryKind,
|
|
|
|
GridLayoutItemKind,
|
|
|
|
QueryOptionsSpec,
|
|
|
|
QueryVariableKind,
|
|
|
|
TextVariableKind,
|
|
|
|
IntervalVariableKind,
|
|
|
|
DatasourceVariableKind,
|
|
|
|
CustomVariableKind,
|
|
|
|
ConstantVariableKind,
|
|
|
|
GroupByVariableKind,
|
|
|
|
AdhocVariableKind,
|
2024-11-28 18:36:09 +08:00
|
|
|
AnnotationQueryKind,
|
2024-12-09 23:23:23 +08:00
|
|
|
DataLink,
|
2025-01-10 23:09:06 +08:00
|
|
|
RepeatOptions,
|
2025-01-16 20:18:47 +08:00
|
|
|
} from '../../../../../packages/grafana-schema/src/schema/dashboard/v2alpha0';
|
2024-11-28 18:36:09 +08:00
|
|
|
import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet';
|
2024-11-19 20:01:40 +08:00
|
|
|
import { DashboardScene, DashboardSceneState } from '../scene/DashboardScene';
|
|
|
|
import { PanelTimeRange } from '../scene/PanelTimeRange';
|
|
|
|
import { DashboardGridItem } from '../scene/layout-default/DashboardGridItem';
|
|
|
|
import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLayoutManager';
|
|
|
|
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
|
2025-01-10 23:09:06 +08:00
|
|
|
import {
|
|
|
|
getPanelIdForVizPanel,
|
|
|
|
getQueryRunnerFor,
|
|
|
|
getVizPanelKeyForPanelId,
|
|
|
|
isLibraryPanel,
|
|
|
|
calculateGridItemDimensions,
|
|
|
|
} from '../utils/utils';
|
2024-11-19 20:01:40 +08:00
|
|
|
|
|
|
|
import { sceneVariablesSetToSchemaV2Variables } from './sceneVariablesSetToVariables';
|
2024-11-28 17:45:31 +08:00
|
|
|
import { transformCursorSynctoEnum } from './transformToV2TypesUtils';
|
2024-11-19 20:01:40 +08:00
|
|
|
|
|
|
|
// FIXME: This is temporary to avoid creating partial types for all the new schema, it has some performance implications, but it's fine for now
|
|
|
|
type DeepPartial<T> = T extends object
|
|
|
|
? {
|
|
|
|
[P in keyof T]?: DeepPartial<T[P]>;
|
|
|
|
}
|
|
|
|
: T;
|
|
|
|
|
2024-12-13 17:20:22 +08:00
|
|
|
export function transformSceneToSaveModelSchemaV2(scene: DashboardScene, isSnapshot = false): DashboardV2Spec {
|
2024-11-19 20:01:40 +08:00
|
|
|
const oldDash = scene.state;
|
|
|
|
const timeRange = oldDash.$timeRange!.state;
|
|
|
|
|
|
|
|
const controlsState = oldDash.controls?.state;
|
|
|
|
const refreshPicker = controlsState?.refreshPicker;
|
|
|
|
|
|
|
|
const dashboardSchemaV2: DeepPartial<DashboardV2Spec> = {
|
|
|
|
//dashboard settings
|
2025-01-01 02:43:04 +08:00
|
|
|
id: oldDash.id ? oldDash.id : undefined,
|
2024-11-19 20:01:40 +08:00
|
|
|
title: oldDash.title,
|
|
|
|
description: oldDash.description ?? '',
|
|
|
|
cursorSync: getCursorSync(oldDash),
|
|
|
|
liveNow: getLiveNow(oldDash),
|
|
|
|
preload: oldDash.preload,
|
|
|
|
editable: oldDash.editable,
|
2024-11-28 17:45:31 +08:00
|
|
|
links: oldDash.links,
|
2024-11-19 20:01:40 +08:00
|
|
|
tags: oldDash.tags,
|
2024-11-20 20:53:58 +08:00
|
|
|
schemaVersion: DASHBOARD_SCHEMA_VERSION,
|
2024-11-19 20:01:40 +08:00
|
|
|
// EOF dashboard settings
|
|
|
|
|
|
|
|
// time settings
|
|
|
|
timeSettings: {
|
|
|
|
timezone: timeRange.timeZone,
|
|
|
|
from: timeRange.from,
|
|
|
|
to: timeRange.to,
|
|
|
|
autoRefresh: refreshPicker?.state.refresh || '',
|
|
|
|
autoRefreshIntervals: refreshPicker?.state.intervals,
|
|
|
|
quickRanges: [], //FIXME is coming timepicker.time_options,
|
|
|
|
hideTimepicker: controlsState?.hideTimeControls ?? false,
|
|
|
|
weekStart: timeRange.weekStart,
|
|
|
|
fiscalYearStartMonth: timeRange.fiscalYearStartMonth,
|
|
|
|
nowDelay: timeRange.UNSAFE_nowDelay,
|
|
|
|
},
|
|
|
|
// EOF time settings
|
|
|
|
|
|
|
|
// variables
|
|
|
|
variables: getVariables(oldDash),
|
|
|
|
// EOF variables
|
|
|
|
|
|
|
|
// elements
|
|
|
|
elements: getElements(oldDash),
|
|
|
|
// EOF elements
|
|
|
|
|
|
|
|
// annotations
|
2024-11-28 18:36:09 +08:00
|
|
|
annotations: getAnnotations(oldDash),
|
2024-11-19 20:01:40 +08:00
|
|
|
// EOF annotations
|
|
|
|
|
|
|
|
// layout
|
|
|
|
layout: {
|
|
|
|
kind: 'GridLayout',
|
|
|
|
spec: {
|
2025-01-10 23:09:06 +08:00
|
|
|
items: getGridLayoutItems(oldDash, isSnapshot),
|
2024-11-19 20:01:40 +08:00
|
|
|
},
|
|
|
|
},
|
|
|
|
// EOF layout
|
|
|
|
};
|
|
|
|
|
2024-12-19 19:00:59 +08:00
|
|
|
try {
|
|
|
|
validateDashboardSchemaV2(dashboardSchemaV2);
|
|
|
|
return dashboardSchemaV2 as DashboardV2Spec;
|
|
|
|
} catch (reason) {
|
|
|
|
console.error('Error transforming dashboard to schema v2: ' + reason, dashboardSchemaV2);
|
|
|
|
throw new Error('Error transforming dashboard to schema v2: ' + reason);
|
2024-11-19 20:01:40 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function getCursorSync(state: DashboardSceneState) {
|
|
|
|
const cursorSync = state.$behaviors?.find((b): b is behaviors.CursorSync => b instanceof behaviors.CursorSync)?.state
|
|
|
|
.sync;
|
|
|
|
|
|
|
|
return transformCursorSynctoEnum(cursorSync);
|
|
|
|
}
|
|
|
|
|
|
|
|
function getLiveNow(state: DashboardSceneState) {
|
|
|
|
const liveNow =
|
|
|
|
state.$behaviors?.find((b): b is behaviors.LiveNowTimer => b instanceof behaviors.LiveNowTimer)?.isEnabled ||
|
|
|
|
undefined;
|
|
|
|
// hack for validator
|
|
|
|
if (liveNow === undefined) {
|
|
|
|
return Boolean(defaultDashboardV2Spec().liveNow);
|
|
|
|
}
|
|
|
|
return Boolean(liveNow);
|
|
|
|
}
|
|
|
|
|
|
|
|
function getGridLayoutItems(state: DashboardSceneState, isSnapshot?: boolean): GridLayoutItemKind[] {
|
|
|
|
const body = state.body;
|
2025-01-10 23:09:06 +08:00
|
|
|
let elements: GridLayoutItemKind[] = [];
|
2024-11-19 20:01:40 +08:00
|
|
|
if (body instanceof DefaultGridLayoutManager) {
|
|
|
|
for (const child of body.state.grid.state.children) {
|
|
|
|
if (child instanceof DashboardGridItem) {
|
|
|
|
// TODO: handle panel repeater scenario
|
2025-01-10 23:09:06 +08:00
|
|
|
if (child.state.variableName) {
|
|
|
|
elements = elements.concat(repeaterToLayoutItems(child, isSnapshot));
|
|
|
|
} else {
|
|
|
|
elements.push(gridItemToGridLayoutItemKind(child, isSnapshot));
|
|
|
|
}
|
2024-11-19 20:01:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: OLD transformer code
|
|
|
|
// if (child instanceof SceneGridRow) {
|
|
|
|
// // Skip repeat clones or when generating a snapshot
|
|
|
|
// if (child.state.key!.indexOf('-clone-') > 0 && !isSnapshot) {
|
|
|
|
// continue;
|
|
|
|
// }
|
|
|
|
// gridRowToSaveModel(child, panels, isSnapshot);
|
|
|
|
// }
|
|
|
|
}
|
|
|
|
}
|
2025-01-10 23:09:06 +08:00
|
|
|
|
2024-11-19 20:01:40 +08:00
|
|
|
return elements;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function gridItemToGridLayoutItemKind(gridItem: DashboardGridItem, isSnapshot = false): GridLayoutItemKind {
|
|
|
|
let elementGridItem: GridLayoutItemKind | undefined;
|
|
|
|
let x = 0,
|
|
|
|
y = 0,
|
|
|
|
width = 0,
|
|
|
|
height = 0;
|
|
|
|
|
|
|
|
let gridItem_ = gridItem;
|
|
|
|
|
|
|
|
if (!(gridItem_.state.body instanceof VizPanel)) {
|
|
|
|
throw new Error('DashboardGridItem body expected to be VizPanel');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the grid position and size
|
|
|
|
height = (gridItem_.state.variableName ? gridItem_.state.itemHeight : gridItem_.state.height) ?? 0;
|
|
|
|
x = gridItem_.state.x ?? 0;
|
|
|
|
y = gridItem_.state.y ?? 0;
|
|
|
|
width = gridItem_.state.width ?? 0;
|
2025-01-10 23:09:06 +08:00
|
|
|
const repeatVar = gridItem_.state.variableName;
|
2024-11-19 20:01:40 +08:00
|
|
|
|
|
|
|
// FIXME: which name should we use for the element reference, key or something else ?
|
|
|
|
const elementName = gridItem_.state.body.state.key ?? 'DefaultName';
|
|
|
|
elementGridItem = {
|
|
|
|
kind: 'GridLayoutItem',
|
|
|
|
spec: {
|
|
|
|
x,
|
|
|
|
y,
|
|
|
|
width: width,
|
|
|
|
height: height,
|
|
|
|
element: {
|
|
|
|
kind: 'ElementReference',
|
|
|
|
name: elementName,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2025-01-10 23:09:06 +08:00
|
|
|
if (repeatVar) {
|
|
|
|
const repeat: RepeatOptions = {
|
|
|
|
mode: 'variable',
|
|
|
|
value: repeatVar,
|
|
|
|
};
|
|
|
|
|
|
|
|
if (gridItem_.state.maxPerRow) {
|
|
|
|
repeat.maxPerRow = gridItem_.getMaxPerRow();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (gridItem_.state.repeatDirection) {
|
|
|
|
repeat.direction = gridItem_.getRepeatDirection();
|
|
|
|
}
|
|
|
|
|
|
|
|
elementGridItem.spec.repeat = repeat;
|
|
|
|
}
|
|
|
|
|
2024-11-19 20:01:40 +08:00
|
|
|
if (!elementGridItem) {
|
|
|
|
throw new Error('Unsupported grid item type');
|
|
|
|
}
|
|
|
|
|
|
|
|
return elementGridItem;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getElements(state: DashboardSceneState) {
|
|
|
|
const panels = state.body.getVizPanels() ?? [];
|
|
|
|
const panelsArray = panels.reduce((acc: PanelKind[], vizPanel: VizPanel) => {
|
|
|
|
const elementSpec: PanelKind = {
|
|
|
|
kind: 'Panel',
|
|
|
|
spec: {
|
2024-12-17 23:35:55 +08:00
|
|
|
id: getPanelIdForVizPanel(vizPanel),
|
2024-11-19 20:01:40 +08:00
|
|
|
title: vizPanel.state.title,
|
|
|
|
description: vizPanel.state.description ?? '',
|
|
|
|
links: getPanelLinks(vizPanel),
|
|
|
|
data: {
|
|
|
|
kind: 'QueryGroup',
|
|
|
|
spec: {
|
|
|
|
queries: getVizPanelQueries(vizPanel),
|
|
|
|
transformations: getVizPanelTransformations(vizPanel),
|
|
|
|
queryOptions: getVizPanelQueryOptions(vizPanel),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
vizConfig: {
|
|
|
|
kind: vizPanel.state.pluginId,
|
|
|
|
spec: {
|
|
|
|
pluginVersion: vizPanel.state.pluginVersion ?? '',
|
|
|
|
options: vizPanel.state.options,
|
|
|
|
fieldConfig: (vizPanel.state.fieldConfig as FieldConfigSource) ?? defaultFieldConfigSource(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
acc.push(elementSpec);
|
|
|
|
return acc;
|
|
|
|
}, []);
|
|
|
|
// create elements
|
|
|
|
|
|
|
|
const elements = createElements(panelsArray);
|
|
|
|
return elements;
|
|
|
|
}
|
|
|
|
|
2024-12-09 23:23:23 +08:00
|
|
|
function getPanelLinks(panel: VizPanel): DataLink[] {
|
2024-11-19 20:01:40 +08:00
|
|
|
const vizLinks = dashboardSceneGraph.getPanelLinks(panel);
|
|
|
|
if (vizLinks) {
|
2024-12-09 23:23:23 +08:00
|
|
|
return vizLinks.state.rawLinks ?? [];
|
2024-11-19 20:01:40 +08:00
|
|
|
}
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
function getVizPanelQueries(vizPanel: VizPanel): PanelQueryKind[] {
|
|
|
|
const queries: PanelQueryKind[] = [];
|
|
|
|
const queryRunner = getQueryRunnerFor(vizPanel);
|
|
|
|
const vizPanelQueries = queryRunner?.state.queries;
|
|
|
|
const datasource = queryRunner?.state.datasource;
|
|
|
|
|
|
|
|
if (vizPanelQueries) {
|
|
|
|
vizPanelQueries.forEach((query) => {
|
|
|
|
const dataQuery: DataQueryKind = {
|
|
|
|
kind: getDataQueryKind(query),
|
2025-01-01 02:43:04 +08:00
|
|
|
spec: omit(query, 'datasource', 'refId', 'hide'),
|
2024-11-19 20:01:40 +08:00
|
|
|
};
|
|
|
|
const querySpec: PanelQuerySpec = {
|
2024-11-28 18:36:09 +08:00
|
|
|
datasource: datasource ?? getDefaultDataSourceRef(),
|
2024-11-19 20:01:40 +08:00
|
|
|
query: dataQuery,
|
|
|
|
refId: query.refId,
|
2025-01-01 02:43:04 +08:00
|
|
|
hidden: Boolean(query.hide),
|
2024-11-19 20:01:40 +08:00
|
|
|
};
|
|
|
|
queries.push({
|
|
|
|
kind: 'PanelQuery',
|
|
|
|
spec: querySpec,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return queries;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getDataQueryKind(query: SceneDataQuery): string {
|
2024-11-28 18:36:09 +08:00
|
|
|
// If the query has a datasource, use the datasource type, otherwise return empty kind
|
|
|
|
return query.datasource?.type ?? getDefaultDataSourceRef()?.type ?? '';
|
2024-11-19 20:01:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
export function getDataQuerySpec(query: SceneDataQuery): Record<string, any> {
|
|
|
|
const dataQuerySpec = {
|
|
|
|
kind: getDataQueryKind(query),
|
|
|
|
spec: query,
|
|
|
|
};
|
|
|
|
return dataQuerySpec;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getVizPanelTransformations(vizPanel: VizPanel): TransformationKind[] {
|
|
|
|
let transformations: TransformationKind[] = [];
|
|
|
|
const dataProvider = vizPanel.state.$data;
|
|
|
|
if (dataProvider instanceof SceneDataTransformer) {
|
|
|
|
const transformationList = dataProvider.state.transformations;
|
|
|
|
if (transformationList.length === 0) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
transformationList.forEach((transformationItem) => {
|
|
|
|
const transformation = transformationItem as DataTransformerConfig;
|
|
|
|
const transformationSpec: DataTransformerConfig = {
|
|
|
|
id: transformation.id,
|
|
|
|
disabled: transformation.disabled,
|
|
|
|
filter: {
|
|
|
|
id: transformation.filter?.id ?? '',
|
|
|
|
options: transformation.filter?.options ?? {},
|
|
|
|
},
|
|
|
|
options: transformation.options,
|
|
|
|
};
|
|
|
|
|
2025-01-01 02:43:04 +08:00
|
|
|
if (transformation.topic !== undefined) {
|
|
|
|
transformationSpec.topic = transformation.topic;
|
|
|
|
}
|
|
|
|
|
2024-11-19 20:01:40 +08:00
|
|
|
transformations.push({
|
|
|
|
kind: transformation.id,
|
|
|
|
spec: transformationSpec,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return transformations;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getVizPanelQueryOptions(vizPanel: VizPanel): QueryOptionsSpec {
|
|
|
|
let queryOptions: QueryOptionsSpec = {};
|
|
|
|
const queryRunner = getQueryRunnerFor(vizPanel);
|
|
|
|
|
|
|
|
if (queryRunner) {
|
|
|
|
queryOptions.maxDataPoints = queryRunner.state.maxDataPoints;
|
|
|
|
|
|
|
|
if (queryRunner.state.cacheTimeout) {
|
|
|
|
queryOptions.cacheTimeout = queryRunner.state.cacheTimeout;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (queryRunner.state.queryCachingTTL) {
|
|
|
|
queryOptions.queryCachingTTL = queryRunner.state.queryCachingTTL;
|
|
|
|
}
|
|
|
|
if (queryRunner.state.minInterval) {
|
|
|
|
queryOptions.interval = queryRunner.state.minInterval;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const panelTime = vizPanel.state.$timeRange;
|
|
|
|
|
|
|
|
if (panelTime instanceof PanelTimeRange) {
|
|
|
|
queryOptions.timeFrom = panelTime.state.timeFrom;
|
|
|
|
queryOptions.timeShift = panelTime.state.timeShift;
|
2025-01-01 02:43:04 +08:00
|
|
|
queryOptions.hideTimeOverride = panelTime.state.hideTimeOverride;
|
2024-11-19 20:01:40 +08:00
|
|
|
}
|
|
|
|
return queryOptions;
|
|
|
|
}
|
|
|
|
|
|
|
|
function createElements(panels: PanelKind[]): Record<string, PanelKind> {
|
|
|
|
return panels.reduce(
|
|
|
|
(acc, panel) => {
|
2024-12-17 23:35:55 +08:00
|
|
|
const key = getVizPanelKeyForPanelId(panel.spec.id);
|
2024-11-19 20:01:40 +08:00
|
|
|
acc[key] = panel;
|
|
|
|
return acc;
|
|
|
|
},
|
|
|
|
{} as Record<string, PanelKind>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2025-01-10 23:09:06 +08:00
|
|
|
function repeaterToLayoutItems(repeater: DashboardGridItem, isSnapshot = false): GridLayoutItemKind[] {
|
|
|
|
if (!isSnapshot) {
|
|
|
|
return [gridItemToGridLayoutItemKind(repeater)];
|
|
|
|
} else {
|
|
|
|
if (repeater.state.body instanceof VizPanel && isLibraryPanel(repeater.state.body)) {
|
|
|
|
// TODO: implement
|
|
|
|
// const { x = 0, y = 0, width: w = 0, height: h = 0 } = repeater.state;
|
|
|
|
// return [vizPanelToPanel(repeater.state.body, { x, y, w, h }, isSnapshot)];
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (repeater.state.repeatedPanels) {
|
|
|
|
const { h, w, columnCount } = calculateGridItemDimensions(repeater);
|
|
|
|
const panels = repeater.state.repeatedPanels!.map((panel, index) => {
|
|
|
|
let x = 0,
|
|
|
|
y = 0;
|
|
|
|
if (repeater.state.repeatDirection === 'v') {
|
|
|
|
x = repeater.state.x!;
|
|
|
|
y = index * h;
|
|
|
|
} else {
|
|
|
|
x = (index % columnCount) * w;
|
|
|
|
y = repeater.state.y! + Math.floor(index / columnCount) * h;
|
|
|
|
}
|
|
|
|
|
|
|
|
const gridPos = { x, y, w, h };
|
|
|
|
|
|
|
|
const result: GridLayoutItemKind = {
|
|
|
|
kind: 'GridLayoutItem',
|
|
|
|
spec: {
|
|
|
|
x: gridPos.x,
|
|
|
|
y: gridPos.y,
|
|
|
|
width: gridPos.w,
|
|
|
|
height: gridPos.h,
|
|
|
|
repeat: {
|
|
|
|
mode: 'variable',
|
|
|
|
value: repeater.state.variableName!,
|
|
|
|
maxPerRow: repeater.getMaxPerRow(),
|
|
|
|
direction: repeater.state.repeatDirection,
|
|
|
|
},
|
|
|
|
element: {
|
|
|
|
kind: 'ElementReference',
|
|
|
|
name: panel.state.key!,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
return result;
|
|
|
|
});
|
|
|
|
|
|
|
|
return panels;
|
|
|
|
}
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-19 20:01:40 +08:00
|
|
|
function getVariables(oldDash: DashboardSceneState) {
|
|
|
|
const variablesSet = oldDash.$variables;
|
|
|
|
|
|
|
|
// variables is an array of all variables kind (union)
|
|
|
|
let variables: Array<
|
|
|
|
| QueryVariableKind
|
|
|
|
| TextVariableKind
|
|
|
|
| IntervalVariableKind
|
|
|
|
| DatasourceVariableKind
|
|
|
|
| CustomVariableKind
|
|
|
|
| ConstantVariableKind
|
|
|
|
| GroupByVariableKind
|
|
|
|
| AdhocVariableKind
|
|
|
|
> = [];
|
|
|
|
|
|
|
|
if (variablesSet instanceof SceneVariableSet) {
|
|
|
|
variables = sceneVariablesSetToSchemaV2Variables(variablesSet);
|
|
|
|
}
|
|
|
|
|
|
|
|
return variables;
|
|
|
|
}
|
|
|
|
|
2024-11-28 18:36:09 +08:00
|
|
|
function getAnnotations(state: DashboardSceneState): AnnotationQueryKind[] {
|
|
|
|
const data = state.$data;
|
|
|
|
if (!(data instanceof DashboardDataLayerSet)) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
const annotations: AnnotationQueryKind[] = [];
|
|
|
|
for (const layer of data.state.annotationLayers) {
|
|
|
|
if (!(layer instanceof dataLayers.AnnotationsDataLayer)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const result: AnnotationQueryKind = {
|
|
|
|
kind: 'AnnotationQuery',
|
|
|
|
spec: {
|
2025-01-01 02:43:04 +08:00
|
|
|
builtIn: Boolean(layer.state.query.builtIn),
|
2024-11-28 18:36:09 +08:00
|
|
|
name: layer.state.query.name,
|
|
|
|
datasource: layer.state.query.datasource || getDefaultDataSourceRef(),
|
|
|
|
enable: Boolean(layer.state.isEnabled),
|
|
|
|
hide: Boolean(layer.state.isHidden),
|
|
|
|
iconColor: layer.state.query.iconColor,
|
|
|
|
},
|
|
|
|
};
|
2025-01-01 02:43:04 +08:00
|
|
|
|
|
|
|
// Check if DataQueryKind exists
|
|
|
|
const queryKind = getAnnotationQueryKind(layer.state.query);
|
|
|
|
if (layer.state.query.query?.kind === queryKind) {
|
|
|
|
result.spec.query = {
|
|
|
|
kind: queryKind,
|
|
|
|
spec: layer.state.query.query.spec,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2025-01-08 01:54:56 +08:00
|
|
|
// If filter is an empty array, don't save it
|
|
|
|
if (layer.state.query.filter?.ids?.length) {
|
|
|
|
result.spec.filter = layer.state.query.filter;
|
|
|
|
}
|
|
|
|
|
2024-11-28 18:36:09 +08:00
|
|
|
annotations.push(result);
|
|
|
|
}
|
|
|
|
return annotations;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getAnnotationQueryKind(annotationQuery: AnnotationQuery): string {
|
|
|
|
if (annotationQuery.datasource?.type) {
|
|
|
|
return annotationQuery.datasource.type;
|
|
|
|
} else {
|
|
|
|
const ds = getDefaultDataSourceRef();
|
|
|
|
if (ds) {
|
|
|
|
return ds.type!; // in the datasource list from bootData "id" is the type
|
|
|
|
}
|
|
|
|
// if we can't find the default datasource, return grafana as default
|
|
|
|
return 'grafana';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-01-09 17:51:53 +08:00
|
|
|
export function getDefaultDataSourceRef(): DataSourceRef | undefined {
|
2024-11-28 18:36:09 +08:00
|
|
|
// we need to return the default datasource configured in the BootConfig
|
|
|
|
const defaultDatasource = config.bootData.settings.defaultDatasource;
|
|
|
|
|
|
|
|
// get default datasource type
|
2024-12-19 19:00:59 +08:00
|
|
|
const dsList = config.bootData.settings.datasources ?? {};
|
2024-11-28 18:36:09 +08:00
|
|
|
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 undefined;
|
|
|
|
}
|
|
|
|
|
2024-11-19 20:01:40 +08:00
|
|
|
// Function to know if the dashboard transformed is a valid DashboardV2Spec
|
2024-12-19 19:00:59 +08:00
|
|
|
function validateDashboardSchemaV2(dash: any): dash is DashboardV2Spec {
|
2024-11-19 20:01:40 +08:00
|
|
|
if (typeof dash !== 'object' || dash === null) {
|
2024-12-19 19:00:59 +08:00
|
|
|
throw new Error('Dashboard is not an object or is null');
|
2024-11-19 20:01:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof dash.title !== 'string') {
|
2024-12-19 19:00:59 +08:00
|
|
|
throw new Error('Title is not a string');
|
2024-11-19 20:01:40 +08:00
|
|
|
}
|
|
|
|
if (typeof dash.description !== 'string') {
|
2024-12-19 19:00:59 +08:00
|
|
|
throw new Error('Description is not a string');
|
2024-11-19 20:01:40 +08:00
|
|
|
}
|
|
|
|
if (typeof dash.cursorSync !== 'string') {
|
2024-12-19 19:00:59 +08:00
|
|
|
throw new Error('CursorSync is not a string');
|
2024-11-19 20:01:40 +08:00
|
|
|
}
|
|
|
|
if (typeof dash.liveNow !== 'boolean') {
|
2024-12-19 19:00:59 +08:00
|
|
|
throw new Error('LiveNow is not a boolean');
|
2024-11-19 20:01:40 +08:00
|
|
|
}
|
|
|
|
if (typeof dash.preload !== 'boolean') {
|
2024-12-19 19:00:59 +08:00
|
|
|
throw new Error('Preload is not a boolean');
|
2024-11-19 20:01:40 +08:00
|
|
|
}
|
|
|
|
if (typeof dash.editable !== 'boolean') {
|
2024-12-19 19:00:59 +08:00
|
|
|
throw new Error('Editable is not a boolean');
|
2024-11-19 20:01:40 +08:00
|
|
|
}
|
|
|
|
if (!Array.isArray(dash.links)) {
|
2024-12-19 19:00:59 +08:00
|
|
|
throw new Error('Links is not an array');
|
2024-11-19 20:01:40 +08:00
|
|
|
}
|
|
|
|
if (!Array.isArray(dash.tags)) {
|
2024-12-19 19:00:59 +08:00
|
|
|
throw new Error('Tags is not an array');
|
2024-11-19 20:01:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (dash.id !== undefined && typeof dash.id !== 'number') {
|
2024-12-19 19:00:59 +08:00
|
|
|
throw new Error('ID is not a number');
|
2024-11-19 20:01:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Time settings
|
|
|
|
if (typeof dash.timeSettings !== 'object' || dash.timeSettings === null) {
|
2024-12-19 19:00:59 +08:00
|
|
|
throw new Error('TimeSettings is not an object or is null');
|
2024-11-19 20:01:40 +08:00
|
|
|
}
|
|
|
|
if (typeof dash.timeSettings.timezone !== 'string') {
|
2024-12-19 19:00:59 +08:00
|
|
|
throw new Error('Timezone is not a string');
|
2024-11-19 20:01:40 +08:00
|
|
|
}
|
|
|
|
if (typeof dash.timeSettings.from !== 'string') {
|
2024-12-19 19:00:59 +08:00
|
|
|
throw new Error('From is not a string');
|
2024-11-19 20:01:40 +08:00
|
|
|
}
|
|
|
|
if (typeof dash.timeSettings.to !== 'string') {
|
2024-12-19 19:00:59 +08:00
|
|
|
throw new Error('To is not a string');
|
2024-11-19 20:01:40 +08:00
|
|
|
}
|
|
|
|
if (typeof dash.timeSettings.autoRefresh !== 'string') {
|
2024-12-19 19:00:59 +08:00
|
|
|
throw new Error('AutoRefresh is not a string');
|
2024-11-19 20:01:40 +08:00
|
|
|
}
|
|
|
|
if (!Array.isArray(dash.timeSettings.autoRefreshIntervals)) {
|
2024-12-19 19:00:59 +08:00
|
|
|
throw new Error('AutoRefreshIntervals is not an array');
|
2024-11-19 20:01:40 +08:00
|
|
|
}
|
|
|
|
if (!Array.isArray(dash.timeSettings.quickRanges)) {
|
2024-12-19 19:00:59 +08:00
|
|
|
throw new Error('QuickRanges is not an array');
|
2024-11-19 20:01:40 +08:00
|
|
|
}
|
|
|
|
if (typeof dash.timeSettings.hideTimepicker !== 'boolean') {
|
2024-12-19 19:00:59 +08:00
|
|
|
throw new Error('HideTimepicker is not a boolean');
|
2024-11-19 20:01:40 +08:00
|
|
|
}
|
|
|
|
if (typeof dash.timeSettings.weekStart !== 'string') {
|
2024-12-19 19:00:59 +08:00
|
|
|
throw new Error('WeekStart is not a string');
|
2024-11-19 20:01:40 +08:00
|
|
|
}
|
|
|
|
if (typeof dash.timeSettings.fiscalYearStartMonth !== 'number') {
|
2024-12-19 19:00:59 +08:00
|
|
|
throw new Error('FiscalYearStartMonth is not a number');
|
2024-11-19 20:01:40 +08:00
|
|
|
}
|
|
|
|
if (dash.timeSettings.nowDelay !== undefined && typeof dash.timeSettings.nowDelay !== 'string') {
|
2024-12-19 19:00:59 +08:00
|
|
|
throw new Error('NowDelay is not a string');
|
2024-11-19 20:01:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Other sections
|
|
|
|
if (!Array.isArray(dash.variables)) {
|
2024-12-19 19:00:59 +08:00
|
|
|
throw new Error('Variables is not an array');
|
2024-11-19 20:01:40 +08:00
|
|
|
}
|
|
|
|
if (typeof dash.elements !== 'object' || dash.elements === null) {
|
2024-12-19 19:00:59 +08:00
|
|
|
throw new Error('Elements is not an object or is null');
|
2024-11-19 20:01:40 +08:00
|
|
|
}
|
|
|
|
if (!Array.isArray(dash.annotations)) {
|
2024-12-19 19:00:59 +08:00
|
|
|
throw new Error('Annotations is not an array');
|
2024-11-19 20:01:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Layout
|
|
|
|
if (typeof dash.layout !== 'object' || dash.layout === null) {
|
2024-12-19 19:00:59 +08:00
|
|
|
throw new Error('Layout is not an object or is null');
|
2024-11-19 20:01:40 +08:00
|
|
|
}
|
|
|
|
if (dash.layout.kind !== 'GridLayout') {
|
2024-12-19 19:00:59 +08:00
|
|
|
throw new Error('Layout kind is not GridLayout');
|
2024-11-19 20:01:40 +08:00
|
|
|
}
|
|
|
|
if (typeof dash.layout.spec !== 'object' || dash.layout.spec === null) {
|
2024-12-19 19:00:59 +08:00
|
|
|
throw new Error('Layout spec is not an object or is null');
|
2024-11-19 20:01:40 +08:00
|
|
|
}
|
|
|
|
if (!Array.isArray(dash.layout.spec.items)) {
|
2024-12-19 19:00:59 +08:00
|
|
|
throw new Error('Layout spec items is not an array');
|
2024-11-19 20:01:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|