Skip to content

Commit f7ddd0a

Browse files
committed
DashboardExporter: Add original files from the Grafana code base
Will be removed later.
1 parent 8ce3c5b commit f7ddd0a

File tree

2 files changed

+733
-0
lines changed

2 files changed

+733
-0
lines changed
Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
// https://github.com/grafana/grafana/blob/v8.3.6/public/app/features/dashboard/components/DashExportModal/DashboardExporter.ts
2+
// License: AGPL-3.0
3+
4+
import { defaults, each, sortBy } from 'lodash';
5+
6+
import config from 'app/core/config';
7+
import { DashboardModel } from '../../state/DashboardModel';
8+
import { PanelModel } from 'app/features/dashboard/state';
9+
import { PanelPluginMeta } from '@grafana/data';
10+
import { getDataSourceSrv } from '@grafana/runtime';
11+
import { VariableOption, VariableRefresh } from '../../../variables/types';
12+
import { isConstant, isQuery } from '../../../variables/guard';
13+
import { LibraryElementKind } from '../../../library-panels/types';
14+
import { isPanelModelLibraryPanel } from '../../../library-panels/guard';
15+
16+
interface Input {
17+
name: string;
18+
type: string;
19+
label: string;
20+
value: any;
21+
description: string;
22+
}
23+
24+
interface Requires {
25+
[key: string]: {
26+
type: string;
27+
id: string;
28+
name: string;
29+
version: string;
30+
};
31+
}
32+
33+
interface DataSources {
34+
[key: string]: {
35+
name: string;
36+
label: string;
37+
description: string;
38+
type: string;
39+
pluginId: string;
40+
pluginName: string;
41+
};
42+
}
43+
44+
export interface LibraryElementExport {
45+
name: string;
46+
uid: string;
47+
model: any;
48+
kind: LibraryElementKind;
49+
}
50+
51+
export class DashboardExporter {
52+
makeExportable(dashboard: DashboardModel) {
53+
// clean up repeated rows and panels,
54+
// this is done on the live real dashboard instance, not on a clone
55+
// so we need to undo this
56+
// this is pretty hacky and needs to be changed
57+
dashboard.cleanUpRepeats();
58+
59+
const saveModel = dashboard.getSaveModelClone();
60+
saveModel.id = null;
61+
62+
// undo repeat cleanup
63+
dashboard.processRepeats();
64+
65+
const inputs: Input[] = [];
66+
const requires: Requires = {};
67+
const datasources: DataSources = {};
68+
const promises: Array<Promise<void>> = [];
69+
const variableLookup: { [key: string]: any } = {};
70+
const libraryPanels: Map<string, LibraryElementExport> = new Map<string, LibraryElementExport>();
71+
72+
for (const variable of saveModel.getVariables()) {
73+
variableLookup[variable.name] = variable;
74+
}
75+
76+
const templateizeDatasourceUsage = (obj: any) => {
77+
let datasource: string = obj.datasource;
78+
let datasourceVariable: any = null;
79+
80+
// ignore data source properties that contain a variable
81+
if (datasource && (datasource as any).uid) {
82+
const uid = (datasource as any).uid as string;
83+
if (uid.indexOf('$') === 0) {
84+
datasourceVariable = variableLookup[uid.substring(1)];
85+
if (datasourceVariable && datasourceVariable.current) {
86+
datasource = datasourceVariable.current.value;
87+
}
88+
}
89+
}
90+
91+
promises.push(
92+
getDataSourceSrv()
93+
.get(datasource)
94+
.then((ds) => {
95+
if (ds.meta?.builtIn) {
96+
return;
97+
}
98+
99+
// add data source type to require list
100+
requires['datasource' + ds.meta?.id] = {
101+
type: 'datasource',
102+
id: ds.meta.id,
103+
name: ds.meta.name,
104+
version: ds.meta.info.version || '1.0.0',
105+
};
106+
107+
// if used via variable we can skip templatizing usage
108+
if (datasourceVariable) {
109+
return;
110+
}
111+
112+
const refName = 'DS_' + ds.name.replace(' ', '_').toUpperCase();
113+
datasources[refName] = {
114+
name: refName,
115+
label: ds.name,
116+
description: '',
117+
type: 'datasource',
118+
pluginId: ds.meta?.id,
119+
pluginName: ds.meta?.name,
120+
};
121+
122+
if (!obj.datasource || typeof obj.datasource === 'string') {
123+
obj.datasource = '${' + refName + '}';
124+
} else {
125+
obj.datasource.uid = '${' + refName + '}';
126+
}
127+
})
128+
);
129+
};
130+
131+
const processPanel = (panel: PanelModel) => {
132+
if (panel.datasource !== undefined && panel.datasource !== null) {
133+
templateizeDatasourceUsage(panel);
134+
}
135+
136+
if (panel.targets) {
137+
for (const target of panel.targets) {
138+
if (target.datasource !== undefined) {
139+
templateizeDatasourceUsage(target);
140+
}
141+
}
142+
}
143+
144+
const panelDef: PanelPluginMeta = config.panels[panel.type];
145+
if (panelDef) {
146+
requires['panel' + panelDef.id] = {
147+
type: 'panel',
148+
id: panelDef.id,
149+
name: panelDef.name,
150+
version: panelDef.info.version,
151+
};
152+
}
153+
};
154+
155+
const processLibraryPanels = (panel: any) => {
156+
if (isPanelModelLibraryPanel(panel)) {
157+
const { libraryPanel, ...model } = panel;
158+
const { name, uid } = libraryPanel;
159+
if (!libraryPanels.has(uid)) {
160+
libraryPanels.set(uid, { name, uid, kind: LibraryElementKind.Panel, model });
161+
}
162+
}
163+
};
164+
165+
// check up panel data sources
166+
for (const panel of saveModel.panels) {
167+
processPanel(panel);
168+
169+
// handle collapsed rows
170+
if (panel.collapsed !== undefined && panel.collapsed === true && panel.panels) {
171+
for (const rowPanel of panel.panels) {
172+
processPanel(rowPanel);
173+
}
174+
}
175+
}
176+
177+
// templatize template vars
178+
for (const variable of saveModel.getVariables()) {
179+
if (isQuery(variable)) {
180+
templateizeDatasourceUsage(variable);
181+
variable.options = [];
182+
variable.current = {} as unknown as VariableOption;
183+
variable.refresh =
184+
variable.refresh !== VariableRefresh.never ? variable.refresh : VariableRefresh.onDashboardLoad;
185+
}
186+
}
187+
188+
// templatize annotations vars
189+
for (const annotationDef of saveModel.annotations.list) {
190+
templateizeDatasourceUsage(annotationDef);
191+
}
192+
193+
// add grafana version
194+
requires['grafana'] = {
195+
type: 'grafana',
196+
id: 'grafana',
197+
name: 'Grafana',
198+
version: config.buildInfo.version,
199+
};
200+
201+
return Promise.all(promises)
202+
.then(() => {
203+
each(datasources, (value: any) => {
204+
inputs.push(value);
205+
});
206+
207+
// we need to process all panels again after all the promises are resolved
208+
// so all data sources, variables and targets have been templateized when we process library panels
209+
for (const panel of saveModel.panels) {
210+
processLibraryPanels(panel);
211+
if (panel.collapsed !== undefined && panel.collapsed === true && panel.panels) {
212+
for (const rowPanel of panel.panels) {
213+
processLibraryPanels(rowPanel);
214+
}
215+
}
216+
}
217+
218+
// templatize constants
219+
for (const variable of saveModel.getVariables()) {
220+
if (isConstant(variable)) {
221+
const refName = 'VAR_' + variable.name.replace(' ', '_').toUpperCase();
222+
inputs.push({
223+
name: refName,
224+
type: 'constant',
225+
label: variable.label || variable.name,
226+
value: variable.query,
227+
description: '',
228+
});
229+
// update current and option
230+
variable.query = '${' + refName + '}';
231+
variable.current = {
232+
value: variable.query,
233+
text: variable.query,
234+
selected: false,
235+
};
236+
variable.options = [variable.current];
237+
}
238+
}
239+
240+
// make inputs and requires a top thing
241+
const newObj: { [key: string]: {} } = {};
242+
newObj['__inputs'] = inputs;
243+
newObj['__elements'] = [...libraryPanels.values()];
244+
newObj['__requires'] = sortBy(requires, ['id']);
245+
246+
defaults(newObj, saveModel);
247+
return newObj;
248+
})
249+
.catch((err) => {
250+
console.error('Export failed:', err);
251+
return {
252+
error: err,
253+
};
254+
});
255+
}
256+
}

0 commit comments

Comments
 (0)