DashboardExporter.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. // @ts-ignore
  2. import _ from 'lodash';
  3. import config from 'app/core/config';
  4. import { DashboardModel } from '../../state/DashboardModel';
  5. import DatasourceSrv from 'app/features/plugins/datasource_srv';
  6. import { PanelModel } from 'app/features/dashboard/state';
  7. import { PanelPlugin } from 'app/types/plugins';
  8. interface Input {
  9. name: string;
  10. type: string;
  11. label: string;
  12. value: any;
  13. description: string;
  14. }
  15. interface Requires {
  16. [key: string]: {
  17. type: string;
  18. id: string;
  19. name: string;
  20. version: string;
  21. };
  22. }
  23. interface DataSources {
  24. [key: string]: {
  25. name: string;
  26. label: string;
  27. description: string;
  28. type: string;
  29. pluginId: string;
  30. pluginName: string;
  31. };
  32. }
  33. export class DashboardExporter {
  34. constructor(private datasourceSrv: DatasourceSrv) {}
  35. makeExportable(dashboard: DashboardModel) {
  36. // clean up repeated rows and panels,
  37. // this is done on the live real dashboard instance, not on a clone
  38. // so we need to undo this
  39. // this is pretty hacky and needs to be changed
  40. dashboard.cleanUpRepeats();
  41. const saveModel = dashboard.getSaveModelClone();
  42. saveModel.id = null;
  43. // undo repeat cleanup
  44. dashboard.processRepeats();
  45. const inputs: Input[] = [];
  46. const requires: Requires = {};
  47. const datasources: DataSources = {};
  48. const promises: Array<Promise<void>> = [];
  49. const variableLookup: { [key: string]: any } = {};
  50. for (const variable of saveModel.templating.list) {
  51. variableLookup[variable.name] = variable;
  52. }
  53. const templateizeDatasourceUsage = (obj: any) => {
  54. let datasource: string = obj.datasource;
  55. let datasourceVariable: any = null;
  56. // ignore data source properties that contain a variable
  57. if (datasource && datasource.indexOf('$') === 0) {
  58. datasourceVariable = variableLookup[datasource.substring(1)];
  59. if (datasourceVariable && datasourceVariable.current) {
  60. datasource = datasourceVariable.current.value;
  61. }
  62. }
  63. promises.push(
  64. this.datasourceSrv.get(datasource).then(ds => {
  65. if (ds.meta.builtIn) {
  66. return;
  67. }
  68. // add data source type to require list
  69. requires['datasource' + ds.meta.id] = {
  70. type: 'datasource',
  71. id: ds.meta.id,
  72. name: ds.meta.name,
  73. version: ds.meta.info.version || '1.0.0',
  74. };
  75. // if used via variable we can skip templatizing usage
  76. if (datasourceVariable) {
  77. return;
  78. }
  79. const refName = 'DS_' + ds.name.replace(' ', '_').toUpperCase();
  80. datasources[refName] = {
  81. name: refName,
  82. label: ds.name,
  83. description: '',
  84. type: 'datasource',
  85. pluginId: ds.meta.id,
  86. pluginName: ds.meta.name,
  87. };
  88. obj.datasource = '${' + refName + '}';
  89. })
  90. );
  91. };
  92. const processPanel = (panel: PanelModel) => {
  93. if (panel.datasource !== undefined) {
  94. templateizeDatasourceUsage(panel);
  95. }
  96. if (panel.targets) {
  97. for (const target of panel.targets) {
  98. if (target.datasource !== undefined) {
  99. templateizeDatasourceUsage(target);
  100. }
  101. }
  102. }
  103. const panelDef: PanelPlugin = config.panels[panel.type];
  104. if (panelDef) {
  105. requires['panel' + panelDef.id] = {
  106. type: 'panel',
  107. id: panelDef.id,
  108. name: panelDef.name,
  109. version: panelDef.info.version,
  110. };
  111. }
  112. };
  113. // check up panel data sources
  114. for (const panel of saveModel.panels) {
  115. processPanel(panel);
  116. // handle collapsed rows
  117. if (panel.collapsed !== undefined && panel.collapsed === true && panel.panels) {
  118. for (const rowPanel of panel.panels) {
  119. processPanel(rowPanel);
  120. }
  121. }
  122. }
  123. // templatize template vars
  124. for (const variable of saveModel.templating.list) {
  125. if (variable.type === 'query') {
  126. templateizeDatasourceUsage(variable);
  127. variable.options = [];
  128. variable.current = {};
  129. variable.refresh = variable.refresh > 0 ? variable.refresh : 1;
  130. }
  131. }
  132. // templatize annotations vars
  133. for (const annotationDef of saveModel.annotations.list) {
  134. templateizeDatasourceUsage(annotationDef);
  135. }
  136. // add grafana version
  137. requires['grafana'] = {
  138. type: 'grafana',
  139. id: 'grafana',
  140. name: 'Grafana',
  141. version: config.buildInfo.version,
  142. };
  143. return Promise.all(promises)
  144. .then(() => {
  145. _.each(datasources, (value: any) => {
  146. inputs.push(value);
  147. });
  148. // templatize constants
  149. for (const variable of saveModel.templating.list) {
  150. if (variable.type === 'constant') {
  151. const refName = 'VAR_' + variable.name.replace(' ', '_').toUpperCase();
  152. inputs.push({
  153. name: refName,
  154. type: 'constant',
  155. label: variable.label || variable.name,
  156. value: variable.current.value,
  157. description: '',
  158. });
  159. // update current and option
  160. variable.query = '${' + refName + '}';
  161. variable.options[0] = variable.current = {
  162. value: variable.query,
  163. text: variable.query,
  164. };
  165. }
  166. }
  167. // make inputs and requires a top thing
  168. const newObj: { [key: string]: {} } = {};
  169. newObj['__inputs'] = inputs;
  170. newObj['__requires'] = _.sortBy(requires, ['id']);
  171. _.defaults(newObj, saveModel);
  172. return newObj;
  173. })
  174. .catch(err => {
  175. console.log('Export failed:', err);
  176. return {
  177. error: err,
  178. };
  179. });
  180. }
  181. }