mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
* 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:
parent
66a8307a1a
commit
a2ccf779ba
5 changed files with 273 additions and 68 deletions
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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' ],
|
||||
]
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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');
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue