From 8d70f13393c42fbcc3a4e3ca6d861d108eecf1fc Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Mon, 11 Dec 2017 12:42:53 +0100 Subject: [PATCH] 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 --- .../datasource/prometheus/datasource.ts | 7 +- .../panel/table/specs/transformers.jest.ts | 65 +++++++++++++---- .../app/plugins/panel/table/transformers.ts | 73 ++++++++----------- 3 files changed, 82 insertions(+), 63 deletions(-) diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index ec295760d49..f90dacc34c1 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/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) { diff --git a/public/app/plugins/panel/table/specs/transformers.jest.ts b/public/app/plugins/panel/table/specs/transformers.jest.ts index 1c7fc3e7852..18b3cc213d8 100644 --- a/public/app/plugins/panel/table/specs/transformers.jest.ts +++ b/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); }); }); }); diff --git a/public/app/plugins/panel/table/transformers.ts b/public/app/plugins/panel/table/transformers.ts index 5081ab785b6..08f259bb168 100644 --- a/public/app/plugins/panel/table/transformers.ts +++ b/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;