file_export.ts 6.0 KB

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