-
Notifications
You must be signed in to change notification settings - Fork 187
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c1f4e51
commit 2644b91
Showing
8 changed files
with
596 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
98 changes: 98 additions & 0 deletions
98
public/kibana-integrations/search-bar/filter-generator.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
/* | ||
* 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 { getPhraseScript } from '@kbn/es-query'; | ||
|
||
// Adds a filter to a passed state | ||
export function getFilterGenerator(queryFilter) { | ||
const filterGen = {}; | ||
|
||
filterGen.generate = (field, values, operation, index) => { | ||
values = Array.isArray(values) ? values : [values]; | ||
const fieldName = _.isObject(field) ? field.name : field; | ||
const filters = _.flatten([queryFilter.getAppFilters()]); | ||
const newFilters = []; | ||
|
||
const negate = (operation === '-'); | ||
|
||
// TODO: On array fields, negating does not negate the combination, rather all terms | ||
_.each(values, function (value) { | ||
let filter; | ||
const existing = _.find(filters, function (filter) { | ||
if (!filter) return; | ||
|
||
if (fieldName === '_exists_' && filter.exists) { | ||
return filter.exists.field === value; | ||
} | ||
|
||
if (_.has(filter, 'query.match')) { | ||
return filter.query.match[fieldName] && filter.query.match[fieldName].query === value; | ||
} | ||
|
||
if (filter.script) { | ||
return filter.meta.field === fieldName && filter.script.script.params.value === value; | ||
} | ||
}); | ||
|
||
if (existing) { | ||
existing.meta.disabled = false; | ||
if (existing.meta.negate !== negate) { | ||
existing.meta.negate = !existing.meta.negate; | ||
} | ||
newFilters.push(existing); | ||
return; | ||
} | ||
|
||
switch (fieldName) { | ||
case '_exists_': | ||
filter = { | ||
meta: { negate, index }, | ||
exists: { | ||
field: value | ||
} | ||
}; | ||
break; | ||
default: | ||
if (field.scripted) { | ||
filter = { | ||
meta: { negate, index, field: fieldName }, | ||
script: getPhraseScript(field, value) | ||
}; | ||
} else { | ||
filter = { meta: { negate, index }, query: { match: {} } }; | ||
filter.query.match[fieldName] = { query: value, type: 'phrase' }; | ||
} | ||
|
||
break; | ||
} | ||
|
||
newFilters.push(filter); | ||
}); | ||
|
||
return newFilters; | ||
}; | ||
|
||
filterGen.add = function (field, values, operation, index) { | ||
const newFilters = this.generate(field, values, operation, index); | ||
return queryFilter.addFilters(newFilters); | ||
}; | ||
|
||
return filterGen; | ||
} |
206 changes: 206 additions & 0 deletions
206
public/kibana-integrations/search-bar/filter-manager.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,206 @@ | ||
/* | ||
* 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, isFilterPinned, FilterStateStore } from '@kbn/es-query'; | ||
|
||
import _ from 'lodash'; | ||
import { Subject } from 'rxjs'; | ||
|
||
import { npSetup } from 'ui/new_platform'; | ||
|
||
// @ts-ignore | ||
import { compareFilters } from 'plugins/data/filter/filter_manager/lib/compare_filters'; | ||
// @ts-ignore | ||
import { mapAndFlattenFilters } from 'plugins/data/filter/filter_manager/lib/map_and_flatten_filters'; | ||
// @ts-ignore | ||
import { uniqFilters } from 'plugins/data/filter/filter_manager/lib/uniq_filters'; | ||
// @ts-ignore | ||
import { extractTimeFilter } from 'plugins/data/filter/filter_manager/lib/extract_time_filter'; | ||
// @ts-ignore | ||
import { changeTimeFilter } from 'plugins/data/filter/filter_manager/lib/change_time_filter'; | ||
|
||
import { onlyDisabledFiltersChanged } from './only_disabled'; | ||
|
||
import { PartitionedFilters } from 'plugins/data/filter/filter_manager/partitioned_filters'; | ||
|
||
import { IndexPatterns } from 'plugins/data/index_patterns'; | ||
|
||
export class FilterManager { | ||
private indexPatterns: IndexPatterns; | ||
private filters: Filter[] = []; | ||
private updated$: Subject<void> = new Subject(); | ||
private fetch$: Subject<void> = new Subject(); | ||
|
||
constructor(indexPatterns: IndexPatterns) { | ||
this.indexPatterns = indexPatterns; | ||
} | ||
|
||
private mergeIncomingFilters(partitionedFilters: PartitionedFilters): Filter[] { | ||
const globalFilters = partitionedFilters.globalFilters; | ||
const appFilters = partitionedFilters.appFilters; | ||
|
||
// existing globalFilters should be mutated by appFilters | ||
_.each(appFilters, function (filter, i) { | ||
const match = _.find(globalFilters, function (globalFilter) { | ||
return compareFilters(globalFilter, filter); | ||
}); | ||
|
||
// no match, do nothing | ||
if (!match) return; | ||
|
||
// matching filter in globalState, update global and remove from appState | ||
_.assign(match.meta, filter.meta); | ||
appFilters.splice(i, 1); | ||
}); | ||
|
||
return FilterManager.mergeFilters(appFilters, globalFilters); | ||
} | ||
|
||
private static mergeFilters(appFilters: Filter[], globalFilters: Filter[]): Filter[] { | ||
return uniqFilters(appFilters.reverse().concat(globalFilters.reverse())).reverse(); | ||
} | ||
|
||
private static partitionFilters(filters: Filter[]): PartitionedFilters { | ||
const [globalFilters, appFilters] = _.partition(filters, isFilterPinned); | ||
return { | ||
globalFilters, | ||
appFilters, | ||
}; | ||
} | ||
|
||
private handleStateUpdate(newFilters: Filter[]) { | ||
// global filters should always be first | ||
newFilters.sort(({ $state: a }: Filter, { $state: b }: Filter): number => { | ||
return a!.store === FilterStateStore.GLOBAL_STATE && | ||
b!.store !== FilterStateStore.GLOBAL_STATE | ||
? -1 | ||
: 1; | ||
}); | ||
|
||
const filtersUpdated = !_.isEqual(this.filters, newFilters); | ||
const updatedOnlyDisabledFilters = onlyDisabledFiltersChanged(newFilters, this.filters); | ||
|
||
this.filters = newFilters; | ||
if (filtersUpdated) { | ||
this.updated$.next(); | ||
if (!updatedOnlyDisabledFilters) { | ||
this.fetch$.next(); | ||
} | ||
} | ||
} | ||
|
||
/* Getters */ | ||
|
||
public getFilters() { | ||
return _.cloneDeep(this.filters); | ||
} | ||
|
||
public getAppFilters() { | ||
const { appFilters } = this.getPartitionedFilters(); | ||
return appFilters; | ||
} | ||
|
||
public getGlobalFilters() { | ||
const { globalFilters } = this.getPartitionedFilters(); | ||
return globalFilters; | ||
} | ||
|
||
public getPartitionedFilters(): PartitionedFilters { | ||
return FilterManager.partitionFilters(this.getFilters()); | ||
} | ||
|
||
public getUpdates$() { | ||
return this.updated$.asObservable(); | ||
} | ||
|
||
public getFetches$() { | ||
return this.fetch$.asObservable(); | ||
} | ||
|
||
/* Setters */ | ||
|
||
public async addFilters(filters: Filter[] | Filter, pinFilterStatus?: boolean) { | ||
if (!Array.isArray(filters)) { | ||
filters = [filters]; | ||
} | ||
|
||
if (filters.length === 0) { | ||
return; | ||
} | ||
|
||
const { uiSettings } = npSetup.core; | ||
if (pinFilterStatus === undefined) { | ||
pinFilterStatus = uiSettings.get('filters:pinnedByDefault'); | ||
} | ||
|
||
// Set the store of all filters. For now. | ||
// In the future, all filters should come in with filter state store already set. | ||
const store = pinFilterStatus ? FilterStateStore.GLOBAL_STATE : FilterStateStore.APP_STATE; | ||
FilterManager.setFiltersStore(filters, store); | ||
|
||
const mappedFilters = await mapAndFlattenFilters(this.indexPatterns, filters); | ||
|
||
// This is where we add new filters to the correct place (app \ global) | ||
const newPartitionedFilters = FilterManager.partitionFilters(mappedFilters); | ||
const currentFilters = this.getPartitionedFilters(); | ||
currentFilters.appFilters.push(...newPartitionedFilters.appFilters); | ||
currentFilters.globalFilters.push(...newPartitionedFilters.globalFilters); | ||
|
||
const newFilters = this.mergeIncomingFilters(currentFilters); | ||
this.handleStateUpdate(newFilters); | ||
} | ||
|
||
public async setFilters(newFilters: Filter[]) { | ||
const mappedFilters = await mapAndFlattenFilters(this.indexPatterns, newFilters); | ||
const newPartitionedFilters = FilterManager.partitionFilters(mappedFilters); | ||
const mergedFilters = this.mergeIncomingFilters(newPartitionedFilters); | ||
this.handleStateUpdate(mergedFilters); | ||
} | ||
|
||
public removeFilter(filter: Filter) { | ||
const filterIndex = _.findIndex(this.filters, item => { | ||
return _.isEqual(item.meta, filter.meta) && _.isEqual(item.query, filter.query); | ||
}); | ||
|
||
if (filterIndex >= 0) { | ||
const newFilters = _.cloneDeep(this.filters); | ||
newFilters.splice(filterIndex, 1); | ||
this.handleStateUpdate(newFilters); | ||
} | ||
} | ||
|
||
public async removeAll() { | ||
await this.setFilters([]); | ||
} | ||
|
||
public async addFiltersAndChangeTimeFilter(filters: Filter[]) { | ||
const timeFilter = await extractTimeFilter(this.indexPatterns, filters); | ||
if (timeFilter) changeTimeFilter(timeFilter); | ||
return this.addFilters(filters.filter(filter => filter !== timeFilter)); | ||
} | ||
|
||
public static setFiltersStore(filters: Filter[], store: FilterStateStore) { | ||
_.map(filters, (filter: Filter) => { | ||
// Override status only for filters that didn't have state in the first place. | ||
if (filter.$state === undefined) { | ||
filter.$state = { store }; | ||
} | ||
}); | ||
} | ||
} |
Oops, something went wrong.