Welcome to mirror list, hosted at ThFree Co, Russian Federation.

csv_export.js « monitoring « javascripts « assets « app - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: eaeed4a54d4bde5fc36ec1b8beb76d4cdd72383c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import { getSeriesLabel } from '~/helpers/monitor_helper';

/**
 * Returns a label for a header of the csv.
 *
 * Includes double quotes ("") in case the header includes commas or other separator.
 *
 * @param {String} axisLabel
 * @param {String} metricLabel
 * @param {Object} metricAttributes
 */
const csvHeader = (axisLabel, metricLabel, metricAttributes = {}) =>
  `${axisLabel} > ${getSeriesLabel(metricLabel, metricAttributes)}`;

/**
 * Returns an array with the header labels given a list of metrics
 *
 * ```
 * metrics = [
 *   {
 *      label: "..." // user-defined label
 *      result: [
 *        {
 *           metric: { ... } // metricAttributes
 *        },
 *        ...
 *      ]
 *   },
 *   ...
 * ]
 * ```
 *
 * When metrics have a `label` or `metricAttributes`, they are
 * used to generate the column name.
 *
 * @param {String} axisLabel - Main label
 * @param {Array} metrics - Metrics with results
 */
const csvMetricHeaders = (axisLabel, metrics) =>
  metrics.flatMap(({ label, result }) =>
    // The `metric` in a `result` is a map of `metricAttributes`
    // contains key-values to identify the series, rename it
    // here for clarity.
    result.map(({ metric: metricAttributes }) => {
      return csvHeader(axisLabel, label, metricAttributes);
    }),
  );

/**
 * Returns a (flat) array with all the values arrays in each
 * metric and series
 *
 * ```
 * metrics = [
 *   {
 *      result: [
 *        {
 *           values: [ ... ] // `values`
 *        },
 *        ...
 *      ]
 *   },
 *   ...
 * ]
 * ```
 *
 * @param {Array} metrics - Metrics with results
 */
const csvMetricValues = (metrics) =>
  metrics.flatMap(({ result }) => result.map((res) => res.values || []));

/**
 * Returns headers and rows for csv, sorted by their timestamp.
 *
 * {
 *   headers: ["timestamp", "<col_1_name>", "col_2_name"],
 *   rows: [
 *     [ <timestamp>, <col_1_value>, <col_2_value> ],
 *     [ <timestamp>, <col_1_value>, <col_2_value> ]
 *     ...
 *   ]
 * }
 *
 * @param {Array} metricHeaders
 * @param {Array} metricValues
 */
const csvData = (metricHeaders, metricValues) => {
  const rowsByTimestamp = {};

  metricValues.forEach((values, colIndex) => {
    values.forEach(([timestamp, value]) => {
      if (!rowsByTimestamp[timestamp]) {
        rowsByTimestamp[timestamp] = [];
      }
      // `value` should be in the right column
      rowsByTimestamp[timestamp][colIndex] = value;
    });
  });

  const rows = Object.keys(rowsByTimestamp)
    .sort()
    .map((timestamp) => {
      // force each row to have the same number of entries
      rowsByTimestamp[timestamp].length = metricHeaders.length;
      // add timestamp as the first entry
      return [timestamp, ...rowsByTimestamp[timestamp]];
    });

  // Escape double quotes and enclose headers:
  // "If double-quotes are used to enclose fields, then a double-quote
  // appearing inside a field must be escaped by preceding it with
  // another double quote."
  // https://tools.ietf.org/html/rfc4180#page-2
  const headers = metricHeaders.map((header) => `"${header.replace(/"/g, '""')}"`);

  return {
    headers: ['timestamp', ...headers],
    rows,
  };
};

/**
 * Returns dashboard panel's data in a string in CSV format
 *
 * @param {Object} graphData - Panel contents
 * @returns {String}
 */
export const graphDataToCsv = (graphData) => {
  const delimiter = ',';
  const br = '\r\n';
  const { metrics = [], y_label: axisLabel } = graphData;

  const metricsWithResults = metrics.filter((metric) => metric.result);
  const metricHeaders = csvMetricHeaders(axisLabel, metricsWithResults);
  const metricValues = csvMetricValues(metricsWithResults);
  const { headers, rows } = csvData(metricHeaders, metricValues);

  if (rows.length === 0) {
    return '';
  }

  const headerLine = headers.join(delimiter) + br;
  const lines = rows.map((row) => row.join(delimiter));

  return headerLine + lines.join(br) + br;
};