From 8438352f333f11409651b5f39eec9256ade5ee65 Mon Sep 17 00:00:00 2001 From: Liza K Date: Sun, 10 Nov 2019 17:14:49 +0200 Subject: [PATCH 1/4] Moved filter generator to NP --- .../kibana/public/dashboard/dashboard_app.tsx | 3 - .../context/query_parameters/actions.js | 14 +- .../public/discover/angular/discover.js | 8 +- .../angular/doc_table/actions/filter.js | 26 --- .../discover/embeddable/search_embeddable.ts | 27 +-- .../kibana/public/discover/kibana_services.ts | 2 - .../__tests__/filter_generator.js | 166 ------------------ .../public/filter_manager/filter_generator.js | 98 ----------- src/legacy/ui/public/filter_manager/index.js | 1 - .../common/es_query/filters/exists_filter.ts | 2 +- .../es_query/filters/phrase_filter.test.ts | 2 +- .../common/es_query/filters/phrase_filter.ts | 2 +- .../common/es_query/filters/phrases_filter.ts | 2 +- .../filters/query_string_filter.test.ts | 2 +- .../es_query/filters/query_string_filter.ts | 2 +- .../es_query/filters/range_filter.test.ts | 2 +- .../common/es_query/filters/range_filter.ts | 2 +- .../data/common/es_query/filters/types.ts | 6 - src/plugins/data/common/types.ts | 6 + .../data/public/query/filter_manager/index.ts | 1 + .../lib/generate_filter.test.ts | 130 ++++++++++++++ .../filter_manager/lib/generate_filters.ts | 100 +++++++++++ .../data/public/suggestions_provider/types.ts | 3 +- .../suggestions_provider/value_suggestions.ts | 3 +- 24 files changed, 267 insertions(+), 343 deletions(-) delete mode 100644 src/legacy/core_plugins/kibana/public/discover/angular/doc_table/actions/filter.js delete mode 100644 src/legacy/ui/public/filter_manager/__tests__/filter_generator.js delete mode 100644 src/legacy/ui/public/filter_manager/filter_generator.js create mode 100644 src/plugins/data/public/query/filter_manager/lib/generate_filter.test.ts create mode 100644 src/plugins/data/public/query/filter_manager/lib/generate_filters.ts diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx index 5fa3a938ed9df..88aec4d485c35 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx @@ -26,9 +26,6 @@ import { IInjector } from 'ui/chrome'; // @ts-ignore import * as filterActions from 'plugins/kibana/discover/doc_table/actions/filter'; -// @ts-ignore -import { getFilterGenerator } from 'ui/filter_manager'; - import { AppStateClass as TAppStateClass, AppState as TAppState, diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/actions.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/actions.js index 9f7b180e8fe7d..308af7d487141 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/actions.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/actions.js @@ -18,7 +18,8 @@ */ import _ from 'lodash'; -import { getServices, getFilterGenerator } from '../../../kibana_services'; +import { generateFilters } from '../../../../../../../../plugins/data/public'; +import { npStart } from 'ui/new_platform'; import { MAX_CONTEXT_SIZE, @@ -27,9 +28,8 @@ import { } from './constants'; -export function QueryParameterActionsProvider(indexPatterns, Private) { - const queryFilter = Private(getServices().FilterBarQueryFilterProvider); - const filterGen = getFilterGenerator(queryFilter); +export function QueryParameterActionsProvider(indexPatterns) { + const filterManager = npStart.plugins.data.filterManager; const setPredecessorCount = (state) => (predecessorCount) => ( state.queryParameters.predecessorCount = clamp( @@ -55,13 +55,13 @@ export function QueryParameterActionsProvider(indexPatterns, Private) { ); const updateFilters = () => filters => { - queryFilter.setFilters(filters); + filterManager.setFilters(filters); }; const addFilter = (state) => async (field, values, operation) => { const indexPatternId = state.queryParameters.indexPatternId; - const newFilters = filterGen.generate(field, values, operation, indexPatternId); - queryFilter.addFilters(newFilters); + const newFilters = generateFilters(field, values, operation, indexPatternId); + filterManager.addFilters(newFilters); const indexPattern = await indexPatterns.get(indexPatternId); indexPattern.popularizeField(field.name, 1); }; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js index ed5049aa912e0..a6443867f7800 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js @@ -31,7 +31,6 @@ import './doc_table'; import { getSort } from './doc_table/lib/get_sort'; import { getSortForSearchSource } from './doc_table/lib/get_sort_for_search_source'; import * as columnActions from './doc_table/actions/columns'; -import * as filterActions from './doc_table/actions/filter'; import indexTemplate from './discover.html'; import { showOpenSearchPanel } from '../top_nav/show_open_search_panel'; @@ -41,7 +40,6 @@ import { getPainlessError } from './get_painless_error'; import { angular, buildVislibDimensions, - getFilterGenerator, getRequestInspectorStats, getResponseInspectorStats, getServices, @@ -76,7 +74,7 @@ const { import { getRootBreadcrumbs, getSavedSearchBreadcrumbs } from '../breadcrumbs'; import { extractTimeFilter, changeTimeFilter } from '../../../../data/public'; import { start as data } from '../../../../data/public/legacy'; - +import { generateFilters } from '../../../../../../plugins/data/public'; const { savedQueryService } = data.search.services; @@ -196,7 +194,6 @@ function discoverController( const shareContextMenuExtensions = Private(ShareContextMenuExtensionsRegistryProvider); const queryFilter = Private(FilterBarQueryFilterProvider); - const filterGen = getFilterGenerator(queryFilter); const inspectorAdapters = { requests: new RequestAdapter() @@ -901,7 +898,8 @@ function discoverController( // TODO: On array fields, negating does not negate the combination, rather all terms $scope.filterQuery = function (field, values, operation) { $scope.indexPattern.popularizeField(field, 1); - filterActions.addFilter(field, values, operation, $scope.indexPattern.id, $scope.state, filterGen); + const newFilters = generateFilters(queryFilter, field, values, operation, $scope.indexPattern.id); + return queryFilter.addFilters(newFilters); }; $scope.addColumn = function addColumn(columnName) { diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/actions/filter.js b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/actions/filter.js deleted file mode 100644 index 1a2854ec15412..0000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/actions/filter.js +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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 function addFilter(field, values = [], operation, index, state, filterGen) { - if (!Array.isArray(values)) { - values = [values]; - } - - filterGen.add(field, values, operation, index); -} diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts index 31b28d21fe8d8..db651deb08b03 100644 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts @@ -21,10 +21,14 @@ import * as Rx from 'rxjs'; import { Subscription } from 'rxjs'; import { i18n } from '@kbn/i18n'; import { TExecuteTriggerActions } from 'src/plugins/ui_actions/public'; -import { TimeRange, onlyDisabledFiltersChanged } from '../../../../../../plugins/data/public'; +import { + TimeRange, + onlyDisabledFiltersChanged, + FilterManager, +} from '../../../../../../plugins/data/public'; import { setup as data } from '../../../../data/public/legacy'; import { Query, getTime } from '../../../../data/public'; -import { esFilters } from '../../../../../../plugins/data/public'; +import { esFilters, generateFilters } from '../../../../../../plugins/data/public'; import { APPLY_FILTER_TRIGGER, Container, @@ -39,7 +43,6 @@ import { getSortForSearchSource } from '../angular/doc_table/lib/get_sort_for_se import { Adapters, angular, - getFilterGenerator, getRequestInspectorStats, getResponseInspectorStats, getServices, @@ -66,18 +69,6 @@ interface SearchScope extends ng.IScope { isLoading?: boolean; } -export interface FilterManager { - generate: ( - field: { - name: string; - scripted: boolean; - }, - values: string | string[], - operation: string, - index: number - ) => esFilters.Filter[]; -} - interface SearchEmbeddableConfig { $rootScope: ng.IRootScopeService; $compile: ng.ICompileService; @@ -101,7 +92,7 @@ export class SearchEmbeddable extends Embeddable private autoRefreshFetchSubscription?: Subscription; private subscription?: Subscription; public readonly type = SEARCH_EMBEDDABLE_TYPE; - private filterGen: FilterManager; + private filterManager: FilterManager; private abortController?: AbortController; private prevTimeRange?: TimeRange; @@ -128,7 +119,7 @@ export class SearchEmbeddable extends Embeddable parent ); - this.filterGen = getFilterGenerator(queryFilter); + this.filterManager = queryFilter as FilterManager; this.savedSearch = savedSearch; this.$rootScope = $rootScope; this.$compile = $compile; @@ -245,7 +236,7 @@ export class SearchEmbeddable extends Embeddable }; searchScope.filter = async (field, value, operator) => { - let filters = this.filterGen.generate(field, value, operator, indexPattern.id); + let filters = generateFilters(this.filterManager, field, value, operator, indexPattern.id); filters = filters.map(filter => ({ ...filter, $state: { store: esFilters.FilterStateStore.APP_STATE }, diff --git a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts index b78d05e68acad..3990e0967c6b9 100644 --- a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts @@ -84,8 +84,6 @@ export { angular }; export { buildVislibDimensions } from 'ui/visualize/loader/pipeline_helpers/build_pipeline'; // @ts-ignore export { callAfterBindingsWorkaround } from 'ui/compat'; -// @ts-ignore -export { getFilterGenerator } from 'ui/filter_manager'; export { getRequestInspectorStats, getResponseInspectorStats, diff --git a/src/legacy/ui/public/filter_manager/__tests__/filter_generator.js b/src/legacy/ui/public/filter_manager/__tests__/filter_generator.js deleted file mode 100644 index 5b6455bf20847..0000000000000 --- a/src/legacy/ui/public/filter_manager/__tests__/filter_generator.js +++ /dev/null @@ -1,166 +0,0 @@ -/* - * 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'; -import sinon from 'sinon'; -import MockState from 'fixtures/mock_state'; -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { getFilterGenerator } from '..'; -import { FilterBarQueryFilterProvider } from '../../filter_manager/query_filter'; -import { uniqFilters, esFilters } from '../../../../../plugins/data/public'; - -let queryFilter; -let filterGen; -let appState; - -function checkAddFilters(length, comps, idx) { - idx = idx || 0; - const filters = queryFilter.addFilters.getCall(idx).args[0]; - - expect(filters.length).to.be(length); - if (!Array.isArray(comps)) return; - comps.forEach(function (comp, i) { - expect(filters[i]).to.eql(comp); - }); -} - -describe('Filter Manager', function () { - beforeEach(ngMock.module( - 'kibana', - 'kibana/global_state', - function ($provide) { - $provide.service('indexPatterns', require('fixtures/mock_index_patterns')); - - appState = new MockState({ filters: [] }); - $provide.service('getAppState', function () { - return function () { return appState; }; - }); - } - )); - - beforeEach(ngMock.inject(function (_$rootScope_, Private) { - - // mock required queryFilter methods, used in the manager - queryFilter = Private(FilterBarQueryFilterProvider); - filterGen = getFilterGenerator(queryFilter); - sinon.stub(queryFilter, 'getAppFilters').callsFake(() => appState.filters); - sinon.stub(queryFilter, 'addFilters').callsFake((filters) => { - if (!Array.isArray(filters)) filters = [filters]; - appState.filters = uniqFilters(appState.filters.concat(filters)); - }); - })); - - it('should have an `add` function', function () { - expect(filterGen.add).to.be.a(Function); - }); - - it('should add a filter', function () { - filterGen.add('myField', 1, '+', 'myIndex'); - expect(queryFilter.addFilters.callCount).to.be(1); - checkAddFilters(1, [{ - meta: { index: 'myIndex', negate: false }, - query: { match_phrase: { myField: 1 } } - }]); - }); - - it('should add multiple filters if passed an array of values', function () { - filterGen.add('myField', [1, 2, 3], '+', 'myIndex'); - expect(queryFilter.addFilters.callCount).to.be(1); - checkAddFilters(3, [{ - meta: { index: 'myIndex', negate: false }, - query: { match_phrase: { myField: 1 } } - }, { - meta: { index: 'myIndex', negate: false }, - query: { match_phrase: { myField: 2 } } - }, { - meta: { index: 'myIndex', negate: false }, - query: { match_phrase: { myField: 3 } } - }]); - }); - - it('should add an exists filter if _exists_ is used as the field', function () { - filterGen.add('_exists_', 'myField', '+', 'myIndex'); - checkAddFilters(1, [{ - meta: { index: 'myIndex', negate: false }, - exists: { field: 'myField' } - }]); - }); - - it('should negate existing filter instead of added a conflicting filter', function () { - filterGen.add('myField', 1, '+', 'myIndex'); - checkAddFilters(1, [{ - meta: { index: 'myIndex', negate: false }, - query: { match_phrase: { myField: 1 } } - }], 0); - expect(appState.filters).to.have.length(1); - - // NOTE: negating exists filters also forces disabled to false - filterGen.add('myField', 1, '-', 'myIndex'); - checkAddFilters(1, [{ - meta: { index: 'myIndex', negate: true, disabled: false }, - query: { match_phrase: { myField: 1 } } - }], 1); - expect(appState.filters).to.have.length(1); - - filterGen.add('_exists_', 'myField', '+', 'myIndex'); - checkAddFilters(1, [{ - meta: { index: 'myIndex', negate: false }, - exists: { field: 'myField' } - }], 2); - expect(appState.filters).to.have.length(2); - - filterGen.add('_exists_', 'myField', '-', 'myIndex'); - checkAddFilters(1, [{ - meta: { index: 'myIndex', negate: true, disabled: false }, - exists: { field: 'myField' } - }], 3); - expect(appState.filters).to.have.length(2); - - const scriptedField = { name: 'scriptedField', scripted: true, script: 1, lang: 'painless' }; - filterGen.add(scriptedField, 1, '+', 'myIndex'); - checkAddFilters(1, [{ - meta: { index: 'myIndex', negate: false, field: 'scriptedField' }, - script: esFilters.getPhraseScript(scriptedField, 1) - }], 4); - expect(appState.filters).to.have.length(3); - - filterGen.add(scriptedField, 1, '-', 'myIndex'); - checkAddFilters(1, [{ - meta: { index: 'myIndex', negate: true, disabled: false, field: 'scriptedField' }, - script: esFilters.getPhraseScript(scriptedField, 1) - }], 5); - expect(appState.filters).to.have.length(3); - }); - - it('should enable matching filters being changed', function () { - _.each([true, false], function (negate) { - appState.filters = [{ - query: { match_phrase: { myField: 1 } }, - meta: { disabled: true, negate: negate } - }]; - expect(appState.filters.length).to.be(1); - expect(appState.filters[0].meta.disabled).to.be(true); - - filterGen.add('myField', 1, '+', 'myIndex'); - expect(appState.filters.length).to.be(1); - expect(appState.filters[0].meta.disabled).to.be(false); - }); - }); -}); diff --git a/src/legacy/ui/public/filter_manager/filter_generator.js b/src/legacy/ui/public/filter_manager/filter_generator.js deleted file mode 100644 index e11e0ff6653a7..0000000000000 --- a/src/legacy/ui/public/filter_manager/filter_generator.js +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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'; -import { esFilters } from '../../../../plugins/data/public'; - -// Adds a filter to a passed state -export function getFilterGenerator(queryFilter) { - const filterGen = {}; - - filterGen.generate = (field, values, operation, index) => { - values = Array.isArray(values) ? values : [values]; - const fieldName = _.isObject(field) ? field.name : field; - const filters = _.flatten([queryFilter.getAppFilters()]); - const newFilters = []; - - const negate = (operation === '-'); - - // TODO: On array fields, negating does not negate the combination, rather all terms - _.each(values, function (value) { - let filter; - const existing = _.find(filters, function (filter) { - if (!filter) return; - - if (fieldName === '_exists_' && filter.exists) { - return filter.exists.field === value; - } - - if (esFilters.isPhraseFilter(filter)) { - return esFilters.getPhraseFilterField(filter) === fieldName && esFilters.getPhraseFilterValue(filter) === value; - } - - if (filter.script) { - return filter.meta.field === fieldName && filter.script.script.params.value === value; - } - }); - - if (existing) { - existing.meta.disabled = false; - if (existing.meta.negate !== negate) { - existing.meta.negate = !existing.meta.negate; - } - newFilters.push(existing); - return; - } - - switch (fieldName) { - case '_exists_': - filter = { - meta: { negate, index }, - exists: { - field: value - } - }; - break; - default: - if (field.scripted) { - filter = { - meta: { negate, index, field: fieldName }, - script: esFilters.getPhraseScript(field, value) - }; - } else { - filter = { meta: { negate, index }, query: { match_phrase: {} } }; - filter.query.match_phrase[fieldName] = value; - } - - break; - } - - newFilters.push(filter); - }); - - return newFilters; - }; - - filterGen.add = function (field, values, operation, index) { - const newFilters = this.generate(field, values, operation, index); - return queryFilter.addFilters(newFilters); - }; - - return filterGen; -} diff --git a/src/legacy/ui/public/filter_manager/index.js b/src/legacy/ui/public/filter_manager/index.js index 6adc4e0965ccd..ce99d4cac3017 100644 --- a/src/legacy/ui/public/filter_manager/index.js +++ b/src/legacy/ui/public/filter_manager/index.js @@ -17,4 +17,3 @@ * under the License. */ -export { getFilterGenerator } from './filter_generator'; diff --git a/src/plugins/data/common/es_query/filters/exists_filter.ts b/src/plugins/data/common/es_query/filters/exists_filter.ts index 9125048e5f6cd..79d054614e5ed 100644 --- a/src/plugins/data/common/es_query/filters/exists_filter.ts +++ b/src/plugins/data/common/es_query/filters/exists_filter.ts @@ -18,7 +18,7 @@ */ import { Filter, FilterMeta } from './meta_filter'; -import { IndexPattern, Field } from './types'; +import { Field, IndexPattern } from '../../types'; export type ExistsFilterMeta = FilterMeta; diff --git a/src/plugins/data/common/es_query/filters/phrase_filter.test.ts b/src/plugins/data/common/es_query/filters/phrase_filter.test.ts index ec13e28c583d1..250ec792fbb57 100644 --- a/src/plugins/data/common/es_query/filters/phrase_filter.test.ts +++ b/src/plugins/data/common/es_query/filters/phrase_filter.test.ts @@ -18,7 +18,7 @@ */ import { buildInlineScriptForPhraseFilter, buildPhraseFilter } from './phrase_filter'; -import { IndexPattern } from './types'; +import { IndexPattern } from '../../types'; import { getField } from '../__tests__/fields_mock'; describe('Phrase filter builder', () => { diff --git a/src/plugins/data/common/es_query/filters/phrase_filter.ts b/src/plugins/data/common/es_query/filters/phrase_filter.ts index 15c5c9d4ad2e6..1161509b286f4 100644 --- a/src/plugins/data/common/es_query/filters/phrase_filter.ts +++ b/src/plugins/data/common/es_query/filters/phrase_filter.ts @@ -19,7 +19,7 @@ import { get, isPlainObject } from 'lodash'; import { Filter, FilterMeta } from './meta_filter'; -import { IndexPattern, Field } from './types'; +import { Field, IndexPattern } from '../../types'; export type PhraseFilterMeta = FilterMeta & { params?: { diff --git a/src/plugins/data/common/es_query/filters/phrases_filter.ts b/src/plugins/data/common/es_query/filters/phrases_filter.ts index e4606695c0f6a..e207a3ff5961b 100644 --- a/src/plugins/data/common/es_query/filters/phrases_filter.ts +++ b/src/plugins/data/common/es_query/filters/phrases_filter.ts @@ -18,7 +18,7 @@ */ import { Filter, FilterMeta } from './meta_filter'; -import { Field, IndexPattern } from './types'; +import { Field, IndexPattern } from '../../types'; import { getPhraseScript } from './phrase_filter'; export type PhrasesFilterMeta = FilterMeta & { diff --git a/src/plugins/data/common/es_query/filters/query_string_filter.test.ts b/src/plugins/data/common/es_query/filters/query_string_filter.test.ts index 839e4f6359257..5a580db0c57b8 100644 --- a/src/plugins/data/common/es_query/filters/query_string_filter.test.ts +++ b/src/plugins/data/common/es_query/filters/query_string_filter.test.ts @@ -18,7 +18,7 @@ */ import { buildQueryFilter } from './query_string_filter'; -import { IndexPattern } from './types'; +import { IndexPattern } from '../../types'; describe('Phrase filter builder', () => { let indexPattern: IndexPattern; diff --git a/src/plugins/data/common/es_query/filters/query_string_filter.ts b/src/plugins/data/common/es_query/filters/query_string_filter.ts index 901dc724aa4e4..d2374162b195f 100644 --- a/src/plugins/data/common/es_query/filters/query_string_filter.ts +++ b/src/plugins/data/common/es_query/filters/query_string_filter.ts @@ -18,7 +18,7 @@ */ import { Filter, FilterMeta } from './meta_filter'; -import { IndexPattern } from './types'; +import { IndexPattern } from '../../types'; export type QueryStringFilterMeta = FilterMeta; diff --git a/src/plugins/data/common/es_query/filters/range_filter.test.ts b/src/plugins/data/common/es_query/filters/range_filter.test.ts index 9008dc2a67294..a8506aba1b393 100644 --- a/src/plugins/data/common/es_query/filters/range_filter.test.ts +++ b/src/plugins/data/common/es_query/filters/range_filter.test.ts @@ -19,7 +19,7 @@ import { each } from 'lodash'; import { buildRangeFilter, RangeFilter } from './range_filter'; -import { IndexPattern, Field } from './types'; +import { Field, IndexPattern } from '../../types'; import { getField } from '../__tests__/fields_mock'; describe('Range filter builder', () => { diff --git a/src/plugins/data/common/es_query/filters/range_filter.ts b/src/plugins/data/common/es_query/filters/range_filter.ts index d7931f191e52b..c2513a9dc0c5e 100644 --- a/src/plugins/data/common/es_query/filters/range_filter.ts +++ b/src/plugins/data/common/es_query/filters/range_filter.ts @@ -18,7 +18,7 @@ */ import { map, reduce, mapValues, get, keys, pick } from 'lodash'; import { Filter, FilterMeta } from './meta_filter'; -import { Field, IndexPattern } from './types'; +import { Field, IndexPattern } from '../../types'; const OPERANDS_IN_RANGE = 2; diff --git a/src/plugins/data/common/es_query/filters/types.ts b/src/plugins/data/common/es_query/filters/types.ts index 2814735061999..a242df4811c05 100644 --- a/src/plugins/data/common/es_query/filters/types.ts +++ b/src/plugins/data/common/es_query/filters/types.ts @@ -49,9 +49,3 @@ export enum FILTERS { GEO_BOUNDING_BOX = 'geo_bounding_box', GEO_POLYGON = 'geo_polygon', } - -// We can't import the real types from the data plugin, so need to either duplicate -// them here or figure out another solution, perhaps housing them in this package -// will be replaces after Fieds / IndexPattern will be moved into new platform -export type Field = any; -export type IndexPattern = any; diff --git a/src/plugins/data/common/types.ts b/src/plugins/data/common/types.ts index 9eda75d8abd0b..eae7c26e3ab3f 100644 --- a/src/plugins/data/common/types.ts +++ b/src/plugins/data/common/types.ts @@ -21,3 +21,9 @@ export * from './field_formats/types'; export * from './timefilter/types'; export * from './query/types'; export * from './kbn_field_types/types'; + +// We can't import the real types from the data plugin, so need to either duplicate +// them here or figure out another solution, perhaps housing them in this package +// will be replaces after Fieds / IndexPattern will be moved into new platform +export type Field = any; +export type IndexPattern = any; diff --git a/src/plugins/data/public/query/filter_manager/index.ts b/src/plugins/data/public/query/filter_manager/index.ts index 7955cdd825ee6..ce7a479151797 100644 --- a/src/plugins/data/public/query/filter_manager/index.ts +++ b/src/plugins/data/public/query/filter_manager/index.ts @@ -22,3 +22,4 @@ export { FilterManager } from './filter_manager'; export { uniqFilters } from './lib/uniq_filters'; export { mapAndFlattenFilters } from './lib/map_and_flatten_filters'; export { onlyDisabledFiltersChanged } from './lib/only_disabled'; +export { generateFilters } from './lib/generate_filters'; diff --git a/src/plugins/data/public/query/filter_manager/lib/generate_filter.test.ts b/src/plugins/data/public/query/filter_manager/lib/generate_filter.test.ts new file mode 100644 index 0000000000000..46cf0fd9c111e --- /dev/null +++ b/src/plugins/data/public/query/filter_manager/lib/generate_filter.test.ts @@ -0,0 +1,130 @@ +/* + * 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 { generateFilters } from './generate_filters'; +import { FilterManager } from '../filter_manager'; +import { esFilters } from '../../..'; + +const INDEX_NAME = 'my-index'; +const EXISTS_FIELD_NAME = '_exists_'; +const FIELD = { + name: 'my-field', +}; +const PHRASE_VALUE = 'my-value'; + +describe('Generate filters', () => { + let mockFilterManager: FilterManager; + let filtersArray: esFilters.Filter[]; + + beforeEach(() => { + filtersArray = []; + mockFilterManager = { + getAppFilters: () => { + return filtersArray; + }, + } as FilterManager; + }); + + it('should create exists filter', () => { + const filters = generateFilters( + mockFilterManager, + EXISTS_FIELD_NAME, + FIELD.name, + '', + INDEX_NAME + ); + expect(filters).toHaveLength(1); + expect(filters[0].meta.index === INDEX_NAME); + expect(filters[0].meta.negate).toBeFalsy(); + expect(esFilters.isExistsFilter(filters[0])).toBeTruthy(); + }); + + it('should create negated exists filter', () => { + const filters = generateFilters( + mockFilterManager, + EXISTS_FIELD_NAME, + FIELD.name, + '-', + INDEX_NAME + ); + expect(filters).toHaveLength(1); + expect(filters[0].meta.index === INDEX_NAME); + expect(filters[0].meta.negate).toBeTruthy(); + expect(esFilters.isExistsFilter(filters[0])).toBeTruthy(); + }); + + it('should update and re-enable EXISTING exists filter', () => { + const filter = esFilters.buildExistsFilter(FIELD, { id: INDEX_NAME }); + filter.meta.disabled = true; + filtersArray.push(filter); + + const filters = generateFilters(mockFilterManager, '_exists_', FIELD.name, '-', INDEX_NAME); + expect(filters).toHaveLength(1); + expect(filters[0].meta.index === INDEX_NAME); + expect(filters[0].meta.negate).toBeTruthy(); + expect(filters[0].meta.disabled).toBeFalsy(); + expect(esFilters.isExistsFilter(filters[0])).toBeTruthy(); + }); + + it('should create phrase filter', () => { + const filters = generateFilters(mockFilterManager, FIELD, PHRASE_VALUE, '', INDEX_NAME); + expect(filters).toHaveLength(1); + expect(filters[0].meta.index === INDEX_NAME); + expect(filters[0].meta.negate).toBeFalsy(); + expect(esFilters.isPhraseFilter(filters[0])).toBeTruthy(); + expect((filters[0] as esFilters.PhraseFilter).query.match_phrase).toEqual({ + [FIELD.name]: PHRASE_VALUE, + }); + }); + + it('should create negated phrase filter', () => { + const filters = generateFilters(mockFilterManager, FIELD, PHRASE_VALUE, '-', INDEX_NAME); + expect(filters).toHaveLength(1); + expect(filters[0].meta.index === INDEX_NAME); + expect(filters[0].meta.negate).toBeTruthy(); + expect(esFilters.isPhraseFilter(filters[0])).toBeTruthy(); + expect((filters[0] as esFilters.PhraseFilter).query.match_phrase).toEqual({ + [FIELD.name]: PHRASE_VALUE, + }); + }); + + it('should create multiple phrase filters', () => { + const ANOTHER_PHRASE = 'another-value'; + const filters = generateFilters( + mockFilterManager, + FIELD, + [PHRASE_VALUE, ANOTHER_PHRASE], + '', + INDEX_NAME + ); + expect(filters).toHaveLength(2); + expect(filters[0].meta.index === INDEX_NAME); + expect(filters[0].meta.negate).toBeFalsy(); + expect(filters[1].meta.index === INDEX_NAME); + expect(filters[1].meta.negate).toBeFalsy(); + expect(esFilters.isPhraseFilter(filters[0])).toBeTruthy(); + expect(esFilters.isPhraseFilter(filters[1])).toBeTruthy(); + expect((filters[0] as esFilters.PhraseFilter).query.match_phrase).toEqual({ + [FIELD.name]: PHRASE_VALUE, + }); + expect((filters[1] as esFilters.PhraseFilter).query.match_phrase).toEqual({ + [FIELD.name]: ANOTHER_PHRASE, + }); + }); +}); diff --git a/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts b/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts new file mode 100644 index 0000000000000..e889a788cb77b --- /dev/null +++ b/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts @@ -0,0 +1,100 @@ +/* + * 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'; +import { FilterManager, esFilters, Field } from '../../..'; + +function getExistingFilter( + appFilters: esFilters.Filter[], + fieldName: string, + value: any +): esFilters.Filter | undefined { + // TODO: On array fields, negating does not negate the combination, rather all terms + return _.find(appFilters, function(filter) { + if (!filter) return; + + if (fieldName === '_exists_' && esFilters.isExistsFilter(filter)) { + return filter.exists!.field === value; + } + + if (esFilters.isPhraseFilter(filter)) { + return ( + esFilters.getPhraseFilterField(filter) === fieldName && + esFilters.getPhraseFilterValue(filter) === value + ); + } + + if (esFilters.isScriptedPhraseFilter(filter)) { + return filter.meta.field === fieldName && filter.meta.script!.script.params.value === value; + } + }); +} + +function updateExistingFilter(existingFilter: esFilters.Filter, negate: boolean) { + existingFilter.meta.disabled = false; + if (existingFilter.meta.negate !== negate) { + existingFilter.meta.negate = !existingFilter.meta.negate; + } +} + +export function generateFilters( + filterManager: FilterManager, + field: Field, + values: any, + operation: string, + index: string +): esFilters.Filter[] { + values = Array.isArray(values) ? values : [values]; + const fieldName = _.isObject(field) ? field.name : field; + const newFilters: esFilters.Filter[] = []; + const appFilters = filterManager.getAppFilters(); + + const negate = operation === '-'; + let filter; + + _.each(values, function(value) { + const existing = getExistingFilter(appFilters, fieldName, value); + + if (existing) { + updateExistingFilter(existing, negate); + filter = existing; + } else { + const tmpIndexPattern = { id: index }; + switch (fieldName) { + case '_exists_': + filter = esFilters.buildExistsFilter( + { + name: value, + }, + tmpIndexPattern + ); + break; + default: + filter = esFilters.buildPhraseFilter(field, value, tmpIndexPattern); + break; + } + + filter.meta.negate = negate; + } + + newFilters.push(filter); + }); + + return newFilters; +} diff --git a/src/plugins/data/public/suggestions_provider/types.ts b/src/plugins/data/public/suggestions_provider/types.ts index eac380dde6a62..2171f5366dc88 100644 --- a/src/plugins/data/public/suggestions_provider/types.ts +++ b/src/plugins/data/public/suggestions_provider/types.ts @@ -17,7 +17,6 @@ * under the License. */ -// Should be import { Field } from './index_patterns'; -export type Field = any; +import { Field } from '..'; export type IGetSuggestions = (index: string, field: Field, query: string, boolFilter?: any) => any; diff --git a/src/plugins/data/public/suggestions_provider/value_suggestions.ts b/src/plugins/data/public/suggestions_provider/value_suggestions.ts index 03eaa5d9594d2..c769f64025b0e 100644 --- a/src/plugins/data/public/suggestions_provider/value_suggestions.ts +++ b/src/plugins/data/public/suggestions_provider/value_suggestions.ts @@ -20,7 +20,8 @@ import { memoize } from 'lodash'; import { UiSettingsClientContract, HttpServiceBase } from 'src/core/public'; -import { IGetSuggestions, Field } from './types'; +import { IGetSuggestions } from './types'; +import { Field } from '..'; export function getSuggestionsProvider( uiSettings: UiSettingsClientContract, From f0ab1cf7c185a065ac45282baa417537dd9b7494 Mon Sep 17 00:00:00 2001 From: Liza K Date: Sun, 10 Nov 2019 17:34:07 +0200 Subject: [PATCH 2/4] Deleted unused test --- .../context/query_parameters/actions.js | 2 +- .../doc_table/__tests__/actions/filter.js | 66 ------------------- 2 files changed, 1 insertion(+), 67 deletions(-) delete mode 100644 src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/actions/filter.js diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/actions.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/actions.js index 308af7d487141..5b878746731c9 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/actions.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/actions.js @@ -29,7 +29,7 @@ import { export function QueryParameterActionsProvider(indexPatterns) { - const filterManager = npStart.plugins.data.filterManager; + const { filterManager } = npStart.plugins.data; const setPredecessorCount = (state) => (predecessorCount) => ( state.queryParameters.predecessorCount = clamp( diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/actions/filter.js b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/actions/filter.js deleted file mode 100644 index 1f5db791469b9..0000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/actions/filter.js +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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 { addFilter } from '../../actions/filter'; -import StubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; -import NoDigestPromises from 'test_utils/no_digest_promises'; -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import sinon from 'sinon'; - -function getFilterGeneratorStub() { - return { - add: sinon.stub() - }; -} - -describe('doc table filter actions', function () { - NoDigestPromises.activateForSuite(); - - let filterGen; - let indexPattern; - - beforeEach(ngMock.module( - 'kibana', - function ($provide) { - $provide.service('indexPatterns', require('fixtures/mock_index_patterns')); - } - )); - - beforeEach(ngMock.inject(function (Private) { - indexPattern = Private(StubbedLogstashIndexPatternProvider); - filterGen = getFilterGeneratorStub(); - })); - - describe('add', function () { - - it('should defer to the FilterManager when dealing with a lucene query', function () { - const state = { - query: { query: 'foo', language: 'lucene' } - }; - const args = ['foo', ['bar'], '+', indexPattern, ]; - addFilter('foo', ['bar'], '+', indexPattern, state, filterGen); - expect(filterGen.add.calledOnce).to.be(true); - expect(filterGen.add.calledWith(...args)).to.be(true); - }); - - }); - - -}); From a90bf37ec0d7f2f00af121c465a7819b33f4a2bb Mon Sep 17 00:00:00 2001 From: Liza K Date: Mon, 11 Nov 2019 13:54:00 +0200 Subject: [PATCH 3/4] Fixed browser test + fix to discover actions --- .../angular/context/api/__tests__/_stubs.js | 3 ++- .../__tests__/action_add_filter.js | 25 +++++++------------ .../context/query_parameters/actions.js | 4 +-- .../filter_manager/lib/generate_filters.ts | 16 ++++++------ 4 files changed, 21 insertions(+), 27 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/_stubs.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/_stubs.js index f472ff9250eb5..b3d37083b37f7 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/_stubs.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/_stubs.js @@ -26,7 +26,8 @@ export function createIndexPatternsStub() { get: sinon.spy(indexPatternId => Promise.resolve({ id: indexPatternId, - isTimeNanosBased: () => false + isTimeNanosBased: () => false, + popularizeField: () => {}, }) ), }; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_add_filter.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_add_filter.js index b136b03bd500b..5a445a65939ed 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_add_filter.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_add_filter.js @@ -19,32 +19,33 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; -import sinon from 'sinon'; import { getServices } from '../../../../kibana_services'; import { createStateStub } from './_utils'; import { QueryParameterActionsProvider } from '../actions'; - +import { createIndexPatternsStub } from '../../api/__tests__/_stubs'; +import { npStart } from 'ui/new_platform'; describe('context app', function () { beforeEach(ngMock.module('kibana')); + beforeEach(ngMock.module(function createServiceStubs($provide) { + $provide.value('indexPatterns', createIndexPatternsStub()); + })); + describe('action addFilter', function () { - let filterManagerStub; let addFilter; beforeEach(ngMock.inject(function createPrivateStubs(Private) { - filterManagerStub = createQueryFilterStub(); - Private.stub(getServices().FilterBarQueryFilterProvider, filterManagerStub); - + Private.stub(getServices().FilterBarQueryFilterProvider); addFilter = Private(QueryParameterActionsProvider).addFilter; })); it('should pass the given arguments to the filterManager', function () { const state = createStateStub(); + const filterManagerAddStub = npStart.plugins.data.query.filterManager.addFilters; addFilter(state)('FIELD_NAME', 'FIELD_VALUE', 'FILTER_OPERATION'); - const filterManagerAddStub = filterManagerStub.addFilters; //get the generated filter const generatedFilter = filterManagerAddStub.firstCall.args[0][0]; const queryKeys = Object.keys(generatedFilter.query.match_phrase); @@ -55,20 +56,12 @@ describe('context app', function () { it('should pass the index pattern id to the filterManager', function () { const state = createStateStub(); + const filterManagerAddStub = npStart.plugins.data.query.filterManager.addFilters; addFilter(state)('FIELD_NAME', 'FIELD_VALUE', 'FILTER_OPERATION'); - const filterManagerAddStub = filterManagerStub.addFilters; const generatedFilter = filterManagerAddStub.firstCall.args[0][0]; - expect(filterManagerAddStub.calledOnce).to.be(true); expect(generatedFilter.meta.index).to.eql('INDEX_PATTERN_ID'); }); }); }); - -function createQueryFilterStub() { - return { - addFilters: sinon.stub(), - getAppFilters: sinon.stub(), - }; -} diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/actions.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/actions.js index 5b878746731c9..10fe6c0e2eda1 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/actions.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/actions.js @@ -29,7 +29,7 @@ import { export function QueryParameterActionsProvider(indexPatterns) { - const { filterManager } = npStart.plugins.data; + const { filterManager } = npStart.plugins.data.query; const setPredecessorCount = (state) => (predecessorCount) => ( state.queryParameters.predecessorCount = clamp( @@ -60,7 +60,7 @@ export function QueryParameterActionsProvider(indexPatterns) { const addFilter = (state) => async (field, values, operation) => { const indexPatternId = state.queryParameters.indexPatternId; - const newFilters = generateFilters(field, values, operation, indexPatternId); + const newFilters = generateFilters(filterManager, field, values, operation, indexPatternId); filterManager.addFilters(newFilters); const indexPattern = await indexPatterns.get(indexPatternId); indexPattern.popularizeField(field.name, 1); diff --git a/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts b/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts index e889a788cb77b..703993e76c315 100644 --- a/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts +++ b/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts @@ -61,7 +61,12 @@ export function generateFilters( index: string ): esFilters.Filter[] { values = Array.isArray(values) ? values : [values]; - const fieldName = _.isObject(field) ? field.name : field; + const fieldObj = _.isObject(field) + ? field + : { + name: field, + }; + const fieldName = fieldObj.name; const newFilters: esFilters.Filter[] = []; const appFilters = filterManager.getAppFilters(); @@ -78,15 +83,10 @@ export function generateFilters( const tmpIndexPattern = { id: index }; switch (fieldName) { case '_exists_': - filter = esFilters.buildExistsFilter( - { - name: value, - }, - tmpIndexPattern - ); + filter = esFilters.buildExistsFilter(fieldObj, tmpIndexPattern); break; default: - filter = esFilters.buildPhraseFilter(field, value, tmpIndexPattern); + filter = esFilters.buildPhraseFilter(fieldObj, value, tmpIndexPattern); break; } From da3a487542a020e248e8f03f8db016f032322221 Mon Sep 17 00:00:00 2001 From: Liza K Date: Thu, 14 Nov 2019 14:42:53 +0200 Subject: [PATCH 4/4] Added jsdcos --- .../query/filter_manager/lib/generate_filters.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts b/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts index 703993e76c315..5c4cdc2717338 100644 --- a/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts +++ b/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts @@ -53,9 +53,21 @@ function updateExistingFilter(existingFilter: esFilters.Filter, negate: boolean) } } +/** + * Generate filter objects, as a result of triggering a filter action on a + * specific index pattern field. + * + * @param {FilterManager} filterManager - The active filter manager to lookup for existing filters + * @param {Field | string} field - The field for which filters should be generated + * @param {any} values - One or more values to filter for. + * @param {string} operation - "-" to create a negated filter + * @param {string} index - Index string to generate filters for + * + * @returns {object} An array of filters to be added back to filterManager + */ export function generateFilters( filterManager: FilterManager, - field: Field, + field: Field | string, values: any, operation: string, index: string