Commit 8d70f133 by David Kaltschmidt

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
parent 011b2cd1
...@@ -118,7 +118,7 @@ export class PrometheusDatasource { ...@@ -118,7 +118,7 @@ export class PrometheusDatasource {
} }
if (activeTargets[index].format === "table") { 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 { } else {
for (let metricData of response.data.data.result) { for (let metricData of response.data.data.result) {
if (response.data.data.resultType === 'matrix') { if (response.data.data.resultType === 'matrix') {
...@@ -301,7 +301,7 @@ export class PrometheusDatasource { ...@@ -301,7 +301,7 @@ export class PrometheusDatasource {
return { target: metricLabel, datapoints: dps }; return { target: metricLabel, datapoints: dps };
} }
transformMetricDataToTable(md) { transformMetricDataToTable(md, resultCount: number, resultIndex: number) {
var table = new TableModel(); var table = new TableModel();
var i, j; var i, j;
var metricLabels = {}; var metricLabels = {};
...@@ -326,7 +326,8 @@ export class PrometheusDatasource { ...@@ -326,7 +326,8 @@ export class PrometheusDatasource {
metricLabels[label] = labelIndex + 1; metricLabels[label] = labelIndex + 1;
table.columns.push({text: label}); 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. // Populate rows, set value to empty string when label not present.
_.each(md, function(series) { _.each(md, function(series) {
......
...@@ -139,7 +139,7 @@ describe('when transforming time series table', () => { ...@@ -139,7 +139,7 @@ describe('when transforming time series table', () => {
{ text: 'Time' }, { text: 'Time' },
{ text: 'Label Key 1' }, { text: 'Label Key 1' },
{ text: 'Label Key 2' }, { text: 'Label Key 2' },
{ text: 'Value' }, { text: 'Value #A' },
], ],
rows: [ rows: [
[time, 'Label Value 1', 'Label Value 2', 42], [time, 'Label Value 1', 'Label Value 2', 42],
...@@ -151,7 +151,7 @@ describe('when transforming time series table', () => { ...@@ -151,7 +151,7 @@ describe('when transforming time series table', () => {
{ text: 'Time' }, { text: 'Time' },
{ text: 'Label Key 1' }, { text: 'Label Key 1' },
{ text: 'Label Key 2' }, { text: 'Label Key 2' },
{ text: 'Value' }, { text: 'Value #B' },
], ],
rows: [ rows: [
[time, 'Label Value 1', 'Label Value 2', 13], [time, 'Label Value 1', 'Label Value 2', 13],
...@@ -163,11 +163,23 @@ describe('when transforming time series table', () => { ...@@ -163,11 +163,23 @@ describe('when transforming time series table', () => {
{ text: 'Time' }, { text: 'Time' },
{ text: 'Label Key 1' }, { text: 'Label Key 1' },
{ text: 'Label Key 2' }, { text: 'Label Key 2' },
{ text: 'Value' }, { text: 'Value #C' },
], ],
rows: [ rows: [
[time, 'Label Value 1', 'Label Value 2', 4], [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', () => { ...@@ -177,7 +189,7 @@ describe('when transforming time series table', () => {
columns: [ columns: [
{ text: 'Time' }, { text: 'Time' },
{ text: 'Label Key 1' }, { text: 'Label Key 1' },
{ text: 'Value' }, { text: 'Value #A' },
], ],
rows: [ rows: [
[time, 'Label Value 1', 42], [time, 'Label Value 1', 42],
...@@ -188,11 +200,22 @@ describe('when transforming time series table', () => { ...@@ -188,11 +200,22 @@ describe('when transforming time series table', () => {
columns: [ columns: [
{ text: 'Time' }, { text: 'Time' },
{ text: 'Label Key 2' }, { text: 'Label Key 2' },
{ text: 'Value' }, { text: 'Value #B' },
], ],
rows: [ rows: [
[time, 'Label Value 2', 13], [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', () => { ...@@ -217,9 +240,10 @@ describe('when transforming time series table', () => {
var columns = transformers[transform].getColumns(multipleQueriesDataDifferentLabels); var columns = transformers[transform].getColumns(multipleQueriesDataDifferentLabels);
expect(columns[0].text).toBe('Time'); expect(columns[0].text).toBe('Time');
expect(columns[1].text).toBe('Label Key 1'); expect(columns[1].text).toBe('Label Key 1');
expect(columns[2].text).toBe('Label Key 2'); expect(columns[2].text).toBe('Value #A');
expect(columns[3].text).toBe('Value #A'); expect(columns[3].text).toBe('Label Key 2');
expect(columns[4].text).toBe('Value #B'); expect(columns[4].text).toBe('Value #B');
expect(columns[5].text).toBe('Value #C');
}); });
}); });
...@@ -255,32 +279,41 @@ describe('when transforming time series table', () => { ...@@ -255,32 +279,41 @@ describe('when transforming time series table', () => {
expect(table.rows[0][2]).toBe(42); 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); 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][0]).toBe(time);
expect(table.rows[0][1]).toBe('Label Value 1'); expect(table.rows[0][1]).toBe('Label Value 1');
expect(table.rows[0][2]).toBe('Label Value 2'); expect(table.rows[0][2]).toBe('Label Value 2');
expect(table.rows[0][3]).toBe(42); expect(table.rows[0][3]).toBe(42);
expect(table.rows[0][4]).toBe(13); expect(table.rows[0][4]).toBe(13);
expect(table.rows[0][5]).toBe(4); 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); table = transformDataToTable(multipleQueriesDataDifferentLabels, panel);
expect(table.rows.length).toBe(2); expect(table.rows.length).toBe(2);
expect(table.columns.length).toBe(6);
expect(table.rows[0][0]).toBe(time); expect(table.rows[0][0]).toBe(time);
expect(table.rows[0][1]).toBe('Label Value 1'); expect(table.rows[0][1]).toBe('Label Value 1');
expect(table.rows[0][2]).toBeUndefined(); expect(table.rows[0][2]).toBe(42);
expect(table.rows[0][3]).toBe(42); expect(table.rows[0][3]).toBe('Label Value 2');
expect(table.rows[0][4]).toBeUndefined(); expect(table.rows[0][4]).toBe(13);
expect(table.rows[0][5]).toBeUndefined();
expect(table.rows[1][0]).toBe(time); expect(table.rows[1][0]).toBe(time);
expect(table.rows[1][1]).toBeUndefined(); expect(table.rows[1][1]).toBe('Label Value 3');
expect(table.rows[1][2]).toBe('Label Value 2'); expect(table.rows[1][2]).toBeUndefined();
expect(table.rows[1][3]).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);
}); });
}); });
}); });
......
...@@ -144,28 +144,18 @@ transformers['table'] = { ...@@ -144,28 +144,18 @@ transformers['table'] = {
// Track column indexes: name -> index // Track column indexes: name -> index
const columnNames = {}; const columnNames = {};
// Union of all non-value columns // Union of all columns
const columns = data.reduce((acc, d, i) => { const columns = data.reduce((acc, d, i) => {
d.columns.forEach((col, j) => { d.columns.forEach((col, j) => {
const { text } = col; const { text } = col;
if (text !== 'Value') {
if (columnNames[text] === undefined) { if (columnNames[text] === undefined) {
columnNames[text] = acc.length; columnNames[text] = acc.length;
acc.push(col); acc.push(col);
} }
}
}); });
return acc; 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; return columns;
}, },
transform: function(data, panel, model) { transform: function(data, panel, model) {
...@@ -194,27 +184,15 @@ transformers['table'] = { ...@@ -194,27 +184,15 @@ transformers['table'] = {
const indexes = []; const indexes = [];
d.columns.forEach((col, j) => { d.columns.forEach((col, j) => {
const { text } = col; const { text } = col;
if (text !== 'Value') {
if (columnNames[text] === undefined) { if (columnNames[text] === undefined) {
columnNames[text] = acc.length; columnNames[text] = acc.length;
acc.push(col); acc.push(col);
} }
indexes[j] = columnNames[text]; indexes[j] = columnNames[text];
}
}); });
columnIndexes.push(indexes); columnIndexes.push(indexes);
return acc; 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; model.columns = columns;
...@@ -231,29 +209,36 @@ transformers['table'] = { ...@@ -231,29 +209,36 @@ transformers['table'] = {
return acc; return acc;
}, []); }, []);
// Merge rows that have same columns // Merge rows that have same values for columns
const mergedRows = {}; const mergedRows = {};
rows = rows.reduce((acc, row, i) => { rows = rows.reduce((acc, row, rowIndex) => {
if (!mergedRows[i]) { if (!mergedRows[rowIndex]) {
let offset = i + 1; let offset = rowIndex + 1;
while (offset < rows.length) { while (offset < rows.length) {
const match = _.findIndex(rows, (other, j) => { // Find next row that has the same field values unless the respective field is undefined
let same = true; const match = _.findIndex(rows, (otherRow) => {
for (let index = 0; index < nonValueColumnCount; index++) { let fieldsAreTheSame = true;
if (row[index] !== other[index]) { let foundFieldToMatch = false;
same = 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; break;
} }
} }
return same; return fieldsAreTheSame && foundFieldToMatch;
}, offset); }, offset);
if (match > -1) { if (match > -1) {
const matchedRow = rows[match]; const matchedRow = rows[match];
// Merge values into current row // Merge values into current row
for (let index = nonValueColumnCount; index < columns.length; index++) { for (let columnIndex = 0; columnIndex < columns.length; columnIndex++) {
if (row[index] === undefined && matchedRow[index] !== undefined) { if (row[columnIndex] === undefined && matchedRow[columnIndex] !== undefined) {
row[index] = matchedRow[index]; row[columnIndex] = matchedRow[columnIndex];
break;
} }
} }
mergedRows[match] = matchedRow; mergedRows[match] = matchedRow;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment