fixing other bucket filters (#24217) (#24403)

* fixing other bucket filters

* adding selenium tests

* using lodash flatten

* using lodash flatten

* fixing based on tims review

* Fix functional tests
This commit is contained in:
Tim Roes 2018-10-23 22:37:57 +02:00 committed by GitHub
parent 260cf636f9
commit 2488277279
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 131 additions and 35 deletions

View file

@ -112,6 +112,8 @@ const buildOtherBucketAgg = (aggConfigs, aggWithOtherBucket, response) => {
const filterAgg = aggConfigs.createAggConfig({
type: 'filters',
id: 'other',
}, {
addToAggConfigs: false,
});
// nest all the child aggregations of aggWithOtherBucket
@ -129,8 +131,8 @@ const buildOtherBucketAgg = (aggConfigs, aggWithOtherBucket, response) => {
if (aggIndex < index) {
_.each(agg.buckets, (bucket, bucketObjKey) => {
const bucketKey = currentAgg.getKey(bucket, Number.isInteger(bucketObjKey) ? null : bucketObjKey);
const filter = _.cloneDeep(bucket.filter) || currentAgg.createFilter(bucketKey);
const newFilters = [...filters, filter];
const filter = _.cloneDeep(bucket.filters) || currentAgg.createFilter(bucketKey);
const newFilters = _.flatten([...filters, filter]);
walkBucketTree(newAggIndex, bucket, newAgg.id, newFilters, `${key}-${bucketKey.toString()}`);
});
return;

View file

@ -0,0 +1,29 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import _ from 'lodash';
export function FilterBarPushFiltersProvider() {
return function ($state) {
if (!_.isObject($state)) throw new Error('pushFilters requires a state object');
return function (filters) {
$state.$newFilters = filters;
};
};
}

View file

@ -43,7 +43,7 @@ const LegacyResponseHandlerProvider = function () {
const splitMap = {};
let splitIndex = 0;
table.rows.forEach(row => {
table.rows.forEach((row, rowIndex) => {
const splitValue = row[splitColumn.id];
const splitColumnIndex = table.columns.findIndex(column => column === splitColumn);
@ -74,6 +74,11 @@ const LegacyResponseHandlerProvider = function () {
const newRow = _.map(converted.tables[tableIndex].tables[0].columns, column => {
const value = row[column.id];
const aggConfigResult = new AggConfigResult(column.aggConfig, previousSplitAgg, value, value);
aggConfigResult.rawData = {
table: table,
columnIndex: table.columns.findIndex(c => c.id === column.id),
rowIndex: rowIndex,
};
if (column.aggConfig.type.type === 'buckets') {
previousSplitAgg = aggConfigResult;
}
@ -86,11 +91,16 @@ const LegacyResponseHandlerProvider = function () {
converted.tables.push({
columns: table.columns.map(column => ({ title: column.name, ...column })),
rows: table.rows.map(row => {
rows: table.rows.map((row, rowIndex) => {
let previousSplitAgg;
return table.columns.map(column => {
return table.columns.map((column, columnIndex) => {
const value = row[column.id];
const aggConfigResult = new AggConfigResult(column.aggConfig, previousSplitAgg, value, value);
aggConfigResult.rawData = {
table: table,
column: columnIndex,
row: rowIndex,
};
if (column.aggConfig.type.type === 'buckets') {
previousSplitAgg = aggConfigResult;
}

View file

@ -34,7 +34,7 @@ import { AggConfigs } from './agg_configs';
import { PersistedState } from '../persisted_state';
import { onBrushEvent } from '../utils/brush_event';
import { FilterBarQueryFilterProvider } from '../filter_bar/query_filter';
import { FilterBarClickHandlerProvider } from '../filter_bar/filter_bar_click_handler';
import { FilterBarPushFiltersProvider } from '../filter_bar/push_filters';
import { updateVisualizationConfig } from './vis_update';
import { SearchSourceProvider } from '../courier/search_source';
import { SavedObjectsClientProvider } from '../saved_objects';
@ -54,7 +54,7 @@ const getTerms = (table, columnIndex, rowIndex) => {
return row[column.id] === table.rows[rowIndex][column.id] || i >= columnIndex;
});
});
const terms = rows.map(row => row[columnIndex]);
const terms = rows.map(row => row[table.columns[columnIndex].id]);
return [...new Set(terms.filter(term => {
const notOther = term !== '__other__';
@ -66,9 +66,9 @@ const getTerms = (table, columnIndex, rowIndex) => {
export function VisProvider(Private, indexPatterns, getAppState) {
const visTypes = Private(VisTypesRegistryProvider);
const queryFilter = Private(FilterBarQueryFilterProvider);
const filterBarClickHandler = Private(FilterBarClickHandlerProvider);
const SearchSource = Private(SearchSourceProvider);
const savedObjectsClient = Private(SavedObjectsClientProvider);
const filterBarPushFilters = Private(FilterBarPushFiltersProvider);
class Vis extends EventEmitter {
constructor(indexPattern, visState) {
@ -99,14 +99,22 @@ export function VisProvider(Private, indexPatterns, getAppState) {
// the filter method will be removed in the near feature
// you should rather use addFilter method below
filter: (event) => {
let data = event.datum.aggConfigResult;
const filters = [];
while (data.$parent) {
const { key, rawData } = data.$parent;
const { table, column, row } = rawData;
filters.push(this.API.events.createFilter(table, column, row, key));
data = data.$parent;
}
const appState = getAppState();
filterBarClickHandler(appState)(event);
filterBarPushFilters(appState)(_.flatten(filters));
},
addFilter: (data, columnIndex, rowIndex, cellValue) => {
createFilter: (data, columnIndex, rowIndex, cellValue) => {
const { aggConfig, id: columnId } = data.columns[columnIndex];
let filter = [];
const value = rowIndex > -1 ? data.rows[rowIndex][columnId] : cellValue;
if (!value) {
if (value === null || value === undefined) {
return;
}
if (aggConfig.type.name === 'terms' && aggConfig.params.otherBucket) {
@ -115,6 +123,10 @@ export function VisProvider(Private, indexPatterns, getAppState) {
} else {
filter = aggConfig.createFilter(value);
}
return filter;
},
addFilter: (data, columnIndex, rowIndex, cellValue) => {
const filter = this.API.events.createFilter(data, columnIndex, rowIndex, cellValue);
queryFilter.addFilters(filter);
}, brush: (event) => {
onBrushEvent(event, getAppState());

View file

@ -21,6 +21,7 @@ import expect from 'expect.js';
export default function ({ getService, getPageObjects }) {
const log = getService('log');
const filterBar = getService('filterBar');
const PageObjects = getPageObjects(['common', 'visualize', 'header', 'settings']);
const fromTime = '2015-09-19 06:31:44.000';
const toTime = '2015-09-23 18:31:44.000';
@ -89,29 +90,64 @@ export default function ({ getService, getPageObjects }) {
expect(data).to.eql(expectedTableData);
});
it('should show other and missing bucket', async function () {
const expectedTableData = [ 'win 8', 'win xp', 'win 7', 'ios', 'Missing', 'Other' ];
describe('other bucket', () => {
it('should show other and missing bucket', async function () {
const expectedTableData = [ 'win 8', 'win xp', 'win 7', 'ios', 'Missing', 'Other' ];
await PageObjects.visualize.navigateToNewVisualization();
log.debug('clickPieChart');
await PageObjects.visualize.clickPieChart();
await PageObjects.visualize.clickNewSearch();
log.debug('Set absolute time range from \"' + fromTime + '\" to \"' + toTime + '\"');
await PageObjects.header.setAbsoluteRange(fromTime, toTime);
log.debug('select bucket Split Slices');
await PageObjects.visualize.clickBucket('Split Slices');
log.debug('Click aggregation Histogram');
await PageObjects.visualize.selectAggregation('Terms');
log.debug('Click field memory');
await PageObjects.visualize.selectField('machine.os.raw');
await PageObjects.visualize.toggleOtherBucket();
await PageObjects.visualize.toggleMissingBucket();
log.debug('clickGo');
await PageObjects.visualize.clickGo();
await PageObjects.common.sleep(1003);
const pieData = await PageObjects.visualize.getPieChartLabels();
log.debug('pieData.length = ' + pieData.length);
expect(pieData).to.eql(expectedTableData);
await PageObjects.visualize.navigateToNewVisualization();
log.debug('clickPieChart');
await PageObjects.visualize.clickPieChart();
await PageObjects.visualize.clickNewSearch();
log.debug(`Set absolute time range from "${fromTime}" to "${toTime}"`);
await PageObjects.header.setAbsoluteRange(fromTime, toTime);
log.debug('select bucket Split Slices');
await PageObjects.visualize.clickBucket('Split Slices');
log.debug('Click aggregation Terms');
await PageObjects.visualize.selectAggregation('Terms');
log.debug('Click field machine.os.raw');
await PageObjects.visualize.selectField('machine.os.raw');
await PageObjects.visualize.toggleOtherBucket();
await PageObjects.visualize.toggleMissingBucket();
log.debug('clickGo');
await PageObjects.visualize.clickGo();
await PageObjects.common.sleep(1003);
const pieData = await PageObjects.visualize.getPieChartLabels();
log.debug(`pieData.length = ${pieData.length}`);
expect(pieData).to.eql(expectedTableData);
});
it('should apply correct filter on other bucket', async () => {
const expectedTableData = [ 'Missing', 'osx' ];
await PageObjects.visualize.filterPieSlice('Other');
await PageObjects.header.waitUntilLoadingHasFinished();
const pieData = await PageObjects.visualize.getPieChartLabels();
log.debug(`pieData.length = ${pieData.length}`);
expect(pieData).to.eql(expectedTableData);
await filterBar.removeFilter('machine.os.raw');
});
it('should show two levels of other buckets', async () => {
const expectedTableData = [ 'win 8', 'CN', 'IN', 'US', 'ID', 'BR', 'Other', 'win xp',
'CN', 'IN', 'US', 'ID', 'BR', 'Other', 'win 7', 'CN', 'IN', 'US', 'ID', 'BR', 'Other',
'ios', 'IN', 'CN', 'US', 'ID', 'BR', 'Other', 'Missing', 'CN', 'IN', 'US', 'BR', 'PK',
'Other', 'Other', 'IN', 'CN', 'US', 'ID', 'BR', 'Other' ];
await PageObjects.visualize.toggleOpenEditor(2, 'false');
await PageObjects.visualize.clickAddBucket();
await PageObjects.visualize.clickBucket('Split Slices');
await PageObjects.visualize.selectAggregation('Terms');
log.debug('Click field geo.src');
await PageObjects.visualize.selectField('geo.src');
await PageObjects.visualize.toggleOtherBucket();
await PageObjects.visualize.toggleMissingBucket();
log.debug('clickGo');
await PageObjects.visualize.clickGo();
await PageObjects.header.waitUntilLoadingHasFinished();
const pieData = await PageObjects.visualize.getPieChartLabels();
log.debug(`pieData.length = ${pieData.length}`);
expect(pieData).to.eql(expectedTableData);
});
});
describe('disabled aggs', () => {

View file

@ -586,11 +586,11 @@ export function VisualizePageProvider({ getService, getPageObjects }) {
}
async toggleOtherBucket() {
return await find.clickByCssSelector('input[name="showOther"]');
return await find.clickByCssSelector('vis-editor-agg-params:not(.ng-hide) input[name="showOther"]');
}
async toggleMissingBucket() {
return await find.clickByCssSelector('input[name="showMissing"]');
return await find.clickByCssSelector('vis-editor-agg-params:not(.ng-hide) input[name="showMissing"]');
}
async isApplyEnabled() {
@ -1114,6 +1114,13 @@ export function VisualizePageProvider({ getService, getPageObjects }) {
return await bucketType.click();
}
async filterPieSlice(name) {
const slice = await this.getPieSlice(name);
// Since slice is an SVG element we can't simply use .click() for it
await remote.moveMouseTo(slice);
await remote.clickMouseButton();
}
async getPieSlice(name) {
return await testSubjects.find(`pieSlice-${name.split(' ').join('-')}`);
}