Fix courier issues causing showMetricsOnAllLevels to break (#24488) (#24530)

* Fix courier tabify caching issue

* Better code style

* Change to named lodash import

* Fix missing DSL parameters on hierarchical query

* Add functional test for hierarchical agg configs
This commit is contained in:
Tim Roes 2018-10-24 23:58:36 +02:00 committed by GitHub
parent 66a8307a1a
commit a2ccf779ba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 273 additions and 68 deletions

View file

@ -7,7 +7,7 @@
<div class="checkbox">
<label>
<input type="checkbox" ng-model="editorState.params.showMetricsAtAllLevels">
<input type="checkbox" ng-model="editorState.params.showMetricsAtAllLevels" data-test-subj="showMetricsAtAllLevels">
Show metrics for every bucket/level
</label>
</div>

View file

@ -17,7 +17,7 @@
* under the License.
*/
import _ from 'lodash';
import { cloneDeep, has } from 'lodash';
import { VisRequestHandlersRegistryProvider } from '../../registry/vis_request_handlers';
import { calculateObjectHash } from '../lib/calculate_object_hash';
@ -72,7 +72,7 @@ const CourierRequestHandlerProvider = function () {
return {
name: 'courier',
handler: function (vis, { searchSource, aggs, timeRange, query, filters, forceFetch, partialRows }) {
handler: async function (vis, { searchSource, aggs, timeRange, query, filters, forceFetch, partialRows }) {
// Create a new search source that inherits the original search source
// but has the appropriate timeRange applied via a filter.
@ -98,7 +98,7 @@ const CourierRequestHandlerProvider = function () {
});
requestSearchSource.setField('aggs', function () {
return aggs.toDsl();
return aggs.toDsl(vis.isHierarchical());
});
requestSearchSource.onRequestStart((searchSource, searchRequest) => {
@ -114,72 +114,74 @@ const CourierRequestHandlerProvider = function () {
requestSearchSource.setField('filter', filters);
requestSearchSource.setField('query', query);
const shouldQuery = (requestBodyHash) => {
if (!searchSource.lastQuery || forceFetch) return true;
if (searchSource.lastQuery !== requestBodyHash) return true;
return false;
const reqBody = await requestSearchSource.getSearchRequestBody();
const queryHash = calculateObjectHash(reqBody);
// We only need to reexecute the query, if forceFetch was true or the hash of the request body has changed
// since the last request.
const shouldQuery = forceFetch || (searchSource.lastQuery !== queryHash);
if (shouldQuery) {
const lastAggConfig = aggs;
vis.API.inspectorAdapters.requests.reset();
const request = vis.API.inspectorAdapters.requests.start('Data', {
description: `This request queries Elasticsearch to fetch the data for the visualization.`,
});
request.stats(getRequestInspectorStats(requestSearchSource));
const response = await requestSearchSource.fetch();
searchSource.lastQuery = queryHash;
request
.stats(getResponseInspectorStats(searchSource, response))
.ok({ json: response });
searchSource.rawResponse = response;
let resp = cloneDeep(response);
for (const agg of aggs) {
if (has(agg, 'type.postFlightRequest')) {
resp = await agg.type.postFlightRequest(
resp,
aggs,
agg,
requestSearchSource,
vis.API.inspectorAdapters
);
}
}
searchSource.finalResponse = resp;
vis.API.inspectorAdapters.data.setTabularLoader(
() => buildTabularInspectorData(vis, searchSource, lastAggConfig),
{ returnsFormattedValues: true }
);
requestSearchSource.getSearchRequestBody().then(req => {
request.json(req);
});
}
const parsedTimeRange = timeRange ? getTime(aggs.indexPattern, timeRange) : null;
const tabifyAggs = vis.getAggConfig();
const tabifyParams = {
metricsAtAllLevels: vis.isHierarchical(),
partialRows,
timeRange: parsedTimeRange ? parsedTimeRange.range : undefined,
};
return new Promise((resolve, reject) => {
return requestSearchSource.getSearchRequestBody().then(q => {
const queryHash = calculateObjectHash(q);
if (shouldQuery(queryHash)) {
const lastAggConfig = aggs;
vis.API.inspectorAdapters.requests.reset();
const request = vis.API.inspectorAdapters.requests.start('Data', {
description: `This request queries Elasticsearch to fetch the data for the visualization.`,
});
request.stats(getRequestInspectorStats(requestSearchSource));
const tabifyCacheHash = calculateObjectHash({ tabifyAggs, ...tabifyParams });
// We only need to reexecute tabify, if either we did a new request or some input params to tabify changed
const shouldCalculateNewTabify = shouldQuery || (searchSource.lastTabifyHash !== tabifyCacheHash);
requestSearchSource.fetch().then(resp => {
searchSource.lastQuery = queryHash;
if (shouldCalculateNewTabify) {
searchSource.lastTabifyHash = tabifyCacheHash;
searchSource.tabifiedResponse = tabifyAggResponse(tabifyAggs, searchSource.finalResponse, tabifyParams);
}
request
.stats(getResponseInspectorStats(searchSource, resp))
.ok({ json: resp });
searchSource.rawResponse = resp;
return _.cloneDeep(resp);
}).then(async resp => {
for (const agg of aggs) {
if (_.has(agg, 'type.postFlightRequest')) {
resp = await agg.type.postFlightRequest(
resp,
aggs,
agg,
requestSearchSource,
vis.API.inspectorAdapters
);
}
}
searchSource.finalResponse = resp;
const parsedTimeRange = timeRange ? getTime(aggs.indexPattern, timeRange) : null;
searchSource.tabifiedResponse = tabifyAggResponse(vis.getAggConfig(), resp, {
metricsAtAllLevels: vis.isHierarchical(),
partialRows,
timeRange: parsedTimeRange ? parsedTimeRange.range : undefined,
});
vis.API.inspectorAdapters.data.setTabularLoader(
() => buildTabularInspectorData(vis, searchSource, lastAggConfig),
{ returnsFormattedValues: true }
);
resolve(searchSource.tabifiedResponse);
}).catch(e => reject(e));
requestSearchSource.getSearchRequestBody().then(req => {
request.json(req);
});
} else {
resolve(searchSource.tabifiedResponse);
}
});
});
return searchSource.tabifiedResponse;
}
};
};

View file

@ -192,5 +192,169 @@ export default function ({ getService, getPageObjects }) {
log.debug(data);
expect(data.length).to.be.greaterThan(0);
});
describe('metricsOnAllLevels', () => {
before(async () => {
await PageObjects.visualize.navigateToNewVisualization();
await PageObjects.visualize.clickDataTable();
await PageObjects.visualize.clickNewSearch();
await PageObjects.header.setAbsoluteRange(fromTime, toTime);
await PageObjects.visualize.clickBucket('Split Rows');
await PageObjects.visualize.selectAggregation('Terms');
await PageObjects.visualize.selectField('extension.raw');
await PageObjects.visualize.setSize(2);
await PageObjects.visualize.toggleOpenEditor(2, 'false');
await PageObjects.visualize.clickAddBucket();
await PageObjects.visualize.clickBucket('Split Rows');
await PageObjects.visualize.selectAggregation('Terms');
await PageObjects.visualize.selectField('geo.dest');
await PageObjects.visualize.toggleOpenEditor(3, 'false');
await PageObjects.visualize.clickGo();
});
it('should show correct data without showMetricsAtAllLevels', async () => {
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 () => {
await PageObjects.visualize.clickOptionsTab();
await PageObjects.visualize.checkCheckbox('showMetricsAtAllLevels');
await PageObjects.visualize.clickGo();
const data = await PageObjects.visualize.getTableVisContent();
expect(data).to.be.eql([
[ 'jpg', '9,109', 'CN', '1,718' ],
[ 'jpg', '9,109', 'IN', '1,511' ],
[ 'jpg', '9,109', 'US', '770' ],
[ 'jpg', '9,109', 'ID', '314' ],
[ 'jpg', '9,109', 'PK', '244' ],
[ 'css', '2,159', 'CN', '422' ],
[ 'css', '2,159', 'IN', '346' ],
[ 'css', '2,159', 'US', '189' ],
[ 'css', '2,159', 'ID', '68' ],
[ 'css', '2,159', 'BR', '58' ],
]);
});
it('should show metrics other than count on each level', async () => {
await PageObjects.visualize.clickData();
await PageObjects.visualize.clickAddMetric();
await PageObjects.visualize.clickBucket('Metric', 'metric');
await PageObjects.visualize.selectAggregation('Average', 'metrics');
await PageObjects.visualize.selectField('bytes', 'metrics');
await PageObjects.visualize.clickGo();
const data = await PageObjects.visualize.getTableVisContent();
expect(data).to.be.eql([
[ 'jpg', '9,109', '5.469KB', 'CN', '1,718', '5.477KB' ],
[ 'jpg', '9,109', '5.469KB', 'IN', '1,511', '5.456KB' ],
[ 'jpg', '9,109', '5.469KB', 'US', '770', '5.371KB' ],
[ 'jpg', '9,109', '5.469KB', 'ID', '314', '5.424KB' ],
[ 'jpg', '9,109', '5.469KB', 'PK', '244', '5.41KB' ],
[ 'css', '2,159', '5.566KB', 'CN', '422', '5.712KB' ],
[ 'css', '2,159', '5.566KB', 'IN', '346', '5.754KB' ],
[ 'css', '2,159', '5.566KB', 'US', '189', '5.333KB' ],
[ 'css', '2,159', '5.566KB', 'ID', '68', '4.82KB' ],
[ 'css', '2,159', '5.566KB', 'BR', '58', '5.915KB' ],
]);
});
});
describe('split tables', () => {
before(async () => {
await PageObjects.visualize.navigateToNewVisualization();
await PageObjects.visualize.clickDataTable();
await PageObjects.visualize.clickNewSearch();
await PageObjects.header.setAbsoluteRange(fromTime, toTime);
await PageObjects.visualize.clickBucket('Split Table');
await PageObjects.visualize.selectAggregation('Terms');
await PageObjects.visualize.selectField('extension.raw');
await PageObjects.visualize.setSize(2);
await PageObjects.visualize.toggleOpenEditor(2, 'false');
await PageObjects.visualize.clickAddBucket();
await PageObjects.visualize.clickBucket('Split Rows');
await PageObjects.visualize.selectAggregation('Terms');
await PageObjects.visualize.selectField('geo.dest');
await PageObjects.visualize.setSize(3);
await PageObjects.visualize.toggleOpenEditor(3, 'false');
await PageObjects.visualize.clickAddBucket();
await PageObjects.visualize.clickBucket('Split Rows');
await PageObjects.visualize.selectAggregation('Terms');
await PageObjects.visualize.selectField('geo.src');
await PageObjects.visualize.setSize(3);
await PageObjects.visualize.clickGo();
});
it('should have a splitted table', async () => {
const data = await PageObjects.visualize.getTableVisContent();
expect(data).to.be.eql([
[
[ 'CN', 'CN', '330' ],
[ 'CN', 'IN', '274' ],
[ 'CN', 'US', '140' ],
[ 'IN', 'CN', '286' ],
[ 'IN', 'IN', '281' ],
[ 'IN', 'US', '133' ],
[ 'US', 'CN', '135' ],
[ 'US', 'IN', '134' ],
[ 'US', 'US', '52' ],
],
[
[ 'CN', 'CN', '90' ],
[ 'CN', 'IN', '84' ],
[ 'CN', 'US', '27' ],
[ 'IN', 'CN', '69' ],
[ 'IN', 'IN', '58' ],
[ 'IN', 'US', '34' ],
[ 'US', 'IN', '36' ],
[ 'US', 'CN', '29' ],
[ 'US', 'US', '13' ],
]
]);
});
it('should not show metrics for split bucket when using showMetricsAtAllLevels', async () => {
await PageObjects.visualize.clickOptionsTab();
await PageObjects.visualize.checkCheckbox('showMetricsAtAllLevels');
await PageObjects.visualize.clickGo();
const data = await PageObjects.visualize.getTableVisContent();
expect(data).to.be.eql([
[
[ 'CN', '1,718', 'CN', '330' ],
[ 'CN', '1,718', 'IN', '274' ],
[ 'CN', '1,718', 'US', '140' ],
[ 'IN', '1,511', 'CN', '286' ],
[ 'IN', '1,511', 'IN', '281' ],
[ 'IN', '1,511', 'US', '133' ],
[ 'US', '770', 'CN', '135' ],
[ 'US', '770', 'IN', '134' ],
[ 'US', '770', 'US', '52' ],
],
[
[ 'CN', '422', 'CN', '90' ],
[ 'CN', '422', 'IN', '84' ],
[ 'CN', '422', 'US', '27' ],
[ 'IN', '346', 'CN', '69' ],
[ 'IN', '346', 'IN', '58' ],
[ 'IN', '346', 'US', '34' ],
[ 'US', '189', 'IN', '36' ],
[ 'US', '189', 'CN', '29' ],
[ 'US', '189', 'US', '13' ],
]
]);
});
});
});
}

View file

@ -31,6 +31,7 @@ export function VisualizePageProvider({ getService, getPageObjects }) {
const log = getService('log');
const flyout = getService('flyout');
const renderable = getService('renderable');
const table = getService('table');
const PageObjects = getPageObjects(['common', 'header']);
const defaultFindTimeout = config.get('timeouts.find');
@ -595,9 +596,9 @@ export function VisualizePageProvider({ getService, getPageObjects }) {
}
async setSize(newValue) {
const input = await find.byCssSelector('input[name="size"]');
const input = await find.byCssSelector(`vis-editor-agg-params[aria-hidden="false"] input[name="size"]`);
await input.clearValue();
await input.type(newValue);
await input.type(String(newValue));
}
async toggleDisabledAgg(agg) {
@ -940,11 +941,45 @@ export function VisualizePageProvider({ getService, getPageObjects }) {
return await rect.getAttribute('height');
}
/**
* If you are writing new tests, you should rather look into getTableVisContent method instead.
*/
async getTableVisData() {
const dataTable = await testSubjects.find('paginated-table-body');
return await dataTable.getVisibleText();
}
/**
* This function is the newer function to retrieve data from within a table visualization.
* It uses a better return format, than the old getTableVisData, by properly splitting
* cell values into arrays. Please use this function for newer tests.
*/
async getTableVisContent({ stripEmptyRows = true } = { }) {
const container = await testSubjects.find('tableVis');
const allTables = await testSubjects.findAllDescendant('paginated-table-body', container);
if (allTables.length === 0) {
return [];
}
const allData = await Promise.all(allTables.map(async (t) => {
let data = await table.getDataFromElement(t);
if (stripEmptyRows) {
data = data.filter(row => row.length > 0 && row.some(cell => cell.trim().length > 0));
}
return data;
}));
if (allTables.length === 1) {
// If there was only one table we return only the data for that table
// This prevents an unnecessary array around that single table, which
// is the case we have in most tests.
return allData[0];
}
return allData;
}
async getInspectorTableData() {
// TODO: we should use datat-test-subj=inspectorTable as soon as EUI supports it
const inspectorPanel = await testSubjects.find('inspectorPanel');

View file

@ -25,6 +25,10 @@ export function TableProvider({ getService }) {
async getDataFromTestSubj(testSubj) {
const table = await testSubjects.find(testSubj);
return await this.getDataFromElement(table);
}
async getDataFromElement(table) {
// Convert the data into a nested array format:
// [ [cell1_in_row1, cell2_in_row1], [cell1_in_row2, cell2_in_row2] ]
const rows = await table.findAllByTagName('tr');