diff --git a/packages/kbn-es-query/src/filters/contains.js b/packages/kbn-es-query/src/filters/contains.js new file mode 100644 index 0000000000000..580d4cc3d8df8 --- /dev/null +++ b/packages/kbn-es-query/src/filters/contains.js @@ -0,0 +1,37 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// Creates an filter where the given field contains the given value +export function buildContainsFilter(field, value, indexPattern) { + const index = indexPattern.id; + const type = 'contains'; + const key = field.name; + + const filter = { + meta: { index, type, key, value }, + }; + filter.query = { + wildcard: { + [field.name]: { + value: `*${value}*`, + }, + }, + }; + return filter; +} diff --git a/packages/kbn-es-query/src/filters/index.d.ts b/packages/kbn-es-query/src/filters/index.d.ts index c46a767e38ea4..adda67ee797ca 100644 --- a/packages/kbn-es-query/src/filters/index.d.ts +++ b/packages/kbn-es-query/src/filters/index.d.ts @@ -18,13 +18,26 @@ */ import { Field, IndexPattern } from 'ui/index_patterns'; -import { CustomFilter, ExistsFilter, PhraseFilter, PhrasesFilter, RangeFilter } from './lib'; +import { + ContainsFilter, + CustomFilter, + ExistsFilter, + PhraseFilter, + PhrasesFilter, + RangeFilter, +} from './lib'; import { RangeFilterParams } from './lib/range_filter'; export * from './lib'; export function buildExistsFilter(field: Field, indexPattern: IndexPattern): ExistsFilter; +export function buildContainsFilter( + field: Field, + value: string, + indexPattern: IndexPattern +): ContainsFilter; + export function buildPhraseFilter( field: Field, value: string, diff --git a/packages/kbn-es-query/src/filters/index.js b/packages/kbn-es-query/src/filters/index.js index d7d092eabd8a2..766bc2adc0166 100644 --- a/packages/kbn-es-query/src/filters/index.js +++ b/packages/kbn-es-query/src/filters/index.js @@ -17,6 +17,7 @@ * under the License. */ +export * from './contains'; export * from './exists'; export * from './phrase'; export * from './phrases'; diff --git a/packages/kbn-es-query/src/filters/lib/contains_filter.ts b/packages/kbn-es-query/src/filters/lib/contains_filter.ts new file mode 100644 index 0000000000000..724ee470e0d68 --- /dev/null +++ b/packages/kbn-es-query/src/filters/lib/contains_filter.ts @@ -0,0 +1,30 @@ +/* + * 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 type ContainsFilterMeta = FilterMeta & { + params: { + value: string; + }; +}; + +export type ContainsFilter = Filter & { + meta: ContainsFilterMeta; +}; diff --git a/packages/kbn-es-query/src/filters/lib/index.ts b/packages/kbn-es-query/src/filters/lib/index.ts index fdf87c84eb5ca..2123025c87887 100644 --- a/packages/kbn-es-query/src/filters/lib/index.ts +++ b/packages/kbn-es-query/src/filters/lib/index.ts @@ -21,6 +21,7 @@ export * from './meta_filter'; // The actual filter types +import { ContainsFilter } from './contains_filter'; import { CustomFilter } from './custom_filter'; import { ExistsFilter } from './exists_filter'; import { GeoBoundingBoxFilter } from './geo_bounding_box_filter'; @@ -30,6 +31,7 @@ import { PhrasesFilter } from './phrases_filter'; import { QueryStringFilter } from './query_string_filter'; import { RangeFilter } from './range_filter'; export { + ContainsFilter, CustomFilter, ExistsFilter, GeoBoundingBoxFilter, @@ -42,6 +44,7 @@ export { // Any filter associated with a field (used in the filter bar/editor) export type FieldFilter = + | ContainsFilter | ExistsFilter | GeoBoundingBoxFilter | GeoPolygonFilter diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/contains_value_input.tsx b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/contains_value_input.tsx new file mode 100644 index 0000000000000..8d4fa52cbba0a --- /dev/null +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/contains_value_input.tsx @@ -0,0 +1,56 @@ +/* + * 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 { EuiFormRow } from '@elastic/eui'; +import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; +import React, { Component } from 'react'; +import { Field } from 'ui/index_patterns'; +import { ValueInputType } from './value_input_type'; + +interface Props { + field?: Field; + value?: string; + onChange: (value: string | number | boolean) => void; + intl: InjectedIntl; +} + +class ContainsValueInputUI extends Component { + public render() { + return ( + + + + ); + } +} + +export const ContainsValueInput = injectI18n(ContainsValueInputUI); diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/index.tsx b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/index.tsx index 51879e938e186..e7828c09c8a64 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/index.tsx +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/index.tsx @@ -50,6 +50,7 @@ import { isFilterValid, } from './lib/filter_editor_utils'; import { Operator } from './lib/filter_operators'; +import { ContainsValueInput } from './contains_value_input'; import { PhraseValueInput } from './phrase_value_input'; import { PhrasesValuesInput } from './phrases_values_input'; import { RangeValueInput } from './range_value_input'; @@ -314,6 +315,14 @@ class FilterEditorUI extends Component { switch (this.state.selectedOperator.type) { case 'exists': return ''; + case 'contains': + return ( + + ); case 'phrase': return ( { private isUnknownFilterType() { const { type } = this.props.filter.meta; - return !!type && !['phrase', 'phrases', 'range', 'exists'].includes(type); + return !!type && !['phrase', 'phrases', 'range', 'exists', 'contains'].includes(type); } private getIndexPatternFromFilter() { diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_editor_utils.ts b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_editor_utils.ts index b1b456e482ac9..7a42f585d369a 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_editor_utils.ts +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_editor_utils.ts @@ -19,10 +19,12 @@ import dateMath from '@elastic/datemath'; import { + buildContainsFilter, buildExistsFilter, buildPhraseFilter, buildPhrasesFilter, buildRangeFilter, + ContainsFilter, FieldFilter, Filter, FilterMeta, @@ -70,6 +72,8 @@ export function getOperatorOptions(field: Field) { export function getFilterParams(filter: Filter) { switch (filter.meta.type) { + case 'contains': + return (filter as ContainsFilter).meta.value; case 'phrase': return (filter as PhraseFilter).meta.params.query; case 'phrases': @@ -122,6 +126,11 @@ export function isFilterValid( return validateParams(params.from, field.type) || validateParams(params.to, field.type); case 'exists': return true; + case 'contains': + if (typeof params !== 'string') { + return false; + } + return true; default: throw new Error(`Unknown operator type: ${operator.type}`); } @@ -158,6 +167,8 @@ function buildBaseFilter( return buildRangeFilter(field, newParams, indexPattern); case 'exists': return buildExistsFilter(field, indexPattern); + case 'contains': + return buildContainsFilter(field, params, indexPattern); default: throw new Error(`Unknown operator type: ${operator.type}`); } diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_operators.ts b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_operators.ts index 469f5355df106..cff4a10b724ce 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_operators.ts +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_operators.ts @@ -94,6 +94,15 @@ export const doesNotExistOperator = { negate: true, }; +export const containsOperator = { + message: i18n.translate('data.filter.filterEditor.containsOperatorOptionLabel', { + defaultMessage: 'contains', + }), + type: 'contains', + negate: false, + fieldTypes: ['string'], +}; + export const FILTER_OPERATORS: Operator[] = [ isOperator, isNotOperator, @@ -103,4 +112,5 @@ export const FILTER_OPERATORS: Operator[] = [ isNotBetweenOperator, existsOperator, doesNotExistOperator, + containsOperator, ]; diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_view/index.tsx b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_view/index.tsx index dca2101d435cf..994679bfcb276 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_view/index.tsx +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_view/index.tsx @@ -21,7 +21,11 @@ import { EuiBadge } from '@elastic/eui'; import { Filter, isFilterPinned } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import React, { SFC } from 'react'; -import { existsOperator, isOneOfOperator } from '../filter_editor/lib/filter_operators'; +import { + containsOperator, + existsOperator, + isOneOfOperator, +} from '../filter_editor/lib/filter_operators'; interface Props { filter: Filter; @@ -90,6 +94,8 @@ export function getFilterDisplayText(filter: Filter) { return `${prefix}${filter.meta.key}: ${filter.meta.value}`; case 'geo_polygon': return `${prefix}${filter.meta.key}: ${filter.meta.value}`; + case 'contains': + return `${prefix}${filter.meta.key} ${containsOperator.message} ${filter.meta.value}`; case 'phrase': return `${prefix}${filter.meta.key}: ${filter.meta.value}`; case 'phrases': diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_contains.js b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_contains.js new file mode 100644 index 0000000000000..abc957022adb5 --- /dev/null +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_contains.js @@ -0,0 +1,27 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export async function mapContains(filter) { + const { type, key, value, params } = filter.meta; + if (type !== 'contains') { + throw filter; + } else { + return { type, key, value, params }; + } +} 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.js index ab468e9243273..54caecf440c28 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.js @@ -27,6 +27,7 @@ import { mapMissing } from './map_missing'; import { mapQueryString } from './map_query_string'; import { mapGeoBoundingBox } from './map_geo_bounding_box'; import { mapGeoPolygon } from './map_geo_polygon'; +import { mapContains } from './map_contains'; import { mapDefault } from './map_default'; import { generateMappingChain } from './generate_mapping_chain'; @@ -35,7 +36,7 @@ export async function mapFilter(indexPatterns, filter) { // Each mapper is a simple promise function that test if the mapper can // handle the mapping or not. If it handles it then it will resolve with - // and object that has the key and value for the filter. Otherwise it will + // an object that has the key and value for the filter. Otherwise it will // reject it with the original filter. We had to go down the promise interface // because mapTerms and mapRange need access to the indexPatterns to format // the values and that's only available through the field formatters. @@ -57,6 +58,7 @@ export async function mapFilter(indexPatterns, filter) { mapQueryString, mapGeoBoundingBox(indexPatterns), mapGeoPolygon(indexPatterns), + mapContains, mapDefault, ];