diff --git a/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js b/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js index e433e2c5c86b278..55a3ae7fb5c210a 100644 --- a/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js @@ -65,12 +65,6 @@ import { tabifyAggResponse } from 'ui/agg_response/tabify'; import { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; import { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; import { getRootBreadcrumbs, getSavedSearchBreadcrumbs } from '../breadcrumbs'; -import { createPhraseFilter } from '../../../../../../ui/public/filter_bar/filters/phrase_filter'; -import { - createMetaFilter, enable, disable, pin, unpin, - toggleDisabled, - toggleNegation, togglePinned, -} from '../../../../../../ui/public/filter_bar/filters/meta_filter'; const app = uiModules.get('apps/discover', [ 'kibana/notify', @@ -188,63 +182,6 @@ function discoverController( requests: new RequestAdapter() }; - - $scope.reactFilters = [ - createMetaFilter(createPhraseFilter({ field: '@tags.keyword', value: 'security', index: 'foo' })), - createMetaFilter(createPhraseFilter({ field: '@tags.keyword', value: 'error', index: 'foo' })), - createMetaFilter(createPhraseFilter({ field: '@tags.keyword', value: 'info', index: 'foo' })), - createMetaFilter(createPhraseFilter({ field: '@tags.keyword', value: 'foo', index: 'foo' })), - ]; - - $scope.onToggleNegate = (filter) => { - const index = $scope.reactFilters.indexOf(filter); - $scope.reactFilters[index] = toggleNegation(filter); - }; - - $scope.onTogglePin = (filter) => { - const index = $scope.reactFilters.indexOf(filter); - $scope.reactFilters[index] = togglePinned(filter); - }; - - $scope.onToggleDisabled = (filter) => { - const index = $scope.reactFilters.indexOf(filter); - $scope.reactFilters[index] = toggleDisabled(filter); - }; - - $scope.onDelete = (filterToDelete) => { - $scope.reactFilters = $scope.reactFilters.filter((filter) => filter !== filterToDelete); - }; - - $scope.onAllFiltersAction = (action) => { - if (action === 'delete') { - $scope.reactFilters = []; - } - else { - $scope.reactFilters.forEach((filter, index) => { - switch (action) { - case 'enable': - $scope.reactFilters[index] = enable(filter); - break; - case 'disable': - $scope.reactFilters[index] = disable(filter); - break; - case 'pin': - $scope.reactFilters[index] = pin(filter); - break; - case 'unpin': - $scope.reactFilters[index] = unpin(filter); - break; - case 'toggleNegate': - $scope.reactFilters[index] = toggleNegation(filter); - break; - case 'toggleDisabled': - $scope.reactFilters[index] = toggleDisabled(filter); - break; - } - }); - } - }; - $scope.getDocLink = getDocLink; $scope.intervalOptions = intervalOptions; $scope.showInterval = false; @@ -413,6 +350,13 @@ function discoverController( const $state = $scope.state = new AppState(getStateDefaults()); + $scope.filters = queryFilter.getFilters(); + + $scope.onFiltersUpdated = filters => { + // The filters will automatically be set when the queryFilter emits an update event (see below) + queryFilter.setFilters(filters); + }; + const getFieldCounts = async () => { // the field counts aren't set until we have the data back, // so we wait for the fetch to be done before proceeding @@ -549,6 +493,7 @@ function discoverController( // update data source when filters update $scope.$listen(queryFilter, 'update', function () { + $scope.filters = queryFilter.getFilters(); return $scope.updateDataSource().then(function () { $state.save(); }); diff --git a/src/legacy/core_plugins/kibana/public/discover/index.html b/src/legacy/core_plugins/kibana/public/discover/index.html index 61e5b0e9ebe1187..d60227d1a9259c6 100644 --- a/src/legacy/core_plugins/kibana/public/discover/index.html +++ b/src/legacy/core_plugins/kibana/public/discover/index.html @@ -33,12 +33,8 @@

on-query-submit="updateQueryAndFetch" app-name="'discover'" index-patterns="[indexPattern]" - filters="reactFilters" - on-toggle-filter-negate="onToggleNegate" - on-toggle-filter-pin="onTogglePin" - on-toggle-filter-disabled="onToggleDisabled" - on-filter-delete="onDelete" - on-all-filters-action="onAllFiltersAction" + filters="filters" + on-filters-updated="onFiltersUpdated" > diff --git a/src/ui/public/filter_bar/filters/custom_filter.ts b/src/ui/public/filter_bar/filters/custom_filter.ts new file mode 100644 index 000000000000000..e15cc31e7e9d9cd --- /dev/null +++ b/src/ui/public/filter_bar/filters/custom_filter.ts @@ -0,0 +1,24 @@ +/* + * 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 { MetaFilter } from './meta_filter'; + +export type CustomFilter = MetaFilter & { + query: any; +}; diff --git a/src/ui/public/filter_bar/filters/exists_filter.ts b/src/ui/public/filter_bar/filters/exists_filter.ts new file mode 100644 index 000000000000000..6d31916ea5f0f0c --- /dev/null +++ b/src/ui/public/filter_bar/filters/exists_filter.ts @@ -0,0 +1,28 @@ +/* + * 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 { FilterMeta, MetaFilter } from './meta_filter'; + +export type ExistsFilterMeta = FilterMeta & { + key: string; // The name of the field +}; + +export type ExistsFilter = MetaFilter & { + meta: ExistsFilterMeta; +}; diff --git a/src/ui/public/filter_bar/filters/filter_views/custom_filter_views.tsx b/src/ui/public/filter_bar/filters/filter_views/custom_filter_views.tsx new file mode 100644 index 000000000000000..eb01e75318bf04e --- /dev/null +++ b/src/ui/public/filter_bar/filters/filter_views/custom_filter_views.tsx @@ -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 { CustomFilter } from '../custom_filter'; +import { FilterViews } from './index'; + +export function getCustomFilterViews(filter: CustomFilter): FilterViews { + return { + getDisplayText() { + return JSON.stringify(filter.query); + }, + }; +} diff --git a/src/ui/public/filter_bar/filters/filter_views/exists_filter_views.tsx b/src/ui/public/filter_bar/filters/filter_views/exists_filter_views.tsx new file mode 100644 index 000000000000000..6c60b39e47cb5f0 --- /dev/null +++ b/src/ui/public/filter_bar/filters/filter_views/exists_filter_views.tsx @@ -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 { ExistsFilter } from '../exists_filter'; +import { FilterViews } from './index'; + +export function getExistsFilterViews(filter: ExistsFilter): FilterViews { + return { + getDisplayText() { + return `${filter.meta.key} exists`; + }, + }; +} diff --git a/src/ui/public/filter_bar/filters/filter_views/geo_bounding_box_filter_views.tsx b/src/ui/public/filter_bar/filters/filter_views/geo_bounding_box_filter_views.tsx new file mode 100644 index 000000000000000..8057caa504e1adf --- /dev/null +++ b/src/ui/public/filter_bar/filters/filter_views/geo_bounding_box_filter_views.tsx @@ -0,0 +1,32 @@ +/* + * 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 { GeoBoundingBoxFilter } from '../geo_bounding_box_filter'; +import { FilterViews } from './index'; + +export function getGeoBoundingBoxFilterViews(filter: GeoBoundingBoxFilter): FilterViews { + return { + getDisplayText() { + const { meta } = filter; + const { key, params } = meta; + const { bottom_right: bottomRight, top_left: topLeft } = params; + return `${key}: ${JSON.stringify(topLeft)} to ${JSON.stringify(bottomRight)}`; + }, + }; +} diff --git a/src/ui/public/filter_bar/filters/filter_views/geo_polygon_filter_views.tsx b/src/ui/public/filter_bar/filters/filter_views/geo_polygon_filter_views.tsx new file mode 100644 index 000000000000000..507116a0a118783 --- /dev/null +++ b/src/ui/public/filter_bar/filters/filter_views/geo_polygon_filter_views.tsx @@ -0,0 +1,32 @@ +/* + * 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 { GeoPolygonFilter } from '../geo_polygon_filter'; +import { FilterViews } from './index'; + +export function getGeoPolygonFilterViews(filter: GeoPolygonFilter): FilterViews { + return { + getDisplayText() { + const { meta } = filter; + const { key, params } = meta; + const { points } = params; + return `${key}: ${points.map(point => JSON.stringify(point)).join(', ')}`; + }, + }; +} diff --git a/src/ui/public/filter_bar/filters/filter_views/index.ts b/src/ui/public/filter_bar/filters/filter_views/index.ts index 990ebf590b2bdcb..48098458c8f3c38 100644 --- a/src/ui/public/filter_bar/filters/filter_views/index.ts +++ b/src/ui/public/filter_bar/filters/filter_views/index.ts @@ -17,26 +17,56 @@ * under the License. */ -import { MetaFilter } from 'src/ui/public/filter_bar/filters/meta_filter'; -import { Filter } from 'ui/filter_bar/filters'; -import { PhraseFilter } from 'ui/filter_bar/filters/phrase_filter'; +import { + CustomFilter, + ExistsFilter, + GeoBoundingBoxFilter, + GeoPolygonFilter, + MetaFilter, + PhraseFilter, + PhrasesFilter, + QueryFilter, + RangeFilter, +} from '../index'; +import { getCustomFilterViews } from './custom_filter_views'; +import { getExistsFilterViews } from './exists_filter_views'; +import { getGeoBoundingBoxFilterViews } from './geo_bounding_box_filter_views'; +import { getGeoPolygonFilterViews } from './geo_polygon_filter_views'; import { getPhraseFilterViews } from './phrase_filter_views'; +import { getPhrasesFilterViews } from './phrases_filter_views'; +import { getQueryFilterViews } from './query_filter_views'; +import { getRangeFilterViews } from './range_filter_views'; export interface FilterViews { getDisplayText: () => string; } -export function getFilterDisplayText(metaFilter: MetaFilter): string { - const prefix = metaFilter.negate ? 'NOT ' : ''; - const filterText = getViewsForType(metaFilter.filter).getDisplayText(); +export function getFilterDisplayText(filter: MetaFilter): string { + if (filter.meta.alias !== null) { + return filter.meta.alias; + } + const prefix = filter.meta.negate ? 'NOT ' : ''; + const filterText = getViewsForType(filter).getDisplayText(); return `${prefix}${filterText}`; } -function getViewsForType(filter: Filter) { - switch (filter.type) { - case 'PhraseFilter': +function getViewsForType(filter: MetaFilter) { + switch (filter.meta.type) { + case 'exists': + return getExistsFilterViews(filter as ExistsFilter); + case 'geo_bounding_box': + return getGeoBoundingBoxFilterViews(filter as GeoBoundingBoxFilter); + case 'geo_polygon': + return getGeoPolygonFilterViews(filter as GeoPolygonFilter); + case 'phrase': return getPhraseFilterViews(filter as PhraseFilter); + case 'phrases': + return getPhrasesFilterViews(filter as PhrasesFilter); + case 'query_string': + return getQueryFilterViews(filter as QueryFilter); + case 'range': + return getRangeFilterViews(filter as RangeFilter); default: - throw new Error(`Unknown type: ${filter.type}`); + return getCustomFilterViews(filter as CustomFilter); } } diff --git a/src/ui/public/filter_bar/filters/filter_views/phrase_filter_views.tsx b/src/ui/public/filter_bar/filters/filter_views/phrase_filter_views.tsx index 476b850de090a8c..9e258048faa510c 100644 --- a/src/ui/public/filter_bar/filters/filter_views/phrase_filter_views.tsx +++ b/src/ui/public/filter_bar/filters/filter_views/phrase_filter_views.tsx @@ -17,13 +17,13 @@ * under the License. */ -import { FilterViews } from 'ui/filter_bar/filters/filter_views/index'; -import { PhraseFilter } from 'ui/filter_bar/filters/phrase_filter'; +import { PhraseFilter } from '../phrase_filter'; +import { FilterViews } from './index'; export function getPhraseFilterViews(filter: PhraseFilter): FilterViews { return { getDisplayText() { - return `${filter.field} : ${filter.value}`; + return `${filter.meta.key} : ${filter.meta.value}`; }, }; } diff --git a/src/ui/public/filter_bar/filters/filter_views/phrases_filter_views.tsx b/src/ui/public/filter_bar/filters/filter_views/phrases_filter_views.tsx new file mode 100644 index 000000000000000..e3def7acfa8d8b4 --- /dev/null +++ b/src/ui/public/filter_bar/filters/filter_views/phrases_filter_views.tsx @@ -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 { PhrasesFilter } from '../phrases_filter'; +import { FilterViews } from './index'; + +export function getPhrasesFilterViews(filter: PhrasesFilter): FilterViews { + return { + getDisplayText() { + return `${filter.meta.key} is one of ${filter.meta.value}`; + }, + }; +} diff --git a/src/ui/public/filter_bar/filters/filter_views/query_filter_views.tsx b/src/ui/public/filter_bar/filters/filter_views/query_filter_views.tsx new file mode 100644 index 000000000000000..297ae891d39205b --- /dev/null +++ b/src/ui/public/filter_bar/filters/filter_views/query_filter_views.tsx @@ -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 { QueryFilter } from '../query_filter'; +import { FilterViews } from './index'; + +export function getQueryFilterViews(filter: QueryFilter): FilterViews { + return { + getDisplayText() { + return filter.meta.value; + }, + }; +} diff --git a/src/ui/public/filter_bar/filters/filter_views/range_filter_views.tsx b/src/ui/public/filter_bar/filters/filter_views/range_filter_views.tsx new file mode 100644 index 000000000000000..3a19120006b0107 --- /dev/null +++ b/src/ui/public/filter_bar/filters/filter_views/range_filter_views.tsx @@ -0,0 +1,50 @@ +/* + * 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 { RangeFilter } from '../range_filter'; +import { FilterViews } from './index'; + +export function getRangeFilterViews(filter: RangeFilter): FilterViews { + return { + getDisplayText() { + const { meta } = filter; + const { key, params } = meta; + const { gt, gte, lt, lte } = params; + return `${key}: ${getFrom(gt, gte)} to ${getTo(lt, lte)}`; + }, + }; +} + +function getFrom(gt: string | number | undefined, gte: string | number | undefined): string { + if (typeof gt !== 'undefined' && gt !== null) { + return `${gt}`; + } else if (typeof gte !== 'undefined' && gte !== null) { + return `${gte}`; + } + return '-∞'; +} + +function getTo(lt: string | number | undefined, lte: string | number | undefined): string { + if (typeof lt !== 'undefined' && lt !== null) { + return `${lt}`; + } else if (typeof lte !== 'undefined' && lte !== null) { + return `${lte}`; + } + return '∞'; +} diff --git a/src/ui/public/filter_bar/filters/geo_bounding_box_filter.ts b/src/ui/public/filter_bar/filters/geo_bounding_box_filter.ts new file mode 100644 index 000000000000000..fd6cae7a650d972 --- /dev/null +++ b/src/ui/public/filter_bar/filters/geo_bounding_box_filter.ts @@ -0,0 +1,32 @@ +/* + * 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 { FilterMeta, LatLon, MetaFilter } from './meta_filter'; + +export type GeoBoundingBoxFilterMeta = FilterMeta & { + key: string; // The name of the field + params: { + bottom_right: LatLon; + top_left: LatLon; + }; +}; + +export type GeoBoundingBoxFilter = MetaFilter & { + meta: GeoBoundingBoxFilterMeta; +}; diff --git a/src/ui/public/filter_bar/filters/geo_polygon_filter.ts b/src/ui/public/filter_bar/filters/geo_polygon_filter.ts new file mode 100644 index 000000000000000..b71effdabc74aa4 --- /dev/null +++ b/src/ui/public/filter_bar/filters/geo_polygon_filter.ts @@ -0,0 +1,31 @@ +/* + * 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 { FilterMeta, LatLon, MetaFilter } from './meta_filter'; + +export type GeoPolygonFilterMeta = FilterMeta & { + key: string; // The name of the field + params: { + points: LatLon[]; + }; +}; + +export type GeoPolygonFilter = MetaFilter & { + meta: GeoPolygonFilterMeta; +}; diff --git a/src/ui/public/filter_bar/filters/index.ts b/src/ui/public/filter_bar/filters/index.ts index 7dc64c1fa6a2283..cc68b65c5d9e2f0 100644 --- a/src/ui/public/filter_bar/filters/index.ts +++ b/src/ui/public/filter_bar/filters/index.ts @@ -17,7 +17,27 @@ * under the License. */ -export interface Filter { - type: string; - toElasticsearchQuery: () => any; -} +// The interface the other filters extend +export { MetaFilter } from './meta_filter'; + +// Helper functions that can be invoked on any filter +export { + isFilterPinned, + toggleFilterDisabled, + toggleFilterNegated, + toggleFilterPinned, + enableFilter, + disableFilter, + pinFilter, + unpinFilter, +} from './meta_filter'; + +// The actual filter types +export { CustomFilter } from './custom_filter'; +export { ExistsFilter } from './exists_filter'; +export { GeoBoundingBoxFilter } from './geo_bounding_box_filter'; +export { GeoPolygonFilter } from './geo_polygon_filter'; +export { PhraseFilter } from './phrase_filter'; +export { PhrasesFilter } from './phrases_filter'; +export { QueryFilter } from './query_filter'; +export { RangeFilter } from './range_filter'; diff --git a/src/ui/public/filter_bar/filters/meta_filter.ts b/src/ui/public/filter_bar/filters/meta_filter.ts index 894a1d9666cd7f0..5477e498bac17cd 100644 --- a/src/ui/public/filter_bar/filters/meta_filter.ts +++ b/src/ui/public/filter_bar/filters/meta_filter.ts @@ -17,103 +17,68 @@ * under the License. */ -import { Filter } from 'ui/filter_bar/filters/index'; +export enum FilterStateStore { + APP_STATE = 'appState', + GLOBAL_STATE = 'globalState', +} -export interface MetaFilter { - filter: Filter; +export interface FilterState { + store: FilterStateStore; +} + +export interface FilterMeta { + type: string; disabled: boolean; negate: boolean; - pinned: boolean; - index?: string; - toElasticsearchQuery: () => void; + alias: string | null; + index: string; } -interface CreateMetaFilterOptions { - disabled?: boolean; - negate?: boolean; - pinned?: boolean; - index?: string; +export interface MetaFilter { + $state: FilterState; + meta: FilterMeta; + query?: any; } -export function createMetaFilter( - filter: Filter, - { disabled = false, negate = false, pinned = false, index }: CreateMetaFilterOptions = {} -): MetaFilter { - return { - filter, - disabled, - negate, - pinned, - index, - toElasticsearchQuery: () => { - // TODO implement me - // if negate === true then wrap filter in a `not` filter - // call underlying filter's toElasticsearchQuery - // e.g. Object.getPrototypeOf(this).toElasticsearchQuery(); - }, - }; +export interface LatLon { + lat: number; + lon: number; } -export function enable(metaFilter: MetaFilter) { - return { - ...metaFilter, - disabled: false, - }; +export function isFilterPinned(filter: MetaFilter) { + return filter.$state.store === FilterStateStore.GLOBAL_STATE; } -export function disable(metaFilter: MetaFilter) { - return { - ...metaFilter, - disabled: true, - }; +export function toggleFilterDisabled(filter: MetaFilter) { + const disabled = !filter.meta.disabled; + const meta = { ...filter.meta, disabled }; + return { ...filter, meta }; } -export function pin(metaFilter: MetaFilter) { - return { - ...metaFilter, - pinned: true, - }; +export function toggleFilterNegated(filter: MetaFilter) { + const negate = !filter.meta.negate; + const meta = { ...filter.meta, negate }; + return { ...filter, meta }; } -export function unpin(metaFilter: MetaFilter) { - return { - ...metaFilter, - pinned: false, - }; +export function toggleFilterPinned(filter: MetaFilter) { + const store = isFilterPinned(filter) ? FilterStateStore.APP_STATE : FilterStateStore.GLOBAL_STATE; + const $state = { ...filter.$state, store }; + return { ...filter, $state }; } -export function toggleNegation(metaFilter: MetaFilter) { - const negate = !metaFilter.negate; - return { - ...metaFilter, - negate, - }; +export function enableFilter(filter: MetaFilter) { + return !filter.meta.disabled ? filter : toggleFilterDisabled(filter); } -export function togglePinned(metaFilter: MetaFilter) { - const pinned = !metaFilter.pinned; - return { - ...metaFilter, - pinned, - }; +export function disableFilter(filter: MetaFilter) { + return filter.meta.disabled ? filter : toggleFilterDisabled(filter); } -export function toggleDisabled(metaFilter: MetaFilter) { - const disabled = !metaFilter.disabled; - return { - ...metaFilter, - disabled, - }; +export function pinFilter(filter: MetaFilter) { + return isFilterPinned(filter) ? filter : toggleFilterPinned(filter); } -export function updateFilter(metaFilter: MetaFilter, updateObject: Partial) { - const updatedFilter = { - ...metaFilter.filter, - ...updateObject, - }; - - return { - ...metaFilter, - filter: updatedFilter, - }; +export function unpinFilter(filter: MetaFilter) { + return !isFilterPinned(filter) ? filter : toggleFilterPinned(filter); } diff --git a/src/ui/public/filter_bar/filters/phrase_filter.ts b/src/ui/public/filter_bar/filters/phrase_filter.ts index ddb3c4845a54b03..30520bfe14a1df2 100644 --- a/src/ui/public/filter_bar/filters/phrase_filter.ts +++ b/src/ui/public/filter_bar/filters/phrase_filter.ts @@ -17,27 +17,16 @@ * under the License. */ -import { Filter } from 'ui/filter_bar/filters/index'; +import { FilterMeta, MetaFilter } from './meta_filter'; -export type PhraseFilter = Filter & { - field: string; - value: string | number; +export type PhraseFilterMeta = FilterMeta & { + key: string; // The name of the field + value: string; // The formatted value + params: { + query: string; // The unformatted value + }; }; -interface CreatePhraseFilterOptions { - field: string; - value: string | number; -} - -export function createPhraseFilter(options: CreatePhraseFilterOptions): PhraseFilter { - const { field, value } = options; - return { - type: 'PhraseFilter', - field, - value, - toElasticsearchQuery() { - // TODO implement me - return {}; - }, - }; -} +export type PhraseFilter = MetaFilter & { + meta: PhraseFilterMeta; +}; diff --git a/src/ui/public/filter_bar/filters/phrases_filter.ts b/src/ui/public/filter_bar/filters/phrases_filter.ts new file mode 100644 index 000000000000000..8ff813d976e1116 --- /dev/null +++ b/src/ui/public/filter_bar/filters/phrases_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 { FilterMeta, MetaFilter } from './meta_filter'; + +export type PhrasesFilterMeta = FilterMeta & { + key: string; // The name of the field + params: string[]; // The unformatted values + value: string; // The formatted values concatenated together +}; + +export type PhrasesFilter = MetaFilter & { + meta: PhrasesFilterMeta; +}; diff --git a/src/ui/public/filter_bar/filters/query_filter.ts b/src/ui/public/filter_bar/filters/query_filter.ts new file mode 100644 index 000000000000000..400c5d28dedd36c --- /dev/null +++ b/src/ui/public/filter_bar/filters/query_filter.ts @@ -0,0 +1,28 @@ +/* + * 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 { FilterMeta, MetaFilter } from './meta_filter'; + +export type QueryFilterMeta = FilterMeta & { + value: string; // The query string +}; + +export type QueryFilter = MetaFilter & { + meta: QueryFilterMeta; +}; diff --git a/src/ui/public/filter_bar/filters/range_filter.ts b/src/ui/public/filter_bar/filters/range_filter.ts new file mode 100644 index 000000000000000..9951aff8c19a965 --- /dev/null +++ b/src/ui/public/filter_bar/filters/range_filter.ts @@ -0,0 +1,34 @@ +/* + * 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 { FilterMeta, MetaFilter } from './meta_filter'; + +export type RangeFilterMeta = FilterMeta & { + key: string; // The name of the field + params: { + gt?: number | string; + gte?: number | string; + lte?: number | string; + lt?: number | string; + }; +}; + +export type RangeFilter = MetaFilter & { + meta: RangeFilterMeta; +}; diff --git a/src/ui/public/filter_bar/query_filter.js b/src/ui/public/filter_bar/query_filter.js index 0c1e68c084535e6..8a7133e3d486cc8 100644 --- a/src/ui/public/filter_bar/query_filter.js +++ b/src/ui/public/filter_bar/query_filter.js @@ -216,6 +216,16 @@ export function FilterBarQueryFilterProvider(Private, $rootScope, getAppState, g executeOnFilters(pin); }; + queryFilter.setFilters = filters => { + const appState = getAppState(); + const [globalFilters, appFilters] = _.partition(filters, filter => { + return filter.$state.store === 'globalState'; + }); + globalState.filters = globalFilters; + if (appState) appState.filters = appFilters; + saveState(); + }; + initWatchers(); return queryFilter; diff --git a/src/ui/public/filter_bar/react/filter_bar.tsx b/src/ui/public/filter_bar/react/filter_bar.tsx index 11912834ec42dfb..cdb0a69036fd239 100644 --- a/src/ui/public/filter_bar/react/filter_bar.tsx +++ b/src/ui/public/filter_bar/react/filter_bar.tsx @@ -20,33 +20,93 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import classNames from 'classnames'; import React, { Component } from 'react'; -import { MetaFilter } from 'ui/filter_bar/filters/meta_filter'; -import { FilterItem } from 'ui/filter_bar/react/filter_item'; +import { FilterOptions } from 'ui/search_bar/components/filter_options'; +import { + disableFilter, + enableFilter, + MetaFilter, + pinFilter, + toggleFilterDisabled, + toggleFilterNegated, + toggleFilterPinned, + unpinFilter, +} from '../filters'; +import { FilterItem } from './filter_item'; interface Props { filters: MetaFilter[]; - onToggleNegate: (filter: MetaFilter) => void; - onToggleDisabled: (filter: MetaFilter) => void; - onTogglePin: (filter: MetaFilter) => void; - onDelete: (filter: MetaFilter) => void; + onFiltersUpdated: (filters: MetaFilter[]) => void; className: string; } export class FilterBar extends Component { - public onToggleNegate = (filter: MetaFilter) => { - this.props.onToggleNegate(filter); + public onToggleNegated = (i: number) => { + const filters = [...this.props.filters]; + filters[i] = toggleFilterNegated(filters[i]); + this.props.onFiltersUpdated(filters); }; - public onTogglePin = (filter: MetaFilter) => { - this.props.onTogglePin(filter); + public onTogglePinned = (i: number) => { + const filters = [...this.props.filters]; + filters[i] = toggleFilterPinned(filters[i]); + this.props.onFiltersUpdated(filters); }; - public onToggleDisabled = (filter: MetaFilter) => { - this.props.onToggleDisabled(filter); + public onToggleDisabled = (i: number) => { + const filters = [...this.props.filters]; + filters[i] = toggleFilterDisabled(filters[i]); + this.props.onFiltersUpdated(filters); }; - public onDelete = (filter: MetaFilter) => { - this.props.onDelete(filter); + public onAdd = (filter: MetaFilter) => { + const filters = [...this.props.filters, filter]; + this.props.onFiltersUpdated(filters); + }; + + public onRemove = (i: number) => { + const filters = [...this.props.filters]; + filters.splice(i, 1); + this.props.onFiltersUpdated(filters); + }; + + public onUpdate = (i: number, filter: MetaFilter) => { + const filters = [...this.props.filters]; + filters[i] = filter; + this.props.onFiltersUpdated(filters); + }; + + public onEnableAll = () => { + const filters = this.props.filters.map(filter => enableFilter(filter)); + this.props.onFiltersUpdated(filters); + }; + + public onDisableAll = () => { + const filters = this.props.filters.map(filter => disableFilter(filter)); + this.props.onFiltersUpdated(filters); + }; + + public onPinAll = () => { + const filters = this.props.filters.map(filter => pinFilter(filter)); + this.props.onFiltersUpdated(filters); + }; + + public onUnpinAll = () => { + const filters = this.props.filters.map(filter => unpinFilter(filter)); + this.props.onFiltersUpdated(filters); + }; + + public onToggleAllNegated = () => { + const filters = this.props.filters.map(filter => toggleFilterNegated(filter)); + this.props.onFiltersUpdated(filters); + }; + + public onToggleAllDisabled = () => { + const filters = this.props.filters.map(filter => toggleFilterDisabled(filter)); + this.props.onFiltersUpdated(filters); + }; + + public onRemoveAll = () => { + this.props.onFiltersUpdated([]); }; public render() { @@ -57,10 +117,10 @@ export class FilterBar extends Component { this.onTogglePinned(i)} + onToggleNegated={() => this.onToggleNegated(i)} + onToggleDisabled={() => this.onToggleDisabled(i)} + onRemove={() => this.onRemove(i)} /> ); @@ -68,14 +128,35 @@ export class FilterBar extends Component { return ( - {/* TODO display pinned filters first*/} - {filterItems} + + + + + + + {/* TODO display pinned filters first*/} + {filterItems} + + ); } diff --git a/src/ui/public/filter_bar/react/filter_editor/index.tsx b/src/ui/public/filter_bar/react/filter_editor/index.tsx index 277e0a3b517c656..d9c78d245771b04 100644 --- a/src/ui/public/filter_bar/react/filter_editor/index.tsx +++ b/src/ui/public/filter_bar/react/filter_editor/index.tsx @@ -29,8 +29,9 @@ import { EuiSpacer, EuiSwitch, } from '@elastic/eui'; +import { noop } from 'lodash'; import React, { Component } from 'react'; -import { MetaFilter } from 'ui/filter_bar/filters/meta_filter'; +import { MetaFilter } from '../../filters'; const fieldOptions = [ { @@ -88,7 +89,6 @@ const valueOptions = [ ]; interface Props { - foo: string; filter: MetaFilter; } @@ -245,7 +245,7 @@ export class FilterEditor extends Component {
- {}} /> +
)} @@ -254,12 +254,12 @@ export class FilterEditor extends Component { - {}}> + Add - {} : this.resetForm}> + {this.props.filter ? 'Cancel' : 'Reset form'} diff --git a/src/ui/public/filter_bar/react/filter_item.tsx b/src/ui/public/filter_bar/react/filter_item.tsx index ca742c6d84f6d64..922784bd11d3343 100644 --- a/src/ui/public/filter_bar/react/filter_item.tsx +++ b/src/ui/public/filter_bar/react/filter_item.tsx @@ -20,17 +20,17 @@ import { EuiBadge, EuiContextMenu, EuiPopover } from '@elastic/eui'; import classNames from 'classnames'; import React, { Component } from 'react'; -import { getFilterDisplayText } from 'ui/filter_bar/filters/filter_views'; -import { MetaFilter } from 'ui/filter_bar/filters/meta_filter'; -import { FilterEditor } from 'ui/filter_bar/react/filter_editor'; +import { isFilterPinned, MetaFilter } from '../filters'; +import { getFilterDisplayText } from '../filters/filter_views'; +import { FilterEditor } from './filter_editor'; interface Props { filter: MetaFilter; className?: string; - onTogglePin: (filter: MetaFilter) => void; - onToggleNegate: (filter: MetaFilter) => void; + onTogglePinned: (filter: MetaFilter) => void; + onToggleNegated: (filter: MetaFilter) => void; onToggleDisabled: (filter: MetaFilter) => void; - onDelete: (filter: MetaFilter) => void; + onRemove: (filter: MetaFilter) => void; } interface State { @@ -43,14 +43,14 @@ export class FilterItem extends Component { }; public render() { - const filter = this.props.filter; - const { negate, disabled, pinned } = filter; + const { filter } = this.props; + const { negate, disabled } = filter.meta; const classes = classNames( 'globalFilterItem', { 'globalFilterItem-isDisabled': disabled, - 'globalFilterItem-isPinned': pinned, + 'globalFilterItem-isPinned': isFilterPinned(filter), 'globalFilterItem-isExcluded': negate, }, this.props.className @@ -61,7 +61,7 @@ export class FilterItem extends Component { id={'foo'} className={classes} title={'foo'} - iconOnClick={() => this.props.onDelete(filter)} + iconOnClick={() => this.props.onRemove(filter)} iconOnClickAriaLabel={`Delete filter`} iconType="cross" // @ts-ignore @@ -83,11 +83,11 @@ export class FilterItem extends Component { id: 0, items: [ { - name: `${pinned ? 'Unpin' : 'Pin across all apps'}`, + name: `${isFilterPinned(filter) ? 'Unpin' : 'Pin across all apps'}`, icon: 'pin', onClick: () => { this.closePopover(); - this.props.onTogglePin(filter); + this.props.onTogglePinned(filter); }, }, { @@ -100,7 +100,7 @@ export class FilterItem extends Component { icon: `${negate ? 'plusInCircle' : 'minusInCircle'}`, onClick: () => { this.closePopover(); - this.props.onToggleNegate(filter); + this.props.onToggleNegated(filter); }, }, { @@ -116,7 +116,7 @@ export class FilterItem extends Component { icon: 'trash', onClick: () => { this.closePopover(); - this.props.onDelete(filter); + this.props.onRemove(filter); }, }, ], @@ -126,7 +126,7 @@ export class FilterItem extends Component { width: 400, content: (
- +
), }, diff --git a/src/ui/public/search_bar/components/filter_options.tsx b/src/ui/public/search_bar/components/filter_options.tsx index 64d773ba886ba66..50df6449d15b86e 100644 --- a/src/ui/public/search_bar/components/filter_options.tsx +++ b/src/ui/public/search_bar/components/filter_options.tsx @@ -22,7 +22,13 @@ import { Component } from 'react'; import React from 'react'; interface Props { - onAction: (action: string) => void; + onEnableAll: () => void; + onDisableAll: () => void; + onPinAll: () => void; + onUnpinAll: () => void; + onToggleAllNegated: () => void; + onToggleAllDisabled: () => void; + onRemoveAll: () => void; } interface State { @@ -53,7 +59,7 @@ export class FilterOptions extends Component { icon: 'eye', onClick: () => { this.closePopover(); - this.props.onAction('enable'); + this.props.onEnableAll(); }, }, { @@ -61,7 +67,7 @@ export class FilterOptions extends Component { icon: 'eyeClosed', onClick: () => { this.closePopover(); - this.props.onAction('disable'); + this.props.onDisableAll(); }, }, { @@ -69,7 +75,7 @@ export class FilterOptions extends Component { icon: 'pin', onClick: () => { this.closePopover(); - this.props.onAction('pin'); + this.props.onPinAll(); }, }, { @@ -77,7 +83,7 @@ export class FilterOptions extends Component { icon: 'pin', onClick: () => { this.closePopover(); - this.props.onAction('unpin'); + this.props.onUnpinAll(); }, }, { @@ -85,7 +91,7 @@ export class FilterOptions extends Component { icon: 'invert', onClick: () => { this.closePopover(); - this.props.onAction('toggleNegate'); + this.props.onToggleAllNegated(); }, }, { @@ -93,7 +99,7 @@ export class FilterOptions extends Component { icon: 'eye', onClick: () => { this.closePopover(); - this.props.onAction('toggleDisabled'); + this.props.onToggleAllDisabled(); }, }, { @@ -101,7 +107,7 @@ export class FilterOptions extends Component { icon: 'trash', onClick: () => { this.closePopover(); - this.props.onAction('delete'); + this.props.onRemoveAll(); }, }, ], diff --git a/src/ui/public/search_bar/components/search_bar.tsx b/src/ui/public/search_bar/components/search_bar.tsx index 384c90f698da7c1..b5e3797ae2f9870 100644 --- a/src/ui/public/search_bar/components/search_bar.tsx +++ b/src/ui/public/search_bar/components/search_bar.tsx @@ -26,7 +26,6 @@ import { MetaFilter } from 'ui/filter_bar/filters/meta_filter'; import { FilterBar } from 'ui/filter_bar/react'; import { IndexPattern } from 'ui/index_patterns'; import { QueryBar } from 'ui/query_bar'; -import { FilterOptions } from 'ui/search_bar/components/filter_options'; import { Storage } from 'ui/storage'; // TODO combine all the filter actions into a single event handler? @@ -41,11 +40,7 @@ interface Props { indexPatterns: IndexPattern[]; store: Storage; filters: MetaFilter[]; - onToggleFilterNegate: (filter: MetaFilter) => void; - onToggleFilterDisabled: (filter: MetaFilter) => void; - onToggleFilterPin: (filter: MetaFilter) => void; - onFilterDelete: (filter: MetaFilter) => void; - onAllFiltersAction: (action: string) => void; + onFiltersUpdated: (filters: MetaFilter[]) => void; } interface State { @@ -141,27 +136,11 @@ export class SearchBar extends Component { this.filterBarRef = node; }} > - - - - - - - - - +