file_export.ts 5.1 KB

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