Ensure extra columns are not shown in table vis when showPartialRows:true (#25690)

This commit is contained in:
Luke Elmers 2018-12-17 17:42:55 -07:00 committed by GitHub
parent 46538d22e4
commit e6f30a2ea0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 93 additions and 31 deletions

View file

@ -21,7 +21,7 @@
<div class="checkbox"> <div class="checkbox">
<label> <label>
<input type="checkbox" ng-model="editorState.params.showPartialRows"> <input type="checkbox" ng-model="editorState.params.showPartialRows" data-test-subj="showPartialRows">
<span <span
i18n-id="tableVis.params.showPartialRowsLabel" i18n-id="tableVis.params.showPartialRowsLabel"
i18n-default-message="Show partial rows" i18n-default-message="Show partial rows"

View file

@ -42,7 +42,7 @@ describe('buildHierarchicalData', function () {
const buildHierarchicalData = async (aggs, response) => { const buildHierarchicalData = async (aggs, response) => {
const vis = new Vis(indexPattern, { type: 'histogram', aggs: aggs }); const vis = new Vis(indexPattern, { type: 'histogram', aggs: aggs });
vis.isHierarchical = () => true; vis.isHierarchical = () => true;
const data = tabifyAggResponse(vis.aggs, response, { metricsAtAllLevels: true }); const data = tabifyAggResponse(vis.aggs, response, { minimalColumns: false });
return await responseHandler(data); return await responseHandler(data);
}; };

View file

@ -49,7 +49,7 @@ describe('tabifyAggResponse Integration', function () {
normalizeIds(vis); normalizeIds(vis);
const resp = tabifyAggResponse(vis.getAggConfig(), fixtures.metricOnly, { const resp = tabifyAggResponse(vis.getAggConfig(), fixtures.metricOnly, {
metricsAtAllLevels: vis.isHierarchical() minimalColumns: !vis.isHierarchical(),
}); });
expect(resp).to.have.property('rows').and.property('columns'); expect(resp).to.have.property('rows').and.property('columns');
@ -138,7 +138,9 @@ describe('tabifyAggResponse Integration', function () {
// the default for a non-hierarchical vis is to display // the default for a non-hierarchical vis is to display
// only complete rows, and only put the metrics at the end. // only complete rows, and only put the metrics at the end.
const tabbed = tabifyAggResponse(vis.getAggConfig(), esResp, { minimalColumns: true }); const tabbed = tabifyAggResponse(vis.getAggConfig(), esResp, {
minimalColumns: true,
});
expectColumns(tabbed, [ext, src, os, avg]); expectColumns(tabbed, [ext, src, os, avg]);
@ -159,7 +161,9 @@ describe('tabifyAggResponse Integration', function () {
// the existing bucket and it's metric // the existing bucket and it's metric
vis.isHierarchical = _.constant(true); vis.isHierarchical = _.constant(true);
const tabbed = tabifyAggResponse(vis.getAggConfig(), esResp, { metricsAtAllLevels: true }); const tabbed = tabifyAggResponse(vis.getAggConfig(), esResp, {
minimalColumns: false,
});
expectColumns(tabbed, [ext, avg, src, avg, os, avg]); expectColumns(tabbed, [ext, avg, src, avg, os, avg]);

View file

@ -74,8 +74,8 @@ describe('TabbedAggResponseWriter class', function () {
expect(responseWriter.columns.length).to.eql(3); expect(responseWriter.columns.length).to.eql(3);
}); });
it('correctly generates columns with metricsAtAllLevels set to true', () => { it('correctly generates columns with minimalColumns set to false', () => {
const minimalColumnsResponseWriter = createResponseWritter(twoSplitsAggConfig, { metricsAtAllLevels: true }); const minimalColumnsResponseWriter = createResponseWritter(twoSplitsAggConfig, { minimalColumns: false });
expect(minimalColumnsResponseWriter.columns.length).to.eql(4); expect(minimalColumnsResponseWriter.columns.length).to.eql(4);
}); });

View file

@ -27,10 +27,16 @@ const getColumn = (agg, i) => {
}; };
}; };
export function tabifyGetColumns(aggs, minimal) { /**
* Builds tabify columns.
*
* @param {AggConfigs} aggs - the agg configs object to which the aggregation response correlates
* @param {boolean} minimalColumns - setting to true will only return a column for the last bucket/metric instead of one for each level
*/
export function tabifyGetColumns(aggs, minimalColumns) {
// pick the columns // pick the columns
if (minimal) { if (minimalColumns) {
return aggs.map((agg, i) => getColumn(agg, i)); return aggs.map((agg, i) => getColumn(agg, i));
} }

View file

@ -25,22 +25,27 @@ import { tabifyGetColumns } from './_get_columns';
* produces a table, or a series of tables. * produces a table, or a series of tables.
* *
* @param {AggConfigs} aggs - the agg configs object to which the aggregation response correlates * @param {AggConfigs} aggs - the agg configs object to which the aggregation response correlates
* @param {boolean} metricsAtAllLevels - setting to true will produce metrics for every bucket * @param {boolean} minimalColumns - setting to true will only return a column for the last bucket/metric instead of one for each level
* @param {boolean} partialRows - setting to true will not remove rows with missing values * @param {boolean} partialRows - vis.params.showPartialRows: determines whether to return rows with incomplete data
* @param {Object} timeRange - time range object, if provided
*/ */
function TabbedAggResponseWriter(aggs, { metricsAtAllLevels = false, partialRows = false, timeRange } = {}) { function TabbedAggResponseWriter(aggs, {
minimalColumns = true,
partialRows = false,
timeRange
} = {}) {
// Private
this._removePartialRows = !partialRows;
// Public
this.rowBuffer = {}; this.rowBuffer = {};
this.bucketBuffer = []; this.bucketBuffer = [];
this.metricBuffer = []; this.metricBuffer = [];
this.metricsForAllBuckets = metricsAtAllLevels;
this.partialRows = partialRows;
this.aggs = aggs; this.aggs = aggs;
this.columns = tabifyGetColumns(aggs.getResponseAggs(), !metricsAtAllLevels); this.partialRows = partialRows;
this.columns = tabifyGetColumns(aggs.getResponseAggs(), minimalColumns);
this.aggStack = [...this.columns]; this.aggStack = [...this.columns];
this.rows = []; this.rows = [];
// Extract the time range object if provided // Extract the time range object if provided
if (timeRange) { if (timeRange) {
const timeRangeKey = Object.keys(timeRange)[0]; const timeRangeKey = Object.keys(timeRange)[0];
@ -67,7 +72,7 @@ TabbedAggResponseWriter.prototype.row = function () {
this.rowBuffer[metric.id] = metric.value; this.rowBuffer[metric.id] = metric.value;
}); });
if (!toArray(this.rowBuffer).length || (!this.partialRows && this.isPartialRow(this.rowBuffer))) { if (!toArray(this.rowBuffer).length || (this._removePartialRows && this.isPartialRow(this.rowBuffer))) {
return; return;
} }

View file

@ -21,6 +21,16 @@ import _ from 'lodash';
import { TabbedAggResponseWriter } from './_response_writer'; import { TabbedAggResponseWriter } from './_response_writer';
import { TabifyBuckets } from './_buckets'; import { TabifyBuckets } from './_buckets';
/**
* Sets up the ResponseWriter and kicks off bucket collection.
*
* @param {AggConfigs} aggs - the agg configs object to which the aggregation response correlates
* @param {Object} esResponse - response that came back from Elasticsearch
* @param {Object} respOpts - options object for the ResponseWriter with params set by Courier
* @param {boolean} respOpts.minimalColumns - setting to true will only return a column for the last bucket/metric instead of one for each level
* @param {boolean} respOpts.partialRows - vis.params.showPartialRows: determines whether to return rows with incomplete data
* @param {Object} respOpts.timeRange - time range object, if provided
*/
export function tabifyAggResponse(aggs, esResponse, respOpts = {}) { export function tabifyAggResponse(aggs, esResponse, respOpts = {}) {
const write = new TabbedAggResponseWriter(aggs, respOpts); const write = new TabbedAggResponseWriter(aggs, respOpts);
@ -34,7 +44,7 @@ export function tabifyAggResponse(aggs, esResponse, respOpts = {}) {
} }
/** /**
* read an aggregation from a bucket, which is *might* be found at key (if * read an aggregation from a bucket, which *might* be found at key (if
* the response came in object form), and will recurse down the aggregation * the response came in object form), and will recurse down the aggregation
* tree and will pass the read values to the ResponseWriter. * tree and will pass the read values to the ResponseWriter.
* *

View file

@ -57,7 +57,9 @@ describe('AggTable Directive', function () {
vis2.aggs.forEach(function (agg, i) { vis2.aggs.forEach(function (agg, i) {
agg.id = 'agg_' + (i + 1); agg.id = 'agg_' + (i + 1);
}); });
tabifiedData.threeTermBuckets = tabifyAggResponse(vis2.aggs, fixtures.threeTermBuckets, { metricsAtAllLevels: true }); tabifiedData.threeTermBuckets = tabifyAggResponse(vis2.aggs, fixtures.threeTermBuckets, {
minimalColumns: false,
});
const vis3 = new Vis(indexPattern, { const vis3 = new Vis(indexPattern, {
type: 'table', type: 'table',

View file

@ -38,8 +38,9 @@ const CourierRequestHandlerProvider = function () {
filters, filters,
forceFetch, forceFetch,
partialRows, partialRows,
isHierarchical, metricsAtAllLevels,
inspectorAdapters, inspectorAdapters,
minimalColumns,
queryFilter queryFilter
}) { }) {
@ -67,7 +68,7 @@ const CourierRequestHandlerProvider = function () {
}); });
requestSearchSource.setField('aggs', function () { requestSearchSource.setField('aggs', function () {
return aggs.toDsl(isHierarchical); return aggs.toDsl(metricsAtAllLevels);
}); });
requestSearchSource.onRequestStart((searchSource, searchRequest) => { requestSearchSource.onRequestStart((searchSource, searchRequest) => {
@ -133,7 +134,7 @@ const CourierRequestHandlerProvider = function () {
const parsedTimeRange = timeRange ? getTime(aggs.indexPattern, timeRange) : null; const parsedTimeRange = timeRange ? getTime(aggs.indexPattern, timeRange) : null;
const tabifyParams = { const tabifyParams = {
metricsAtAllLevels: isHierarchical, minimalColumns,
partialRows, partialRows,
timeRange: parsedTimeRange ? parsedTimeRange.range : undefined, timeRange: parsedTimeRange ? parsedTimeRange.range : undefined,
}; };

View file

@ -36,7 +36,8 @@ export interface RequestHandlerParams {
uiState?: PersistedState; uiState?: PersistedState;
partialRows?: boolean; partialRows?: boolean;
inspectorAdapters?: Adapters; inspectorAdapters?: Adapters;
isHierarchical?: boolean; metricsAtAllLevels?: boolean;
minimalColumns?: boolean;
visParams?: any; visParams?: any;
} }

View file

@ -128,8 +128,12 @@ describe('No global chart settings', function () {
})); }));
beforeEach(async () => { beforeEach(async () => {
const table1 = tabifyAggResponse(stubVis1.aggs, fixtures.threeTermBuckets, { metricsAtAllLevels: true }); const table1 = tabifyAggResponse(stubVis1.aggs, fixtures.threeTermBuckets, {
const table2 = tabifyAggResponse(stubVis2.aggs, fixtures.threeTermBuckets, { metricsAtAllLevels: true }); minimalColumns: false,
});
const table2 = tabifyAggResponse(stubVis2.aggs, fixtures.threeTermBuckets, {
minimalColumns: false,
});
data1 = await responseHandler(table1); data1 = await responseHandler(table1);
data2 = await responseHandler(table2); data2 = await responseHandler(table2);
@ -219,7 +223,9 @@ describe('Vislib PieChart Class Test Suite', function () {
})); }));
beforeEach(async () => { beforeEach(async () => {
const table = tabifyAggResponse(stubVis.aggs, fixtures.threeTermBuckets, { metricsAtAllLevels: true }); const table = tabifyAggResponse(stubVis.aggs, fixtures.threeTermBuckets, {
minimalColumns: false,
});
data = await responseHandler(table); data = await responseHandler(table);
vis.render(data, persistedState); vis.render(data, persistedState);
}); });

View file

@ -73,10 +73,18 @@ export class VisualizeDataLoader {
this.vis.showRequestError = false; this.vis.showRequestError = false;
try { try {
// Vis types that have a `showMetricsAtAllLevels` param (e.g. data table) should tell
// tabify whether to return columns for each bucket based on the param value. Vis types
// without this param should default to returning all columns if they are hierarchical.
const minimalColumns =
typeof this.vis.params.showMetricsAtAllLevels !== 'undefined'
? !this.vis.params.showMetricsAtAllLevels
: !this.vis.isHierarchical();
// searchSource is only there for courier request handler // searchSource is only there for courier request handler
const requestHandlerResponse = await this.requestHandler({ const requestHandlerResponse = await this.requestHandler({
partialRows: this.vis.params.partialRows || this.vis.type.requiresPartialRows, partialRows: this.vis.type.requiresPartialRows || this.vis.params.showPartialRows,
isHierarchical: this.vis.isHierarchical(), minimalColumns,
metricsAtAllLevels: this.vis.isHierarchical(),
visParams: this.vis.params, visParams: this.vis.params,
...params, ...params,
filters: params.filters filters: params.filters

View file

@ -267,6 +267,25 @@ export default function ({ getService, getPageObjects }) {
]); ]);
}); });
it('should show correct data without showMetricsAtAllLevels even if showPartialRows is selected', async () => {
await PageObjects.visualize.clickOptionsTab();
await PageObjects.visualize.checkCheckbox('showPartialRows');
await PageObjects.visualize.clickGo();
const data = await PageObjects.visualize.getTableVisContent();
expect(data).to.be.eql([
[ 'jpg', 'CN', '1,718' ],
[ 'jpg', 'IN', '1,511' ],
[ 'jpg', 'US', '770' ],
[ 'jpg', 'ID', '314' ],
[ 'jpg', 'PK', '244' ],
[ 'css', 'CN', '422' ],
[ 'css', 'IN', '346' ],
[ 'css', 'US', '189' ],
[ 'css', 'ID', '68' ],
[ 'css', 'BR', '58' ],
]);
});
it('should show metrics on each level', async () => { it('should show metrics on each level', async () => {
await PageObjects.visualize.clickOptionsTab(); await PageObjects.visualize.clickOptionsTab();
await PageObjects.visualize.checkCheckbox('showMetricsAtAllLevels'); await PageObjects.visualize.checkCheckbox('showMetricsAtAllLevels');
@ -364,7 +383,7 @@ export default function ({ getService, getPageObjects }) {
]); ]);
}); });
it('should not show metrics for split bucket when using showMetricsAtAllLevels', async () => { it('should show metrics for split bucket when using showMetricsAtAllLevels', async () => {
await PageObjects.visualize.clickOptionsTab(); await PageObjects.visualize.clickOptionsTab();
await PageObjects.visualize.checkCheckbox('showMetricsAtAllLevels'); await PageObjects.visualize.checkCheckbox('showMetricsAtAllLevels');
await PageObjects.visualize.clickGo(); await PageObjects.visualize.clickGo();