Skip to content

Commit

Permalink
add "contains" filter
Browse files Browse the repository at this point in the history
  • Loading branch information
shaharmor committed Jul 22, 2019
1 parent 7bb01ec commit ae1d953
Show file tree
Hide file tree
Showing 12 changed files with 209 additions and 4 deletions.
37 changes: 37 additions & 0 deletions packages/kbn-es-query/src/filters/contains.js
Original file line number Diff line number Diff line change
@@ -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;
}
15 changes: 14 additions & 1 deletion packages/kbn-es-query/src/filters/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions packages/kbn-es-query/src/filters/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* under the License.
*/

export * from './contains';
export * from './exists';
export * from './phrase';
export * from './phrases';
Expand Down
30 changes: 30 additions & 0 deletions packages/kbn-es-query/src/filters/lib/contains_filter.ts
Original file line number Diff line number Diff line change
@@ -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;
};
3 changes: 3 additions & 0 deletions packages/kbn-es-query/src/filters/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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,
Expand All @@ -42,6 +44,7 @@ export {

// Any filter associated with a field (used in the filter bar/editor)
export type FieldFilter =
| ContainsFilter
| ExistsFilter
| GeoBoundingBoxFilter
| GeoPolygonFilter
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Props> {
public render() {
return (
<EuiFormRow
label={this.props.intl.formatMessage({
id: 'data.filter.filterEditor.valueInputLabel',
defaultMessage: 'Value',
})}
>
<ValueInputType
placeholder={this.props.intl.formatMessage({
id: 'data.filter.filterEditor.valueInputPlaceholder',
defaultMessage: 'Enter a value',
})}
value={this.props.value}
onChange={this.props.onChange}
type={this.props.field ? this.props.field.type : 'string'}
/>
</EuiFormRow>
);
}
}

export const ContainsValueInput = injectI18n(ContainsValueInputUI);
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -314,6 +315,14 @@ class FilterEditorUI extends Component<Props, State> {
switch (this.state.selectedOperator.type) {
case 'exists':
return '';
case 'contains':
return (
<ContainsValueInput
field={this.state.selectedField}
value={this.state.params}
onChange={this.onParamsChange}
/>
);
case 'phrase':
return (
<PhraseValueInput
Expand Down Expand Up @@ -351,7 +360,7 @@ class FilterEditorUI extends Component<Props, State> {

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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@

import dateMath from '@elastic/datemath';
import {
buildContainsFilter,
buildExistsFilter,
buildPhraseFilter,
buildPhrasesFilter,
buildRangeFilter,
ContainsFilter,
FieldFilter,
Filter,
FilterMeta,
Expand Down Expand Up @@ -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':
Expand Down Expand Up @@ -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}`);
}
Expand Down Expand Up @@ -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}`);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -103,4 +112,5 @@ export const FILTER_OPERATORS: Operator[] = [
isNotBetweenOperator,
existsOperator,
doesNotExistOperator,
containsOperator,
];
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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':
Expand Down
Original file line number Diff line number Diff line change
@@ -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 };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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.
Expand All @@ -57,6 +58,7 @@ export async function mapFilter(indexPatterns, filter) {
mapQueryString,
mapGeoBoundingBox(indexPatterns),
mapGeoPolygon(indexPatterns),
mapContains,
mapDefault,
];

Expand Down

0 comments on commit ae1d953

Please sign in to comment.