mirror of
https://github.com/grafana/grafana.git
synced 2025-02-15 10:03:33 -06:00
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
This commit is contained in:
parent
011b2cd1b2
commit
8d70f13393
@ -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) {
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user