file_export.ts 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  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 } from '@grafana/ui/src/utils/moment_wrapper';
  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]) || isNumber(row[i]) || isNullOrUndefined(row[i])) {
  37. text += row[i];
  38. } else {
  39. text += `${QUOTE}${csvEscaped(htmlUnescaped(htmlDecoded(row[i])))}${QUOTE}`;
  40. }
  41. if (i < row.length - 1) {
  42. text += END_COLUMN;
  43. }
  44. }
  45. return addEndRowDelimiter ? text + END_ROW : text;
  46. }
  47. export function convertSeriesListToCsv(seriesList, dateTimeFormat = DEFAULT_DATETIME_FORMAT, excel = false) {
  48. let text = formatSpecialHeader(excel) + formatRow(['Series', 'Time', 'Value']);
  49. for (let seriesIndex = 0; seriesIndex < seriesList.length; seriesIndex += 1) {
  50. for (let i = 0; i < seriesList[seriesIndex].datapoints.length; i += 1) {
  51. text += formatRow(
  52. [
  53. seriesList[seriesIndex].alias,
  54. dateTime(seriesList[seriesIndex].datapoints[i][POINT_TIME_INDEX]).format(dateTimeFormat),
  55. seriesList[seriesIndex].datapoints[i][POINT_VALUE_INDEX],
  56. ],
  57. i < seriesList[seriesIndex].datapoints.length - 1 || seriesIndex < seriesList.length - 1
  58. );
  59. }
  60. }
  61. return text;
  62. }
  63. export function exportSeriesListToCsv(seriesList, dateTimeFormat = DEFAULT_DATETIME_FORMAT, excel = false) {
  64. const text = convertSeriesListToCsv(seriesList, dateTimeFormat, excel);
  65. saveSaveBlob(text, EXPORT_FILENAME);
  66. }
  67. export function convertSeriesListToCsvColumns(seriesList, dateTimeFormat = DEFAULT_DATETIME_FORMAT, excel = false) {
  68. // add header
  69. let text =
  70. formatSpecialHeader(excel) +
  71. formatRow(
  72. ['Time'].concat(
  73. seriesList.map(val => {
  74. return val.alias;
  75. })
  76. )
  77. );
  78. // process data
  79. const extendedDatapointsList = mergeSeriesByTime(seriesList);
  80. // make text
  81. for (let i = 0; i < extendedDatapointsList[0].length; i += 1) {
  82. const timestamp = dateTime(extendedDatapointsList[0][i][POINT_TIME_INDEX]).format(dateTimeFormat);
  83. text += formatRow(
  84. [timestamp].concat(
  85. extendedDatapointsList.map(datapoints => {
  86. return datapoints[i][POINT_VALUE_INDEX];
  87. })
  88. ),
  89. i < extendedDatapointsList[0].length - 1
  90. );
  91. }
  92. return text;
  93. }
  94. /**
  95. * Collect all unique timestamps from series list and use it to fill
  96. * missing points by null.
  97. */
  98. function mergeSeriesByTime(seriesList) {
  99. let timestamps = [];
  100. for (let i = 0; i < seriesList.length; i++) {
  101. const seriesPoints = seriesList[i].datapoints;
  102. for (let j = 0; j < seriesPoints.length; j++) {
  103. timestamps.push(seriesPoints[j][POINT_TIME_INDEX]);
  104. }
  105. }
  106. timestamps = sortedUniq(timestamps.sort());
  107. const result = [];
  108. for (let i = 0; i < seriesList.length; i++) {
  109. const seriesPoints = seriesList[i].datapoints;
  110. const seriesTimestamps = seriesPoints.map(p => p[POINT_TIME_INDEX]);
  111. const extendedDatapoints = [];
  112. for (let j = 0; j < timestamps.length; j++) {
  113. const timestamp = timestamps[j];
  114. const pointIndex = sortedIndexOf(seriesTimestamps, timestamp);
  115. if (pointIndex !== -1) {
  116. extendedDatapoints.push(seriesPoints[pointIndex]);
  117. } else {
  118. extendedDatapoints.push([null, timestamp]);
  119. }
  120. }
  121. result.push(extendedDatapoints);
  122. }
  123. return result;
  124. }
  125. export function exportSeriesListToCsvColumns(seriesList, dateTimeFormat = DEFAULT_DATETIME_FORMAT, excel = false) {
  126. const text = convertSeriesListToCsvColumns(seriesList, dateTimeFormat, excel);
  127. saveSaveBlob(text, EXPORT_FILENAME);
  128. }
  129. export function convertTableDataToCsv(table, excel = false) {
  130. let text = formatSpecialHeader(excel);
  131. // add headline
  132. text += formatRow(table.columns.map(val => val.title || val.text));
  133. // process data
  134. for (let i = 0; i < table.rows.length; i += 1) {
  135. text += formatRow(table.rows[i], i < table.rows.length - 1);
  136. }
  137. return text;
  138. }
  139. export function exportTableDataToCsv(table, excel = false) {
  140. const text = convertTableDataToCsv(table, excel);
  141. saveSaveBlob(text, EXPORT_FILENAME);
  142. }
  143. export function saveSaveBlob(payload, fname) {
  144. const blob = new Blob([payload], { type: 'text/csv;charset=utf-8;header=present;' });
  145. saveAs(blob, fname);
  146. }