Support nested fields in existing filter types (#49537)
* Add automatic support for nested fields in existing filter types * Index pattern could be undefined * add test for handleNestedFilter function * remove console.log * add tests for all "getFilterField" functions * update migrateFilters to work on full filter objects so that it doesn't have to worry about queries that have been wrapped with `nested` * add test to ensure fromFilters auto wraps filters on nested fields * Add smoke test for nested filter and move filter editor tests into their own suite for easier running and debugging * fix bad type change * dedupe filterToQuery logic * fix helper that wasn't doing what it said it did * Convert test from pre-merge to jest * Use new time range style
This commit is contained in:
parent
c7046a080f
commit
77dca06253
|
@ -0,0 +1,322 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const indexPatternResponse = {
|
||||
id: 'logstash-*',
|
||||
title: 'logstash-*',
|
||||
fields: [
|
||||
{
|
||||
name: 'bytes',
|
||||
type: 'number',
|
||||
esTypes: ['long'],
|
||||
count: 10,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: true,
|
||||
},
|
||||
{
|
||||
name: 'ssl',
|
||||
type: 'boolean',
|
||||
esTypes: ['boolean'],
|
||||
count: 20,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: true,
|
||||
},
|
||||
{
|
||||
name: '@timestamp',
|
||||
type: 'date',
|
||||
esTypes: ['date'],
|
||||
count: 30,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: true,
|
||||
},
|
||||
{
|
||||
name: 'time',
|
||||
type: 'date',
|
||||
esTypes: ['date'],
|
||||
count: 30,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: true,
|
||||
},
|
||||
{
|
||||
name: '@tags',
|
||||
type: 'string',
|
||||
esTypes: ['keyword'],
|
||||
count: 0,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: true,
|
||||
},
|
||||
{
|
||||
name: 'utc_time',
|
||||
type: 'date',
|
||||
esTypes: ['date'],
|
||||
count: 0,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: true,
|
||||
},
|
||||
{
|
||||
name: 'phpmemory',
|
||||
type: 'number',
|
||||
esTypes: ['integer'],
|
||||
count: 0,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: true,
|
||||
},
|
||||
{
|
||||
name: 'ip',
|
||||
type: 'ip',
|
||||
esTypes: ['ip'],
|
||||
count: 0,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: true,
|
||||
},
|
||||
{
|
||||
name: 'request_body',
|
||||
type: 'attachment',
|
||||
esTypes: ['attachment'],
|
||||
count: 0,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: true,
|
||||
},
|
||||
{
|
||||
name: 'point',
|
||||
type: 'geo_point',
|
||||
esTypes: ['geo_point'],
|
||||
count: 0,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: true,
|
||||
},
|
||||
{
|
||||
name: 'area',
|
||||
type: 'geo_shape',
|
||||
esTypes: ['geo_shape'],
|
||||
count: 0,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: true,
|
||||
},
|
||||
{
|
||||
name: 'hashed',
|
||||
type: 'murmur3',
|
||||
esTypes: ['murmur3'],
|
||||
count: 0,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
aggregatable: false,
|
||||
readFromDocValues: false,
|
||||
},
|
||||
{
|
||||
name: 'geo.coordinates',
|
||||
type: 'geo_point',
|
||||
esTypes: ['geo_point'],
|
||||
count: 0,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: true,
|
||||
},
|
||||
{
|
||||
name: 'extension',
|
||||
type: 'string',
|
||||
esTypes: ['keyword'],
|
||||
count: 0,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: true,
|
||||
},
|
||||
{
|
||||
name: 'machine.os',
|
||||
type: 'string',
|
||||
esTypes: ['text'],
|
||||
count: 0,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: false,
|
||||
},
|
||||
{
|
||||
name: 'machine.os.raw',
|
||||
type: 'string',
|
||||
esTypes: ['keyword'],
|
||||
count: 0,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: true,
|
||||
subType: { multi: { parent: 'machine.os' } },
|
||||
},
|
||||
{
|
||||
name: 'geo.src',
|
||||
type: 'string',
|
||||
esTypes: ['keyword'],
|
||||
count: 0,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: true,
|
||||
},
|
||||
{
|
||||
name: '_id',
|
||||
type: 'string',
|
||||
esTypes: ['_id'],
|
||||
count: 0,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: false,
|
||||
},
|
||||
{
|
||||
name: '_type',
|
||||
type: 'string',
|
||||
esTypes: ['_type'],
|
||||
count: 0,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: false,
|
||||
},
|
||||
{
|
||||
name: '_source',
|
||||
type: '_source',
|
||||
esTypes: ['_source'],
|
||||
count: 0,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: false,
|
||||
},
|
||||
{
|
||||
name: 'non-filterable',
|
||||
type: 'string',
|
||||
esTypes: ['text'],
|
||||
count: 0,
|
||||
scripted: false,
|
||||
searchable: false,
|
||||
aggregatable: true,
|
||||
readFromDocValues: false,
|
||||
},
|
||||
{
|
||||
name: 'non-sortable',
|
||||
type: 'string',
|
||||
esTypes: ['text'],
|
||||
count: 0,
|
||||
scripted: false,
|
||||
searchable: false,
|
||||
aggregatable: false,
|
||||
readFromDocValues: false,
|
||||
},
|
||||
{
|
||||
name: 'custom_user_field',
|
||||
type: 'conflict',
|
||||
esTypes: ['long', 'text'],
|
||||
count: 0,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: true,
|
||||
},
|
||||
{
|
||||
name: 'script string',
|
||||
type: 'string',
|
||||
count: 0,
|
||||
scripted: true,
|
||||
script: "'i am a string'",
|
||||
lang: 'expression',
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: false,
|
||||
},
|
||||
{
|
||||
name: 'script number',
|
||||
type: 'number',
|
||||
count: 0,
|
||||
scripted: true,
|
||||
script: '1234',
|
||||
lang: 'expression',
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: false,
|
||||
},
|
||||
{
|
||||
name: 'script date',
|
||||
type: 'date',
|
||||
count: 0,
|
||||
scripted: true,
|
||||
script: '1234',
|
||||
lang: 'painless',
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: false,
|
||||
},
|
||||
{
|
||||
name: 'script murmur3',
|
||||
type: 'murmur3',
|
||||
count: 0,
|
||||
scripted: true,
|
||||
script: '1234',
|
||||
lang: 'expression',
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: false,
|
||||
},
|
||||
{
|
||||
name: 'nestedField.child',
|
||||
type: 'string',
|
||||
esTypes: ['text'],
|
||||
count: 0,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
aggregatable: false,
|
||||
readFromDocValues: false,
|
||||
subType: { nested: { path: 'nestedField' } },
|
||||
},
|
||||
{
|
||||
name: 'nestedField.nestedChild.doublyNestedChild',
|
||||
type: 'string',
|
||||
esTypes: ['text'],
|
||||
count: 0,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
aggregatable: false,
|
||||
readFromDocValues: false,
|
||||
subType: { nested: { path: 'nestedField.nestedChild' } },
|
||||
},
|
||||
],
|
||||
};
|
|
@ -144,5 +144,30 @@ describe('build query', () => {
|
|||
|
||||
expect(result.filter).toEqual(expectedESQueries);
|
||||
});
|
||||
|
||||
test('should wrap filters targeting nested fields in a nested query', () => {
|
||||
const filters = [
|
||||
{
|
||||
exists: { field: 'nestedField.child' },
|
||||
meta: { type: 'exists', alias: '', disabled: false, negate: false },
|
||||
},
|
||||
];
|
||||
|
||||
const expectedESQueries = [
|
||||
{
|
||||
nested: {
|
||||
path: 'nestedField',
|
||||
query: {
|
||||
exists: {
|
||||
field: 'nestedField.child',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const result = buildQueryFromFilters(filters, indexPattern);
|
||||
expect(result.filter).toEqual(expectedESQueries);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -21,6 +21,7 @@ import { migrateFilter } from './migrate_filter';
|
|||
import { filterMatchesIndex } from './filter_matches_index';
|
||||
import { Filter, cleanFilter, isFilterDisabled } from '../filters';
|
||||
import { IIndexPattern } from '../../index_patterns';
|
||||
import { handleNestedFilter } from './handle_nested_filter';
|
||||
|
||||
/**
|
||||
* Create a filter that can be reversed for filters with negate set
|
||||
|
@ -59,20 +60,22 @@ export const buildQueryFromFilters = (
|
|||
) => {
|
||||
filters = filters.filter(filter => filter && !isFilterDisabled(filter));
|
||||
|
||||
const filtersToESQueries = (negate: boolean) => {
|
||||
return filters
|
||||
.filter(filterNegate(negate))
|
||||
.filter(filter => !ignoreFilterIfFieldNotInIndex || filterMatchesIndex(filter, indexPattern))
|
||||
.map(filter => {
|
||||
return migrateFilter(filter, indexPattern);
|
||||
})
|
||||
.map(filter => handleNestedFilter(filter, indexPattern))
|
||||
.map(translateToQuery)
|
||||
.map(cleanFilter);
|
||||
};
|
||||
|
||||
return {
|
||||
must: [],
|
||||
filter: filters
|
||||
.filter(filterNegate(false))
|
||||
.filter(filter => !ignoreFilterIfFieldNotInIndex || filterMatchesIndex(filter, indexPattern))
|
||||
.map(translateToQuery)
|
||||
.map(cleanFilter)
|
||||
.map(filter => migrateFilter(filter, indexPattern)),
|
||||
filter: filtersToESQueries(false),
|
||||
should: [],
|
||||
must_not: filters
|
||||
.filter(filterNegate(true))
|
||||
.filter(filter => !ignoreFilterIfFieldNotInIndex || filterMatchesIndex(filter, indexPattern))
|
||||
.map(translateToQuery)
|
||||
.map(cleanFilter)
|
||||
.map(filter => migrateFilter(filter, indexPattern)),
|
||||
must_not: filtersToESQueries(true),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* 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 { handleNestedFilter } from './handle_nested_filter';
|
||||
import { fields } from '../../index_patterns/mocks';
|
||||
import { buildPhraseFilter, buildQueryFilter } from '../filters';
|
||||
import { IFieldType, IIndexPattern } from '../../index_patterns';
|
||||
|
||||
describe('handleNestedFilter', function() {
|
||||
const indexPattern: IIndexPattern = ({
|
||||
id: 'logstash-*',
|
||||
fields,
|
||||
} as unknown) as IIndexPattern;
|
||||
|
||||
it("should return the filter's query wrapped in nested query if the target field is nested", () => {
|
||||
const field = getField('nestedField.child');
|
||||
const filter = buildPhraseFilter(field!, 'foo', indexPattern);
|
||||
const result = handleNestedFilter(filter, indexPattern);
|
||||
expect(result).toEqual({
|
||||
meta: {
|
||||
index: 'logstash-*',
|
||||
},
|
||||
nested: {
|
||||
path: 'nestedField',
|
||||
query: {
|
||||
match_phrase: {
|
||||
'nestedField.child': 'foo',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return filter untouched if it does not target a nested field', () => {
|
||||
const field = getField('extension');
|
||||
const filter = buildPhraseFilter(field!, 'jpg', indexPattern);
|
||||
const result = handleNestedFilter(filter, indexPattern);
|
||||
expect(result).toBe(filter);
|
||||
});
|
||||
|
||||
it('should return filter untouched if it does not target a field from the given index pattern', () => {
|
||||
const field = { ...getField('extension'), name: 'notarealfield' };
|
||||
const filter = buildPhraseFilter(field as IFieldType, 'jpg', indexPattern);
|
||||
const result = handleNestedFilter(filter, indexPattern);
|
||||
expect(result).toBe(filter);
|
||||
});
|
||||
|
||||
it('should return filter untouched if no index pattern is provided', () => {
|
||||
const field = getField('extension');
|
||||
const filter = buildPhraseFilter(field!, 'jpg', indexPattern);
|
||||
const result = handleNestedFilter(filter);
|
||||
expect(result).toBe(filter);
|
||||
});
|
||||
|
||||
it('should return the filter untouched if a target field cannot be determined', () => {
|
||||
// for example, we don't support query_string queries
|
||||
const filter = buildQueryFilter(
|
||||
{
|
||||
query: {
|
||||
query_string: {
|
||||
query: 'response:200',
|
||||
},
|
||||
},
|
||||
},
|
||||
'logstash-*',
|
||||
'foo'
|
||||
);
|
||||
const result = handleNestedFilter(filter);
|
||||
expect(result).toBe(filter);
|
||||
});
|
||||
|
||||
function getField(name: string) {
|
||||
return indexPattern.fields.find(field => field.name === name);
|
||||
}
|
||||
});
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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 { getFilterField, cleanFilter, Filter } from '../filters';
|
||||
import { IIndexPattern } from '../../index_patterns';
|
||||
|
||||
export const handleNestedFilter = (filter: Filter, indexPattern?: IIndexPattern) => {
|
||||
if (!indexPattern) return filter;
|
||||
|
||||
const fieldName = getFilterField(filter);
|
||||
if (!fieldName) {
|
||||
return filter;
|
||||
}
|
||||
|
||||
const field = indexPattern.fields.find(indexPatternField => indexPatternField.name === fieldName);
|
||||
if (!field || !field.subType || !field.subType.nested || !field.subType.nested.path) {
|
||||
return filter;
|
||||
}
|
||||
|
||||
const query = cleanFilter(filter);
|
||||
|
||||
return {
|
||||
meta: filter.meta,
|
||||
nested: {
|
||||
path: field.subType.nested.path,
|
||||
query: query.query || query,
|
||||
},
|
||||
};
|
||||
};
|
|
@ -23,26 +23,32 @@ import { PhraseFilter, MatchAllFilter } from '../filters';
|
|||
|
||||
describe('migrateFilter', function() {
|
||||
const oldMatchPhraseFilter = ({
|
||||
match: {
|
||||
fieldFoo: {
|
||||
query: 'foobar',
|
||||
type: 'phrase',
|
||||
query: {
|
||||
match: {
|
||||
fieldFoo: {
|
||||
query: 'foobar',
|
||||
type: 'phrase',
|
||||
},
|
||||
},
|
||||
},
|
||||
meta: {},
|
||||
} as unknown) as DeprecatedMatchPhraseFilter;
|
||||
|
||||
const newMatchPhraseFilter = ({
|
||||
match_phrase: {
|
||||
fieldFoo: {
|
||||
query: 'foobar',
|
||||
query: {
|
||||
match_phrase: {
|
||||
fieldFoo: {
|
||||
query: 'foobar',
|
||||
},
|
||||
},
|
||||
},
|
||||
meta: {},
|
||||
} as unknown) as PhraseFilter;
|
||||
|
||||
it('should migrate match filters of type phrase', function() {
|
||||
const migratedFilter = migrateFilter(oldMatchPhraseFilter, undefined);
|
||||
|
||||
expect(isEqual(migratedFilter, newMatchPhraseFilter)).toBe(true);
|
||||
expect(migratedFilter).toEqual(newMatchPhraseFilter);
|
||||
});
|
||||
|
||||
it('should not modify the original filter', function() {
|
||||
|
|
|
@ -22,31 +22,27 @@ import { getConvertedValueForField } from '../filters';
|
|||
import { Filter } from '../filters';
|
||||
import { IIndexPattern } from '../../index_patterns';
|
||||
|
||||
/** @deprecated
|
||||
* see https://github.com/elastic/elasticsearch/pull/17508
|
||||
* */
|
||||
export interface DeprecatedMatchPhraseFilter extends Filter {
|
||||
match: {
|
||||
[field: string]: {
|
||||
query: any;
|
||||
type: 'phrase';
|
||||
query: {
|
||||
match: {
|
||||
[field: string]: {
|
||||
query: any;
|
||||
type: 'phrase';
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/** @deprecated
|
||||
* see https://github.com/elastic/elasticsearch/pull/17508
|
||||
* */
|
||||
function isMatchPhraseFilter(filter: any): filter is DeprecatedMatchPhraseFilter {
|
||||
const fieldName = filter.match && Object.keys(filter.match)[0];
|
||||
function isDeprecatedMatchPhraseFilter(filter: any): filter is DeprecatedMatchPhraseFilter {
|
||||
const fieldName = filter.query && filter.query.match && Object.keys(filter.query.match)[0];
|
||||
|
||||
return Boolean(fieldName && get(filter, ['match', fieldName, 'type']) === 'phrase');
|
||||
return Boolean(fieldName && get(filter, ['query', 'match', fieldName, 'type']) === 'phrase');
|
||||
}
|
||||
|
||||
export function migrateFilter(filter: Filter, indexPattern?: IIndexPattern) {
|
||||
if (isMatchPhraseFilter(filter)) {
|
||||
const fieldName = Object.keys(filter.match)[0];
|
||||
const params: Record<string, any> = get(filter, ['match', fieldName]);
|
||||
if (isDeprecatedMatchPhraseFilter(filter)) {
|
||||
const fieldName = Object.keys(filter.query.match)[0];
|
||||
const params: Record<string, any> = get(filter, ['query', 'match', fieldName]);
|
||||
if (indexPattern) {
|
||||
const field = indexPattern.fields.find(f => f.name === fieldName);
|
||||
|
||||
|
@ -55,8 +51,11 @@ export function migrateFilter(filter: Filter, indexPattern?: IIndexPattern) {
|
|||
}
|
||||
}
|
||||
return {
|
||||
match_phrase: {
|
||||
[fieldName]: omit(params, 'type'),
|
||||
...filter,
|
||||
query: {
|
||||
match_phrase: {
|
||||
[fieldName]: omit(params, 'type'),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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 { buildExistsFilter, getExistsFilterField } from './exists_filter';
|
||||
import { IIndexPattern } from '../../index_patterns';
|
||||
import { fields } from '../../index_patterns/fields/fields.mocks.ts';
|
||||
|
||||
describe('exists filter', function() {
|
||||
const indexPattern: IIndexPattern = ({
|
||||
fields,
|
||||
} as unknown) as IIndexPattern;
|
||||
|
||||
describe('getExistsFilterField', function() {
|
||||
it('should return the name of the field an exists query is targeting', () => {
|
||||
const field = indexPattern.fields.find(patternField => patternField.name === 'extension');
|
||||
const filter = buildExistsFilter(field!, indexPattern);
|
||||
const result = getExistsFilterField(filter);
|
||||
expect(result).toBe('extension');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -33,6 +33,10 @@ export type ExistsFilter = Filter & {
|
|||
|
||||
export const isExistsFilter = (filter: any): filter is ExistsFilter => filter && filter.exists;
|
||||
|
||||
export const getExistsFilterField = (filter: ExistsFilter) => {
|
||||
return filter.exists && filter.exists.field;
|
||||
};
|
||||
|
||||
export const buildExistsFilter = (field: IFieldType, indexPattern: IIndexPattern) => {
|
||||
return {
|
||||
meta: {
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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 { getGeoBoundingBoxFilterField } from './geo_bounding_box_filter';
|
||||
|
||||
describe('geo_bounding_box filter', function() {
|
||||
describe('getGeoBoundingBoxFilterField', function() {
|
||||
it('should return the name of the field a geo_bounding_box query is targeting', () => {
|
||||
const filter = {
|
||||
geo_bounding_box: {
|
||||
geoPointField: {
|
||||
bottom_right: { lat: 1, lon: 1 },
|
||||
top_left: { lat: 1, lon: 1 },
|
||||
},
|
||||
ignore_unmapped: true,
|
||||
},
|
||||
meta: {
|
||||
disabled: false,
|
||||
negate: false,
|
||||
alias: null,
|
||||
params: {
|
||||
bottom_right: { lat: 1, lon: 1 },
|
||||
top_left: { lat: 1, lon: 1 },
|
||||
},
|
||||
},
|
||||
};
|
||||
const result = getGeoBoundingBoxFilterField(filter);
|
||||
expect(result).toBe('geoPointField');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -33,3 +33,10 @@ export type GeoBoundingBoxFilter = Filter & {
|
|||
|
||||
export const isGeoBoundingBoxFilter = (filter: any): filter is GeoBoundingBoxFilter =>
|
||||
filter && filter.geo_bounding_box;
|
||||
|
||||
export const getGeoBoundingBoxFilterField = (filter: GeoBoundingBoxFilter) => {
|
||||
return (
|
||||
filter.geo_bounding_box &&
|
||||
Object.keys(filter.geo_bounding_box).find(key => key !== 'ignore_unmapped')
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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 { getGeoPolygonFilterField } from './geo_polygon_filter';
|
||||
|
||||
describe('geo_polygon filter', function() {
|
||||
describe('getGeoPolygonFilterField', function() {
|
||||
it('should return the name of the field a geo_polygon query is targeting', () => {
|
||||
const filter = {
|
||||
geo_polygon: {
|
||||
geoPointField: {
|
||||
points: [{ lat: 1, lon: 1 }],
|
||||
},
|
||||
ignore_unmapped: true,
|
||||
},
|
||||
meta: {
|
||||
disabled: false,
|
||||
negate: false,
|
||||
alias: null,
|
||||
params: {
|
||||
points: [{ lat: 1, lon: 1 }],
|
||||
},
|
||||
},
|
||||
};
|
||||
const result = getGeoPolygonFilterField(filter);
|
||||
expect(result).toBe('geoPointField');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -32,3 +32,9 @@ export type GeoPolygonFilter = Filter & {
|
|||
|
||||
export const isGeoPolygonFilter = (filter: any): filter is GeoPolygonFilter =>
|
||||
filter && filter.geo_polygon;
|
||||
|
||||
export const getGeoPolygonFilterField = (filter: GeoPolygonFilter) => {
|
||||
return (
|
||||
filter.geo_polygon && Object.keys(filter.geo_polygon).find(key => key !== 'ignore_unmapped')
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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 { buildPhraseFilter } from './phrase_filter';
|
||||
import { buildQueryFilter } from './query_string_filter';
|
||||
import { getFilterField } from './get_filter_field';
|
||||
import { IIndexPattern } from '../../index_patterns';
|
||||
import { fields } from '../../index_patterns/fields/fields.mocks.ts';
|
||||
|
||||
describe('getFilterField', function() {
|
||||
const indexPattern: IIndexPattern = ({
|
||||
id: 'logstash-*',
|
||||
fields,
|
||||
} as unknown) as IIndexPattern;
|
||||
|
||||
it('should return the field name from known filter types that target a specific field', () => {
|
||||
const field = indexPattern.fields.find(patternField => patternField.name === 'extension');
|
||||
const filter = buildPhraseFilter(field!, 'jpg', indexPattern);
|
||||
const result = getFilterField(filter);
|
||||
expect(result).toBe('extension');
|
||||
});
|
||||
|
||||
it('should return undefined for filters that do not target a specific field', () => {
|
||||
const filter = buildQueryFilter(
|
||||
{
|
||||
query: {
|
||||
query_string: {
|
||||
query: 'response:200 and extension:jpg',
|
||||
},
|
||||
},
|
||||
},
|
||||
indexPattern.id!,
|
||||
''
|
||||
);
|
||||
const result = getFilterField(filter);
|
||||
expect(result).toBe(undefined);
|
||||
});
|
||||
});
|
53
src/plugins/data/common/es_query/filters/get_filter_field.ts
Normal file
53
src/plugins/data/common/es_query/filters/get_filter_field.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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 { Filter } from './meta_filter';
|
||||
import { getExistsFilterField, isExistsFilter } from './exists_filter';
|
||||
import { getGeoBoundingBoxFilterField, isGeoBoundingBoxFilter } from './geo_bounding_box_filter';
|
||||
import { getGeoPolygonFilterField, isGeoPolygonFilter } from './geo_polygon_filter';
|
||||
import { getPhraseFilterField, isPhraseFilter } from './phrase_filter';
|
||||
import { getPhrasesFilterField, isPhrasesFilter } from './phrases_filter';
|
||||
import { getRangeFilterField, isRangeFilter } from './range_filter';
|
||||
import { getMissingFilterField, isMissingFilter } from './missing_filter';
|
||||
|
||||
export const getFilterField = (filter: Filter) => {
|
||||
if (isExistsFilter(filter)) {
|
||||
return getExistsFilterField(filter);
|
||||
}
|
||||
if (isGeoBoundingBoxFilter(filter)) {
|
||||
return getGeoBoundingBoxFilterField(filter);
|
||||
}
|
||||
if (isGeoPolygonFilter(filter)) {
|
||||
return getGeoPolygonFilterField(filter);
|
||||
}
|
||||
if (isPhraseFilter(filter)) {
|
||||
return getPhraseFilterField(filter);
|
||||
}
|
||||
if (isPhrasesFilter(filter)) {
|
||||
return getPhrasesFilterField(filter);
|
||||
}
|
||||
if (isRangeFilter(filter)) {
|
||||
return getRangeFilterField(filter);
|
||||
}
|
||||
if (isMissingFilter(filter)) {
|
||||
return getMissingFilterField(filter);
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
|
@ -22,6 +22,7 @@ import { Filter } from './meta_filter';
|
|||
|
||||
export * from './build_filters';
|
||||
export * from './get_filter_params';
|
||||
export * from './get_filter_field';
|
||||
|
||||
export * from './custom_filter';
|
||||
export * from './exists_filter';
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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 { getMissingFilterField } from './missing_filter';
|
||||
|
||||
describe('missing filter', function() {
|
||||
describe('getMissingFilterField', function() {
|
||||
it('should return the name of the field an missing query is targeting', () => {
|
||||
const filter = {
|
||||
missing: {
|
||||
field: 'extension',
|
||||
},
|
||||
meta: {
|
||||
disabled: false,
|
||||
negate: false,
|
||||
alias: null,
|
||||
},
|
||||
};
|
||||
const result = getMissingFilterField(filter);
|
||||
expect(result).toBe('extension');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -27,3 +27,7 @@ export type MissingFilter = Filter & {
|
|||
};
|
||||
|
||||
export const isMissingFilter = (filter: any): filter is MissingFilter => filter && filter.missing;
|
||||
|
||||
export const getMissingFilterField = (filter: MissingFilter) => {
|
||||
return filter.missing && filter.missing.field;
|
||||
};
|
||||
|
|
|
@ -17,8 +17,12 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { buildInlineScriptForPhraseFilter, buildPhraseFilter } from './phrase_filter';
|
||||
import { getField } from '../../index_patterns/mocks';
|
||||
import {
|
||||
buildInlineScriptForPhraseFilter,
|
||||
buildPhraseFilter,
|
||||
getPhraseFilterField,
|
||||
} from './phrase_filter';
|
||||
import { fields, getField } from '../../index_patterns/mocks';
|
||||
import { IIndexPattern } from '../../index_patterns';
|
||||
|
||||
describe('Phrase filter builder', () => {
|
||||
|
@ -95,3 +99,16 @@ describe('buildInlineScriptForPhraseFilter', () => {
|
|||
expect(buildInlineScriptForPhraseFilter(field)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPhraseFilterField', function() {
|
||||
const indexPattern: IIndexPattern = ({
|
||||
fields,
|
||||
} as unknown) as IIndexPattern;
|
||||
|
||||
it('should return the name of the field a phrase query is targeting', () => {
|
||||
const field = indexPattern.fields.find(patternField => patternField.name === 'extension');
|
||||
const filter = buildPhraseFilter(field!, 'jpg', indexPattern);
|
||||
const result = getPhraseFilterField(filter);
|
||||
expect(result).toBe('extension');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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 { buildPhrasesFilter, getPhrasesFilterField } from './phrases_filter';
|
||||
import { IIndexPattern } from '../../index_patterns';
|
||||
import { fields } from '../../index_patterns/fields/fields.mocks.ts';
|
||||
|
||||
describe('phrases filter', function() {
|
||||
const indexPattern: IIndexPattern = ({
|
||||
fields,
|
||||
} as unknown) as IIndexPattern;
|
||||
|
||||
describe('getPhrasesFilterField', function() {
|
||||
it('should return the name of the field a phrases query is targeting', () => {
|
||||
const field = indexPattern.fields.find(patternField => patternField.name === 'extension');
|
||||
const filter = buildPhrasesFilter(field!, ['jpg', 'png'], indexPattern);
|
||||
const result = getPhrasesFilterField(filter);
|
||||
expect(result).toBe('extension');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -32,7 +32,13 @@ export type PhrasesFilter = Filter & {
|
|||
};
|
||||
|
||||
export const isPhrasesFilter = (filter: any): filter is PhrasesFilter =>
|
||||
filter && filter.meta.type === FILTERS.PHRASES;
|
||||
filter?.meta?.type === FILTERS.PHRASES;
|
||||
|
||||
export const getPhrasesFilterField = (filter: PhrasesFilter) => {
|
||||
// Phrases is a newer filter type that has always been created via a constructor that ensures
|
||||
// `meta.key` is set to the field name
|
||||
return filter.meta.key;
|
||||
};
|
||||
|
||||
// Creates a filter where the given field matches one or more of the given values
|
||||
// params should be an array of values
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
import { buildQueryFilter } from './query_string_filter';
|
||||
|
||||
describe('Phrase filter builder', () => {
|
||||
describe('Query string filter builder', () => {
|
||||
it('should be a function', () => {
|
||||
expect(typeof buildQueryFilter).toBe('function');
|
||||
});
|
||||
|
|
|
@ -18,8 +18,8 @@
|
|||
*/
|
||||
|
||||
import { each } from 'lodash';
|
||||
import { buildRangeFilter, RangeFilter } from './range_filter';
|
||||
import { getField } from '../../index_patterns/mocks';
|
||||
import { buildRangeFilter, getRangeFilterField, RangeFilter } from './range_filter';
|
||||
import { fields, getField } from '../../index_patterns/mocks';
|
||||
import { IIndexPattern, IFieldType } from '../../index_patterns';
|
||||
|
||||
describe('Range filter builder', () => {
|
||||
|
@ -172,3 +172,16 @@ describe('Range filter builder', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRangeFilterField', function() {
|
||||
const indexPattern: IIndexPattern = ({
|
||||
fields,
|
||||
} as unknown) as IIndexPattern;
|
||||
|
||||
test('should return the name of the field a range query is targeting', () => {
|
||||
const field = indexPattern.fields.find(patternField => patternField.name === 'bytes');
|
||||
const filter = buildRangeFilter(field!, {}, indexPattern);
|
||||
const result = getRangeFilterField(filter);
|
||||
expect(result).toBe('bytes');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -88,6 +88,10 @@ export const isScriptedRangeFilter = (filter: any): filter is RangeFilter => {
|
|||
return hasRangeKeys(params);
|
||||
};
|
||||
|
||||
export const getRangeFilterField = (filter: RangeFilter) => {
|
||||
return filter.range && Object.keys(filter.range)[0];
|
||||
};
|
||||
|
||||
const formatValue = (field: IFieldType, params: any[]) =>
|
||||
map(params, (val: any, key: string) => get(operators, key) + format(field, val)).join(' ');
|
||||
|
||||
|
|
|
@ -25,7 +25,6 @@ export default function ({ getService, getPageObjects }) {
|
|||
const esArchiver = getService('esArchiver');
|
||||
const browser = getService('browser');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const filterBar = getService('filterBar');
|
||||
const queryBar = getService('queryBar');
|
||||
const PageObjects = getPageObjects(['common', 'discover', 'header', 'timePicker']);
|
||||
const defaultSettings = {
|
||||
|
@ -173,20 +172,6 @@ export default function ({ getService, getPageObjects }) {
|
|||
|
||||
});
|
||||
|
||||
describe('filter editor', function () {
|
||||
it('should add a phrases filter', async function () {
|
||||
await filterBar.addFilter('extension.raw', 'is one of', 'jpg');
|
||||
expect(await filterBar.hasFilter('extension.raw', 'jpg')).to.be(true);
|
||||
});
|
||||
|
||||
it('should show the phrases if you re-open a phrases filter', async function () {
|
||||
await filterBar.clickEditFilter('extension.raw', 'jpg');
|
||||
const phrases = await filterBar.getFilterEditorSelectedPhrases();
|
||||
expect(phrases.length).to.be(1);
|
||||
expect(phrases[0]).to.be('jpg');
|
||||
});
|
||||
});
|
||||
|
||||
describe('data-shared-item', function () {
|
||||
it('should have correct data-shared-item title and description', async () => {
|
||||
const expected = {
|
||||
|
|
73
test/functional/apps/discover/_filter_editor.js
Normal file
73
test/functional/apps/discover/_filter_editor.js
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* 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 expect from '@kbn/expect';
|
||||
|
||||
export default function ({ getService, getPageObjects }) {
|
||||
const log = getService('log');
|
||||
const retry = getService('retry');
|
||||
const esArchiver = getService('esArchiver');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const filterBar = getService('filterBar');
|
||||
const PageObjects = getPageObjects(['common', 'discover', 'timePicker']);
|
||||
const defaultSettings = {
|
||||
defaultIndex: 'logstash-*',
|
||||
};
|
||||
|
||||
describe('discover filter editor', function describeIndexTests() {
|
||||
|
||||
before(async function () {
|
||||
log.debug('load kibana index with default index pattern');
|
||||
await esArchiver.loadIfNeeded('discover');
|
||||
|
||||
// and load a set of makelogs data
|
||||
await esArchiver.loadIfNeeded('logstash_functional');
|
||||
await kibanaServer.uiSettings.replace(defaultSettings);
|
||||
log.debug('discover filter editor');
|
||||
await PageObjects.common.navigateToApp('discover');
|
||||
await PageObjects.timePicker.setDefaultAbsoluteRange();
|
||||
});
|
||||
|
||||
describe('filter editor', function () {
|
||||
it('should add a phrases filter', async function () {
|
||||
await filterBar.addFilter('extension.raw', 'is one of', 'jpg');
|
||||
expect(await filterBar.hasFilter('extension.raw', 'jpg')).to.be(true);
|
||||
});
|
||||
|
||||
it('should show the phrases if you re-open a phrases filter', async function () {
|
||||
await filterBar.clickEditFilter('extension.raw', 'jpg');
|
||||
const phrases = await filterBar.getFilterEditorSelectedPhrases();
|
||||
expect(phrases.length).to.be(1);
|
||||
expect(phrases[0]).to.be('jpg');
|
||||
await filterBar.ensureFieldEditorModalIsClosed();
|
||||
});
|
||||
|
||||
it('should support filtering on nested fields', async () => {
|
||||
await filterBar.addFilter('nestedField.child', 'is', 'nestedValue');
|
||||
expect(await filterBar.hasFilter('nestedField.child', 'nestedValue')).to.be(true);
|
||||
await retry.try(async function () {
|
||||
expect(await PageObjects.discover.getHitCount()).to.be(
|
||||
'1'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -34,6 +34,7 @@ export default function ({ getService, loadTestFile }) {
|
|||
|
||||
loadTestFile(require.resolve('./_saved_queries'));
|
||||
loadTestFile(require.resolve('./_discover'));
|
||||
loadTestFile(require.resolve('./_filter_editor'));
|
||||
loadTestFile(require.resolve('./_errors'));
|
||||
loadTestFile(require.resolve('./_field_data'));
|
||||
loadTestFile(require.resolve('./_shared_links'));
|
||||
|
|
|
@ -166,6 +166,7 @@ export function FilterBarProvider({ getService, getPageObjects }: FtrProviderCon
|
|||
if (cancelSaveFilterModalButtonExists) {
|
||||
await testSubjects.click('cancelSaveFilter');
|
||||
}
|
||||
await testSubjects.waitForDeleted('cancelSaveFilter');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in a new issue