mirror of
https://github.com/grafana/grafana.git
synced 2025-02-13 00:55:47 -06:00
304 lines
8.8 KiB
TypeScript
304 lines
8.8 KiB
TypeScript
import {
|
|
ConstantVariableModel,
|
|
CustomVariableModel,
|
|
DataSourceVariableModel,
|
|
QueryVariableModel,
|
|
VariableModel,
|
|
} from '@grafana/data';
|
|
import {
|
|
VizPanel,
|
|
SceneTimePicker,
|
|
SceneGridLayout,
|
|
SceneGridRow,
|
|
SceneTimeRange,
|
|
SceneObject,
|
|
SceneQueryRunner,
|
|
SceneVariableSet,
|
|
VariableValueSelectors,
|
|
SceneVariable,
|
|
CustomVariable,
|
|
DataSourceVariable,
|
|
QueryVariable,
|
|
ConstantVariable,
|
|
SceneDataTransformer,
|
|
} from '@grafana/scenes';
|
|
import { StateManagerBase } from 'app/core/services/StateManagerBase';
|
|
import { dashboardLoaderSrv } from 'app/features/dashboard/services/DashboardLoaderSrv';
|
|
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
|
import { DashboardDTO } from 'app/types';
|
|
|
|
import { DashboardScene } from './DashboardScene';
|
|
|
|
export interface DashboardLoaderState {
|
|
dashboard?: DashboardScene;
|
|
isLoading?: boolean;
|
|
loadError?: string;
|
|
}
|
|
|
|
export class DashboardLoader extends StateManagerBase<DashboardLoaderState> {
|
|
private cache: Record<string, DashboardScene> = {};
|
|
|
|
public async load(uid: string) {
|
|
const fromCache = this.cache[uid];
|
|
if (fromCache) {
|
|
this.setState({ dashboard: fromCache });
|
|
return;
|
|
}
|
|
|
|
this.setState({ isLoading: true });
|
|
|
|
try {
|
|
const rsp = await dashboardLoaderSrv.loadDashboard('db', '', uid);
|
|
|
|
if (rsp.dashboard) {
|
|
this.initDashboard(rsp);
|
|
} else {
|
|
throw new Error('Dashboard not found');
|
|
}
|
|
} catch (err) {
|
|
this.setState({ isLoading: false, loadError: String(err) });
|
|
}
|
|
}
|
|
|
|
private initDashboard(rsp: DashboardDTO) {
|
|
// Just to have migrations run
|
|
const oldModel = new DashboardModel(rsp.dashboard, rsp.meta, {
|
|
autoMigrateOldPanels: true,
|
|
});
|
|
|
|
const dashboard = createDashboardSceneFromDashboardModel(oldModel);
|
|
|
|
// We initialize URL sync here as it better to do that before mounting and doing any rendering.
|
|
// But would be nice to have a conditional around this so you can pre-load dashboards without url sync.
|
|
dashboard.initUrlSync();
|
|
|
|
this.cache[rsp.dashboard.uid] = dashboard;
|
|
this.setState({ dashboard, isLoading: false });
|
|
}
|
|
|
|
public clearState() {
|
|
this.setState({ dashboard: undefined, loadError: undefined, isLoading: false });
|
|
}
|
|
}
|
|
|
|
export function createSceneObjectsForPanels(oldPanels: PanelModel[]): SceneObject[] {
|
|
// collects all panels and rows
|
|
const panels: SceneObject[] = [];
|
|
|
|
// indicates expanded row that's currently processed
|
|
let currentRow: PanelModel | null = null;
|
|
// collects panels in the currently processed, expanded row
|
|
let currentRowPanels: SceneObject[] = [];
|
|
|
|
for (const panel of oldPanels) {
|
|
if (panel.type === 'row') {
|
|
if (!currentRow) {
|
|
if (Boolean(panel.collapsed)) {
|
|
// collapsed rows contain their panels within the row model
|
|
panels.push(
|
|
new SceneGridRow({
|
|
title: panel.title,
|
|
isCollapsed: true,
|
|
placement: {
|
|
y: panel.gridPos.y,
|
|
},
|
|
children: panel.panels ? panel.panels.map(createVizPanelFromPanelModel) : [],
|
|
})
|
|
);
|
|
} else {
|
|
// indicate new row to be processed
|
|
currentRow = panel;
|
|
}
|
|
} else {
|
|
// when a row has been processed, and we hit a next one for processing
|
|
if (currentRow.id !== panel.id) {
|
|
// commit previous row panels
|
|
panels.push(
|
|
new SceneGridRow({
|
|
title: currentRow!.title,
|
|
placement: {
|
|
y: currentRow.gridPos.y,
|
|
},
|
|
children: currentRowPanels,
|
|
})
|
|
);
|
|
|
|
currentRow = panel;
|
|
currentRowPanels = [];
|
|
}
|
|
}
|
|
} else {
|
|
const panelObject = createVizPanelFromPanelModel(panel);
|
|
|
|
// when processing an expanded row, collect its panels
|
|
if (currentRow) {
|
|
currentRowPanels.push(panelObject);
|
|
} else {
|
|
panels.push(panelObject);
|
|
}
|
|
}
|
|
}
|
|
|
|
// commit a row if it's the last one
|
|
if (currentRow) {
|
|
panels.push(
|
|
new SceneGridRow({
|
|
title: currentRow!.title,
|
|
placement: {
|
|
y: currentRow.gridPos.y,
|
|
},
|
|
children: currentRowPanels,
|
|
})
|
|
);
|
|
}
|
|
|
|
return panels;
|
|
}
|
|
|
|
export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel) {
|
|
let variables: SceneVariableSet | undefined = undefined;
|
|
|
|
if (oldModel.templating?.list?.length) {
|
|
const variableObjects = oldModel.templating.list
|
|
.map((v) => {
|
|
try {
|
|
return createSceneVariableFromVariableModel(v);
|
|
} catch (err) {
|
|
console.error(err);
|
|
return null;
|
|
}
|
|
})
|
|
// TODO: Remove filter
|
|
// Added temporarily to allow skipping non-compatible variables
|
|
.filter((v): v is SceneVariable => Boolean(v));
|
|
|
|
variables = new SceneVariableSet({
|
|
variables: variableObjects,
|
|
});
|
|
}
|
|
|
|
return new DashboardScene({
|
|
title: oldModel.title,
|
|
uid: oldModel.uid,
|
|
body: new SceneGridLayout({
|
|
children: createSceneObjectsForPanels(oldModel.panels),
|
|
}),
|
|
$timeRange: new SceneTimeRange(oldModel.time),
|
|
actions: [new SceneTimePicker({})],
|
|
$variables: variables,
|
|
...(variables && {
|
|
controls: [new VariableValueSelectors({})],
|
|
}),
|
|
});
|
|
}
|
|
|
|
export function createSceneVariableFromVariableModel(variable: VariableModel): SceneVariable {
|
|
const commonProperties = {
|
|
name: variable.name,
|
|
label: variable.label,
|
|
};
|
|
if (isCustomVariable(variable)) {
|
|
return new CustomVariable({
|
|
...commonProperties,
|
|
value: variable.current.value,
|
|
text: variable.current.text,
|
|
description: variable.description,
|
|
query: variable.query,
|
|
isMulti: variable.multi,
|
|
allValue: variable.allValue || undefined,
|
|
includeAll: variable.includeAll,
|
|
defaultToAll: Boolean(variable.includeAll),
|
|
skipUrlSync: variable.skipUrlSync,
|
|
hide: variable.hide,
|
|
});
|
|
} else if (isQueryVariable(variable)) {
|
|
return new QueryVariable({
|
|
...commonProperties,
|
|
value: variable.current.value,
|
|
text: variable.current.text,
|
|
description: variable.description,
|
|
query: variable.query,
|
|
datasource: variable.datasource,
|
|
sort: variable.sort,
|
|
refresh: variable.refresh,
|
|
regex: variable.regex,
|
|
allValue: variable.allValue || undefined,
|
|
includeAll: variable.includeAll,
|
|
defaultToAll: Boolean(variable.includeAll),
|
|
isMulti: variable.multi,
|
|
skipUrlSync: variable.skipUrlSync,
|
|
hide: variable.hide,
|
|
});
|
|
} else if (isDataSourceVariable(variable)) {
|
|
return new DataSourceVariable({
|
|
...commonProperties,
|
|
value: variable.current.value,
|
|
text: variable.current.text,
|
|
description: variable.description,
|
|
regex: variable.regex,
|
|
pluginId: variable.query,
|
|
allValue: variable.allValue || undefined,
|
|
includeAll: variable.includeAll,
|
|
defaultToAll: Boolean(variable.includeAll),
|
|
skipUrlSync: variable.skipUrlSync,
|
|
isMulti: variable.multi,
|
|
hide: variable.hide,
|
|
});
|
|
} else if (isConstantVariable(variable)) {
|
|
return new ConstantVariable({
|
|
...commonProperties,
|
|
description: variable.description,
|
|
value: variable.query,
|
|
skipUrlSync: variable.skipUrlSync,
|
|
hide: variable.hide,
|
|
});
|
|
} else {
|
|
throw new Error(`Scenes: Unsupported variable type ${variable.type}`);
|
|
}
|
|
}
|
|
|
|
export function createVizPanelFromPanelModel(panel: PanelModel) {
|
|
const queryRunner = new SceneQueryRunner({
|
|
queries: panel.targets,
|
|
maxDataPoints: panel.maxDataPoints ?? undefined,
|
|
});
|
|
|
|
return new VizPanel({
|
|
title: panel.title,
|
|
pluginId: panel.type,
|
|
placement: {
|
|
x: panel.gridPos.x,
|
|
y: panel.gridPos.y,
|
|
width: panel.gridPos.w,
|
|
height: panel.gridPos.h,
|
|
},
|
|
options: panel.options ?? {},
|
|
fieldConfig: panel.fieldConfig,
|
|
pluginVersion: panel.pluginVersion,
|
|
displayMode: panel.transparent ? 'transparent' : undefined,
|
|
// To be replaced with it's own option persited option instead derived
|
|
hoverHeader: !panel.title && !panel.timeFrom && !panel.timeShift,
|
|
$data: panel.transformations?.length
|
|
? new SceneDataTransformer({
|
|
$data: queryRunner,
|
|
transformations: panel.transformations,
|
|
})
|
|
: queryRunner,
|
|
});
|
|
}
|
|
|
|
let loader: DashboardLoader | null = null;
|
|
|
|
export function getDashboardLoader(): DashboardLoader {
|
|
if (!loader) {
|
|
loader = new DashboardLoader({});
|
|
}
|
|
|
|
return loader;
|
|
}
|
|
|
|
const isCustomVariable = (v: VariableModel): v is CustomVariableModel => v.type === 'custom';
|
|
const isQueryVariable = (v: VariableModel): v is QueryVariableModel => v.type === 'query';
|
|
const isDataSourceVariable = (v: VariableModel): v is DataSourceVariableModel => v.type === 'datasource';
|
|
const isConstantVariable = (v: VariableModel): v is ConstantVariableModel => v.type === 'constant';
|