file_export.ts 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. import { isBoolean, isNumber, sortedUniq, sortedIndexOf, unescape as htmlUnescaped } from 'lodash';
  2. import { saveAs } from 'file-saver';
  3. import { isNullOrUndefined } from 'util';
  4. import { dateTime, TimeZone } from '@grafana/data';
  5. const DEFAULT_DATETIME_FORMAT = 'YYYY-MM-DDTHH:mm:ssZ';
  6. const POINT_TIME_INDEX = 1;
  7. const POINT_VALUE_INDEX = 0;
  8. const END_COLUMN = ';';
  9. const END_ROW = '\r\n';
  10. const QUOTE = '"';
  11. const EXPORT_FILENAME = 'grafana_data_export.csv';
  12. interface SeriesListToCsvColumnsOptions {
  13. dateTimeFormat: string;
  14. excel: boolean;
  15. timezone: TimeZone;
  16. }
  17. const defaultOptions: SeriesListToCsvColumnsOptions = {
  18. dateTimeFormat: DEFAULT_DATETIME_FORMAT,
  19. excel: false,
  20. timezone: '',
  21. };
  22. function csvEscaped(text: string) {
  23. if (!text) {
  24. return text;
  25. }
  26. return text
  27. .split(QUOTE)
  28. .join(QUOTE + QUOTE)
  29. .replace(/^([-+=@])/, "'$1")
  30. .replace(/\s+$/, '');
  31. }
  32. const domParser = new DOMParser();
  33. function htmlDecoded(text: string) {
  34. if (!text) {
  35. return text;
  36. }
  37. const regexp = /&[^;]+;/g;
  38. function htmlDecoded(value: string) {
  39. const parsedDom = domParser.parseFromString(value, 'text/html');
  40. return parsedDom.body.textContent;
  41. }
  42. return text.replace(regexp, htmlDecoded).replace(regexp, htmlDecoded);
  43. }
  44. function formatSpecialHeader(useExcelHeader) {
  45. return useExcelHeader ? `sep=${END_COLUMN}${END_ROW}` : '';
  46. }
  47. function formatRow(row, addEndRowDelimiter = true) {
  48. let text = '';
  49. for (let i = 0; i < row.length; i += 1) {
  50. if (isBoolean(row[i]) || isNumber(row[i]) || isNullOrUndefined(row[i])) {
  51. text += row[i];
  52. } else {
  53. text += `${QUOTE}${csvEscaped(htmlUnescaped(htmlDecoded(row[i])))}${QUOTE}`;
  54. }
  55. if (i < row.length - 1) {
  56. text += END_COLUMN;
  57. }
  58. }
  59. return addEndRowDelimiter ? text + END_ROW : text;
  60. }
  61. export function convertSeriesListToCsv(seriesList, options: Partial<SeriesListToCsvColumnsOptions>) {
  62. const { dateTimeFormat, excel, timezone } = { ...defaultOptions, ...options };
  63. let text = formatSpecialHeader(excel) + formatRow(['Series', 'Time', 'Value']);
  64. for (let seriesIndex = 0; seriesIndex < seriesList.length; seriesIndex += 1) {
  65. for (let i = 0; i < seriesList[seriesIndex].datapoints.length; i += 1) {
  66. text += formatRow(
  67. [
  68. seriesList[seriesIndex].alias,
  69. timezone === 'utc'
  70. ? dateTime(seriesList[seriesIndex].datapoints[i][POINT_TIME_INDEX])
  71. .utc()
  72. .format(dateTimeFormat)
  73. : dateTime(seriesList[seriesIndex].datapoints[i][POINT_TIME_INDEX]).format(dateTimeFormat),
  74. seriesList[seriesIndex].datapoints[i][POINT_VALUE_INDEX],
  75. ],
  76. i < seriesList[seriesIndex].datapoints.length - 1 || seriesIndex < seriesList.length - 1
  77. );
  78. }
  79. }
  80. return text;
  81. }
  82. export function exportSeriesListToCsv(seriesList, options: Partial<SeriesListToCsvColumnsOptions>) {
  83. const text = convertSeriesListToCsv(seriesList, options);
  84. saveSaveBlob(text, EXPORT_FILENAME);
  85. }
  86. export function convertSeriesListToCsvColumns(seriesList, options: Partial<SeriesListToCsvColumnsOptions>) {
  87. const { dateTimeFormat, excel, timezone } = { ...defaultOptions, ...options };
  88. // add header
  89. let text =
  90. formatSpecialHeader(excel) +
  91. formatRow(
  92. ['Time'].concat(
  93. seriesList.map(val => {
  94. return val.alias;
  95. })
  96. )
  97. );
  98. // process data
  99. const extendedDatapointsList = mergeSeriesByTime(seriesList);
  100. // make text
  101. for (let i = 0; i < extendedDatapointsList[0].length; i += 1) {
  102. const timestamp =
  103. timezone === 'utc'
  104. ? dateTime(extendedDatapointsList[0][i][POINT_TIME_INDEX])
  105. .utc()
  106. .format(dateTimeFormat)
  107. : dateTime(extendedDatapointsList[0][i][POINT_TIME_INDEX]).format(dateTimeFormat);
  108. text += formatRow(
  109. [timestamp].concat(
  110. extendedDatapointsList.map(datapoints => {
  111. return datapoints[i][POINT_VALUE_INDEX];
  112. })
  113. ),
  114. i < extendedDatapointsList[0].length - 1
  115. );
  116. }
  117. return text;
  118. }
  119. /**
  120. * Collect all unique timestamps from series list and use it to fill
  121. * missing points by null.
  122. */
  123. function mergeSeriesByTime(seriesList) {
  124. let timestamps = [];
  125. for (let i = 0; i < seriesList.length; i++) {
  126. const seriesPoints = seriesList[i].datapoints;
  127. for (let j = 0; j < seriesPoints.length; j++) {
  128. timestamps.push(seriesPoints[j][POINT_TIME_INDEX]);
  129. }
  130. }
  131. timestamps = sortedUniq(timestamps.sort());
  132. const result = [];
  133. for (let i = 0; i < seriesList.length; i++) {
  134. const seriesPoints = seriesList[i].datapoints;
  135. const seriesTimestamps = seriesPoints.map(p => p[POINT_TIME_INDEX]);
  136. const extendedDatapoints = [];
  137. for (let j = 0; j < timestamps.length; j++) {
  138. const timestamp = timestamps[j];
  139. const pointIndex = sortedIndexOf(seriesTimestamps, timestamp);
  140. if (pointIndex !== -1) {
  141. extendedDatapoints.push(seriesPoints[pointIndex]);
  142. } else {
  143. extendedDatapoints.push([null, timestamp]);
  144. }
  145. }
  146. result.push(extendedDatapoints);
  147. }
  148. return result;
  149. }
  150. export function exportSeriesListToCsvColumns(seriesList, options: Partial<SeriesListToCsvColumnsOptions>) {
  151. const text = convertSeriesListToCsvColumns(seriesList, options);
  152. saveSaveBlob(text, EXPORT_FILENAME);
  153. }
  154. export function convertTableDataToCsv(table, excel = false) {
  155. let text = formatSpecialHeader(excel);
  156. // add headline
  157. text += formatRow(table.columns.map(val => val.title || val.text));
  158. // process data
  159. for (let i = 0; i < table.rows.length; i += 1) {
  160. text += formatRow(table.rows[i], i < table.rows.length - 1);
  161. }
  162. return text;
  163. }
  164. export function exportTableDataToCsv(table, excel = false) {
  165. const text = convertTableDataToCsv(table, excel);
  166. saveSaveBlob(text, EXPORT_FILENAME);
  167. }
  168. export function saveSaveBlob(payload, fname) {
  169. const blob = new Blob([payload], { type: 'text/csv;charset=utf-8;header=present;' });
  170. saveAs(blob, fname);
  171. }