Merge pull request #13761 from grafana/davkal/explore-reuse-table-merge

Explore: reuse table merge from table panel
This commit is contained in:
David
2018-10-23 16:00:57 +02:00
committed by GitHub
6 changed files with 237 additions and 146 deletions

View File

@@ -143,24 +143,6 @@ describe('when transforming time series table', () => {
},
];
const multipleQueriesDataDifferentLabels = [
{
type: 'table',
columns: [{ text: 'Time' }, { text: 'Label Key 1' }, { text: 'Value #A' }],
rows: [[time, 'Label Value 1', 42]],
},
{
type: 'table',
columns: [{ text: 'Time' }, { text: 'Label Key 2' }, { 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]],
},
];
describe('getColumns', () => {
it('should return data columns given a single query', () => {
const columns = transformers[transform].getColumns(singleQueryData);
@@ -177,16 +159,6 @@ describe('when transforming time series table', () => {
expect(columns[3].text).toBe('Value #A');
expect(columns[4].text).toBe('Value #B');
});
it('should return the union of data columns given a multiple queries with different labels', () => {
const columns = transformers[transform].getColumns(multipleQueriesDataDifferentLabels);
expect(columns[0].text).toBe('Time');
expect(columns[1].text).toBe('Label Key 1');
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');
});
});
describe('transform', () => {
@@ -237,26 +209,6 @@ describe('when transforming time series table', () => {
expect(table.rows[1][4]).toBeUndefined();
expect(table.rows[1][5]).toBe(7);
});
it('should return 2 rows for multiple 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]).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]).toBe('Label Value 3');
expect(table.rows[1][2]).toBeUndefined();
expect(table.rows[1][3]).toBeUndefined();
expect(table.rows[1][4]).toBeUndefined();
expect(table.rows[1][5]).toBe(7);
});
});
});
});

View File

@@ -1,7 +1,7 @@
import _ from 'lodash';
import flatten from '../../../core/utils/flatten';
import TimeSeries from '../../../core/time_series2';
import TableModel from '../../../core/table_model';
import flatten from 'app/core/utils/flatten';
import TimeSeries from 'app/core/time_series2';
import TableModel, { mergeTablesIntoModel } from 'app/core/table_model';
const transformers = {};
@@ -168,97 +168,7 @@ transformers['table'] = {
};
}
// Single query returns data columns and rows as is
if (data.length === 1) {
model.columns = [...data[0].columns];
model.rows = [...data[0].rows];
return;
}
// Track column indexes of union: name -> index
const columnNames = {};
// Union of all non-value columns
const columnsUnion = data.reduce((acc, series) => {
series.columns.forEach(col => {
const { text } = col;
if (columnNames[text] === undefined) {
columnNames[text] = acc.length;
acc.push(col);
}
});
return acc;
}, []);
// Map old column index to union index per series, e.g.,
// given columnNames {A: 0, B: 1} and
// data [{columns: [{ text: 'A' }]}, {columns: [{ text: 'B' }]}] => [[0], [1]]
const columnIndexMapper = data.map(series => series.columns.map(col => columnNames[col.text]));
// Flatten rows of all series and adjust new column indexes
const flattenedRows = data.reduce((acc, series, seriesIndex) => {
const mapper = columnIndexMapper[seriesIndex];
series.rows.forEach(row => {
const alteredRow = [];
// Shifting entries according to index mapper
mapper.forEach((to, from) => {
alteredRow[to] = row[from];
});
acc.push(alteredRow);
});
return acc;
}, []);
// Returns true if both rows have matching non-empty fields as well as matching
// indexes where one field is empty and the other is not
function areRowsMatching(columns, row, otherRow) {
let foundFieldToMatch = false;
for (let columnIndex = 0; columnIndex < columns.length; columnIndex++) {
if (row[columnIndex] !== undefined && otherRow[columnIndex] !== undefined) {
if (row[columnIndex] !== otherRow[columnIndex]) {
return false;
}
} else if (row[columnIndex] === undefined || otherRow[columnIndex] === undefined) {
foundFieldToMatch = true;
}
}
return foundFieldToMatch;
}
// Merge rows that have same values for columns
const mergedRows = {};
const compactedRows = flattenedRows.reduce((acc, row, rowIndex) => {
if (!mergedRows[rowIndex]) {
// Look from current row onwards
let offset = rowIndex + 1;
// More than one row can be merged into current row
while (offset < flattenedRows.length) {
// Find next row that could be merged
const match = _.findIndex(flattenedRows, otherRow => areRowsMatching(columnsUnion, row, otherRow), offset);
if (match > -1) {
const matchedRow = flattenedRows[match];
// Merge values from match into current row if there is a gap in the current row
for (let columnIndex = 0; columnIndex < columnsUnion.length; columnIndex++) {
if (row[columnIndex] === undefined && matchedRow[columnIndex] !== undefined) {
row[columnIndex] = matchedRow[columnIndex];
}
}
// Don't visit this row again
mergedRows[match] = matchedRow;
// Keep looking for more rows to merge
offset = match + 1;
} else {
// No match found, stop looking
break;
}
}
acc.push(row);
}
return acc;
}, []);
model.columns = columnsUnion;
model.rows = compactedRows;
mergeTablesIntoModel(model, ...data);
},
};