exportDashboard.tsx 4.1 KB

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