Quellcode durchsuchen

Type-agnostic row merge in table transform for multiple queries

* moved unique value naming to datasource (credit: @bergquist)
* merge rows based on same column-values and empty values
* expanded tests
David Kaltschmidt vor 8 Jahren
Ursprung
Commit
8d70f13393

+ 4 - 3
public/app/plugins/datasource/prometheus/datasource.ts

@@ -118,7 +118,7 @@ export class PrometheusDatasource {
         }
 
         if (activeTargets[index].format === "table") {
-          result.push(self.transformMetricDataToTable(response.data.data.result));
+          result.push(self.transformMetricDataToTable(response.data.data.result, responseList.length, index));
         } else {
           for (let metricData of response.data.data.result) {
             if (response.data.data.resultType === 'matrix') {
@@ -301,7 +301,7 @@ export class PrometheusDatasource {
     return { target: metricLabel, datapoints: dps };
   }
 
-  transformMetricDataToTable(md) {
+  transformMetricDataToTable(md, resultCount: number, resultIndex: number) {
     var table = new TableModel();
     var i, j;
     var metricLabels = {};
@@ -326,7 +326,8 @@ export class PrometheusDatasource {
       metricLabels[label] = labelIndex + 1;
       table.columns.push({text: label});
     });
-    table.columns.push({text: 'Value'});
+    let valueText = resultCount > 1 ? `Value #${String.fromCharCode(65 + resultIndex)}` : 'Value';
+    table.columns.push({text: valueText});
 
     // Populate rows, set value to empty string when label not present.
     _.each(md, function(series) {

+ 49 - 16
public/app/plugins/panel/table/specs/transformers.jest.ts

@@ -139,7 +139,7 @@ describe('when transforming time series table', () => {
             { text: 'Time' },
             { text: 'Label Key 1' },
             { text: 'Label Key 2' },
-            { text: 'Value' },
+            { text: 'Value #A' },
           ],
           rows: [
             [time, 'Label Value 1', 'Label Value 2', 42],
@@ -151,7 +151,7 @@ describe('when transforming time series table', () => {
             { text: 'Time' },
             { text: 'Label Key 1' },
             { text: 'Label Key 2' },
-            { text: 'Value' },
+            { text: 'Value #B' },
           ],
           rows: [
             [time, 'Label Value 1', 'Label Value 2', 13],
@@ -163,11 +163,23 @@ describe('when transforming time series table', () => {
             { text: 'Time' },
             { text: 'Label Key 1' },
             { text: 'Label Key 2' },
-            { text: 'Value' },
+            { text: 'Value #C' },
           ],
           rows: [
             [time, 'Label Value 1', 'Label Value 2', 4],
           ],
+        },
+        {
+          type: 'table',
+          columns: [
+            { text: 'Time' },
+            { text: 'Label Key 1' },
+            { text: 'Label Key 2' },
+            { text: 'Value #C' },
+          ],
+          rows: [
+            [time, 'Label Value 1', 'Label Value 2', 7],
+          ],
         }
       ];
 
@@ -177,7 +189,7 @@ describe('when transforming time series table', () => {
           columns: [
             { text: 'Time' },
             { text: 'Label Key 1' },
-            { text: 'Value' },
+            { text: 'Value #A' },
           ],
           rows: [
             [time, 'Label Value 1', 42],
@@ -188,11 +200,22 @@ describe('when transforming time series table', () => {
           columns: [
             { text: 'Time' },
             { text: 'Label Key 2' },
-            { text: 'Value' },
+            { text: 'Value #B' },
           ],
           rows: [
             [time, 'Label Value 2', 13],
           ],
+        },
+        {
+          type: 'table',
+          columns: [
+            { text: 'Time' },
+            { text: 'Label Key 1' },
+            { text: 'Value #C' },
+          ],
+          rows: [
+            [time, 'Label Value 3', 7],
+          ],
         }
       ];
 
@@ -217,9 +240,10 @@ describe('when transforming time series table', () => {
           var columns = transformers[transform].getColumns(multipleQueriesDataDifferentLabels);
           expect(columns[0].text).toBe('Time');
           expect(columns[1].text).toBe('Label Key 1');
-          expect(columns[2].text).toBe('Label Key 2');
-          expect(columns[3].text).toBe('Value #A');
+          expect(columns[2].text).toBe('Value #A');
+          expect(columns[3].text).toBe('Label Key 2');
           expect(columns[4].text).toBe('Value #B');
+          expect(columns[5].text).toBe('Value #C');
         });
       });
 
@@ -255,32 +279,41 @@ describe('when transforming time series table', () => {
           expect(table.rows[0][2]).toBe(42);
         });
 
-        it ('should return 1 row for a mulitple queries with same label values', () => {
+        it ('should return 2 rows for a mulitple queries with same label values plus one extra row', () => {
           table = transformDataToTable(multipleQueriesDataSameLabels, panel);
-          expect(table.rows.length).toBe(1);
+          expect(table.rows.length).toBe(2);
           expect(table.rows[0][0]).toBe(time);
           expect(table.rows[0][1]).toBe('Label Value 1');
           expect(table.rows[0][2]).toBe('Label Value 2');
           expect(table.rows[0][3]).toBe(42);
           expect(table.rows[0][4]).toBe(13);
           expect(table.rows[0][5]).toBe(4);
+          expect(table.rows[1][0]).toBe(time);
+          expect(table.rows[1][1]).toBe('Label Value 1');
+          expect(table.rows[1][2]).toBe('Label Value 2');
+          expect(table.rows[1][3]).toBeUndefined();
+          expect(table.rows[1][4]).toBeUndefined();
+          expect(table.rows[1][5]).toBe(7);
         });
 
-        it ('should return 2 rows for a mulitple queries with different label values', () => {
+        it ('should return 2 rows for mulitple queries with different label values', () => {
           table = transformDataToTable(multipleQueriesDataDifferentLabels, panel);
           expect(table.rows.length).toBe(2);
+          expect(table.columns.length).toBe(6);
 
           expect(table.rows[0][0]).toBe(time);
           expect(table.rows[0][1]).toBe('Label Value 1');
-          expect(table.rows[0][2]).toBeUndefined();
-          expect(table.rows[0][3]).toBe(42);
-          expect(table.rows[0][4]).toBeUndefined();
+          expect(table.rows[0][2]).toBe(42);
+          expect(table.rows[0][3]).toBe('Label Value 2');
+          expect(table.rows[0][4]).toBe(13);
+          expect(table.rows[0][5]).toBeUndefined();
 
           expect(table.rows[1][0]).toBe(time);
-          expect(table.rows[1][1]).toBeUndefined();
-          expect(table.rows[1][2]).toBe('Label Value 2');
+          expect(table.rows[1][1]).toBe('Label Value 3');
+          expect(table.rows[1][2]).toBeUndefined();
           expect(table.rows[1][3]).toBeUndefined();
-          expect(table.rows[1][4]).toBe(13);
+          expect(table.rows[1][4]).toBeUndefined();
+          expect(table.rows[1][5]).toBe(7);
         });
       });
     });

+ 29 - 44
public/app/plugins/panel/table/transformers.ts

@@ -144,28 +144,18 @@ transformers['table'] = {
     // Track column indexes: name -> index
     const columnNames = {};
 
-    // Union of all non-value columns
+    // Union of all columns
     const columns = data.reduce((acc, d, i) => {
       d.columns.forEach((col, j) => {
         const { text } = col;
-        if (text !== 'Value') {
-          if (columnNames[text] === undefined) {
-            columnNames[text] = acc.length;
-            acc.push(col);
-          }
+        if (columnNames[text] === undefined) {
+          columnNames[text] = acc.length;
+          acc.push(col);
         }
       });
       return acc;
     }, []);
 
-    // Append one value column per data set
-    data.forEach((_, i) => {
-      // Value #A, Value #B,...
-      const text = `Value #${String.fromCharCode(65 + i)}`;
-      columnNames[text] = columns.length;
-      columns.push({ text });
-    });
-
     return columns;
   },
   transform: function(data, panel, model) {
@@ -194,27 +184,15 @@ transformers['table'] = {
       const indexes = [];
       d.columns.forEach((col, j) => {
         const { text } = col;
-        if (text !== 'Value') {
-          if (columnNames[text] === undefined) {
-            columnNames[text] = acc.length;
-            acc.push(col);
-          }
-          indexes[j] = columnNames[text];
+        if (columnNames[text] === undefined) {
+          columnNames[text] = acc.length;
+          acc.push(col);
         }
+        indexes[j] = columnNames[text];
       });
       columnIndexes.push(indexes);
       return acc;
     }, []);
-    const nonValueColumnCount = columns.length;
-
-    // Append one value column per data set
-    data.forEach((_, i) => {
-      // Value #A, Value #B,...
-      const text = `Value #${String.fromCharCode(65 + i)}`;
-      columnNames[text] = columns.length;
-      columns.push({ text });
-      columnIndexes[i].push(columnNames[text]);
-    });
 
     model.columns = columns;
 
@@ -231,29 +209,36 @@ transformers['table'] = {
       return acc;
     }, []);
 
-    // Merge rows that have same columns
+    // Merge rows that have same values for columns
     const mergedRows = {};
-    rows = rows.reduce((acc, row, i) => {
-      if (!mergedRows[i]) {
-        let offset = i + 1;
+    rows = rows.reduce((acc, row, rowIndex) => {
+      if (!mergedRows[rowIndex]) {
+        let offset = rowIndex + 1;
         while (offset < rows.length) {
-          const match = _.findIndex(rows, (other, j) => {
-            let same = true;
-            for (let index = 0; index < nonValueColumnCount; index++) {
-              if (row[index] !== other[index]) {
-                same = false;
+          // Find next row that has the same field values unless the respective field is undefined
+          const match = _.findIndex(rows, (otherRow) => {
+            let fieldsAreTheSame = true;
+            let foundFieldToMatch = false;
+            for (let columnIndex = 0; columnIndex < columns.length; columnIndex++) {
+              if (row[columnIndex] !== undefined && otherRow[columnIndex] !== undefined) {
+                if (row[columnIndex] !== otherRow[columnIndex]) {
+                  fieldsAreTheSame = false;
+                }
+              } else if (row[columnIndex] === undefined || otherRow[columnIndex] === undefined) {
+                foundFieldToMatch = true;
+              }
+              if (!fieldsAreTheSame) {
                 break;
               }
             }
-            return same;
+            return fieldsAreTheSame && foundFieldToMatch;
           }, offset);
           if (match > -1) {
             const matchedRow = rows[match];
             // Merge values into current row
-            for (let index = nonValueColumnCount; index < columns.length; index++) {
-              if (row[index] === undefined && matchedRow[index] !== undefined) {
-                row[index] = matchedRow[index];
-                break;
+            for (let columnIndex = 0; columnIndex < columns.length; columnIndex++) {
+              if (row[columnIndex] === undefined && matchedRow[columnIndex] !== undefined) {
+                row[columnIndex] = matchedRow[columnIndex];
               }
             }
             mergedRows[match] = matchedRow;