exportDashboard.tsx 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. import cloneDeep from 'lodash/cloneDeep';
  2. import {addErrorMessage} from 'sentry/actionCreators/indicator';
  3. import type {DashboardDetails} from './types';
  4. type ExcludedProperties = 'createdBy' | 'dateCreated' | 'id' | 'dashboardId' | 'widgetId';
  5. async function exportDashboard() {
  6. try {
  7. const structure = {
  8. base_url: null,
  9. dashboard_id: null,
  10. org_slug: null,
  11. };
  12. const params = getAPIParams(structure);
  13. const apiUrl = `https://${params.base_url}/api/0/organizations/${params.org_slug}/dashboards/${params.dashboard_id}/`;
  14. const response = await fetch(apiUrl);
  15. const jsonData = await response.json();
  16. const normalized = normalizeData(jsonData);
  17. normalized.projects = [];
  18. downloadObjectAsJson(normalized, cleanTitle(normalized.title));
  19. } catch (error) {
  20. addErrorMessage(
  21. 'Could not export dashboard. Please wait or try again with a different dashboard'
  22. );
  23. }
  24. }
  25. function getAPIParams(structure) {
  26. const url = window.location.href;
  27. const regex = {
  28. base_url: /(\/\/)(.*?)(\/)/,
  29. dashboard_id: /(dashboard\/)(.*?)(\/)/,
  30. org_slug: /(\/\/)(.+?)(?=\.)/,
  31. };
  32. for (const attr in regex) {
  33. const match = url.match(regex[attr]);
  34. if (match?.length) {
  35. structure[attr] = match.length >= 3 ? match[2] : null;
  36. }
  37. }
  38. return structure;
  39. }
  40. function normalizeData(
  41. source: DashboardDetails
  42. ): Omit<DashboardDetails, ExcludedProperties> {
  43. const payload: Omit<DashboardDetails, ExcludedProperties> = {
  44. title: '',
  45. filters: {},
  46. projects: [],
  47. widgets: [],
  48. environment: [],
  49. };
  50. for (const property in payload) {
  51. if (property in source) {
  52. let data: any[] = [];
  53. // if there is a nested object with properties that should be deleted
  54. if (['widgets'].includes(property)) {
  55. // get the object properties so that we can loop through them
  56. const type = getPropertyStructure(property);
  57. data = normalizeNestedObject(source[property], type);
  58. } else {
  59. data = source[property];
  60. }
  61. payload[property] = data;
  62. }
  63. }
  64. return payload;
  65. }
  66. function normalizeNestedObject(object, structure) {
  67. const nestedObjectArray: any[] = [];
  68. for (const index in object) {
  69. const nestedObject = cloneDeep(structure);
  70. for (const property in structure) {
  71. if (property in object[index]) {
  72. let data: any[] = [];
  73. if (['queries'].includes(property)) {
  74. // get the object properties so that we can loop through them
  75. const type = getPropertyStructure(property);
  76. data = normalizeNestedObject(object[index][property], type);
  77. } else {
  78. data = object[index][property];
  79. }
  80. nestedObject[property] = data;
  81. }
  82. }
  83. nestedObjectArray.push(nestedObject);
  84. }
  85. return nestedObjectArray;
  86. }
  87. function getPropertyStructure(property) {
  88. let structure = {};
  89. switch (property) {
  90. case 'widgets':
  91. structure = {
  92. title: '',
  93. description: '',
  94. interval: '',
  95. queries: [],
  96. displayType: '',
  97. widgetType: '',
  98. layout: [],
  99. };
  100. break;
  101. case 'queries':
  102. structure = {
  103. aggregates: [],
  104. columns: [],
  105. conditions: [],
  106. name: '',
  107. orderby: '',
  108. fieldAliases: [],
  109. fields: [],
  110. };
  111. break;
  112. default:
  113. structure = {};
  114. }
  115. return structure;
  116. }
  117. function downloadObjectAsJson(exportObj, exportName) {
  118. const dataStr = `data:text/json;charset=utf-8,${encodeURIComponent(
  119. JSON.stringify(exportObj)
  120. )}`;
  121. const downloadAnchorNode = document.createElement('a');
  122. downloadAnchorNode.setAttribute('href', dataStr);
  123. downloadAnchorNode.setAttribute('download', `${exportName}.json`);
  124. document.body.appendChild(downloadAnchorNode); // required for firefox
  125. downloadAnchorNode.click();
  126. downloadAnchorNode.remove();
  127. }
  128. function cleanTitle(title) {
  129. const regex = /[^a-z0-9]/gi;
  130. const formattedTitle = title.replace(regex, '-');
  131. const date = new Date();
  132. return `${formattedTitle}-${date.toISOString()}`;
  133. }
  134. export default exportDashboard;