DashboardExporter.ts 5.7 KB

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