diff --git a/packages/kbn-es-query/src/filters/lib/exists_filter.ts b/packages/kbn-es-query/src/filters/lib/exists_filter.ts index 356d039f4d19b..5843c25c43cff 100644 --- a/packages/kbn-es-query/src/filters/lib/exists_filter.ts +++ b/packages/kbn-es-query/src/filters/lib/exists_filter.ts @@ -21,6 +21,13 @@ import { Filter, FilterMeta } from './meta_filter'; export type ExistsFilterMeta = FilterMeta; +export interface FilterExistsProperty { + field: any; +} + export type ExistsFilter = Filter & { meta: ExistsFilterMeta; + exists?: FilterExistsProperty; }; + +export const isExistsFilter = (filter: any): filter is ExistsFilter => filter && filter.exists; diff --git a/packages/kbn-es-query/src/filters/lib/geo_bounding_box_filter.ts b/packages/kbn-es-query/src/filters/lib/geo_bounding_box_filter.ts index c83e146b093a3..f4673af96b2cd 100644 --- a/packages/kbn-es-query/src/filters/lib/geo_bounding_box_filter.ts +++ b/packages/kbn-es-query/src/filters/lib/geo_bounding_box_filter.ts @@ -28,4 +28,8 @@ export type GeoBoundingBoxFilterMeta = FilterMeta & { export type GeoBoundingBoxFilter = Filter & { meta: GeoBoundingBoxFilterMeta; + geo_bounding_box: any; }; + +export const isGeoBoundingBoxFilter = (filter: any): filter is GeoBoundingBoxFilter => + filter && filter.geo_bounding_box; diff --git a/packages/kbn-es-query/src/filters/lib/geo_polygon_filter.ts b/packages/kbn-es-query/src/filters/lib/geo_polygon_filter.ts index 80b606e13bb3d..4cf82a92d2cef 100644 --- a/packages/kbn-es-query/src/filters/lib/geo_polygon_filter.ts +++ b/packages/kbn-es-query/src/filters/lib/geo_polygon_filter.ts @@ -27,4 +27,8 @@ export type GeoPolygonFilterMeta = FilterMeta & { export type GeoPolygonFilter = Filter & { meta: GeoPolygonFilterMeta; + geo_polygon: any; }; + +export const isGeoPolygonFilter = (filter: any): filter is GeoPolygonFilter => + filter && filter.geo_polygon; diff --git a/packages/kbn-es-query/src/filters/lib/index.ts b/packages/kbn-es-query/src/filters/lib/index.ts index fdf87c84eb5ca..94e90186b5e11 100644 --- a/packages/kbn-es-query/src/filters/lib/index.ts +++ b/packages/kbn-es-query/src/filters/lib/index.ts @@ -22,22 +22,38 @@ export * from './meta_filter'; // The actual filter types import { CustomFilter } from './custom_filter'; -import { ExistsFilter } from './exists_filter'; -import { GeoBoundingBoxFilter } from './geo_bounding_box_filter'; -import { GeoPolygonFilter } from './geo_polygon_filter'; -import { PhraseFilter } from './phrase_filter'; -import { PhrasesFilter } from './phrases_filter'; -import { QueryStringFilter } from './query_string_filter'; -import { RangeFilter } from './range_filter'; +import { ExistsFilter, isExistsFilter } from './exists_filter'; +import { GeoBoundingBoxFilter, isGeoBoundingBoxFilter } from './geo_bounding_box_filter'; +import { GeoPolygonFilter, isGeoPolygonFilter } from './geo_polygon_filter'; +import { PhraseFilter, isPhraseFilter, isScriptedPhraseFilter } from './phrase_filter'; +import { PhrasesFilter, isPhrasesFilter } from './phrases_filter'; +import { QueryStringFilter, isQueryStringFilter } from './query_string_filter'; +import { RangeFilter, isRangeFilter, isScriptedRangeFilter } from './range_filter'; +import { MatchAllFilter, isMatchAllFilter } from './match_all_filter'; +import { MissingFilter, isMissingFilter } from './missing_filter'; + export { CustomFilter, ExistsFilter, + isExistsFilter, GeoBoundingBoxFilter, + isGeoBoundingBoxFilter, GeoPolygonFilter, + isGeoPolygonFilter, PhraseFilter, + isPhraseFilter, + isScriptedPhraseFilter, PhrasesFilter, + isPhrasesFilter, QueryStringFilter, + isQueryStringFilter, RangeFilter, + isRangeFilter, + isScriptedRangeFilter, + MatchAllFilter, + isMatchAllFilter, + MissingFilter, + isMissingFilter, }; // Any filter associated with a field (used in the filter bar/editor) @@ -47,4 +63,19 @@ export type FieldFilter = | GeoPolygonFilter | PhraseFilter | PhrasesFilter - | RangeFilter; + | RangeFilter + | MatchAllFilter + | MissingFilter; + +export enum FILTERS { + CUSTOM = 'custom', + PHRASES = 'phrases', + PHRASE = 'phrase', + EXISTS = 'exists', + MATCH_ALL = 'match_all', + MISSING = 'missing', + QUERY_STRING = 'query_string', + RANGE = 'range', + GEO_BOUNDING_BOX = 'geo_bounding_box', + GEO_POLYGON = 'geo_polygon', +} diff --git a/packages/kbn-es-query/src/filters/lib/match_all_filter.ts b/packages/kbn-es-query/src/filters/lib/match_all_filter.ts new file mode 100644 index 0000000000000..d9876c2fa9509 --- /dev/null +++ b/packages/kbn-es-query/src/filters/lib/match_all_filter.ts @@ -0,0 +1,33 @@ +/* + * 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, FilterMeta } from './meta_filter'; + +export interface MatchAllFilterMeta extends FilterMeta { + field: any; + formattedValue: string; +} + +export type MatchAllFilter = Filter & { + meta: MatchAllFilterMeta; + match_all: any; +}; + +export const isMatchAllFilter = (filter: any): filter is MatchAllFilter => + filter && filter.match_all; diff --git a/packages/kbn-es-query/src/filters/lib/meta_filter.ts b/packages/kbn-es-query/src/filters/lib/meta_filter.ts index c11fb592f0e29..7740dfa644353 100644 --- a/packages/kbn-es-query/src/filters/lib/meta_filter.ts +++ b/packages/kbn-es-query/src/filters/lib/meta_filter.ts @@ -35,12 +35,13 @@ export interface FilterMeta { alias: string | null; key?: string; value?: string; + params?: any; } export interface Filter { $state?: FilterState; meta: FilterMeta; - query?: object; + query?: any; } export interface LatLon { diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_and_flatten_filters.js b/packages/kbn-es-query/src/filters/lib/missing_filter.ts similarity index 73% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_and_flatten_filters.js rename to packages/kbn-es-query/src/filters/lib/missing_filter.ts index e48de790e56f7..5411187cbcfd7 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_and_flatten_filters.js +++ b/packages/kbn-es-query/src/filters/lib/missing_filter.ts @@ -17,13 +17,13 @@ * under the License. */ -import _ from 'lodash'; -import { mapFilter } from './map_filter'; +import { Filter, FilterMeta } from './meta_filter'; -export function mapAndFlattenFilters(indexPatterns, filters) { - const flattened = _(filters) - .flatten() - .compact() - .map(item => mapFilter(indexPatterns, item)).value(); - return Promise.all(flattened); -} +export type MissingFilterMeta = FilterMeta; + +export type MissingFilter = Filter & { + meta: MissingFilterMeta; + missing: any; +}; + +export const isMissingFilter = (filter: any): filter is MissingFilter => filter && filter.missing; diff --git a/packages/kbn-es-query/src/filters/lib/phrase_filter.ts b/packages/kbn-es-query/src/filters/lib/phrase_filter.ts index a8613190ce786..d2f2e8bc1cca9 100644 --- a/packages/kbn-es-query/src/filters/lib/phrase_filter.ts +++ b/packages/kbn-es-query/src/filters/lib/phrase_filter.ts @@ -17,14 +17,27 @@ * under the License. */ +import { get } from 'lodash'; import { Filter, FilterMeta } from './meta_filter'; export type PhraseFilterMeta = FilterMeta & { params: { query: string; // The unformatted value }; + script?: { + script: { + params: any; + }; + }; + field?: any; }; export type PhraseFilter = Filter & { meta: PhraseFilterMeta; }; + +export const isPhraseFilter = (filter: any): filter is PhraseFilter => + filter && (filter.query && filter.query.match); + +export const isScriptedPhraseFilter = (filter: any): filter is PhraseFilter => + Boolean(get(filter, 'script.script.params.value')); diff --git a/packages/kbn-es-query/src/filters/lib/phrases_filter.ts b/packages/kbn-es-query/src/filters/lib/phrases_filter.ts index cc36ad54c8dc4..213afb409a0a6 100644 --- a/packages/kbn-es-query/src/filters/lib/phrases_filter.ts +++ b/packages/kbn-es-query/src/filters/lib/phrases_filter.ts @@ -21,8 +21,12 @@ import { Filter, FilterMeta } from './meta_filter'; export type PhrasesFilterMeta = FilterMeta & { params: string[]; // The unformatted values + field?: string; }; export type PhrasesFilter = Filter & { meta: PhrasesFilterMeta; }; + +export const isPhrasesFilter = (filter: any): filter is PhrasesFilter => + filter && filter.meta.type === 'phrases'; diff --git a/packages/kbn-es-query/src/filters/lib/query_string_filter.ts b/packages/kbn-es-query/src/filters/lib/query_string_filter.ts index 1f6a95844437a..3b3b97fafba9b 100644 --- a/packages/kbn-es-query/src/filters/lib/query_string_filter.ts +++ b/packages/kbn-es-query/src/filters/lib/query_string_filter.ts @@ -23,4 +23,12 @@ export type QueryStringFilterMeta = FilterMeta; export type QueryStringFilter = Filter & { meta: QueryStringFilterMeta; + query?: { + query_string: { + query: string; + }; + }; }; + +export const isQueryStringFilter = (filter: any): filter is QueryStringFilter => + filter && filter.query && filter.query.query_string; diff --git a/packages/kbn-es-query/src/filters/lib/range_filter.ts b/packages/kbn-es-query/src/filters/lib/range_filter.ts index 214652fb3f332..6a6f110df2a13 100644 --- a/packages/kbn-es-query/src/filters/lib/range_filter.ts +++ b/packages/kbn-es-query/src/filters/lib/range_filter.ts @@ -16,20 +16,50 @@ * specific language governing permissions and limitations * under the License. */ - +import { get, keys } from 'lodash'; import { Filter, FilterMeta } from './meta_filter'; -export interface RangeFilterParams { +interface FilterRange { + from?: number | string; + to?: number | string; +} + +interface FilterRangeGt { gt?: number | string; + lt?: number | string; +} + +interface FilterRangeGte { gte?: number | string; lte?: number | string; - lt?: number | string; } +export type RangeFilterParams = FilterRange & FilterRangeGt & FilterRangeGte; + export type RangeFilterMeta = FilterMeta & { params: RangeFilterParams; + field?: any; }; export type RangeFilter = Filter & { meta: RangeFilterMeta; + script?: { + script: { + params: any; + }; + }; + range: { [key: string]: RangeFilterParams }; +}; + +const hasRangeKeys = (params: RangeFilterParams) => + Boolean( + keys(params).find((key: string) => ['gte', 'gt', 'lte', 'lt', 'from', 'to'].includes(key)) + ); + +export const isRangeFilter = (filter: any): filter is RangeFilter => filter && filter.range; + +export const isScriptedRangeFilter = (filter: any): filter is RangeFilter => { + const params: RangeFilterParams = get(filter, 'script.script.params', {}); + + return hasRangeKeys(params); }; diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/fixtures/range_filter.ts b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/fixtures/range_filter.ts index f6daf9cb36f11..46a5181450fea 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/fixtures/range_filter.ts +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/fixtures/range_filter.ts @@ -36,4 +36,5 @@ export const rangeFilter: RangeFilter = { $state: { store: FilterStateStore.APP_STATE, }, + range: {}, }; diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/filter_manager.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/filter_manager.ts index 1605c647a2672..9818c0dd31a99 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/filter_manager.ts +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/filter_manager.ts @@ -17,19 +17,16 @@ * under the License. */ -import { Filter, isFilterPinned, FilterStateStore } from '@kbn/es-query'; +import { Filter, isFilterPinned, isRangeFilter, FilterStateStore } from '@kbn/es-query'; import _ from 'lodash'; import { Subject } from 'rxjs'; import { UiSettingsClientContract } from 'src/core/public'; -// @ts-ignore + import { compareFilters } from './lib/compare_filters'; -// @ts-ignore import { mapAndFlattenFilters } from './lib/map_and_flatten_filters'; -// @ts-ignore import { uniqFilters } from './lib/uniq_filters'; - import { extractTimeFilter } from './lib/extract_time_filter'; import { changeTimeFilter } from './lib/change_time_filter'; import { onlyDisabledFiltersChanged } from './lib/only_disabled'; @@ -194,7 +191,10 @@ export class FilterManager { public async addFiltersAndChangeTimeFilter(filters: Filter[]) { const timeFilter = await extractTimeFilter(this.indexPatterns, filters); - if (timeFilter) changeTimeFilter(this.timefilter, timeFilter); + + if (isRangeFilter(timeFilter)) { + changeTimeFilter(this.timefilter, timeFilter); + } return this.addFilters(filters.filter(filter => filter !== timeFilter)); } diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/index.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/index.ts index dc70ae910835d..a29dcde6f059d 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/index.ts +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/index.ts @@ -20,6 +20,5 @@ export { FilterManager } from './filter_manager'; export { FilterStateManager } from './filter_state_manager'; -// @ts-ignore export { uniqFilters } from './lib/uniq_filters'; export { onlyDisabledFiltersChanged } from './lib/only_disabled'; diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/dedup_filters.js b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/dedup_filters.js deleted file mode 100644 index cbe511b326c0b..0000000000000 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/dedup_filters.js +++ /dev/null @@ -1,68 +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 { dedupFilters } from '../dedup_filters'; -import expect from '@kbn/expect'; - -describe('Filter Bar Directive', function () { - describe('dedupFilters(existing, filters)', function () { - - it('should return only filters which are not in the existing', function () { - const existing = [ - { range: { bytes: { from: 0, to: 1024 } } }, - { query: { match: { _term: { query: 'apache', type: 'phrase' } } } } - ]; - const filters = [ - { range: { bytes: { from: 1024, to: 2048 } } }, - { query: { match: { _term: { query: 'apache', type: 'phrase' } } } } - ]; - const results = dedupFilters(existing, filters); - expect(results).to.contain(filters[0]); - expect(results).to.not.contain(filters[1]); - }); - - it('should ignore the disabled attribute when comparing ', function () { - const existing = [ - { range: { bytes: { from: 0, to: 1024 } } }, - { meta: { disabled: true }, query: { match: { _term: { query: 'apache', type: 'phrase' } } } } - ]; - const filters = [ - { range: { bytes: { from: 1024, to: 2048 } } }, - { query: { match: { _term: { query: 'apache', type: 'phrase' } } } } - ]; - const results = dedupFilters(existing, filters); - expect(results).to.contain(filters[0]); - expect(results).to.not.contain(filters[1]); - }); - - it('should ignore $state attribute', function () { - const existing = [ - { range: { bytes: { from: 0, to: 1024 } } }, - { $state: { store: 'appState' }, query: { match: { _term: { query: 'apache', type: 'phrase' } } } } - ]; - const filters = [ - { range: { bytes: { from: 1024, to: 2048 } } }, - { $state: { store: 'globalState' }, query: { match: { _term: { query: 'apache', type: 'phrase' } } } } - ]; - const results = dedupFilters(existing, filters); - expect(results).to.contain(filters[0]); - expect(results).to.not.contain(filters[1]); - }); - }); -}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/extract_time_filter.js b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/extract_time_filter.js deleted file mode 100644 index fe2a642199cd9..0000000000000 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/extract_time_filter.js +++ /dev/null @@ -1,61 +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 expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { extractTimeFilter } from '../extract_time_filter'; -import IndexPatternMock from 'fixtures/mock_index_patterns'; - -describe('Filter Bar Directive', function () { - describe('extractTimeFilter()', function () { - let mockIndexPatterns; - - beforeEach(ngMock.module( - 'kibana', - 'kibana/courier' - )); - - beforeEach(ngMock.inject(function (Private) { - mockIndexPatterns = Private(IndexPatternMock); - })); - - it('should return the matching filter for the default time field', function (done) { - const filters = [ - { meta: { index: 'logstash-*' }, query: { match: { _type: { query: 'apache', type: 'phrase' } } } }, - { meta: { index: 'logstash-*' }, range: { 'time': { gt: 1388559600000, lt: 1388646000000 } } } - ]; - extractTimeFilter(mockIndexPatterns, filters).then(function (filter) { - expect(filter).to.eql(filters[1]); - done(); - }); - }); - - it('should not return the non-matching filter for the default time field', function (done) { - const filters = [ - { meta: { index: 'logstash-*' }, query: { match: { _type: { query: 'apache', type: 'phrase' } } } }, - { meta: { index: 'logstash-*' }, range: { '@timestamp': { gt: 1388559600000, lt: 1388646000000 } } } - ]; - extractTimeFilter(mockIndexPatterns, filters).then(function (filter) { - expect(filter).to.be(undefined); - done(); - }); - }); - - }); -}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/generate_mapping_chain.js b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/generate_mapping_chain.js deleted file mode 100644 index 3344b938181e5..0000000000000 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/generate_mapping_chain.js +++ /dev/null @@ -1,111 +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 sinon from 'sinon'; -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { generateMappingChain } from '../generate_mapping_chain'; - -describe('Filter Bar Directive', function () { - describe('generateMappingChain()', function () { - beforeEach(ngMock.module('kibana')); - - - it('should create a chaining function which calls the next function if the promise is rejected', function (done) { - const filter = {}; - const mapping = sinon.stub(); - mapping.rejects(filter); - const next = sinon.stub(); - next.resolves('good'); - const chain = generateMappingChain(mapping, next); - chain(filter).then(function (result) { - expect(result).to.be('good'); - sinon.assert.calledOnce(next); - done(); - }); - }); - - it('should create a chaining function which DOES NOT call the next function if the result is resolved', function (done) { - const mapping = sinon.stub(); - mapping.resolves('good'); - const next = sinon.stub(); - next.resolves('bad'); - const chain = generateMappingChain(mapping, next); - chain({}).then(function (result) { - expect(result).to.be('good'); - sinon.assert.notCalled(next); - done(); - }); - }); - - it('should resolve result for the mapping function', function (done) { - const mapping = sinon.stub(); - mapping.resolves({ key: 'test', value: 'example' }); - const next = sinon.stub(); - const chain = generateMappingChain(mapping, next); - chain({}).then(function (result) { - sinon.assert.notCalled(next); - expect(result).to.eql({ key: 'test', value: 'example' }); - done(); - }); - }); - - it('should call the mapping function with the argument to the chain', function (done) { - const mapping = sinon.stub(); - mapping.resolves({ key: 'test', value: 'example' }); - const next = sinon.stub(); - const chain = generateMappingChain(mapping, next); - chain({ test: 'example' }).then(function (result) { - sinon.assert.calledOnce(mapping); - expect(mapping.args[0][0]).to.eql({ test: 'example' }); - sinon.assert.notCalled(next); - expect(result).to.eql({ key: 'test', value: 'example' }); - done(); - }); - }); - - it('should resolve result for the next function', function (done) { - const filter = {}; - const mapping = sinon.stub(); - mapping.rejects(filter); - const next = sinon.stub(); - next.resolves({ key: 'test', value: 'example' }); - const chain = generateMappingChain(mapping, next); - chain(filter).then(function (result) { - sinon.assert.calledOnce(mapping); - sinon.assert.calledOnce(next); - expect(result).to.eql({ key: 'test', value: 'example' }); - done(); - }); - }); - - it('should reject with an error if no functions match', function (done) { - const filter = {}; - const mapping = sinon.stub(); - mapping.rejects(filter); - const chain = generateMappingChain(mapping); - chain(filter).catch(function (err) { - expect(err).to.be.an(Error); - expect(err.message).to.be('No mappings have been found for filter.'); - done(); - }); - }); - - }); -}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/map_and_flatten_filters.js b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/map_and_flatten_filters.js deleted file mode 100644 index 2282e9fdf8f9a..0000000000000 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/map_and_flatten_filters.js +++ /dev/null @@ -1,72 +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 expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { mapAndFlattenFilters } from '../map_and_flatten_filters'; -import IndexPatternMock from 'fixtures/mock_index_patterns'; - -describe('Filter Bar Directive', function () { - describe('mapAndFlattenFilters()', function () { - let mockIndexPatterns; - - beforeEach(ngMock.module( - 'kibana', - 'kibana/courier' - )); - - beforeEach(ngMock.inject(function (Private) { - mockIndexPatterns = Private(IndexPatternMock); - })); - - const filters = [ - null, - [ - { meta: { index: 'logstash-*' }, exists: { field: '_type' } }, - { meta: { index: 'logstash-*' }, missing: { field: '_type' } } - ], - { meta: { index: 'logstash-*' }, query: { query_string: { query: 'foo:bar' } } }, - { meta: { index: 'logstash-*' }, range: { bytes: { lt: 2048, gt: 1024 } } }, - { meta: { index: 'logstash-*' }, query: { match: { _type: { query: 'apache', type: 'phrase' } } } } - ]; - - it('should map and flatten the filters', function (done) { - mapAndFlattenFilters(mockIndexPatterns, filters).then(function (results) { - expect(results).to.have.length(5); - expect(results[0]).to.have.property('meta'); - expect(results[1]).to.have.property('meta'); - expect(results[2]).to.have.property('meta'); - expect(results[3]).to.have.property('meta'); - expect(results[4]).to.have.property('meta'); - expect(results[0].meta).to.have.property('key', '_type'); - expect(results[0].meta).to.have.property('value', 'exists'); - expect(results[1].meta).to.have.property('key', '_type'); - expect(results[1].meta).to.have.property('value', 'missing'); - expect(results[2].meta).to.have.property('key', 'query'); - expect(results[2].meta).to.have.property('value', 'foo:bar'); - expect(results[3].meta).to.have.property('key', 'bytes'); - expect(results[3].meta).to.have.property('value', '1,024 to 2,048'); - expect(results[4].meta).to.have.property('key', '_type'); - expect(results[4].meta).to.have.property('value', 'apache'); - done(); - }); - }); - - }); -}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/map_default.js b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/map_default.js deleted file mode 100644 index d97860ade56f5..0000000000000 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/map_default.js +++ /dev/null @@ -1,62 +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 expect from '@kbn/expect'; -import { mapDefault } from '../map_default'; - -describe('Filter Bar Directive', function () { - describe('mapDefault()', function () { - - it('should return the key and value for matching filters', function (done) { - const filter = { query: { match_all: {} } }; - mapDefault(filter).then(function (result) { - expect(result).to.have.property('key', 'query'); - expect(result).to.have.property('value', '{"match_all":{}}'); - done(); - }); - }); - - it('should work with undefined filter types', function (done) { - const filter = { - 'bool': { - 'must': { - 'term': { - 'geo.src': 'US' - } - } - } - }; - mapDefault(filter).then(function (result) { - expect(result).to.have.property('key', 'bool'); - expect(result).to.have.property('value', JSON.stringify(filter.bool)); - done(); - }); - }); - - it('should return undefined if there is no valid key', function (done) { - const filter = { meta: {} }; - mapDefault(filter).catch(function (result) { - expect(result).to.be(filter); - done(); - }); - }); - - - }); -}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/map_filter.js b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/map_filter.js deleted file mode 100644 index 5867c3b75f10e..0000000000000 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/map_filter.js +++ /dev/null @@ -1,97 +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 expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { mapFilter } from '../map_filter'; -import IndexPatternMock from 'fixtures/mock_index_patterns'; - -describe('Filter Bar Directive', function () { - let mockIndexPatterns; - - beforeEach(ngMock.module( - 'kibana', - 'kibana/courier' - )); - - beforeEach(ngMock.inject(function (Private) { - mockIndexPatterns = Private(IndexPatternMock); - })); - - describe('mapFilter()', function () { - it('should map query filters', function (done) { - const before = { meta: { index: 'logstash-*' }, query: { match: { '_type': { query: 'apache' } } } }; - mapFilter(mockIndexPatterns, before).then(function (after) { - expect(after).to.have.property('meta'); - expect(after.meta).to.have.property('key', '_type'); - expect(after.meta).to.have.property('value', 'apache'); - expect(after.meta).to.have.property('disabled', false); - expect(after.meta).to.have.property('negate', false); - done(); - }); - }); - - it('should map exists filters', function (done) { - const before = { meta: { index: 'logstash-*' }, exists: { field: '@timestamp' } }; - mapFilter(mockIndexPatterns, before).then(function (after) { - expect(after).to.have.property('meta'); - expect(after.meta).to.have.property('key', '@timestamp'); - expect(after.meta).to.have.property('value', 'exists'); - expect(after.meta).to.have.property('disabled', false); - expect(after.meta).to.have.property('negate', false); - done(); - }); - }); - - it('should map missing filters', function (done) { - const before = { meta: { index: 'logstash-*' }, missing: { field: '@timestamp' } }; - mapFilter(mockIndexPatterns, before).then(function (after) { - expect(after).to.have.property('meta'); - expect(after.meta).to.have.property('key', '@timestamp'); - expect(after.meta).to.have.property('value', 'missing'); - expect(after.meta).to.have.property('disabled', false); - expect(after.meta).to.have.property('negate', false); - done(); - }); - }); - - it('should map json filter', function (done) { - const before = { meta: { index: 'logstash-*' }, query: { match_all: {} } }; - mapFilter(mockIndexPatterns, before).then(function (after) { - expect(after).to.have.property('meta'); - expect(after.meta).to.have.property('key', 'query'); - expect(after.meta).to.have.property('value', '{"match_all":{}}'); - expect(after.meta).to.have.property('disabled', false); - expect(after.meta).to.have.property('negate', false); - done(); - }); - }); - - it('should finish with a catch', function (done) { - const before = { meta: { index: 'logstash-*' } }; - mapFilter(mockIndexPatterns, before).catch(function (error) { - expect(error).to.be.an(Error); - expect(error.message).to.be('No mappings have been found for filter.'); - done(); - }); - }); - - }); - -}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/map_geo_bounding_box.js b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/map_geo_bounding_box.js deleted file mode 100644 index 697402942b853..0000000000000 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/map_geo_bounding_box.js +++ /dev/null @@ -1,92 +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 expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { mapGeoBoundingBox } from '../map_geo_bounding_box'; -import IndexPatternMock from 'fixtures/mock_index_patterns'; - -describe('Filter Bar Directive', function () { - describe('mapGeoBoundingBox()', function () { - let mapGeoBoundingBoxFn; - let mockIndexPatterns; - - beforeEach(ngMock.module( - 'kibana', - 'kibana/courier' - )); - - beforeEach(ngMock.inject(function (Private) { - mockIndexPatterns = Private(IndexPatternMock); - mapGeoBoundingBoxFn = mapGeoBoundingBox(mockIndexPatterns); - })); - - it('should return the key and value for matching filters with bounds', function (done) { - const filter = { - meta: { - index: 'logstash-*' - }, - geo_bounding_box: { - point: { // field name - top_left: { lat: 5, lon: 10 }, - bottom_right: { lat: 15, lon: 20 } - } - } - }; - mapGeoBoundingBoxFn(filter).then(function (result) { - expect(result).to.have.property('key', 'point'); - expect(result).to.have.property('value'); - // remove html entities and non-alphanumerics to get the gist of the value - expect(result.value.replace(/&[a-z]+?;/g, '').replace(/[^a-z0-9]/g, '')).to.be('lat5lon10tolat15lon20'); - done(); - }); - }); - - it('should return undefined for none matching', function (done) { - const filter = { meta: { index: 'logstash-*' }, query: { query_string: { query: 'foo:bar' } } }; - mapGeoBoundingBoxFn(filter).catch(function (result) { - expect(result).to.be(filter); - done(); - }); - }); - - it('should return the key and value even when using ignore_unmapped', function (done) { - const filter = { - meta: { - index: 'logstash-*' - }, - geo_bounding_box: { - ignore_unmapped: true, - point: { // field name - top_left: { lat: 5, lon: 10 }, - bottom_right: { lat: 15, lon: 20 } - } - } - }; - mapGeoBoundingBoxFn(filter).then(function (result) { - expect(result).to.have.property('key', 'point'); - expect(result).to.have.property('value'); - // remove html entities and non-alphanumerics to get the gist of the value - expect(result.value.replace(/&[a-z]+?;/g, '').replace(/[^a-z0-9]/g, '')).to.be('lat5lon10tolat15lon20'); - done(); - }); - }); - - }); -}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/map_geo_polygon.js b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/map_geo_polygon.js deleted file mode 100644 index 9526bee500139..0000000000000 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/map_geo_polygon.js +++ /dev/null @@ -1,96 +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 expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { mapGeoPolygon } from '../map_geo_polygon'; -import IndexPatternMock from 'fixtures/mock_index_patterns'; - -describe('Filter Bar Directive', function () { - describe('mapGeoPolygon()', function () { - let mapGeoPolygonFn; - let mockIndexPatterns; - - beforeEach(ngMock.module( - 'kibana', - 'kibana/courier' - )); - - beforeEach(ngMock.inject(function (Private) { - mockIndexPatterns = Private(IndexPatternMock); - mapGeoPolygonFn = mapGeoPolygon(mockIndexPatterns); - })); - - it('should return the key and value for matching filters with bounds', function (done) { - const filter = { - meta: { - index: 'logstash-*' - }, - geo_polygon: { - point: { // field name - points: [ - { lat: 5, lon: 10 }, - { lat: 15, lon: 20 } - ] - } - } - }; - mapGeoPolygonFn(filter).then(function (result) { - expect(result).to.have.property('key', 'point'); - expect(result).to.have.property('value'); - // remove html entities and non-alphanumerics to get the gist of the value - expect(result.value.replace(/&[a-z]+?;/g, '').replace(/[^a-z0-9]/g, '')).to.be('lat5lon10lat15lon20'); - done(); - }); - }); - - it('should return undefined for none matching', function (done) { - const filter = { meta: { index: 'logstash-*' }, query: { query_string: { query: 'foo:bar' } } }; - mapGeoPolygonFn(filter).catch(function (result) { - expect(result).to.be(filter); - done(); - }); - }); - - it('should return the key and value even when using ignore_unmapped', function (done) { - const filter = { - meta: { - index: 'logstash-*' - }, - geo_polygon: { - ignore_unmapped: true, - point: { // field name - points: [ - { lat: 5, lon: 10 }, - { lat: 15, lon: 20 } - ] - } - } - }; - mapGeoPolygonFn(filter).then(function (result) { - expect(result).to.have.property('key', 'point'); - expect(result).to.have.property('value'); - // remove html entities and non-alphanumerics to get the gist of the value - expect(result.value.replace(/&[a-z]+?;/g, '').replace(/[^a-z0-9]/g, '')).to.be('lat5lon10lat15lon20'); - done(); - }); - }); - - }); -}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/map_match_all.js b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/map_match_all.js deleted file mode 100644 index e0d8fdc88f448..0000000000000 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/map_match_all.js +++ /dev/null @@ -1,68 +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 expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { mapMatchAll } from '../map_match_all'; - -describe('filter_manager/lib', function () { - describe('mapMatchAll()', function () { - let filter; - - - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function () { - filter = { - match_all: {}, - meta: { - field: 'foo', - formattedValue: 'bar' - } - }; - })); - - describe('when given a filter that is not match_all', function () { - it('filter is rejected', function (done) { - delete filter.match_all; - mapMatchAll(filter).catch(result => { - expect(result).to.be(filter); - done(); - }); - }); - }); - - describe('when given a match_all filter', function () { - let result; - beforeEach(function (done) { - mapMatchAll(filter).then(r => { - result = r; - done(); - }); - }); - - it('key is set to meta field', function () { - expect(result).to.have.property('key', filter.meta.field); - }); - - it('value is set to meta formattedValue', function () { - expect(result).to.have.property('value', filter.meta.formattedValue); - }); - }); - }); -}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/map_phrase.js b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/map_phrase.js deleted file mode 100644 index 0d6a91ef74b6a..0000000000000 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/map_phrase.js +++ /dev/null @@ -1,58 +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 expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { mapPhrase } from '../map_phrase'; -import IndexPatternMock from 'fixtures/mock_index_patterns'; - -describe('Filter Bar Directive', function () { - describe('mapPhrase()', function () { - let mapPhraseFn; - let mockIndexPatterns; - - beforeEach(ngMock.module( - 'kibana', - 'kibana/courier' - )); - - beforeEach(ngMock.inject(function (Private) { - mockIndexPatterns = Private(IndexPatternMock); - mapPhraseFn = mapPhrase(mockIndexPatterns); - })); - - it('should return the key and value for matching filters', function (done) { - const filter = { meta: { index: 'logstash-*' }, query: { match: { _type: { query: 'apache', type: 'phrase' } } } }; - mapPhraseFn(filter).then(function (result) { - expect(result).to.have.property('key', '_type'); - expect(result).to.have.property('value', 'apache'); - done(); - }); - }); - - it('should return undefined for none matching', function (done) { - const filter = { meta: { index: 'logstash-*' }, query: { query_string: { query: 'foo:bar' } } }; - mapPhraseFn(filter).catch(function (result) { - expect(result).to.be(filter); - done(); - }); - }); - - }); -}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/map_range.js b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/map_range.js deleted file mode 100644 index df8cf9c863eb1..0000000000000 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/map_range.js +++ /dev/null @@ -1,67 +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 expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { mapRange } from '../map_range'; -import IndexPatternMock from 'fixtures/mock_index_patterns'; - -describe('Filter Bar Directive', function () { - describe('mapRange()', function () { - let mapRangeFn; - let mockIndexPatterns; - - beforeEach(ngMock.module( - 'kibana', - 'kibana/courier' - )); - - beforeEach(ngMock.inject(function (Private) { - mockIndexPatterns = Private(IndexPatternMock); - mapRangeFn = mapRange(mockIndexPatterns); - })); - - it('should return the key and value for matching filters with gt/lt', function (done) { - const filter = { meta: { index: 'logstash-*' }, range: { bytes: { lt: 2048, gt: 1024 } } }; - mapRangeFn(filter).then(function (result) { - expect(result).to.have.property('key', 'bytes'); - expect(result).to.have.property('value', '1,024 to 2,048'); - done(); - }); - }); - - it('should return the key and value for matching filters with gte/lte', function (done) { - const filter = { meta: { index: 'logstash-*' }, range: { bytes: { lte: 2048, gte: 1024 } } }; - mapRangeFn(filter).then(function (result) { - expect(result).to.have.property('key', 'bytes'); - expect(result).to.have.property('value', '1,024 to 2,048'); - done(); - }); - }); - - it('should return undefined for none matching', function (done) { - const filter = { meta: { index: 'logstash-*' }, query: { query_string: { query: 'foo:bar' } } }; - mapRangeFn(filter).catch(function (result) { - expect(result).to.be(filter); - done(); - }); - }); - - }); -}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/uniq_filters.js b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/uniq_filters.js deleted file mode 100644 index 569f03f28192b..0000000000000 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/uniq_filters.js +++ /dev/null @@ -1,65 +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 { uniqFilters } from '../uniq_filters'; -import expect from '@kbn/expect'; - -describe('Filter Bar Directive', function () { - describe('uniqFilter', function () { - - it('should filter out dups', function () { - const before = [ - { query: { _type: { match: { query: 'apache', type: 'phrase' } } } }, - { query: { _type: { match: { query: 'apache', type: 'phrase' } } } } - ]; - const results = uniqFilters(before); - expect(results).to.have.length(1); - }); - - it('should filter out duplicates, ignoring meta attributes', function () { - const before = [ - { - meta: { negate: true }, - query: { _type: { match: { query: 'apache', type: 'phrase' } } } - }, - { - meta: { negate: false }, - query: { _type: { match: { query: 'apache', type: 'phrase' } } } - } - ]; - const results = uniqFilters(before); - expect(results).to.have.length(1); - }); - - it('should filter out duplicates, ignoring $state attributes', function () { - const before = [ - { - $state: { store: 'appState' }, - query: { _type: { match: { query: 'apache', type: 'phrase' } } } - }, - { - $state: { store: 'globalState' }, - query: { _type: { match: { query: 'apache', type: 'phrase' } } } - } - ]; - const results = uniqFilters(before); - expect(results).to.have.length(1); - }); - }); -}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/change_time_filter.test.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/change_time_filter.test.ts similarity index 68% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/change_time_filter.test.ts rename to src/legacy/core_plugins/data/public/filter/filter_manager/lib/change_time_filter.test.ts index d3d05d20e9fba..2e397ff931bb6 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/change_time_filter.test.ts +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/change_time_filter.test.ts @@ -16,15 +16,16 @@ * specific language governing permissions and limitations * under the License. */ -import expect from '@kbn/expect'; -import { changeTimeFilter } from '../change_time_filter'; +import { RangeFilter } from '@kbn/es-query'; +import { changeTimeFilter } from './change_time_filter'; import { TimeRange } from 'src/plugins/data/public'; +import { timefilterServiceMock } from '../../../timefilter/timefilter_service.mock'; -import { timefilterServiceMock } from '../../../../timefilter/timefilter_service.mock'; const timefilterMock = timefilterServiceMock.createSetupContract(); const timefilter = timefilterMock.timefilter; let _time: TimeRange | undefined; + timefilter.setTime.mockImplementation((time: any) => { _time = { from: time.from.toISOString(), @@ -35,23 +36,27 @@ timefilter.getTime.mockImplementation(() => { return _time!; }); -describe('changeTimeFilter()', function() { +describe('changeTimeFilter()', () => { const gt = 1388559600000; const lt = 1388646000000; - test('should change the timefilter to match the range gt/lt', function() { - const filter = { range: { '@timestamp': { gt, lt } } }; - changeTimeFilter(timefilter, filter); + test('should change the timefilter to match the range gt/lt', () => { + const filter: any = { range: { '@timestamp': { gt, lt } } }; + changeTimeFilter(timefilter, filter as RangeFilter); + const { to, from } = timefilter.getTime(); - expect(to).to.be(new Date(lt).toISOString()); - expect(from).to.be(new Date(gt).toISOString()); + + expect(to).toBe(new Date(lt).toISOString()); + expect(from).toBe(new Date(gt).toISOString()); }); - test('should change the timefilter to match the range gte/lte', function() { - const filter = { range: { '@timestamp': { gte: gt, lte: lt } } }; - changeTimeFilter(timefilter, filter); + test('should change the timefilter to match the range gte/lte', () => { + const filter: any = { range: { '@timestamp': { gte: gt, lte: lt } } }; + changeTimeFilter(timefilter, filter as RangeFilter); + const { to, from } = timefilter.getTime(); - expect(to).to.be(new Date(lt).toISOString()); - expect(from).to.be(new Date(gt).toISOString()); + + expect(to).toBe(new Date(lt).toISOString()); + expect(from).toBe(new Date(gt).toISOString()); }); }); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/change_time_filter.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/change_time_filter.ts index 6c6ed775951c8..fac40bf53fece 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/change_time_filter.ts +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/change_time_filter.ts @@ -18,14 +18,18 @@ */ import moment from 'moment'; -import _ from 'lodash'; +import { keys } from 'lodash'; +import { RangeFilter, isRangeFilter } from '@kbn/es-query'; import { TimefilterContract } from '../../../timefilter'; -export function changeTimeFilter(timefilter: TimefilterContract, filter: any) { - const key = _.keys(filter.range)[0]; - const values = filter.range[key]; - timefilter.setTime({ - from: moment(values.gt || values.gte), - to: moment(values.lt || values.lte), - }); +export function changeTimeFilter(timeFilter: TimefilterContract, filter: RangeFilter) { + if (isRangeFilter(filter)) { + const key = keys(filter.range)[0]; + const values = filter.range[key]; + + timeFilter.setTime({ + from: moment(values.gt || values.gte), + to: moment(values.lt || values.lte), + }); + } } diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/compare_filters.test.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/compare_filters.test.ts new file mode 100644 index 0000000000000..e8244feb988b6 --- /dev/null +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/compare_filters.test.ts @@ -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 { buildQueryFilter, buildEmptyFilter, FilterStateStore } from '@kbn/es-query'; +import { compareFilters } from './compare_filters'; + +describe('filter manager utilities', () => { + describe('compare filters', () => { + test('should compare filters', () => { + const f1 = buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index' + ); + const f2 = buildEmptyFilter(true); + + expect(compareFilters(f1, f2)).toBeFalsy(); + }); + + test('should compare duplicates', () => { + const f1 = buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index' + ); + const f2 = buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index' + ); + + expect(compareFilters(f1, f2)).toBeTruthy(); + }); + + test('should compare duplicates, ignoring meta attributes', () => { + const f1 = buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index1' + ); + const f2 = buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index2' + ); + + expect(compareFilters(f1, f2)).toBeTruthy(); + }); + + test('should compare duplicates, ignoring $state attributes', () => { + const f1 = { + $state: { store: FilterStateStore.APP_STATE }, + ...buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index'), + }; + const f2 = { + $state: { store: FilterStateStore.GLOBAL_STATE }, + ...buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index'), + }; + + expect(compareFilters(f1, f2)).toBeTruthy(); + }); + }); +}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/compare_filters.js b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/compare_filters.ts similarity index 62% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/compare_filters.js rename to src/legacy/core_plugins/data/public/filter/filter_manager/lib/compare_filters.ts index f9f11f96d194f..44bc333ae2b4f 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/compare_filters.js +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/compare_filters.ts @@ -17,20 +17,31 @@ * under the License. */ -import _ from 'lodash'; -let excludedAttributes; -let comparators; +import { Filter, FilterMeta } from '@kbn/es-query'; +import { defaults, isEqual, omit } from 'lodash'; /** * Compare two filters to see if they match + * * @param {object} first The first filter to compare * @param {object} second The second filter to compare * @param {object} comparatorOptions Parameters to use for comparison + * * @returns {bool} Filters are the same */ -export function compareFilters(first, second, comparatorOptions) { - excludedAttributes = ['$$hashKey', 'meta']; - comparators = _.defaults(comparatorOptions || {}, { +export const compareFilters = (first: Filter, second: Filter, comparatorOptions: any = {}) => { + let comparators: any = {}; + const mapFilter = (filter: Filter) => { + const cleaned: FilterMeta = omit(filter, excludedAttributes); + + if (comparators.negate) cleaned.negate = filter.meta && Boolean(filter.meta.negate); + if (comparators.disabled) cleaned.disabled = filter.meta && Boolean(filter.meta.disabled); + + return cleaned; + }; + const excludedAttributes: string[] = ['$$hashKey', 'meta']; + + comparators = defaults(comparatorOptions || {}, { state: false, negate: false, disabled: false, @@ -38,12 +49,5 @@ export function compareFilters(first, second, comparatorOptions) { if (!comparators.state) excludedAttributes.push('$state'); - return _.isEqual(mapFilter(first), mapFilter(second)); -} - -function mapFilter(filter) { - const cleaned = _.omit(filter, excludedAttributes); - if (comparators.negate) cleaned.negate = filter.meta && !!filter.meta.negate; - if (comparators.disabled) cleaned.disabled = filter.meta && !!filter.meta.disabled; - return cleaned; -} + return isEqual(mapFilter(first), mapFilter(second)); +}; diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/dedup_filters.test.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/dedup_filters.test.ts new file mode 100644 index 0000000000000..75bd9d5dfbd81 --- /dev/null +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/dedup_filters.test.ts @@ -0,0 +1,79 @@ +/* + * 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, buildRangeFilter, FilterStateStore, buildQueryFilter } from '@kbn/es-query'; +import { dedupFilters } from './dedup_filters'; + +describe('filter manager utilities', () => { + describe('dedupFilters(existing, filters)', () => { + test('should return only filters which are not in the existing', () => { + const existing: Filter[] = [ + buildRangeFilter({ name: 'bytes' }, { from: 0, to: 1024 }, 'index'), + buildQueryFilter({ match: { _term: { query: 'apache', type: 'phrase' } } }, 'index'), + ]; + const filters: Filter[] = [ + buildRangeFilter({ name: 'bytes' }, { from: 1024, to: 2048 }, 'index'), + buildQueryFilter({ match: { _term: { query: 'apache', type: 'phrase' } } }, 'index'), + ]; + const results = dedupFilters(existing, filters); + + expect(results).toContain(filters[0]); + expect(results).not.toContain(filters[1]); + }); + + test('should ignore the disabled attribute when comparing ', () => { + const existing: Filter[] = [ + buildRangeFilter({ name: 'bytes' }, { from: 0, to: 1024 }, 'index'), + { + ...buildQueryFilter({ match: { _term: { query: 'apache', type: 'phrase' } } }, 'index'), + meta: { disabled: true, negate: false, alias: null }, + }, + ]; + const filters: Filter[] = [ + buildRangeFilter({ name: 'bytes' }, { from: 1024, to: 2048 }, 'index'), + buildQueryFilter({ match: { _term: { query: 'apache', type: 'phrase' } } }, 'index'), + ]; + const results = dedupFilters(existing, filters); + + expect(results).toContain(filters[0]); + expect(results).not.toContain(filters[1]); + }); + + test('should ignore $state attribute', () => { + const existing: Filter[] = [ + buildRangeFilter({ name: 'bytes' }, { from: 0, to: 1024 }, 'index'), + { + ...buildQueryFilter({ match: { _term: { query: 'apache', type: 'phrase' } } }, 'index'), + $state: { store: FilterStateStore.APP_STATE }, + }, + ]; + const filters: Filter[] = [ + buildRangeFilter({ name: 'bytes' }, { from: 1024, to: 2048 }, 'index'), + { + ...buildQueryFilter({ match: { _term: { query: 'apache', type: 'phrase' } } }, 'index'), + $state: { store: FilterStateStore.GLOBAL_STATE }, + }, + ]; + const results = dedupFilters(existing, filters); + + expect(results).toContain(filters[0]); + expect(results).not.toContain(filters[1]); + }); + }); +}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/dedup_filters.js b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/dedup_filters.ts similarity index 60% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/dedup_filters.js rename to src/legacy/core_plugins/data/public/filter/filter_manager/lib/dedup_filters.ts index 055e23ab2f25f..9565cbd80b779 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/dedup_filters.js +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/dedup_filters.ts @@ -17,22 +17,33 @@ * under the License. */ -import _ from 'lodash'; +import { Filter } from '@kbn/es-query'; +import { filter, find } from 'lodash'; import { compareFilters } from './compare_filters'; /** * Combine 2 filter collections, removing duplicates - * @param {object} existing The filters to compare to - * @param {object} filters The filters being added - * @param {object} comparatorOptions Parameters to use for comparison + * + * @param {object} existingFilters - The filters to compare to + * @param {object} filters - The filters being added + * @param {object} comparatorOptions - Parameters to use for comparison + * * @returns {object} An array of filters that were not in existing */ -export function dedupFilters(existingFilters, filters, comparatorOptions) { - if (!Array.isArray(filters)) filters = [filters]; +export const dedupFilters = ( + existingFilters: Filter[], + filters: Filter[], + comparatorOptions: any = {} +) => { + if (!Array.isArray(filters)) { + filters = [filters]; + } - return _.filter(filters, function (filter) { - return !_.find(existingFilters, function (existingFilter) { - return compareFilters(existingFilter, filter, comparatorOptions); - }); - }); -} + return filter( + filters, + (f: Filter) => + !find(existingFilters, (existingFilter: Filter) => + compareFilters(existingFilter, f, comparatorOptions) + ) + ); +}; diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/extract_time_filter.test.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/extract_time_filter.test.ts new file mode 100644 index 0000000000000..7d8e4edbfd849 --- /dev/null +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/extract_time_filter.test.ts @@ -0,0 +1,57 @@ +/* + * 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, buildRangeFilter, buildQueryFilter } from '@kbn/es-query'; +import { extractTimeFilter } from './extract_time_filter'; +import { IndexPatterns } from '../../../index_patterns'; + +const mockIndexPatterns = jest.fn( + () => + ({ + get: () => ({ + timeFieldName: 'time', + }), + } as any) +); + +describe('filter manager utilities', () => { + describe('extractTimeFilter()', () => { + const indexPatterns = mockIndexPatterns() as IndexPatterns; + + test('should return the matching filter for the default time field', async () => { + const filters: Filter[] = [ + buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'logstash-*'), + buildRangeFilter({ name: 'time' }, { gt: 1388559600000, lt: 1388646000000 }, 'logstash-*'), + ]; + const result = await extractTimeFilter(indexPatterns, filters); + + expect(result).toEqual(filters[1]); + }); + + test('should not return the non-matching filter for the default time field', async () => { + const filters: Filter[] = [ + buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'logstash-*'), + buildRangeFilter({ name: '@timestamp' }, { from: 1, to: 2 }, 'logstash-*'), + ]; + const result = await extractTimeFilter(indexPatterns, filters); + + expect(result).toBeUndefined(); + }); + }); +}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/extract_time_filter.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/extract_time_filter.ts index e6fea9cccd8f0..a951c2df4383a 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/extract_time_filter.ts +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/extract_time_filter.ts @@ -17,22 +17,25 @@ * under the License. */ -import _ from 'lodash'; +import { keys, find, get } from 'lodash'; +import { Filter, isRangeFilter } from '@kbn/es-query'; import { IndexPatterns } from '../../../index_patterns'; -export async function extractTimeFilter(indexPatterns: IndexPatterns, filters: any) { +export async function extractTimeFilter(indexPatterns: IndexPatterns, filters: Filter[]) { // Assume all the index patterns are the same since they will be added // from the same visualization. - const id: string = _.get(filters, '[0].meta.index'); + const id: string = get(filters, '[0].meta.index'); if (id == null) return; const indexPattern = await indexPatterns.get(id); - const filter = _.find(filters, function(obj: any) { - const key = _.keys(obj.range)[0]; - return key === indexPattern.timeFieldName; + return find(filters, (obj: Filter) => { + let key; + + if (isRangeFilter(obj)) { + key = keys(obj.range)[0]; + } + + return Boolean(key && key === indexPattern.timeFieldName); }); - if (filter && filter.range) { - return filter; - } } diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/generate_mapping_chain.test.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/generate_mapping_chain.test.ts new file mode 100644 index 0000000000000..7f9dbb1ea2609 --- /dev/null +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/generate_mapping_chain.test.ts @@ -0,0 +1,114 @@ +/* + * 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 sinon from 'sinon'; +import { Filter, buildEmptyFilter } from '@kbn/es-query'; +import { generateMappingChain } from './generate_mapping_chain'; + +describe('filter manager utilities', () => { + let mapping: any; + let next: any; + + beforeEach(() => { + mapping = sinon.stub(); + next = sinon.stub(); + }); + + describe('generateMappingChain()', () => { + test('should create a chaining function which calls the next function if the promise is rejected', async () => { + const filter: Filter = buildEmptyFilter(true); + + mapping.rejects(filter); + next.resolves('good'); + + const chain = generateMappingChain(mapping, next); + const result = await chain(filter); + + expect(result).toBe('good'); + sinon.assert.calledOnce(next); + }); + + test('should create a chaining function which DOES NOT call the next function if the result is resolved', async () => { + const filter: Filter = buildEmptyFilter(true); + + mapping.resolves('good'); + next.resolves('bad'); + + const chain = generateMappingChain(mapping, next); + const result = await chain(filter); + + expect(result).toBe('good'); + }); + + test('should resolve result for the mapping function', async () => { + const filter: Filter = buildEmptyFilter(true); + + mapping.resolves({ key: 'test', value: 'example' }); + + const chain = generateMappingChain(mapping, next); + const result = await chain(filter); + + sinon.assert.notCalled(next); + expect(result).toEqual({ key: 'test', value: 'example' }); + }); + + test('should call the mapping function with the argument to the chain', async () => { + // @ts-ignore + const filter: Filter = { test: 'example' }; + + mapping.resolves({ key: 'test', value: 'example' }); + + const chain = generateMappingChain(mapping, next); + const result = await chain(filter); + + sinon.assert.calledOnce(mapping); + expect(mapping.args[0][0]).toEqual({ test: 'example' }); + sinon.assert.notCalled(next); + expect(result).toEqual({ key: 'test', value: 'example' }); + }); + + test('should resolve result for the next function', async () => { + const filter: Filter = buildEmptyFilter(true); + + mapping.rejects(filter); + next.resolves({ key: 'test', value: 'example' }); + + const chain = generateMappingChain(mapping, next); + const result = await chain(filter); + + sinon.assert.calledOnce(mapping); + sinon.assert.calledOnce(next); + expect(result).toEqual({ key: 'test', value: 'example' }); + }); + + test('should reject with an error if no functions match', async done => { + const filter: Filter = buildEmptyFilter(true); + + mapping.rejects(filter); + + const chain = generateMappingChain(mapping); + + chain(filter).catch(err => { + expect(err).toBeInstanceOf(Error); + expect(err.message).toBe('No mappings have been found for filter.'); + done(); + }); + }); + }); +}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/generate_mapping_chain.js b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/generate_mapping_chain.ts similarity index 75% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/generate_mapping_chain.js rename to src/legacy/core_plugins/data/public/filter/filter_manager/lib/generate_mapping_chain.ts index 02905999464fd..38c83f3f4a821 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/generate_mapping_chain.js +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/generate_mapping_chain.ts @@ -16,19 +16,19 @@ * specific language governing permissions and limitations * under the License. */ +import { Filter } from '@kbn/es-query'; -export function generateMappingChain(fn, next) { - const noop = function () { - throw new Error('No mappings have been found for filter.'); - }; +const noop = () => { + throw new Error('No mappings have been found for filter.'); +}; - next = next || noop; - return async function (filter) { - return await fn(filter).catch(function (result) { +export const generateMappingChain = (fn: Function, next: Function = noop) => { + return async (filter: Filter) => { + return await fn(filter).catch((result: any) => { if (result === filter) { return next(filter); } throw result; }); }; -} +}; diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_and_flatten_filters.test.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_and_flatten_filters.test.ts new file mode 100644 index 0000000000000..c9fcc745d48e3 --- /dev/null +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_and_flatten_filters.test.ts @@ -0,0 +1,71 @@ +/* + * 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 '@kbn/es-query'; +import { mapAndFlattenFilters } from './map_and_flatten_filters'; +import { StubIndexPatterns } from '../test_helpers/stub_index_pattern'; +import { IndexPatterns } from '../../../index_patterns'; + +describe('filter manager utilities', () => { + describe('mapAndFlattenFilters()', () => { + let mockIndexPatterns: unknown; + let filters: unknown; + + beforeEach(() => { + mockIndexPatterns = new StubIndexPatterns(); + filters = [ + null, + [ + { meta: { index: 'logstash-*' }, exists: { field: '_type' } }, + { meta: { index: 'logstash-*' }, missing: { field: '_type' } }, + ], + { meta: { index: 'logstash-*' }, query: { query_string: { query: 'foo:bar' } } }, + { meta: { index: 'logstash-*' }, range: { bytes: { lt: 2048, gt: 1024 } } }, + { + meta: { index: 'logstash-*' }, + query: { match: { _type: { query: 'apache', type: 'phrase' } } }, + }, + ]; + }); + + test('should map and flatten the filters', async () => { + const results = await mapAndFlattenFilters( + mockIndexPatterns as IndexPatterns, + filters as Filter[] + ); + + expect(results).toHaveLength(5); + expect(results[0]).toHaveProperty('meta'); + expect(results[1]).toHaveProperty('meta'); + expect(results[2]).toHaveProperty('meta'); + expect(results[3]).toHaveProperty('meta'); + expect(results[4]).toHaveProperty('meta'); + expect(results[0].meta).toHaveProperty('key', '_type'); + expect(results[0].meta).toHaveProperty('value', 'exists'); + expect(results[1].meta).toHaveProperty('key', '_type'); + expect(results[1].meta).toHaveProperty('value', 'missing'); + expect(results[2].meta).toHaveProperty('key', 'query'); + expect(results[2].meta).toHaveProperty('value', 'foo:bar'); + expect(results[3].meta).toHaveProperty('key', 'bytes'); + expect(results[3].meta).toHaveProperty('value', '1024 to 2048'); + expect(results[4].meta).toHaveProperty('key', '_type'); + expect(results[4].meta).toHaveProperty('value', 'apache'); + }); + }); +}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_and_flatten_filters.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_and_flatten_filters.ts new file mode 100644 index 0000000000000..1481295896187 --- /dev/null +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_and_flatten_filters.ts @@ -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 { compact, flatten } from 'lodash'; +import { Filter } from '@kbn/es-query'; +import { mapFilter } from './map_filter'; +import { IndexPatterns } from '../../../index_patterns'; + +export const mapAndFlattenFilters = (indexPatterns: IndexPatterns, filters: Filter[]) => { + const promises = compact(flatten(filters)).map((item: Filter) => mapFilter(indexPatterns, item)); + + return Promise.all(promises); +}; diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/map_missing.js b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_default.test.ts similarity index 51% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/map_missing.js rename to src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_default.test.ts index a05c21b6928fb..78f3b40bbbe2c 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/map_missing.js +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_default.test.ts @@ -16,29 +16,27 @@ * specific language governing permissions and limitations * under the License. */ +import { CustomFilter, buildEmptyFilter, buildQueryFilter } from '@kbn/es-query'; +import { mapDefault } from './map_default'; -import expect from '@kbn/expect'; -import { mapMissing } from '../map_missing'; +describe('filter manager utilities', () => { + describe('mapDefault()', () => { + test('should return the key and value for matching filters', async () => { + const filter: CustomFilter = buildQueryFilter({ match_all: {} }, 'index'); + const result = await mapDefault(filter); -describe('Filter Bar Directive', function () { - describe('mapMissing()', function () { - - it('should return the key and value for matching filters', function (done) { - const filter = { missing: { field: '_type' } }; - mapMissing(filter).then(function (result) { - expect(result).to.have.property('key', '_type'); - expect(result).to.have.property('value', 'missing'); - done(); - }); + expect(result).toHaveProperty('key', 'query'); + expect(result).toHaveProperty('value', '{"match_all":{}}'); }); - it('should return undefined for none matching', function (done) { - const filter = { query: { match: { query: 'foo' } } }; - mapMissing(filter).catch(function (result) { - expect(result).to.be(filter); - done(); - }); - }); + test('should return undefined if there is no valid key', async () => { + const filter = buildEmptyFilter(true) as CustomFilter; + try { + await mapDefault(filter); + } catch (e) { + expect(e).toBe(filter); + } + }); }); }); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_default.js b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_default.ts similarity index 74% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_default.js rename to src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_default.ts index 6b9b09829c839..bdfce4e753f65 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_default.js +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_default.ts @@ -17,21 +17,19 @@ * under the License. */ -import angular from 'angular'; -import _ from 'lodash'; +import { Filter, FILTERS } from '@kbn/es-query'; +import { find, keys, get } from 'lodash'; -export async function mapDefault(filter) { +export const mapDefault = async (filter: Filter) => { const metaProperty = /(^\$|meta)/; - - const key = _.find(_.keys(filter), function (key) { - return !key.match(metaProperty); - }); + const key = find(keys(filter), item => !item.match(metaProperty)); if (key) { - const type = 'custom'; - const value = angular.toJson(filter[key]); + const type = FILTERS.CUSTOM; + const value = JSON.stringify(get(filter, key, {})); + return { type, key, value }; } throw filter; -} +}; diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/map_query_string.js b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_exists.test.ts similarity index 50% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/map_query_string.js rename to src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_exists.test.ts index dfcfd56fa9f7d..670056b68234f 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/map_query_string.js +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_exists.test.ts @@ -16,29 +16,29 @@ * specific language governing permissions and limitations * under the License. */ +import { ExistsFilter, buildEmptyFilter, buildExistsFilter } from '@kbn/es-query'; +import { mapExists } from './map_exists'; +import { mapQueryString } from './map_query_string'; -import expect from '@kbn/expect'; -import { mapQueryString } from '../map_query_string'; +describe('filter manager utilities', () => { + describe('mapExists()', () => { + test('should return the key and value for matching filters', async () => { + const filter: ExistsFilter = buildExistsFilter({ name: '_type' }, 'index'); + const result = await mapExists(filter); -describe('Filter Bar Directive', function () { - describe('mapQueryString()', function () { - - it('should return the key and value for matching filters', function (done) { - const filter = { query: { query_string: { query: 'foo:bar' } } }; - mapQueryString(filter).then(function (result) { - expect(result).to.have.property('key', 'query'); - expect(result).to.have.property('value', 'foo:bar'); - done(); - }); + expect(result).toHaveProperty('key', '_type'); + expect(result).toHaveProperty('value', 'exists'); }); - it('should return undefined for none matching', function (done) { - const filter = { query: { match: { query: 'foo' } } }; - mapQueryString(filter).catch(function (result) { - expect(result).to.be(filter); + test('should return undefined for none matching', async done => { + const filter = buildEmptyFilter(true) as ExistsFilter; + + try { + await mapQueryString(filter); + } catch (e) { + expect(e).toBe(filter); done(); - }); + } }); - }); }); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_query_string.js b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_exists.ts similarity index 73% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_query_string.js rename to src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_exists.ts index adb6db3d03143..bf793378342c7 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_query_string.js +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_exists.ts @@ -17,12 +17,16 @@ * under the License. */ -export async function mapQueryString(filter) { - if (filter.query && filter.query.query_string) { - const type = 'query_string'; - const key = 'query'; - const value = filter.query.query_string.query; - return { type, key, value }; +import { Filter, isExistsFilter, FILTERS } from '@kbn/es-query'; +import { get } from 'lodash'; + +export const mapExists = async (filter: Filter) => { + if (isExistsFilter(filter)) { + return { + type: FILTERS.EXISTS, + value: FILTERS.EXISTS, + key: get(filter, 'exists.field'), + }; } throw filter; -} +}; diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_filter.test.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_filter.test.ts new file mode 100644 index 0000000000000..ca701454481ee --- /dev/null +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_filter.test.ts @@ -0,0 +1,95 @@ +/* + * 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 '@kbn/es-query'; +import { mapFilter } from './map_filter'; +import { StubIndexPatterns } from '../test_helpers/stub_index_pattern'; +import { IndexPatterns } from '../../../index_patterns'; + +describe('filter manager utilities', () => { + let indexPatterns: IndexPatterns; + + beforeEach(() => { + const stubIndexPatterns: unknown = new StubIndexPatterns(); + + indexPatterns = stubIndexPatterns as IndexPatterns; + }); + + describe('mapFilter()', () => { + test('should map query filters', async () => { + const before = { + meta: { index: 'logstash-*' }, + query: { match: { _type: { query: 'apache' } } }, + }; + const after = await mapFilter(indexPatterns, before as Filter); + + expect(after).toHaveProperty('meta'); + expect(after.meta).toHaveProperty('key', '_type'); + expect(after.meta).toHaveProperty('value', 'apache'); + expect(after.meta).toHaveProperty('disabled', false); + expect(after.meta).toHaveProperty('negate', false); + }); + + test('should map exists filters', async () => { + const before: any = { meta: { index: 'logstash-*' }, exists: { field: '@timestamp' } }; + const after = await mapFilter(indexPatterns, before as Filter); + + expect(after).toHaveProperty('meta'); + expect(after.meta).toHaveProperty('key', '@timestamp'); + expect(after.meta).toHaveProperty('value', 'exists'); + expect(after.meta).toHaveProperty('disabled', false); + expect(after.meta).toHaveProperty('negate', false); + }); + + test('should map missing filters', async () => { + const before: any = { meta: { index: 'logstash-*' }, missing: { field: '@timestamp' } }; + const after = await mapFilter(indexPatterns, before as Filter); + + expect(after).toHaveProperty('meta'); + expect(after.meta).toHaveProperty('key', '@timestamp'); + expect(after.meta).toHaveProperty('value', 'missing'); + expect(after.meta).toHaveProperty('disabled', false); + expect(after.meta).toHaveProperty('negate', false); + }); + + test('should map json filter', async () => { + const before: any = { meta: { index: 'logstash-*' }, query: { match_all: {} } }; + const after = await mapFilter(indexPatterns, before as Filter); + + expect(after).toHaveProperty('meta'); + expect(after.meta).toHaveProperty('key', 'query'); + expect(after.meta).toHaveProperty('value', '{"match_all":{}}'); + expect(after.meta).toHaveProperty('disabled', false); + expect(after.meta).toHaveProperty('negate', false); + }); + + test('should finish with a catch', async done => { + const before: any = { meta: { index: 'logstash-*' } }; + + try { + await mapFilter(indexPatterns, before as Filter); + } catch (e) { + expect(e).toBeInstanceOf(Error); + expect(e.message).toBe('No mappings have been found for filter.'); + + done(); + } + }); + }); +}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_filter.js b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_filter.ts similarity index 86% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_filter.js rename to src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_filter.ts index ab468e9243273..f607595915a52 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_filter.js +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_filter.ts @@ -17,7 +17,10 @@ * under the License. */ -import _ from 'lodash'; +import { Filter } from '@kbn/es-query'; +import { reduceRight } from 'lodash'; +import { IndexPatterns } from '../../../index_patterns'; + import { mapMatchAll } from './map_match_all'; import { mapPhrase } from './map_phrase'; import { mapPhrases } from './map_phrases'; @@ -30,7 +33,7 @@ import { mapGeoPolygon } from './map_geo_polygon'; import { mapDefault } from './map_default'; import { generateMappingChain } from './generate_mapping_chain'; -export async function mapFilter(indexPatterns, filter) { +export async function mapFilter(indexPatterns: IndexPatterns, filter: Filter) { /** Mappers **/ // Each mapper is a simple promise function that test if the mapper can @@ -60,16 +63,17 @@ export async function mapFilter(indexPatterns, filter) { mapDefault, ]; - const noop = function () { + const noop = () => { throw new Error('No mappings have been found for filter.'); }; // Create a chain of responsibility by reducing all the // mappers down into one function. - const mapFn = _.reduceRight(mappers, function (memo, map) { - return generateMappingChain(map, memo); - }, noop); - + const mapFn = reduceRight( + mappers, + (memo, map) => generateMappingChain(map, memo), + noop + ); const mapped = await mapFn(filter); // Map the filter into an object with the key and value exposed so it's @@ -79,8 +83,8 @@ export async function mapFilter(indexPatterns, filter) { filter.meta.key = mapped.key; filter.meta.value = mapped.value; filter.meta.params = mapped.params; - filter.meta.disabled = !!(filter.meta.disabled); - filter.meta.negate = !!(filter.meta.negate); + filter.meta.disabled = Boolean(filter.meta.disabled); + filter.meta.negate = Boolean(filter.meta.negate); filter.meta.alias = filter.meta.alias || null; return filter; diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_bounding_box.js b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_bounding_box.js deleted file mode 100644 index bfb4bdd4ca3b4..0000000000000 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_bounding_box.js +++ /dev/null @@ -1,58 +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 { SavedObjectNotFound } from '../../../../../../../plugins/kibana_utils/public'; - -function getParams(filter, indexPattern) { - const type = 'geo_bounding_box'; - const key = _.keys(filter.geo_bounding_box) - .filter(key => key !== 'ignore_unmapped')[0]; - const params = filter.geo_bounding_box[key]; - - // Sometimes a filter will end up with an invalid index param. This could happen for a lot of reasons, - // for example a user might manually edit the url or the index pattern's ID might change due to - // external factors e.g. a reindex. We only need the index in order to grab the field formatter, so we fallback - // on displaying the raw value if the index is invalid. - const topLeft = indexPattern - ? indexPattern.fields.byName[key].format.convert(params.top_left) - : JSON.stringify(params.top_left); - const bottomRight = indexPattern - ? indexPattern.fields.byName[key].format.convert(params.bottom_right) - : JSON.stringify(params.bottom_right); - const value = topLeft + ' to ' + bottomRight; - return { type, key, value, params }; -} - -export function mapGeoBoundingBox(indexPatterns) { - return async function (filter) { - if (!filter.geo_bounding_box) { - throw filter; - } - try { - const indexPattern = await indexPatterns.get(filter.meta.index); - return getParams(filter, indexPattern); - } catch (error) { - if (error instanceof SavedObjectNotFound) { - return getParams(filter); - } - throw error; - } - }; -} diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_bounding_box.test.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_bounding_box.test.ts new file mode 100644 index 0000000000000..51b38c343d2a9 --- /dev/null +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_bounding_box.test.ts @@ -0,0 +1,96 @@ +/* + * 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 { mapGeoBoundingBox } from './map_geo_bounding_box'; +import { StubIndexPatterns } from '../test_helpers/stub_index_pattern'; +import { IndexPatterns } from '../../../index_patterns'; + +describe('filter manager utilities', () => { + describe('mapGeoBoundingBox()', () => { + let mapGeoBoundingBoxFn: Function; + + beforeEach(() => { + const indexPatterns: unknown = new StubIndexPatterns(); + + mapGeoBoundingBoxFn = mapGeoBoundingBox(indexPatterns as IndexPatterns); + }); + + test('should return the key and value for matching filters with bounds', async () => { + const filter = { + meta: { + index: 'logstash-*', + }, + geo_bounding_box: { + point: { + // field name + top_left: { lat: 5, lon: 10 }, + bottom_right: { lat: 15, lon: 20 }, + }, + }, + }; + + const result = await mapGeoBoundingBoxFn(filter); + + expect(result).toHaveProperty('key', 'point'); + expect(result).toHaveProperty('value'); + // remove html entities and non-alphanumerics to get the gist of the value + expect(result.value.replace(/&[a-z]+?;/g, '').replace(/[^a-z0-9]/g, '')).toBe( + 'lat5lon10tolat15lon20' + ); + }); + + test('should return the key and value even when using ignore_unmapped', async () => { + const filter = { + meta: { + index: 'logstash-*', + }, + geo_bounding_box: { + ignore_unmapped: true, + point: { + // field name + top_left: { lat: 5, lon: 10 }, + bottom_right: { lat: 15, lon: 20 }, + }, + }, + }; + const result = await mapGeoBoundingBoxFn(filter); + + expect(result).toHaveProperty('key', 'point'); + expect(result).toHaveProperty('value'); + // remove html entities and non-alphanumerics to get the gist of the value + expect(result.value.replace(/&[a-z]+?;/g, '').replace(/[^a-z0-9]/g, '')).toBe( + 'lat5lon10tolat15lon20' + ); + }); + + test('should return undefined for none matching', async done => { + const filter = { + meta: { index: 'logstash-*' }, + query: { query_string: { query: 'foo:bar' } }, + }; + + try { + await mapGeoBoundingBoxFn(filter); + } catch (e) { + expect(e).toBe(filter); + done(); + } + }); + }); +}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_bounding_box.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_bounding_box.ts new file mode 100644 index 0000000000000..de6b63c287cc4 --- /dev/null +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_bounding_box.ts @@ -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 { get } from 'lodash'; +import { GeoBoundingBoxFilter, Filter, FILTERS, isGeoBoundingBoxFilter } from '@kbn/es-query'; +import { IndexPatterns, IndexPattern } from '../../../index_patterns'; +import { SavedObjectNotFound } from '../../../../../../../plugins/kibana_utils/public'; + +const getFormattedValue = (params: any, key: string, indexPattern?: IndexPattern) => { + const formatter: any = + indexPattern && key && get(indexPattern, ['fields', 'byName', key, 'format']); + + return formatter + ? { + topLeft: formatter.convert(params.top_left), + bottomRight: formatter.convert(params.bottom_right), + } + : { + topLeft: JSON.stringify(params.top_left), + bottomRight: JSON.stringify(params.bottom_right), + }; +}; + +const getParams = (filter: GeoBoundingBoxFilter, indexPattern?: IndexPattern) => { + const key = Object.keys(filter.geo_bounding_box).filter(k => k !== 'ignore_unmapped')[0]; + const params = filter.geo_bounding_box[key]; + const { topLeft, bottomRight } = getFormattedValue(params, key, indexPattern); + + return { + key, + params, + type: FILTERS.GEO_BOUNDING_BOX, + value: topLeft + ' to ' + bottomRight, + }; +}; + +export const mapGeoBoundingBox = (indexPatterns: IndexPatterns) => { + return async (filter: Filter) => { + if (!isGeoBoundingBoxFilter(filter)) { + throw filter; + } + + try { + let indexPattern; + + if (filter.meta.index) { + indexPattern = await indexPatterns.get(filter.meta.index); + } + + return getParams(filter, indexPattern); + } catch (error) { + if (error instanceof SavedObjectNotFound) { + return getParams(filter); + } + throw error; + } + }; +}; diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_polygon.test.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_polygon.test.ts new file mode 100644 index 0000000000000..b9427d541e703 --- /dev/null +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_polygon.test.ts @@ -0,0 +1,94 @@ +/* + * 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 { mapGeoPolygon } from './map_geo_polygon'; +import { StubIndexPatterns } from '../test_helpers/stub_index_pattern'; +import { IndexPatterns } from '../../../index_patterns'; + +describe('filter manager utilities', () => { + describe('mapGeoPolygon()', () => { + let mapGeoPolygonFn: Function; + + beforeEach(() => { + const indexPatterns: unknown = new StubIndexPatterns(); + + mapGeoPolygonFn = mapGeoPolygon(indexPatterns as IndexPatterns); + }); + + test('should return the key and value for matching filters with bounds', async () => { + const filter = { + meta: { + index: 'logstash-*', + }, + geo_polygon: { + point: { + points: [{ lat: 5, lon: 10 }, { lat: 15, lon: 20 }], + }, + }, + }; + + const result = await mapGeoPolygonFn(filter); + + expect(result).toHaveProperty('key', 'point'); + expect(result).toHaveProperty('value'); + + // remove html entities and non-alphanumerics to get the gist of the value + expect(result.value.replace(/&[a-z]+?;/g, '').replace(/[^a-z0-9]/g, '')).toBe( + 'lat5lon10lat15lon20' + ); + }); + + test('should return the key and value even when using ignore_unmapped', async () => { + const filter = { + meta: { + index: 'logstash-*', + }, + geo_polygon: { + ignore_unmapped: true, + point: { + points: [{ lat: 5, lon: 10 }, { lat: 15, lon: 20 }], + }, + }, + }; + const result = await mapGeoPolygonFn(filter); + + expect(result).toHaveProperty('key', 'point'); + expect(result).toHaveProperty('value'); + + // remove html entities and non-alphanumerics to get the gist of the value + expect(result.value.replace(/&[a-z]+?;/g, '').replace(/[^a-z0-9]/g, '')).toBe( + 'lat5lon10lat15lon20' + ); + }); + + test('should return undefined for none matching', async done => { + const filter = { + meta: { index: 'logstash-*' }, + query: { query_string: { query: 'foo:bar' } }, + }; + + try { + await mapGeoPolygonFn(filter); + } catch (e) { + expect(e).toBe(filter); + + done(); + } + }); + }); +}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_polygon.js b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_polygon.ts similarity index 50% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_polygon.js rename to src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_polygon.ts index cbe38eb141917..b49e0c7314228 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_polygon.js +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_polygon.ts @@ -16,37 +16,47 @@ * specific language governing permissions and limitations * under the License. */ - -import _ from 'lodash'; +import { get } from 'lodash'; +import { GeoPolygonFilter, Filter, FILTERS, isGeoPolygonFilter } from '@kbn/es-query'; import { SavedObjectNotFound } from '../../../../../../../plugins/kibana_utils/public'; +import { IndexPatterns, IndexPattern } from '../../../index_patterns'; + +const POINTS_SEPARATOR = ', '; + +const getFormattedValue = (value: any, key: string, indexPattern?: IndexPattern) => { + const formatter: any = + indexPattern && key && get(indexPattern, ['fields', 'byName', key, 'format']); + + return formatter ? formatter.convert(value) : JSON.stringify(value); +}; -function getParams(filter, indexPattern) { - const type = 'geo_polygon'; - const key = _.keys(filter.geo_polygon) - .filter(key => key !== 'ignore_unmapped')[0]; +function getParams(filter: GeoPolygonFilter, indexPattern?: IndexPattern) { + const key = Object.keys(filter.geo_polygon).filter(k => k !== 'ignore_unmapped')[0]; const params = filter.geo_polygon[key]; - // Sometimes a filter will end up with an invalid index param. This could happen for a lot of reasons, - // for example a user might manually edit the url or the index pattern's ID might change due to - // external factors e.g. a reindex. We only need the index in order to grab the field formatter, so we fallback - // on displaying the raw value if the index is invalid. - const points = params.points.map((point) => { - return indexPattern - ? indexPattern.fields.byName[key].format.convert(point) - : JSON.stringify(point); - }); - const value = points.join(', '); - return { type, key, value, params }; + return { + key, + params, + type: FILTERS.GEO_POLYGON, + value: (params.points || []) + .map((point: string) => getFormattedValue(point, key, indexPattern)) + .join(POINTS_SEPARATOR), + }; } -export function mapGeoPolygon(indexPatterns) { - return async function (filter) { - if (!filter.geo_polygon) { +export function mapGeoPolygon(indexPatterns: IndexPatterns) { + return async function(filter: Filter) { + if (!isGeoPolygonFilter(filter)) { throw filter; } try { - const indexPattern = await indexPatterns.get(filter.meta.index); + let indexPattern; + + if (filter.meta.index) { + indexPattern = await indexPatterns.get(filter.meta.index); + } + return getParams(filter, indexPattern); } catch (error) { if (error instanceof SavedObjectNotFound) { diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_match_all.test.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_match_all.test.ts new file mode 100644 index 0000000000000..1139f98f51335 --- /dev/null +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_match_all.test.ts @@ -0,0 +1,66 @@ +/* + * 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 { MatchAllFilter } from '@kbn/es-query'; +import { mapMatchAll } from './map_match_all'; + +describe('filter_manager/lib', () => { + describe('mapMatchAll()', () => { + let filter: MatchAllFilter; + + beforeEach(() => { + filter = { + match_all: {}, + meta: { + alias: null, + negate: true, + disabled: false, + field: 'foo', + formattedValue: 'bar', + }, + }; + }); + + describe('when given a filter that is not match_all', () => { + test('filter is rejected', async done => { + delete filter.match_all; + + try { + await mapMatchAll(filter); + } catch (e) { + expect(e).toBe(filter); + done(); + } + }); + }); + + describe('when given a match_all filter', () => { + test('key is set to meta field', async () => { + const result = await mapMatchAll(filter); + + expect(result).toHaveProperty('key', filter.meta.field); + }); + + test('value is set to meta formattedValue', async () => { + const result = await mapMatchAll(filter); + + expect(result).toHaveProperty('value', filter.meta.formattedValue); + }); + }); + }); +}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_match_all.js b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_match_all.ts similarity index 73% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_match_all.js rename to src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_match_all.ts index 2e830946dcd03..a08b60127ac05 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_match_all.js +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_match_all.ts @@ -16,13 +16,15 @@ * specific language governing permissions and limitations * under the License. */ +import { Filter, FILTERS, isMatchAllFilter } from '@kbn/es-query'; -export async function mapMatchAll(filter) { - if (filter.match_all) { - const type = 'match_all'; - const key = filter.meta.field; - const value = filter.meta.formattedValue || 'all'; - return { type, key, value }; +export const mapMatchAll = async (filter: Filter) => { + if (isMatchAllFilter(filter)) { + return { + type: FILTERS.MATCH_ALL, + key: filter.meta.field, + value: filter.meta.formattedValue || 'all', + }; } throw filter; -} +}; diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/map_exists.js b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_missing.test.ts similarity index 51% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/map_exists.js rename to src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_missing.test.ts index b9ff72e6fb22c..d8f7faa8cd9d9 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/__tests__/map_exists.js +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_missing.test.ts @@ -16,32 +16,31 @@ * specific language governing permissions and limitations * under the License. */ +import { MissingFilter, buildEmptyFilter, ExistsFilter } from '@kbn/es-query'; +import { mapMissing } from './map_missing'; -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { mapExists } from '../map_exists'; +describe('filter manager utilities', () => { + describe('mapMissing()', () => { + test('should return the key and value for matching filters', async () => { + const filter: MissingFilter = { + missing: { field: '_type' }, + ...buildEmptyFilter(true), + }; + const result = await mapMissing(filter); -describe('Filter Bar Directive', function () { - describe('mapExists()', function () { - - beforeEach(ngMock.module('kibana')); - - it('should return the key and value for matching filters', function (done) { - const filter = { exists: { field: '_type' } }; - mapExists(filter).then(function (result) { - expect(result).to.have.property('key', '_type'); - expect(result).to.have.property('value', 'exists'); - done(); - }); + expect(result).toHaveProperty('key', '_type'); + expect(result).toHaveProperty('value', 'missing'); }); - it('should return undefined for none matching', function (done) { - const filter = { query: { match: { query: 'foo' } } }; - mapExists(filter).catch(function (result) { - expect(result).to.be(filter); + test('should return undefined for none matching', async done => { + const filter = buildEmptyFilter(true) as ExistsFilter; + + try { + await mapMissing(filter); + } catch (e) { + expect(e).toBe(filter); done(); - }); + } }); - }); }); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_missing.js b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_missing.ts similarity index 75% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_missing.js rename to src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_missing.ts index e2476fc003a47..e398917b7f9be 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_missing.js +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_missing.ts @@ -16,13 +16,16 @@ * specific language governing permissions and limitations * under the License. */ +import { Filter, FILTERS, isMissingFilter } from '@kbn/es-query'; -export async function mapMissing(filter) { - if (filter.missing) { - const type = 'missing'; - const key = filter.missing.field; - const value = type; - return { type, key, value }; +export const mapMissing = async (filter: Filter) => { + if (isMissingFilter(filter)) { + return { + type: FILTERS.MISSING, + value: FILTERS.MISSING, + key: filter.missing.field, + }; } + throw filter; -} +}; diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_phrase.js b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_phrase.js deleted file mode 100644 index a87c315d982a2..0000000000000 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_phrase.js +++ /dev/null @@ -1,64 +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 { SavedObjectNotFound } from '../../../../../../../plugins/kibana_utils/public'; - -function isScriptedPhrase(filter) { - const value = _.get(filter, ['script', 'script', 'params', 'value']); - return typeof value !== 'undefined'; -} - -function getParams(filter, indexPattern) { - const isScriptedPhraseFilter = isScriptedPhrase(filter); - const type = 'phrase'; - const key = isScriptedPhraseFilter ? filter.meta.field : Object.keys(filter.query.match)[0]; - const query = isScriptedPhraseFilter ? filter.script.script.params.value : filter.query.match[key].query; - const params = { query }; - - // Sometimes a filter will end up with an invalid index or field param. This could happen for a lot of reasons, - // for example a user might manually edit the url or the index pattern's ID might change due to - // external factors e.g. a reindex. We only need the index in order to grab the field formatter, so we fallback - // on displaying the raw value if the index or field is invalid. - const value = ( - indexPattern && - indexPattern.fields && - indexPattern.fields.byName[key] - ) ? indexPattern.fields.byName[key].format.convert(query) : query; - return { type, key, value, params }; -} - -export function mapPhrase(indexPatterns) { - return async function (filter) { - const isScriptedPhraseFilter = isScriptedPhrase(filter); - if (!_.has(filter, ['query', 'match']) && !isScriptedPhraseFilter) { - throw filter; - } - - try { - const indexPattern = await indexPatterns.get(filter.meta.index); - return getParams(filter, indexPattern); - } catch (error) { - if (error instanceof SavedObjectNotFound) { - return getParams(filter); - } - throw error; - } - }; -} diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_phrase.test.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_phrase.test.ts new file mode 100644 index 0000000000000..e3eae169e3607 --- /dev/null +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_phrase.test.ts @@ -0,0 +1,58 @@ +/* + * 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 { mapPhrase } from './map_phrase'; +import { StubIndexPatterns } from '../test_helpers/stub_index_pattern'; +import { IndexPatterns } from '../../../index_patterns'; + +describe('filter manager utilities', () => { + describe('mapPhrase()', () => { + let mapPhraseFn: Function; + + beforeEach(() => { + const indexPatterns: unknown = new StubIndexPatterns(); + + mapPhraseFn = mapPhrase(indexPatterns as IndexPatterns); + }); + + test('should return the key and value for matching filters', async () => { + const filter = { + meta: { index: 'logstash-*' }, + query: { match: { _type: { query: 'apache', type: 'phrase' } } }, + }; + const result = await mapPhraseFn(filter); + + expect(result).toHaveProperty('key', '_type'); + expect(result).toHaveProperty('value', 'apache'); + }); + + test('should return undefined for none matching', async done => { + const filter = { + meta: { index: 'logstash-*' }, + query: { query_string: { query: 'foo:bar' } }, + }; + + try { + await mapPhraseFn(filter); + } catch (e) { + expect(e).toBe(filter); + done(); + } + }); + }); +}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_phrase.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_phrase.ts new file mode 100644 index 0000000000000..3ebe363f46df5 --- /dev/null +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_phrase.ts @@ -0,0 +1,80 @@ +/* + * 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 { get } from 'lodash'; +import { + PhraseFilter, + Filter, + FILTERS, + isPhraseFilter, + isScriptedPhraseFilter, +} from '@kbn/es-query'; +import { SavedObjectNotFound } from '../../../../../../../plugins/kibana_utils/public'; +import { IndexPatterns, IndexPattern } from '../../../index_patterns'; + +const getScriptedPhraseValue = (filter: PhraseFilter) => + get(filter, ['script', 'script', 'params', 'value']); + +const getFormattedValue = (value: any, key: string, indexPattern?: IndexPattern) => { + const formatter: any = + indexPattern && key && get(indexPattern, ['fields', 'byName', key, 'format']); + + return formatter ? formatter.convert(value) : value; +}; + +const getParams = (filter: PhraseFilter, indexPattern?: IndexPattern) => { + const scriptedPhraseValue = getScriptedPhraseValue(filter); + const isScriptedFilter = Boolean(scriptedPhraseValue); + const key = isScriptedFilter ? filter.meta.field || '' : Object.keys(filter.query.match)[0]; + const query = scriptedPhraseValue || get(filter, ['query', 'match', key, 'query']); + const params = { query }; + + return { + key, + params, + type: FILTERS.PHRASE, + value: getFormattedValue(query, key, indexPattern), + }; +}; + +export const isMapPhraseFilter = (filter: any): filter is PhraseFilter => + isPhraseFilter(filter) || isScriptedPhraseFilter(filter); + +export const mapPhrase = (indexPatterns: IndexPatterns) => { + return async (filter: Filter) => { + if (!isMapPhraseFilter(filter)) { + throw filter; + } + + try { + let indexPattern; + + if (filter.meta.index) { + indexPattern = await indexPatterns.get(filter.meta.index); + } + + return getParams(filter, indexPattern); + } catch (error) { + if (error instanceof SavedObjectNotFound) { + return getParams(filter); + } + throw error; + } + }; +}; diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_phrases.js b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_phrases.ts similarity index 82% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_phrases.js rename to src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_phrases.ts index 88c0272c71805..92226b68ef326 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_phrases.js +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_phrases.ts @@ -17,11 +17,14 @@ * under the License. */ -export async function mapPhrases(filter) { - const { type, key, value, params } = filter.meta; - if (type !== 'phrases') { +import { Filter, isPhrasesFilter } from '@kbn/es-query'; + +export const mapPhrases = async (filter: Filter) => { + if (!isPhrasesFilter(filter)) { throw filter; - } else { - return { type, key, value, params }; } -} + + const { type, key, value, params } = filter.meta; + + return { type, key, value, params }; +}; diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_query_string.test.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_query_string.test.ts new file mode 100644 index 0000000000000..ebf9f05ba387b --- /dev/null +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_query_string.test.ts @@ -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 { QueryStringFilter, buildQueryFilter, buildEmptyFilter } from '@kbn/es-query'; +import { mapQueryString } from './map_query_string'; + +describe('filter manager utilities', () => { + describe('mapQueryString()', () => { + test('should return the key and value for matching filters', async () => { + const filter: QueryStringFilter = buildQueryFilter( + { query_string: { query: 'foo:bar' } }, + 'index' + ); + const result = await mapQueryString(filter); + + expect(result).toHaveProperty('key', 'query'); + expect(result).toHaveProperty('value', 'foo:bar'); + }); + + test('should return undefined for none matching', async done => { + const filter = buildEmptyFilter(true) as QueryStringFilter; + + try { + await mapQueryString(filter); + } catch (e) { + expect(e).toBe(filter); + done(); + } + }); + }); +}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_exists.js b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_query_string.ts similarity index 73% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_exists.js rename to src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_query_string.ts index 3e26d93f20433..14ad52beaf0b3 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_exists.js +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_query_string.ts @@ -16,13 +16,16 @@ * specific language governing permissions and limitations * under the License. */ +import { Filter, FILTERS, isQueryStringFilter } from '@kbn/es-query'; -export async function mapExists(filter) { - if (filter.exists) { - const type = 'exists'; - const key = filter.exists.field; - const value = type; - return { type, key, value }; +export const mapQueryString = async (filter: Filter) => { + if (isQueryStringFilter(filter)) { + return { + type: FILTERS.QUERY_STRING, + key: 'query', + value: filter.query.query_string.query, + }; } + throw filter; -} +}; diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_range.test.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_range.test.ts new file mode 100644 index 0000000000000..4f81572183005 --- /dev/null +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_range.test.ts @@ -0,0 +1,65 @@ +/* + * 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 { mapRange } from './map_range'; +import { StubIndexPatterns } from '../test_helpers/stub_index_pattern'; +import { IndexPatterns } from '../../../index_patterns'; + +describe('filter manager utilities', () => { + describe('mapRange()', () => { + let mapRangeFn: Function; + + beforeEach(() => { + const indexPatterns: unknown = new StubIndexPatterns(); + + mapRangeFn = mapRange(indexPatterns as IndexPatterns); + }); + + test('should return the key and value for matching filters with gt/lt', async () => { + const filter = { meta: { index: 'logstash-*' }, range: { bytes: { lt: 2048, gt: 1024 } } }; + const result = await mapRangeFn(filter); + + expect(result).toHaveProperty('key', 'bytes'); + expect(result).toHaveProperty('value', '1024 to 2048'); + }); + + test('should return the key and value for matching filters with gte/lte', async () => { + const filter = { meta: { index: 'logstash-*' }, range: { bytes: { lte: 2048, gte: 1024 } } }; + const result = await mapRangeFn(filter); + + expect(result).toHaveProperty('key', 'bytes'); + expect(result).toHaveProperty('value', '1024 to 2048'); + }); + + test('should return undefined for none matching', async done => { + const filter = { + meta: { index: 'logstash-*' }, + query: { query_string: { query: 'foo:bar' } }, + }; + + try { + await mapRangeFn(filter); + } catch (e) { + expect(e).toBe(filter); + + done(); + } + }); + }); +}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_range.js b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_range.ts similarity index 60% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_range.js rename to src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_range.ts index 3c76998989580..4e8ad594a735a 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_range.js +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_range.ts @@ -17,20 +17,20 @@ * under the License. */ -import { has, get } from 'lodash'; +import { Filter, RangeFilter, FILTERS, isRangeFilter, isScriptedRangeFilter } from '@kbn/es-query'; +import { get, has } from 'lodash'; import { SavedObjectNotFound } from '../../../../../../../plugins/kibana_utils/public'; +import { IndexPatterns, IndexPattern } from '../../../index_patterns'; +const getFirstRangeKey = (filter: RangeFilter) => filter.range && Object.keys(filter.range)[0]; +const getRangeByKey = (filter: RangeFilter, key: string) => get(filter, ['range', key]); -function isScriptedRange(filter) { - const params = get(filter, ['script', 'script', 'params']); - return params && Object.keys(params).find(key => ['gte', 'gt', 'lte', 'lt'].includes(key)); -} - -function getParams(filter, indexPattern) { - const isScriptedRangeFilter = isScriptedRange(filter); - const type = 'range'; - const key = isScriptedRangeFilter ? filter.meta.field : Object.keys(filter.range)[0]; - const params = isScriptedRangeFilter ? filter.script.script.params : filter.range[key]; +function getParams(filter: RangeFilter, indexPattern?: IndexPattern) { + const isScriptedRange = isScriptedRangeFilter(filter); + const key: string = (isScriptedRange ? filter.meta.field : getFirstRangeKey(filter)) || ''; + const params: any = isScriptedRange + ? get(filter, 'script.script.params') + : getRangeByKey(filter, key); let left = has(params, 'gte') ? params.gte : params.gt; if (left == null) left = -Infinity; @@ -38,28 +38,37 @@ function getParams(filter, indexPattern) { let right = has(params, 'lte') ? params.lte : params.lt; if (right == null) right = Infinity; + let value = `${left} to ${right}`; + // Sometimes a filter will end up with an invalid index param. This could happen for a lot of reasons, // for example a user might manually edit the url or the index pattern's ID might change due to // external factors e.g. a reindex. We only need the index in order to grab the field formatter, so we fallback // on displaying the raw value if the index is invalid. - let value = `${left} to ${right}`; - if (indexPattern && indexPattern.fields.byName[key]) { + if (key && indexPattern && indexPattern.fields.byName[key]) { const convert = indexPattern.fields.byName[key].format.getConverterFor('text'); + value = `${convert(left)} to ${convert(right)}`; } - return { type, key, value, params }; + return { type: FILTERS.RANGE, key, value, params }; } -export function mapRange(indexPatterns) { - return async function (filter) { - const isScriptedRangeFilter = isScriptedRange(filter); - if (!filter.range && !isScriptedRangeFilter) { +export const isMapRangeFilter = (filter: any): filter is RangeFilter => + isRangeFilter(filter) || isScriptedRangeFilter(filter); + +export const mapRange = (indexPatterns: IndexPatterns) => { + return async (filter: Filter) => { + if (!isMapRangeFilter(filter)) { throw filter; } try { - const indexPattern = await indexPatterns.get(filter.meta.index); + let indexPattern; + + if (filter.meta.index) { + indexPattern = await indexPatterns.get(filter.meta.index); + } + return getParams(filter, indexPattern); } catch (error) { if (error instanceof SavedObjectNotFound) { @@ -68,4 +77,4 @@ export function mapRange(indexPatterns) { throw error; } }; -} +}; diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/only_disabled.test.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/only_disabled.test.ts index 7a3b767b97b1b..3fedcf97a625a 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/only_disabled.test.ts +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/only_disabled.test.ts @@ -18,83 +18,89 @@ */ import { Filter } from '@kbn/es-query'; - import { onlyDisabledFiltersChanged } from './only_disabled'; -import expect from '@kbn/expect'; -describe('Filter Bar Directive', function() { - describe('onlyDisabledFiltersChanged()', function() { - it('should return true if all filters are disabled', function() { +describe('filter manager utilities', () => { + describe('onlyDisabledFiltersChanged()', () => { + test('should return true if all filters are disabled', () => { const filters = [ { meta: { disabled: true } }, { meta: { disabled: true } }, { meta: { disabled: true } }, ] as Filter[]; const newFilters = [{ meta: { disabled: true } }] as Filter[]; - expect(onlyDisabledFiltersChanged(newFilters, filters)).to.be(true); + + expect(onlyDisabledFiltersChanged(newFilters, filters)).toBe(true); }); - it('should return false if there are no old filters', function() { + test('should return false if there are no old filters', () => { const newFilters = [{ meta: { disabled: false } }] as Filter[]; - expect(onlyDisabledFiltersChanged(newFilters, undefined)).to.be(false); + + expect(onlyDisabledFiltersChanged(newFilters, undefined)).toBe(false); }); - it('should return false if there are no new filters', function() { + test('should return false if there are no new filters', () => { const filters = [{ meta: { disabled: false } }] as Filter[]; - expect(onlyDisabledFiltersChanged(undefined, filters)).to.be(false); + + expect(onlyDisabledFiltersChanged(undefined, filters)).toBe(false); }); - it('should return false if all filters are not disabled', function() { + test('should return false if all filters are not disabled', () => { const filters = [ { meta: { disabled: false } }, { meta: { disabled: false } }, { meta: { disabled: false } }, ] as Filter[]; const newFilters = [{ meta: { disabled: false } }] as Filter[]; - expect(onlyDisabledFiltersChanged(newFilters, filters)).to.be(false); + + expect(onlyDisabledFiltersChanged(newFilters, filters)).toBe(false); }); - it('should return false if only old filters are disabled', function() { + test('should return false if only old filters are disabled', () => { const filters = [ { meta: { disabled: true } }, { meta: { disabled: true } }, { meta: { disabled: true } }, ] as Filter[]; const newFilters = [{ meta: { disabled: false } }] as Filter[]; - expect(onlyDisabledFiltersChanged(newFilters, filters)).to.be(false); + + expect(onlyDisabledFiltersChanged(newFilters, filters)).toBe(false); }); - it('should return false if new filters are not disabled', function() { + test('should return false if new filters are not disabled', () => { const filters = [ { meta: { disabled: false } }, { meta: { disabled: false } }, { meta: { disabled: false } }, ] as Filter[]; const newFilters = [{ meta: { disabled: true } }] as Filter[]; - expect(onlyDisabledFiltersChanged(newFilters, filters)).to.be(false); + + expect(onlyDisabledFiltersChanged(newFilters, filters)).toBe(false); }); - it('should return true when all removed filters were disabled', function() { + test('should return true when all removed filters were disabled', () => { const filters = [ { meta: { disabled: true } }, { meta: { disabled: true } }, { meta: { disabled: true } }, ] as Filter[]; const newFilters = [] as Filter[]; - expect(onlyDisabledFiltersChanged(newFilters, filters)).to.be(true); + + expect(onlyDisabledFiltersChanged(newFilters, filters)).toBe(true); }); - it('should return false when all removed filters were not disabled', function() { + test('should return false when all removed filters were not disabled', () => { const filters = [ { meta: { disabled: false } }, { meta: { disabled: false } }, { meta: { disabled: false } }, ] as Filter[]; const newFilters = [] as Filter[]; - expect(onlyDisabledFiltersChanged(newFilters, filters)).to.be(false); + + expect(onlyDisabledFiltersChanged(newFilters, filters)).toBe(false); }); - it('should return true if all changed filters are disabled', function() { + test('should return true if all changed filters are disabled', () => { const filters = [ { meta: { disabled: true, negate: false } }, { meta: { disabled: true, negate: false } }, @@ -103,35 +109,39 @@ describe('Filter Bar Directive', function() { { meta: { disabled: true, negate: true } }, { meta: { disabled: true, negate: true } }, ] as Filter[]; - expect(onlyDisabledFiltersChanged(newFilters, filters)).to.be(true); + + expect(onlyDisabledFiltersChanged(newFilters, filters)).toBe(true); }); - it('should return false if all filters remove were not disabled', function() { + test('should return false if all filters remove were not disabled', () => { const filters = [ { meta: { disabled: false } }, { meta: { disabled: false } }, { meta: { disabled: true } }, ] as Filter[]; const newFilters = [{ meta: { disabled: false } }] as Filter[]; - expect(onlyDisabledFiltersChanged(newFilters, filters)).to.be(false); + + expect(onlyDisabledFiltersChanged(newFilters, filters)).toBe(false); }); - it('should return false when all removed filters are not disabled', function() { + test('should return false when all removed filters are not disabled', () => { const filters = [ { meta: { disabled: true } }, { meta: { disabled: false } }, { meta: { disabled: true } }, ] as Filter[]; const newFilters = [] as Filter[]; - expect(onlyDisabledFiltersChanged(newFilters, filters)).to.be(false); + + expect(onlyDisabledFiltersChanged(newFilters, filters)).toBe(false); }); - it('should not throw with null filters', function() { + test('should not throw with null filters', () => { const filters = [null, { meta: { disabled: true } }] as Filter[]; const newFilters = [] as Filter[]; - expect(function() { + + expect(() => { onlyDisabledFiltersChanged(newFilters, filters); - }).to.not.throwError(); + }).not.toThrowError(); }); }); }); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/only_disabled.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/only_disabled.ts index 24f6b6db5352b..9c0b5f43acb3e 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/only_disabled.ts +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/only_disabled.ts @@ -17,20 +17,20 @@ * under the License. */ -import _ from 'lodash'; import { Filter } from '@kbn/es-query'; +import { filter, isEqual } from 'lodash'; + +const isEnabled = (f: Filter) => f && f.meta && !f.meta.disabled; -const isEnabled = function(filter: Filter) { - return filter && filter.meta && !filter.meta.disabled; -}; /** * Checks to see if only disabled filters have been changed + * * @returns {bool} Only disabled filters */ -export function onlyDisabledFiltersChanged(newFilters?: Filter[], oldFilters?: Filter[]) { +export const onlyDisabledFiltersChanged = (newFilters?: Filter[], oldFilters?: Filter[]) => { // If it's the same - compare only enabled filters - const newEnabledFilters = _.filter(newFilters || [], isEnabled); - const oldEnabledFilters = _.filter(oldFilters || [], isEnabled); + const newEnabledFilters = filter(newFilters || [], isEnabled); + const oldEnabledFilters = filter(oldFilters || [], isEnabled); - return _.isEqual(oldEnabledFilters, newEnabledFilters); -} + return isEqual(oldEnabledFilters, newEnabledFilters); +}; diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/uniq_filters.test.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/uniq_filters.test.ts new file mode 100644 index 0000000000000..86f059913cd96 --- /dev/null +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/uniq_filters.test.ts @@ -0,0 +1,60 @@ +/* + * 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, buildQueryFilter, FilterStateStore } from '@kbn/es-query'; +import { uniqFilters } from './uniq_filters'; + +describe('filter manager utilities', () => { + describe('niqFilter', () => { + test('should filter out dups', () => { + const before: Filter[] = [ + buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index'), + buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index'), + ]; + const results = uniqFilters(before); + + expect(results).toHaveLength(1); + }); + + test('should filter out duplicates, ignoring meta attributes', () => { + const before: Filter[] = [ + buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index1'), + buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index2'), + ]; + const results = uniqFilters(before); + + expect(results).toHaveLength(1); + }); + + test('should filter out duplicates, ignoring $state attributes', () => { + const before: Filter[] = [ + { + $state: { store: FilterStateStore.APP_STATE }, + ...buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index'), + }, + { + $state: { store: FilterStateStore.GLOBAL_STATE }, + ...buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index'), + }, + ]; + const results = uniqFilters(before); + + expect(results).toHaveLength(1); + }); + }); +}); diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/uniq_filters.js b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/uniq_filters.ts similarity index 73% rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/uniq_filters.js rename to src/legacy/core_plugins/data/public/filter/filter_manager/lib/uniq_filters.ts index f8e6d350f8dd9..12e793253371e 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/uniq_filters.js +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/uniq_filters.ts @@ -16,19 +16,24 @@ * specific language governing permissions and limitations * under the License. */ - -import _ from 'lodash'; +import { Filter } from '@kbn/es-query'; +import { each, union } from 'lodash'; import { dedupFilters } from './dedup_filters'; /** * Remove duplicate filters from an array of filters + * * @param {array} filters The filters to remove duplicates from + * @param {object} comparatorOptions - Parameters to use for comparison + * @returns {object} The original filters array with duplicates removed */ -export function uniqFilters(filters, comparatorOptions) { - let results = []; - _.each(filters, function (filter) { - results = _.union(results, dedupFilters(results, [filter], comparatorOptions)); +export const uniqFilters = (filters: Filter[], comparatorOptions: any = {}) => { + let results: Filter[] = []; + + each(filters, (filter: Filter) => { + results = union(results, dedupFilters(results, [filter]), comparatorOptions); }); + return results; -} +};