Просмотр исходного кода

graph panel: fix csv export (series as col) (#10769)

Alexander Zobnin 7 лет назад
Родитель
Сommit
81dc051ae9
2 измененных файлов с 117 добавлено и 6 удалено
  1. 64 0
      public/app/core/specs/file_export.jest.ts
  2. 53 6
      public/app/core/utils/file_export.ts

+ 64 - 0
public/app/core/specs/file_export.jest.ts

@@ -0,0 +1,64 @@
+import * as fileExport from '../utils/file_export';
+import { beforeEach, expect } from 'test/lib/common';
+
+describe('file_export', () => {
+  let ctx: any = {};
+
+  beforeEach(() => {
+    ctx.seriesList = [
+      {
+        alias: 'series_1',
+        datapoints: [
+          [1, 1500026100000],
+          [2, 1500026200000],
+          [null, 1500026300000],
+          [null, 1500026400000],
+          [null, 1500026500000],
+          [6, 1500026600000],
+        ],
+      },
+      {
+        alias: 'series_2',
+        datapoints: [[11, 1500026100000], [12, 1500026200000], [13, 1500026300000], [15, 1500026500000]],
+      },
+    ];
+
+    ctx.timeFormat = 'X'; // Unix timestamp (seconds)
+  });
+
+  describe('when exporting series as rows', () => {
+    it('should export points in proper order', () => {
+      let text = fileExport.convertSeriesListToCsv(ctx.seriesList, ctx.timeFormat);
+      const expectedText =
+        'Series;Time;Value\n' +
+        'series_1;1500026100;1\n' +
+        'series_1;1500026200;2\n' +
+        'series_1;1500026300;null\n' +
+        'series_1;1500026400;null\n' +
+        'series_1;1500026500;null\n' +
+        'series_1;1500026600;6\n' +
+        'series_2;1500026100;11\n' +
+        'series_2;1500026200;12\n' +
+        'series_2;1500026300;13\n' +
+        'series_2;1500026500;15\n';
+
+      expect(text).toBe(expectedText);
+    });
+  });
+
+  describe('when exporting series as columns', () => {
+    it('should export points in proper order', () => {
+      let text = fileExport.convertSeriesListToCsvColumns(ctx.seriesList, ctx.timeFormat);
+      const expectedText =
+        'Time;series_1;series_2\n' +
+        '1500026100;1;11\n' +
+        '1500026200;2;12\n' +
+        '1500026300;null;13\n' +
+        '1500026400;null;null\n' +
+        '1500026500;null;15\n' +
+        '1500026600;6;null\n';
+
+      expect(text).toBe(expectedText);
+    });
+  });
+});

+ 53 - 6
public/app/core/utils/file_export.ts

@@ -3,19 +3,27 @@ import moment from 'moment';
 import { saveAs } from 'file-saver';
 
 const DEFAULT_DATETIME_FORMAT = 'YYYY-MM-DDTHH:mm:ssZ';
+const POINT_TIME_INDEX = 1;
+const POINT_VALUE_INDEX = 0;
 
-export function exportSeriesListToCsv(seriesList, dateTimeFormat = DEFAULT_DATETIME_FORMAT, excel = false) {
+export function convertSeriesListToCsv(seriesList, dateTimeFormat = DEFAULT_DATETIME_FORMAT, excel = false) {
   var text = (excel ? 'sep=;\n' : '') + 'Series;Time;Value\n';
   _.each(seriesList, function(series) {
     _.each(series.datapoints, function(dp) {
-      text += series.alias + ';' + moment(dp[1]).format(dateTimeFormat) + ';' + dp[0] + '\n';
+      text +=
+        series.alias + ';' + moment(dp[POINT_TIME_INDEX]).format(dateTimeFormat) + ';' + dp[POINT_VALUE_INDEX] + '\n';
     });
   });
+  return text;
+}
+
+export function exportSeriesListToCsv(seriesList, dateTimeFormat = DEFAULT_DATETIME_FORMAT, excel = false) {
+  var text = convertSeriesListToCsv(seriesList, dateTimeFormat, excel);
   saveSaveBlob(text, 'grafana_data_export.csv');
 }
 
-export function exportSeriesListToCsvColumns(seriesList, dateTimeFormat = DEFAULT_DATETIME_FORMAT, excel = false) {
-  var text = (excel ? 'sep=;\n' : '') + 'Time;';
+export function convertSeriesListToCsvColumns(seriesList, dateTimeFormat = DEFAULT_DATETIME_FORMAT, excel = false) {
+  let text = (excel ? 'sep=;\n' : '') + 'Time;';
   // add header
   _.each(seriesList, function(series) {
     text += series.alias + ';';
@@ -24,14 +32,15 @@ export function exportSeriesListToCsvColumns(seriesList, dateTimeFormat = DEFAUL
   text += '\n';
 
   // process data
+  seriesList = mergeSeriesByTime(seriesList);
   var dataArr = [[]];
   var sIndex = 1;
   _.each(seriesList, function(series) {
     var cIndex = 0;
     dataArr.push([]);
     _.each(series.datapoints, function(dp) {
-      dataArr[0][cIndex] = moment(dp[1]).format(dateTimeFormat);
-      dataArr[sIndex][cIndex] = dp[0];
+      dataArr[0][cIndex] = moment(dp[POINT_TIME_INDEX]).format(dateTimeFormat);
+      dataArr[sIndex][cIndex] = dp[POINT_VALUE_INDEX];
       cIndex++;
     });
     sIndex++;
@@ -46,6 +55,44 @@ export function exportSeriesListToCsvColumns(seriesList, dateTimeFormat = DEFAUL
     text = text.substring(0, text.length - 1);
     text += '\n';
   }
+
+  return text;
+}
+
+/**
+ * Collect all unique timestamps from series list and use it to fill
+ * missing points by null.
+ */
+function mergeSeriesByTime(seriesList) {
+  let timestamps = [];
+  for (let i = 0; i < seriesList.length; i++) {
+    let seriesPoints = seriesList[i].datapoints;
+    for (let j = 0; j < seriesPoints.length; j++) {
+      timestamps.push(seriesPoints[j][POINT_TIME_INDEX]);
+    }
+  }
+  timestamps = _.sortedUniq(timestamps.sort());
+
+  for (let i = 0; i < seriesList.length; i++) {
+    let seriesPoints = seriesList[i].datapoints;
+    let seriesTimestamps = _.map(seriesPoints, p => p[POINT_TIME_INDEX]);
+    let extendedSeries = [];
+    let pointIndex;
+    for (let j = 0; j < timestamps.length; j++) {
+      pointIndex = _.sortedIndexOf(seriesTimestamps, timestamps[j]);
+      if (pointIndex !== -1) {
+        extendedSeries.push(seriesPoints[pointIndex]);
+      } else {
+        extendedSeries.push([null, timestamps[j]]);
+      }
+    }
+    seriesList[i].datapoints = extendedSeries;
+  }
+  return seriesList;
+}
+
+export function exportSeriesListToCsvColumns(seriesList, dateTimeFormat = DEFAULT_DATETIME_FORMAT, excel = false) {
+  let text = convertSeriesListToCsvColumns(seriesList, dateTimeFormat, excel);
   saveSaveBlob(text, 'grafana_data_export.csv');
 }