Remove indexPatterns dependency from filter service (#47471)

* Get rid of addFiltersAndChangeTimeFilter

* ts fix

* remove timefilter dependency from filter manager

* code review change

* Fixed bug in tests

* changeTimeFilter

* Refactored mappers and filter service to have no dependency on indexPatterns by generating the filter disaplyName in the relevant components.

* Fix map and flatten test

* Fixed filter state manager test

* Remove async from addFIlters and setFilters

* Fixed saved objects test - removed (display)value from url

* Make removeAll sync

* defer setFilters and removeAll in dashboard controller - temp hack

* fixed translation in filter view

* update strings

* Fixed range rendering

* map range converter
This commit is contained in:
Liza Katz 2019-10-15 15:23:24 +03:00 committed by GitHub
parent 27dbcb2796
commit 338851f5d3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 499 additions and 469 deletions

View file

@ -26,6 +26,12 @@ export interface FilterState {
store: FilterStateStore;
}
type FilterFormatterFunction = (value: any) => string;
export interface FilterValueFormatter {
convert: FilterFormatterFunction;
getConverterFor: (type: string) => FilterFormatterFunction;
}
export interface FilterMeta {
// index and type are optional only because when you create a new filter, there are no defaults
index?: string;
@ -34,7 +40,7 @@ export interface FilterMeta {
negate: boolean;
alias: string | null;
key?: string;
value?: string;
value?: string | ((formatter?: FilterValueFormatter) => string);
params?: any;
}

View file

@ -33,10 +33,13 @@ import {
import { Filter } from '@kbn/es-query';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { Component } from 'react';
import { getFilterDisplayText } from '../filter_bar/filter_view';
import { IndexPattern } from '../../index_patterns';
import { getDisplayValueFromFilter } from '../filter_bar/filter_editor/lib/filter_editor_utils';
import { getFilterDisplayText } from '../filter_bar/filter_editor/lib/get_filter_display_text';
interface Props {
filters: Filter[];
indexPatterns: IndexPattern[];
onCancel: () => void;
onSubmit: (filters: Filter[]) => void;
}
@ -57,6 +60,11 @@ export class ApplyFiltersPopover extends Component<Props, State> {
};
}
private getLabel(filter: Filter) {
const filterDisplayValue = getDisplayValueFromFilter(filter, this.props.indexPatterns);
return getFilterDisplayText(filter, filterDisplayValue);
}
public render() {
if (this.props.filters.length === 0) {
return '';
@ -67,7 +75,7 @@ export class ApplyFiltersPopover extends Component<Props, State> {
{this.props.filters.map((filter, i) => (
<EuiFormRow key={i}>
<EuiSwitch
label={getFilterDisplayText(filter)}
label={this.getLabel(filter)}
checked={this.isFilterSelected(i)}
onChange={() => this.toggleFilterSelected(i)}
/>

View file

@ -31,7 +31,7 @@ import {
PhrasesFilter,
RangeFilter,
} from '@kbn/es-query';
import { omit } from 'lodash';
import { omit, get } from 'lodash';
import { Ipv4Address } from '../../../../../../../../plugins/kibana_utils/public';
import { Field, IndexPattern, isFilterable } from '../../../../index_patterns';
import { FILTER_OPERATORS, Operator } from './filter_operators';
@ -43,6 +43,27 @@ export function getIndexPatternFromFilter(
return indexPatterns.find(indexPattern => indexPattern.id === filter.meta.index);
}
function getValueFormatter(indexPattern?: IndexPattern, key?: string) {
if (!indexPattern || !key) return;
let format = get(indexPattern, ['fields', 'byName', key, 'format']);
if (!format && indexPattern.fields.getByName) {
// TODO: Why is indexPatterns sometimes a map and sometimes an array?
format = (indexPattern.fields.getByName(key) as Field).format;
}
return format;
}
export function getDisplayValueFromFilter(filter: Filter, indexPatterns: IndexPattern[]): string {
const indexPattern = getIndexPatternFromFilter(filter, indexPatterns);
if (typeof filter.meta.value === 'function') {
const valueFormatter: any = getValueFormatter(indexPattern, filter.meta.key);
return filter.meta.value(valueFormatter);
} else {
return filter.meta.value || '';
}
}
export function getFieldFromFilter(filter: FieldFilter, indexPattern: IndexPattern) {
return indexPattern.fields.find(field => field.name === filter.meta.key);
}

View file

@ -0,0 +1,53 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { existsOperator, isOneOfOperator } from './filter_operators';
export function getFilterDisplayText(filter: Filter, filterDisplayName: string) {
const prefix = filter.meta.negate
? ` ${i18n.translate('data.filter.filterBar.negatedFilterPrefix', {
defaultMessage: 'NOT ',
})}`
: '';
if (filter.meta.alias !== null) {
return `${prefix}${filter.meta.alias}`;
}
switch (filter.meta.type) {
case 'exists':
return `${prefix}${filter.meta.key} ${existsOperator.message}`;
case 'geo_bounding_box':
return `${prefix}${filter.meta.key}: ${filterDisplayName}`;
case 'geo_polygon':
return `${prefix}${filter.meta.key}: ${filterDisplayName}`;
case 'phrase':
return `${prefix}${filter.meta.key}: ${filterDisplayName}`;
case 'phrases':
return `${prefix}${filter.meta.key} ${isOneOfOperator.message} ${filterDisplayName}`;
case 'query_string':
return `${prefix}${filterDisplayName}`;
case 'range':
return `${prefix}${filter.meta.key}: ${filterDisplayName}`;
default:
return `${prefix}${JSON.stringify(filter.query)}`;
}
}

View file

@ -32,6 +32,7 @@ import { UiSettingsClientContract } from 'src/core/public';
import { IndexPattern } from '../../index_patterns';
import { FilterEditor } from './filter_editor';
import { FilterView } from './filter_view';
import { getDisplayValueFromFilter } from './filter_editor/lib/filter_editor_utils';
interface Props {
id: string;
@ -67,8 +68,9 @@ class FilterItemUI extends Component<Props, State> {
this.props.className
);
const displayName = getDisplayValueFromFilter(filter, this.props.indexPatterns);
const dataTestSubjKey = filter.meta.key ? `filter-key-${filter.meta.key}` : '';
const dataTestSubjValue = filter.meta.value ? `filter-value-${filter.meta.value}` : '';
const dataTestSubjValue = filter.meta.value ? `filter-value-${displayName}` : '';
const dataTestSubjDisabled = `filter-${
this.props.filter.meta.disabled ? 'disabled' : 'enabled'
}`;
@ -76,6 +78,7 @@ class FilterItemUI extends Component<Props, State> {
const badge = (
<FilterView
filter={filter}
displayName={displayName}
className={classes}
iconOnClick={() => this.props.onRemove()}
onClick={this.togglePopover}

View file

@ -21,20 +21,25 @@ 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 { getFilterDisplayText } from '../filter_editor/lib/get_filter_display_text';
interface Props {
filter: Filter;
displayName: string;
[propName: string]: any;
}
export const FilterView: SFC<Props> = ({ filter, iconOnClick, onClick, ...rest }: Props) => {
let title = `Filter: ${getFilterDisplayText(filter)}. ${i18n.translate(
'data.filter.filterBar.moreFilterActionsMessage',
{
defaultMessage: 'Select for more filter actions.',
}
)}`;
export const FilterView: SFC<Props> = ({
filter,
iconOnClick,
onClick,
displayName,
...rest
}: Props) => {
let title = i18n.translate('data.filter.filterBar.moreFilterActionsMessage', {
defaultMessage: 'Filter: {displayText}. Select for more filter actions.',
values: { displayText: getFilterDisplayText(filter, displayName) },
});
if (isFilterPinned(filter)) {
title = `${i18n.translate('data.filter.filterBar.pinnedFilterPrefix', {
@ -67,38 +72,7 @@ export const FilterView: SFC<Props> = ({ filter, iconOnClick, onClick, ...rest }
})}
{...rest}
>
<span>{getFilterDisplayText(filter)}</span>
<span>{getFilterDisplayText(filter, displayName)}</span>
</EuiBadge>
);
};
export function getFilterDisplayText(filter: Filter) {
const prefix = filter.meta.negate
? ` ${i18n.translate('data.filter.filterBar.negatedFilterPrefix', {
defaultMessage: 'NOT ',
})}`
: '';
if (filter.meta.alias !== null) {
return `${prefix}${filter.meta.alias}`;
}
switch (filter.meta.type) {
case 'exists':
return `${prefix}${filter.meta.key} ${existsOperator.message}`;
case 'geo_bounding_box':
return `${prefix}${filter.meta.key}: ${filter.meta.value}`;
case 'geo_polygon':
return `${prefix}${filter.meta.key}: ${filter.meta.value}`;
case 'phrase':
return `${prefix}${filter.meta.key}: ${filter.meta.value}`;
case 'phrases':
return `${prefix}${filter.meta.key} ${isOneOfOperator.message} ${filter.meta.value}`;
case 'query_string':
return `${prefix}${filter.meta.value}`;
case 'range':
return `${prefix}${filter.meta.key}: ${filter.meta.value}`;
default:
return `${prefix}${JSON.stringify(filter.query)}`;
}
}

View file

@ -26,9 +26,7 @@ import { Filter, FilterStateStore } from '@kbn/es-query';
import { FilterStateManager } from './filter_state_manager';
import { FilterManager } from './filter_manager';
import { IndexPatterns } from '../../index_patterns';
import { getFilter } from './test_helpers/get_stub_filter';
import { StubIndexPatterns } from './test_helpers/stub_index_pattern';
import { StubState } from './test_helpers/stub_state';
import { getFiltersArray } from './test_helpers/get_filters_array';
@ -48,18 +46,13 @@ describe('filter_manager', () => {
let updateListener: sinon.SinonSpy<any[], any>;
let filterManager: FilterManager;
let indexPatterns: StubIndexPatterns;
let readyFilters: Filter[];
beforeEach(() => {
updateListener = sinon.stub();
appStateStub = new StubState();
globalStateStub = new StubState();
indexPatterns = new StubIndexPatterns();
filterManager = new FilterManager(
(indexPatterns as unknown) as IndexPatterns,
setupMock.uiSettings
);
filterManager = new FilterManager(setupMock.uiSettings);
readyFilters = getFiltersArray();
// FilterStateManager is tested indirectly.
@ -81,7 +74,7 @@ describe('filter_manager', () => {
fetchSubscription.unsubscribe();
}
await filterManager.removeAll();
filterManager.removeAll();
});
describe('observing', () => {
@ -135,7 +128,7 @@ describe('filter_manager', () => {
test('app state should be set', async () => {
updateSubscription = filterManager.getUpdates$().subscribe(updateListener);
const f1 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34);
await filterManager.setFilters([f1]);
filterManager.setFilters([f1]);
expect(filterManager.getAppFilters()).toHaveLength(1);
expect(filterManager.getGlobalFilters()).toHaveLength(0);
expect(filterManager.getFilters()).toHaveLength(1);
@ -149,7 +142,7 @@ describe('filter_manager', () => {
test('global state should be set', async () => {
updateSubscription = filterManager.getUpdates$().subscribe(updateListener);
const f1 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34);
await filterManager.setFilters([f1]);
filterManager.setFilters([f1]);
expect(filterManager.getAppFilters()).toHaveLength(0);
expect(filterManager.getGlobalFilters()).toHaveLength(1);
expect(filterManager.getFilters()).toHaveLength(1);
@ -164,7 +157,7 @@ describe('filter_manager', () => {
updateSubscription = filterManager.getUpdates$().subscribe(updateListener);
const f1 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34);
const f2 = getFilter(FilterStateStore.APP_STATE, false, false, 'gender', 'FEMALE');
await filterManager.setFilters([f1, f2]);
filterManager.setFilters([f1, f2]);
expect(filterManager.getAppFilters()).toHaveLength(1);
expect(filterManager.getGlobalFilters()).toHaveLength(1);
expect(filterManager.getFilters()).toHaveLength(2);
@ -183,8 +176,8 @@ describe('filter_manager', () => {
const f1 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34);
const f2 = getFilter(FilterStateStore.APP_STATE, false, false, 'gender', 'FEMALE');
await filterManager.setFilters([f1]);
await filterManager.setFilters([f2]);
filterManager.setFilters([f1]);
filterManager.setFilters([f2]);
expect(filterManager.getAppFilters()).toHaveLength(1);
expect(filterManager.getGlobalFilters()).toHaveLength(0);
@ -204,7 +197,7 @@ describe('filter_manager', () => {
const fetchStub = jest.fn();
const f1 = getFilter(FilterStateStore.GLOBAL_STATE, true, false, 'age', 34);
await filterManager.setFilters([f1]);
filterManager.setFilters([f1]);
filterManager.getUpdates$().subscribe({
next: updateStub,
@ -216,7 +209,7 @@ describe('filter_manager', () => {
const f2 = _.cloneDeep(f1);
f2.meta.negate = true;
await filterManager.setFilters([f2]);
filterManager.setFilters([f2]);
// this time, events should be emitted
expect(fetchStub).toBeCalledTimes(0);
@ -228,7 +221,7 @@ describe('filter_manager', () => {
test('app state should accept a single filter', async function() {
updateSubscription = filterManager.getUpdates$().subscribe(updateListener);
const f1 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34);
await filterManager.addFilters(f1);
filterManager.addFilters(f1);
expect(filterManager.getAppFilters()).toHaveLength(1);
expect(filterManager.getGlobalFilters()).toHaveLength(0);
expect(updateListener.callCount).toBe(1);
@ -238,8 +231,8 @@ describe('filter_manager', () => {
test('app state should accept array', async () => {
const f1 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34);
const f2 = getFilter(FilterStateStore.APP_STATE, false, false, 'gender', 'female');
await filterManager.addFilters([f1]);
await filterManager.addFilters([f2]);
filterManager.addFilters([f1]);
filterManager.addFilters([f2]);
expect(filterManager.getAppFilters()).toHaveLength(2);
expect(filterManager.getGlobalFilters()).toHaveLength(0);
expect(appStateStub.filters.length).toBe(2);
@ -248,7 +241,7 @@ describe('filter_manager', () => {
test('global state should accept a single filer', async () => {
updateSubscription = filterManager.getUpdates$().subscribe(updateListener);
const f1 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34);
await filterManager.addFilters(f1);
filterManager.addFilters(f1);
expect(filterManager.getAppFilters()).toHaveLength(0);
expect(filterManager.getGlobalFilters()).toHaveLength(1);
expect(updateListener.callCount).toBe(1);
@ -258,7 +251,7 @@ describe('filter_manager', () => {
test('global state should be accept array', async () => {
const f1 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34);
const f2 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'gender', 'female');
await filterManager.addFilters([f1, f2]);
filterManager.addFilters([f1, f2]);
expect(filterManager.getAppFilters()).toHaveLength(0);
expect(filterManager.getGlobalFilters()).toHaveLength(2);
expect(globalStateStub.filters.length).toBe(2);
@ -268,7 +261,7 @@ describe('filter_manager', () => {
updateSubscription = filterManager.getUpdates$().subscribe(updateListener);
const f1 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34);
const f2 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'gender', 'female');
await filterManager.addFilters([f1, f2]);
filterManager.addFilters([f1, f2]);
expect(filterManager.getAppFilters()).toHaveLength(0);
expect(filterManager.getGlobalFilters()).toHaveLength(2);
expect(updateListener.callCount).toBe(1);
@ -278,7 +271,7 @@ describe('filter_manager', () => {
updateSubscription = filterManager.getUpdates$().subscribe(updateListener);
const f1 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34);
const f2 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34);
await filterManager.addFilters([f1, f2]);
filterManager.addFilters([f1, f2]);
// FILTER SHOULD BE ADDED ONLY ONCE, TO GLOBAL
expect(filterManager.getAppFilters()).toHaveLength(0);
@ -290,7 +283,7 @@ describe('filter_manager', () => {
updateSubscription = filterManager.getUpdates$().subscribe(updateListener);
const f1 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 38);
const f2 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34);
await filterManager.addFilters([f1, f2]);
filterManager.addFilters([f1, f2]);
// FILTER SHOULD BE ADDED TWICE
expect(filterManager.getAppFilters()).toHaveLength(1);
@ -302,7 +295,7 @@ describe('filter_manager', () => {
const f1 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 38);
f1.$state = undefined;
await filterManager.addFilters([f1], true);
filterManager.addFilters([f1], true);
// FILTER SHOULD BE GLOBAL
const f1Output = filterManager.getFilters()[0];
@ -316,7 +309,7 @@ describe('filter_manager', () => {
const f1 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 38);
f1.$state = undefined;
await filterManager.addFilters([f1], false);
filterManager.addFilters([f1], false);
// FILTER SHOULD BE APP
const f1Output = filterManager.getFilters()[0];
@ -328,8 +321,8 @@ describe('filter_manager', () => {
test('should return app and global filters', async function() {
const filters = getFiltersArray();
await filterManager.addFilters(filters[0], false);
await filterManager.addFilters(filters[1], true);
filterManager.addFilters(filters[0], false);
filterManager.addFilters(filters[1], true);
// global filters should be listed first
let res = filterManager.getFilters();
@ -343,16 +336,16 @@ describe('filter_manager', () => {
expect(res[1].query).toEqual(filters[0].query);
// should return updated version of filters
await filterManager.addFilters(filters[2], false);
filterManager.addFilters(filters[2], false);
res = filterManager.getFilters();
expect(res).toHaveLength(3);
});
test('should skip appStateStub filters that match globalStateStub filters', async function() {
await filterManager.addFilters(readyFilters, true);
filterManager.addFilters(readyFilters, true);
const appFilter = _.cloneDeep(readyFilters[1]);
await filterManager.addFilters(appFilter, false);
filterManager.addFilters(appFilter, false);
// global filters should be listed first
const res = filterManager.getFilters();
@ -367,7 +360,7 @@ describe('filter_manager', () => {
const filter = _.cloneDeep(readyFilters[0]);
filter.meta.negate = false;
await filterManager.addFilters(filter);
filterManager.addFilters(filter);
expect(filterManager.getFilters()).toHaveLength(1);
expect(filterManager.getFilters()[0]).toEqual(filter);
@ -375,7 +368,7 @@ describe('filter_manager', () => {
const negatedFilter = _.cloneDeep(readyFilters[0]);
negatedFilter.meta.negate = true;
await filterManager.addFilters(negatedFilter);
filterManager.addFilters(negatedFilter);
// The negated filter should overwrite the positive one
expect(globalStateStub.filters.length).toBe(1);
expect(filterManager.getFilters()).toHaveLength(1);
@ -387,7 +380,7 @@ describe('filter_manager', () => {
const negatedFilter = _.cloneDeep(readyFilters[0]);
negatedFilter.meta.negate = true;
await filterManager.addFilters(negatedFilter);
filterManager.addFilters(negatedFilter);
// The negated filter should overwrite the positive one
expect(globalStateStub.filters.length).toBe(1);
@ -397,7 +390,7 @@ describe('filter_manager', () => {
const filter = _.cloneDeep(readyFilters[0]);
filter.meta.negate = false;
await filterManager.addFilters(filter);
filterManager.addFilters(filter);
expect(globalStateStub.filters.length).toBe(1);
expect(globalStateStub.filters[0]).toEqual(filter);
});
@ -414,7 +407,7 @@ describe('filter_manager', () => {
next: fetchStub,
});
await filterManager.addFilters(readyFilters);
filterManager.addFilters(readyFilters);
// updates should trigger state saves
expect(appStateStub.save.callCount).toBe(1);
@ -429,26 +422,26 @@ describe('filter_manager', () => {
describe('filter reconciliation', function() {
test('should de-dupe appStateStub filters being added', async function() {
const newFilter = _.cloneDeep(readyFilters[1]);
await filterManager.addFilters(readyFilters, false);
filterManager.addFilters(readyFilters, false);
expect(appStateStub.filters.length).toBe(3);
await filterManager.addFilters(newFilter, false);
filterManager.addFilters(newFilter, false);
expect(appStateStub.filters.length).toBe(3);
});
test('should de-dupe globalStateStub filters being added', async function() {
const newFilter = _.cloneDeep(readyFilters[1]);
await filterManager.addFilters(readyFilters, true);
filterManager.addFilters(readyFilters, true);
expect(globalStateStub.filters.length).toBe(3);
await filterManager.addFilters(newFilter, true);
filterManager.addFilters(newFilter, true);
expect(globalStateStub.filters.length).toBe(3);
});
test('should de-dupe globalStateStub filters being set', async () => {
const f1 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34);
const f2 = _.cloneDeep(f1);
await filterManager.setFilters([f1, f2]);
filterManager.setFilters([f1, f2]);
expect(filterManager.getAppFilters()).toHaveLength(0);
expect(filterManager.getGlobalFilters()).toHaveLength(1);
expect(filterManager.getFilters()).toHaveLength(1);
@ -457,7 +450,7 @@ describe('filter_manager', () => {
test('should de-dupe appStateStub filters being set', async () => {
const f1 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34);
const f2 = _.cloneDeep(f1);
await filterManager.setFilters([f1, f2]);
filterManager.setFilters([f1, f2]);
expect(filterManager.getAppFilters()).toHaveLength(1);
expect(filterManager.getGlobalFilters()).toHaveLength(0);
expect(filterManager.getFilters()).toHaveLength(1);
@ -465,14 +458,14 @@ describe('filter_manager', () => {
test('should mutate global filters on appStateStub filter changes', async function() {
const idx = 1;
await filterManager.addFilters(readyFilters, true);
filterManager.addFilters(readyFilters, true);
const appFilter = _.cloneDeep(readyFilters[idx]);
appFilter.meta.negate = true;
appFilter.$state = {
store: FilterStateStore.APP_STATE,
};
await filterManager.addFilters(appFilter);
filterManager.addFilters(appFilter);
const res = filterManager.getFilters();
expect(res).toHaveLength(3);
_.each(res, function(filter, i) {
@ -483,13 +476,13 @@ describe('filter_manager', () => {
});
test('should merge conflicting appStateStub filters', async function() {
await filterManager.addFilters(readyFilters, true);
filterManager.addFilters(readyFilters, true);
const appFilter = _.cloneDeep(readyFilters[1]);
appFilter.meta.negate = true;
appFilter.$state = {
store: FilterStateStore.APP_STATE,
};
await filterManager.addFilters(appFilter, false);
filterManager.addFilters(appFilter, false);
// global filters should be listed first
const res = filterManager.getFilters();
@ -508,8 +501,8 @@ describe('filter_manager', () => {
f.meta.disabled = true;
return f;
});
await filterManager.addFilters(disabledFilters, true);
await filterManager.addFilters(readyFilters, true);
filterManager.addFilters(disabledFilters, true);
filterManager.addFilters(readyFilters, true);
const res = filterManager.getFilters();
expect(res).toHaveLength(3);
@ -527,8 +520,8 @@ describe('filter_manager', () => {
f.meta.disabled = true;
return f;
});
await filterManager.addFilters(disabledFilters, true);
await filterManager.addFilters(readyFilters, false);
filterManager.addFilters(disabledFilters, true);
filterManager.addFilters(readyFilters, false);
const res = filterManager.getFilters();
expect(res).toHaveLength(3);
@ -543,7 +536,7 @@ describe('filter_manager', () => {
describe('remove filters', () => {
test('remove on empty should do nothing and not fire events', async () => {
updateSubscription = filterManager.getUpdates$().subscribe(updateListener);
await filterManager.removeAll();
filterManager.removeAll();
expect(updateListener.called).toBeFalsy();
expect(filterManager.getFilters()).toHaveLength(0);
});
@ -551,10 +544,10 @@ describe('filter_manager', () => {
test('remove on full should clean and fire events', async () => {
const f1 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34);
const f2 = getFilter(FilterStateStore.APP_STATE, false, false, 'gender', 'FEMALE');
await filterManager.setFilters([f1, f2]);
filterManager.setFilters([f1, f2]);
updateSubscription = filterManager.getUpdates$().subscribe(updateListener);
await filterManager.removeAll();
filterManager.removeAll();
expect(updateListener.called).toBeTruthy();
expect(filterManager.getFilters()).toHaveLength(0);
});
@ -563,7 +556,7 @@ describe('filter_manager', () => {
const f1 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34);
const f2 = getFilter(FilterStateStore.APP_STATE, false, false, 'gender', 'FEMALE');
const f3 = getFilter(FilterStateStore.APP_STATE, false, false, 'country', 'US');
await filterManager.setFilters([f1, f2]);
filterManager.setFilters([f1, f2]);
expect(filterManager.getFilters()).toHaveLength(2);
updateSubscription = filterManager.getUpdates$().subscribe(updateListener);
@ -576,7 +569,7 @@ describe('filter_manager', () => {
const f1 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34);
const f2 = getFilter(FilterStateStore.APP_STATE, false, false, 'gender', 'FEMALE');
const f3 = getFilter(FilterStateStore.APP_STATE, false, false, 'country', 'US');
await filterManager.setFilters([f1, f2, f3]);
filterManager.setFilters([f1, f2, f3]);
expect(filterManager.getFilters()).toHaveLength(3);
updateSubscription = filterManager.getUpdates$().subscribe(updateListener);
@ -586,14 +579,14 @@ describe('filter_manager', () => {
});
test('should remove the filter from appStateStub', async function() {
await filterManager.addFilters(readyFilters, false);
filterManager.addFilters(readyFilters, false);
expect(appStateStub.filters).toHaveLength(3);
filterManager.removeFilter(readyFilters[0]);
expect(appStateStub.filters).toHaveLength(2);
});
test('should remove the filter from globalStateStub', async function() {
await filterManager.addFilters(readyFilters, true);
filterManager.addFilters(readyFilters, true);
expect(globalStateStub.filters).toHaveLength(3);
filterManager.removeFilter(readyFilters[0]);
expect(globalStateStub.filters).toHaveLength(2);
@ -603,7 +596,7 @@ describe('filter_manager', () => {
const updateStub = jest.fn();
const fetchStub = jest.fn();
await filterManager.addFilters(readyFilters, false);
filterManager.addFilters(readyFilters, false);
filterManager.getUpdates$().subscribe({
next: updateStub,
@ -621,8 +614,8 @@ describe('filter_manager', () => {
});
test('should remove matching filters', async function() {
await filterManager.addFilters([readyFilters[0], readyFilters[1]], true);
await filterManager.addFilters([readyFilters[2]], false);
filterManager.addFilters([readyFilters[0], readyFilters[1]], true);
filterManager.addFilters([readyFilters[2]], false);
filterManager.removeFilter(readyFilters[0]);
@ -631,8 +624,8 @@ describe('filter_manager', () => {
});
test('should remove matching filters by comparison', async function() {
await filterManager.addFilters([readyFilters[0], readyFilters[1]], true);
await filterManager.addFilters([readyFilters[2]], false);
filterManager.addFilters([readyFilters[0], readyFilters[1]], true);
filterManager.addFilters([readyFilters[2]], false);
filterManager.removeFilter(_.cloneDeep(readyFilters[0]));
@ -645,8 +638,8 @@ describe('filter_manager', () => {
});
test('should do nothing with a non-matching filter', async function() {
await filterManager.addFilters([readyFilters[0], readyFilters[1]], true);
await filterManager.addFilters([readyFilters[2]], false);
filterManager.addFilters([readyFilters[0], readyFilters[1]], true);
filterManager.addFilters([readyFilters[2]], false);
const missedFilter = _.cloneDeep(readyFilters[0]);
missedFilter.meta.negate = !readyFilters[0].meta.negate;
@ -657,12 +650,12 @@ describe('filter_manager', () => {
});
test('should remove all the filters from both states', async function() {
await filterManager.addFilters([readyFilters[0], readyFilters[1]], true);
await filterManager.addFilters([readyFilters[2]], false);
filterManager.addFilters([readyFilters[0], readyFilters[1]], true);
filterManager.addFilters([readyFilters[2]], false);
expect(globalStateStub.filters).toHaveLength(2);
expect(appStateStub.filters).toHaveLength(1);
await filterManager.removeAll();
filterManager.removeAll();
expect(globalStateStub.filters).toHaveLength(0);
expect(appStateStub.filters).toHaveLength(0);
});
@ -670,7 +663,7 @@ describe('filter_manager', () => {
describe('invert', () => {
test('should fire the update and fetch events', async function() {
await filterManager.addFilters(readyFilters);
filterManager.addFilters(readyFilters);
expect(filterManager.getFilters()).toHaveLength(3);
const updateStub = jest.fn();
@ -684,7 +677,7 @@ describe('filter_manager', () => {
});
readyFilters[1].meta.negate = !readyFilters[1].meta.negate;
await filterManager.addFilters(readyFilters[1]);
filterManager.addFilters(readyFilters[1]);
expect(filterManager.getFilters()).toHaveLength(3);
expect(fetchStub).toBeCalledTimes(1);
expect(updateStub).toBeCalledTimes(1);

View file

@ -29,17 +29,14 @@ import { mapAndFlattenFilters } from './lib/map_and_flatten_filters';
import { uniqFilters } from './lib/uniq_filters';
import { onlyDisabledFiltersChanged } from './lib/only_disabled';
import { PartitionedFilters } from './partitioned_filters';
import { IndexPatterns } from '../../index_patterns';
export class FilterManager {
private indexPatterns: IndexPatterns;
private filters: Filter[] = [];
private updated$: Subject<void> = new Subject();
private fetch$: Subject<void> = new Subject();
private uiSettings: UiSettingsClientContract;
constructor(indexPatterns: IndexPatterns, uiSettings: UiSettingsClientContract) {
this.indexPatterns = indexPatterns;
constructor(uiSettings: UiSettingsClientContract) {
this.uiSettings = uiSettings;
}
@ -127,7 +124,7 @@ export class FilterManager {
/* Setters */
public async addFilters(filters: Filter[] | Filter, pinFilterStatus?: boolean) {
public addFilters(filters: Filter[] | Filter, pinFilterStatus?: boolean) {
if (!Array.isArray(filters)) {
filters = [filters];
}
@ -145,7 +142,7 @@ export class FilterManager {
const store = pinFilterStatus ? FilterStateStore.GLOBAL_STATE : FilterStateStore.APP_STATE;
FilterManager.setFiltersStore(filters, store);
const mappedFilters = await mapAndFlattenFilters(this.indexPatterns, filters);
const mappedFilters = mapAndFlattenFilters(filters);
// This is where we add new filters to the correct place (app \ global)
const newPartitionedFilters = FilterManager.partitionFilters(mappedFilters);
@ -157,8 +154,8 @@ export class FilterManager {
this.handleStateUpdate(newFilters);
}
public async setFilters(newFilters: Filter[]) {
const mappedFilters = await mapAndFlattenFilters(this.indexPatterns, newFilters);
public setFilters(newFilters: Filter[]) {
const mappedFilters = mapAndFlattenFilters(newFilters);
const newPartitionedFilters = FilterManager.partitionFilters(mappedFilters);
const mergedFilters = this.mergeIncomingFilters(newPartitionedFilters);
this.handleStateUpdate(mergedFilters);
@ -176,8 +173,8 @@ export class FilterManager {
}
}
public async removeAll() {
await this.setFilters([]);
public removeAll() {
this.setFilters([]);
}
public static setFiltersStore(filters: Filter[], store: FilterStateStore) {

View file

@ -22,11 +22,9 @@ import sinon from 'sinon';
import { FilterStateStore } from '@kbn/es-query';
import { FilterStateManager } from './filter_state_manager';
import { IndexPatterns } from '../../index_patterns';
import { StubState } from './test_helpers/stub_state';
import { getFilter } from './test_helpers/get_stub_filter';
import { FilterManager } from './filter_manager';
import { StubIndexPatterns } from './test_helpers/stub_index_pattern';
import { coreMock } from '../../../../../../core/public/mocks';
const setupMock = coreMock.createSetup();
@ -44,11 +42,7 @@ describe('filter_state_manager', () => {
beforeEach(() => {
appStateStub = new StubState();
globalStateStub = new StubState();
const indexPatterns = new StubIndexPatterns();
filterManager = new FilterManager(
(indexPatterns as unknown) as IndexPatterns,
setupMock.uiSettings
);
filterManager = new FilterManager(setupMock.uiSettings);
});
describe('app_state_undefined', () => {
@ -81,7 +75,7 @@ describe('filter_state_manager', () => {
const f1 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34);
const f2 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34);
await filterManager.setFilters([f1, f2]);
filterManager.setFilters([f1, f2]);
sinon.assert.notCalled(appStateStub.save);
sinon.assert.calledOnce(globalStateStub.save);
@ -89,10 +83,11 @@ describe('filter_state_manager', () => {
});
describe('app_state_defined', () => {
let filterStateManager: FilterStateManager;
beforeEach(() => {
// FilterStateManager is tested indirectly.
// Therefore, we don't need it's instance.
new FilterStateManager(
filterStateManager = new FilterStateManager(
globalStateStub,
() => {
return appStateStub;
@ -101,6 +96,10 @@ describe('filter_state_manager', () => {
);
});
afterEach(() => {
filterStateManager.destroy();
});
test('should update filter manager global filters', done => {
const f1 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34);
globalStateStub.filters.push(f1);
@ -123,27 +122,27 @@ describe('filter_state_manager', () => {
}, 100);
});
test('should update URL when filter manager filters are set', async () => {
test('should update URL when filter manager filters are set', () => {
appStateStub.save = sinon.stub();
globalStateStub.save = sinon.stub();
const f1 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34);
const f2 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34);
await filterManager.setFilters([f1, f2]);
filterManager.setFilters([f1, f2]);
sinon.assert.calledOnce(appStateStub.save);
sinon.assert.calledOnce(globalStateStub.save);
});
test('should update URL when filter manager filters are added', async () => {
test('should update URL when filter manager filters are added', () => {
appStateStub.save = sinon.stub();
globalStateStub.save = sinon.stub();
const f1 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34);
const f2 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34);
await filterManager.addFilters([f1, f2]);
filterManager.addFilters([f1, f2]);
sinon.assert.calledOnce(appStateStub.save);
sinon.assert.calledOnce(globalStateStub.save);
@ -156,13 +155,13 @@ describe('filter_state_manager', () => {
** would cause filter state manager detects those changes
** And triggers *another* filter manager update.
*/
test('should NOT re-trigger filter manager', async done => {
test('should NOT re-trigger filter manager', done => {
const f1 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34);
filterManager.setFilters([f1]);
const setFiltersSpy = sinon.spy(filterManager, 'setFilters');
f1.meta.negate = true;
await filterManager.setFilters([f1]);
filterManager.setFilters([f1]);
setTimeout(() => {
expect(setFiltersSpy.callCount).toEqual(1);

View file

@ -31,27 +31,27 @@ describe('filter manager utilities', () => {
});
describe('generateMappingChain()', () => {
test('should create a chaining function which calls the next function if the promise is rejected', async () => {
test('should create a chaining function which calls the next function if the error is thrown', async () => {
const filter: Filter = buildEmptyFilter(true);
mapping.rejects(filter);
next.resolves('good');
mapping.throws(filter);
next.returns('good');
const chain = generateMappingChain(mapping, next);
const result = await chain(filter);
const result = 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 () => {
test('should create a chaining function which DOES NOT call the next function if the result is returned', async () => {
const filter: Filter = buildEmptyFilter(true);
mapping.resolves('good');
next.resolves('bad');
mapping.returns('good');
next.returns('bad');
const chain = generateMappingChain(mapping, next);
const result = await chain(filter);
const result = chain(filter);
expect(result).toBe('good');
});
@ -59,10 +59,10 @@ describe('filter manager utilities', () => {
test('should resolve result for the mapping function', async () => {
const filter: Filter = buildEmptyFilter(true);
mapping.resolves({ key: 'test', value: 'example' });
mapping.returns({ key: 'test', value: 'example' });
const chain = generateMappingChain(mapping, next);
const result = await chain(filter);
const result = chain(filter);
sinon.assert.notCalled(next);
expect(result).toEqual({ key: 'test', value: 'example' });
@ -72,10 +72,10 @@ describe('filter manager utilities', () => {
// @ts-ignore
const filter: Filter = { test: 'example' };
mapping.resolves({ key: 'test', value: 'example' });
mapping.returns({ key: 'test', value: 'example' });
const chain = generateMappingChain(mapping, next);
const result = await chain(filter);
const result = chain(filter);
sinon.assert.calledOnce(mapping);
expect(mapping.args[0][0]).toEqual({ test: 'example' });
@ -86,29 +86,31 @@ describe('filter manager utilities', () => {
test('should resolve result for the next function', async () => {
const filter: Filter = buildEmptyFilter(true);
mapping.rejects(filter);
next.resolves({ key: 'test', value: 'example' });
mapping.throws(filter);
next.returns({ key: 'test', value: 'example' });
const chain = generateMappingChain(mapping, next);
const result = await chain(filter);
const result = 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 => {
test('should throw an error if no functions match', async done => {
const filter: Filter = buildEmptyFilter(true);
mapping.rejects(filter);
mapping.throws(filter);
const chain = generateMappingChain(mapping);
chain(filter).catch(err => {
try {
chain(filter);
} catch (err) {
expect(err).toBeInstanceOf(Error);
expect(err.message).toBe('No mappings have been found for filter.');
done();
});
}
});
});
});

View file

@ -23,12 +23,14 @@ const noop = () => {
};
export const generateMappingChain = (fn: Function, next: Function = noop) => {
return async (filter: Filter) => {
return await fn(filter).catch((result: any) => {
return (filter: Filter) => {
try {
return fn(filter);
} catch (result) {
if (result === filter) {
return next(filter);
}
throw result;
});
}
};
};

View file

@ -19,16 +19,16 @@
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;
function getDisplayName(filter: Filter) {
return typeof filter.meta.value === 'function' ? filter.meta.value() : filter.meta.value;
}
beforeEach(() => {
mockIndexPatterns = new StubIndexPatterns();
filters = [
null,
[
@ -44,11 +44,8 @@ describe('filter manager utilities', () => {
];
});
test('should map and flatten the filters', async () => {
const results = await mapAndFlattenFilters(
mockIndexPatterns as IndexPatterns,
filters as Filter[]
);
test('should map and flatten the filters', () => {
const results = mapAndFlattenFilters(filters as Filter[]);
expect(results).toHaveLength(5);
expect(results[0]).toHaveProperty('meta');
@ -63,9 +60,11 @@ describe('filter manager utilities', () => {
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[3].meta).toHaveProperty('value');
expect(getDisplayName(results[3])).toBe('1024 to 2048');
expect(results[4].meta).toHaveProperty('key', '_type');
expect(results[4].meta).toHaveProperty('value', 'apache');
expect(results[4].meta).toHaveProperty('value');
expect(getDisplayName(results[4])).toBe('apache');
});
});
});

View file

@ -20,10 +20,7 @@
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);
export const mapAndFlattenFilters = (filters: Filter[]) => {
return compact(flatten(filters)).map((item: Filter) => mapFilter(item));
};

View file

@ -23,7 +23,7 @@ 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);
const result = mapDefault(filter);
expect(result).toHaveProperty('key', 'query');
expect(result).toHaveProperty('value', '{"match_all":{}}');
@ -33,7 +33,7 @@ describe('filter manager utilities', () => {
const filter = buildEmptyFilter(true) as CustomFilter;
try {
await mapDefault(filter);
mapDefault(filter);
} catch (e) {
expect(e).toBe(filter);
}

View file

@ -20,7 +20,7 @@
import { Filter, FILTERS } from '@kbn/es-query';
import { find, keys, get } from 'lodash';
export const mapDefault = async (filter: Filter) => {
export const mapDefault = (filter: Filter) => {
const metaProperty = /(^\$|meta)/;
const key = find(keys(filter), item => !item.match(metaProperty));

View file

@ -24,7 +24,7 @@ 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);
const result = mapExists(filter);
expect(result).toHaveProperty('key', '_type');
expect(result).toHaveProperty('value', 'exists');
@ -34,7 +34,7 @@ describe('filter manager utilities', () => {
const filter = buildEmptyFilter(true) as ExistsFilter;
try {
await mapQueryString(filter);
mapQueryString(filter);
} catch (e) {
expect(e).toBe(filter);
done();

View file

@ -20,7 +20,7 @@
import { Filter, isExistsFilter, FILTERS } from '@kbn/es-query';
import { get } from 'lodash';
export const mapExists = async (filter: Filter) => {
export const mapExists = (filter: Filter) => {
if (isExistsFilter(filter)) {
return {
type: FILTERS.EXISTS,

View file

@ -19,17 +19,11 @@
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;
});
function getDisplayName(filter: Filter) {
return typeof filter.meta.value === 'function' ? filter.meta.value() : filter.meta.value;
}
describe('mapFilter()', () => {
test('should map query filters', async () => {
@ -37,44 +31,48 @@ describe('filter manager utilities', () => {
meta: { index: 'logstash-*' },
query: { match: { _type: { query: 'apache' } } },
};
const after = await mapFilter(indexPatterns, before as Filter);
const after = mapFilter(before as Filter);
expect(after).toHaveProperty('meta');
expect(after.meta).toHaveProperty('key', '_type');
expect(after.meta).toHaveProperty('value', 'apache');
expect(after.meta).toHaveProperty('value');
expect(getDisplayName(after)).toBe('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);
const after = mapFilter(before as Filter);
expect(after).toHaveProperty('meta');
expect(after.meta).toHaveProperty('key', '@timestamp');
expect(after.meta).toHaveProperty('value', 'exists');
expect(after.meta).toHaveProperty('value');
expect(getDisplayName(after)).toBe('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);
const after = mapFilter(before as Filter);
expect(after).toHaveProperty('meta');
expect(after.meta).toHaveProperty('key', '@timestamp');
expect(after.meta).toHaveProperty('value', 'missing');
expect(after.meta).toHaveProperty('value');
expect(getDisplayName(after)).toBe('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);
const after = mapFilter(before as Filter);
expect(after).toHaveProperty('meta');
expect(after.meta).toHaveProperty('key', 'query');
expect(after.meta).toHaveProperty('value', '{"match_all":{}}');
expect(after.meta).toHaveProperty('value');
expect(getDisplayName(after)).toBe('{"match_all":{}}');
expect(after.meta).toHaveProperty('disabled', false);
expect(after.meta).toHaveProperty('negate', false);
});
@ -83,7 +81,7 @@ describe('filter manager utilities', () => {
const before: any = { meta: { index: 'logstash-*' } };
try {
await mapFilter(indexPatterns, before as Filter);
mapFilter(before as Filter);
} catch (e) {
expect(e).toBeInstanceOf(Error);
expect(e.message).toBe('No mappings have been found for filter.');

View file

@ -19,7 +19,6 @@
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';
@ -33,7 +32,7 @@ import { mapGeoPolygon } from './map_geo_polygon';
import { mapDefault } from './map_default';
import { generateMappingChain } from './generate_mapping_chain';
export async function mapFilter(indexPatterns: IndexPatterns, filter: Filter) {
export function mapFilter(filter: Filter) {
/** Mappers **/
// Each mapper is a simple promise function that test if the mapper can
@ -52,14 +51,14 @@ export async function mapFilter(indexPatterns: IndexPatterns, filter: Filter) {
// and add it here. ProTip: These are executed in order listed
const mappers = [
mapMatchAll,
mapRange(indexPatterns),
mapPhrase(indexPatterns),
mapRange,
mapPhrase,
mapPhrases,
mapExists,
mapMissing,
mapQueryString,
mapGeoBoundingBox(indexPatterns),
mapGeoPolygon(indexPatterns),
mapGeoBoundingBox,
mapGeoPolygon,
mapDefault,
];
@ -74,13 +73,15 @@ export async function mapFilter(indexPatterns: IndexPatterns, filter: Filter) {
(memo, map) => generateMappingChain(map, memo),
noop
);
const mapped = await mapFn(filter);
const mapped = mapFn(filter);
// Map the filter into an object with the key and value exposed so it's
// easier to work with in the template
filter.meta = filter.meta || {};
filter.meta.type = mapped.type;
filter.meta.key = mapped.key;
// Display value or formatter function.
filter.meta.value = mapped.value;
filter.meta.params = mapped.params;
filter.meta.disabled = Boolean(filter.meta.disabled);

View file

@ -18,19 +18,10 @@
*/
import { mapGeoBoundingBox } from './map_geo_bounding_box';
import { StubIndexPatterns } from '../test_helpers/stub_index_pattern';
import { IndexPatterns } from '../../../index_patterns';
import { Filter, GeoBoundingBoxFilter } from '@kbn/es-query';
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: {
@ -43,16 +34,20 @@ describe('filter manager utilities', () => {
bottom_right: { lat: 15, lon: 20 },
},
},
};
} as GeoBoundingBoxFilter;
const result = await mapGeoBoundingBoxFn(filter);
const result = mapGeoBoundingBox(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'
);
if (result.value) {
const displayName = result.value();
// remove html entities and non-alphanumerics to get the gist of the value
expect(displayName.replace(/&[a-z]+?;/g, '').replace(/[^a-z0-9]/g, '')).toBe(
'lat5lon10tolat15lon20'
);
}
});
test('should return the key and value even when using ignore_unmapped', async () => {
@ -68,25 +63,29 @@ describe('filter manager utilities', () => {
bottom_right: { lat: 15, lon: 20 },
},
},
};
const result = await mapGeoBoundingBoxFn(filter);
} as GeoBoundingBoxFilter;
const result = mapGeoBoundingBox(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'
);
if (result.value) {
const displayName = result.value();
// remove html entities and non-alphanumerics to get the gist of the value
expect(displayName.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' } },
};
} as Filter;
try {
await mapGeoBoundingBoxFn(filter);
mapGeoBoundingBox(filter);
} catch (e) {
expect(e).toBe(filter);
done();

View file

@ -16,58 +16,46 @@
* 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';
import {
GeoBoundingBoxFilter,
Filter,
FILTERS,
isGeoBoundingBoxFilter,
FilterValueFormatter,
} from '@kbn/es-query';
const getFormattedValue = (params: any, key: string, indexPattern?: IndexPattern) => {
const formatter: any =
indexPattern && key && get(indexPattern, ['fields', 'byName', key, 'format']);
const getFormattedValueFn = (params: any) => {
return (formatter?: FilterValueFormatter) => {
const corners = 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),
};
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),
};
return corners.topLeft + ' to ' + corners.bottomRight;
};
};
const getParams = (filter: GeoBoundingBoxFilter, indexPattern?: IndexPattern) => {
const getParams = (filter: GeoBoundingBoxFilter) => {
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,
value: getFormattedValueFn(params),
};
};
export const mapGeoBoundingBox = (indexPatterns: IndexPatterns) => {
return async (filter: Filter) => {
if (!isGeoBoundingBoxFilter(filter)) {
throw filter;
}
export const mapGeoBoundingBox = (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;
}
};
return getParams(filter);
};

View file

@ -17,19 +17,10 @@
* under the License.
*/
import { mapGeoPolygon } from './map_geo_polygon';
import { StubIndexPatterns } from '../test_helpers/stub_index_pattern';
import { IndexPatterns } from '../../../index_patterns';
import { GeoPolygonFilter, Filter } from '@kbn/es-query';
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: {
@ -40,17 +31,20 @@ describe('filter manager utilities', () => {
points: [{ lat: 5, lon: 10 }, { lat: 15, lon: 20 }],
},
},
};
} as GeoPolygonFilter;
const result = await mapGeoPolygonFn(filter);
const result = mapGeoPolygon(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'
);
if (result.value) {
const displayName = result.value();
// remove html entities and non-alphanumerics to get the gist of the value
expect(displayName.replace(/&[a-z]+?;/g, '').replace(/[^a-z0-9]/g, '')).toBe(
'lat5lon10lat15lon20'
);
}
});
test('should return the key and value even when using ignore_unmapped', async () => {
@ -64,26 +58,29 @@ describe('filter manager utilities', () => {
points: [{ lat: 5, lon: 10 }, { lat: 15, lon: 20 }],
},
},
};
const result = await mapGeoPolygonFn(filter);
} as GeoPolygonFilter;
const result = mapGeoPolygon(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'
);
if (result.value) {
const displayName = result.value();
// remove html entities and non-alphanumerics to get the gist of the value
expect(displayName.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' } },
};
} as Filter;
try {
await mapGeoPolygonFn(filter);
mapGeoPolygon(filter);
} catch (e) {
expect(e).toBe(filter);

View file

@ -16,21 +16,25 @@
* specific language governing permissions and limitations
* under the License.
*/
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';
import {
GeoPolygonFilter,
Filter,
FILTERS,
isGeoPolygonFilter,
FilterValueFormatter,
} from '@kbn/es-query';
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);
const getFormattedValueFn = (points: string[]) => {
return (formatter?: FilterValueFormatter) => {
return points
.map((point: string) => (formatter ? formatter.convert(point) : JSON.stringify(point)))
.join(POINTS_SEPARATOR);
};
};
function getParams(filter: GeoPolygonFilter, indexPattern?: IndexPattern) {
function getParams(filter: GeoPolygonFilter) {
const key = Object.keys(filter.geo_polygon).filter(k => k !== 'ignore_unmapped')[0];
const params = filter.geo_polygon[key];
@ -38,31 +42,13 @@ function getParams(filter: GeoPolygonFilter, indexPattern?: IndexPattern) {
key,
params,
type: FILTERS.GEO_POLYGON,
value: (params.points || [])
.map((point: string) => getFormattedValue(point, key, indexPattern))
.join(POINTS_SEPARATOR),
value: getFormattedValueFn(params.points || []),
};
}
export function mapGeoPolygon(indexPatterns: IndexPatterns) {
return async function(filter: Filter) {
if (!isGeoPolygonFilter(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;
}
};
export function mapGeoPolygon(filter: Filter) {
if (!isGeoPolygonFilter(filter)) {
throw filter;
}
return getParams(filter);
}

View file

@ -41,7 +41,7 @@ describe('filter_manager/lib', () => {
delete filter.match_all;
try {
await mapMatchAll(filter);
mapMatchAll(filter);
} catch (e) {
expect(e).toBe(filter);
done();
@ -51,13 +51,13 @@ describe('filter_manager/lib', () => {
describe('when given a match_all filter', () => {
test('key is set to meta field', async () => {
const result = await mapMatchAll(filter);
const result = mapMatchAll(filter);
expect(result).toHaveProperty('key', filter.meta.field);
});
test('value is set to meta formattedValue', async () => {
const result = await mapMatchAll(filter);
const result = mapMatchAll(filter);
expect(result).toHaveProperty('value', filter.meta.formattedValue);
});

View file

@ -18,7 +18,7 @@
*/
import { Filter, FILTERS, isMatchAllFilter } from '@kbn/es-query';
export const mapMatchAll = async (filter: Filter) => {
export const mapMatchAll = (filter: Filter) => {
if (isMatchAllFilter(filter)) {
return {
type: FILTERS.MATCH_ALL,

View file

@ -26,7 +26,7 @@ describe('filter manager utilities', () => {
missing: { field: '_type' },
...buildEmptyFilter(true),
};
const result = await mapMissing(filter);
const result = mapMissing(filter);
expect(result).toHaveProperty('key', '_type');
expect(result).toHaveProperty('value', 'missing');
@ -36,7 +36,7 @@ describe('filter manager utilities', () => {
const filter = buildEmptyFilter(true) as ExistsFilter;
try {
await mapMissing(filter);
mapMissing(filter);
} catch (e) {
expect(e).toBe(filter);
done();

View file

@ -18,7 +18,7 @@
*/
import { Filter, FILTERS, isMissingFilter } from '@kbn/es-query';
export const mapMissing = async (filter: Filter) => {
export const mapMissing = (filter: Filter) => {
if (isMissingFilter(filter)) {
return {
type: FILTERS.MISSING,

View file

@ -17,38 +17,33 @@
* under the License.
*/
import { mapPhrase } from './map_phrase';
import { StubIndexPatterns } from '../test_helpers/stub_index_pattern';
import { IndexPatterns } from '../../../index_patterns';
import { PhraseFilter, Filter } from '@kbn/es-query';
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);
} as PhraseFilter;
const result = mapPhrase(filter);
expect(result).toHaveProperty('value');
expect(result).toHaveProperty('key', '_type');
expect(result).toHaveProperty('value', 'apache');
if (result.value) {
const displayName = result.value();
expect(displayName).toBe('apache');
}
});
test('should return undefined for none matching', async done => {
const filter = {
meta: { index: 'logstash-*' },
query: { query_string: { query: 'foo:bar' } },
};
} as Filter;
try {
await mapPhraseFn(filter);
mapPhrase(filter);
} catch (e) {
expect(e).toBe(filter);
done();

View file

@ -24,21 +24,19 @@ import {
FILTERS,
isPhraseFilter,
isScriptedPhraseFilter,
FilterValueFormatter,
} 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 getFormattedValueFn = (value: any) => {
return (formatter?: FilterValueFormatter) => {
return formatter ? formatter.convert(value) : value;
};
};
const getParams = (filter: PhraseFilter, indexPattern?: IndexPattern) => {
const getParams = (filter: PhraseFilter) => {
const scriptedPhraseValue = getScriptedPhraseValue(filter);
const isScriptedFilter = Boolean(scriptedPhraseValue);
const key = isScriptedFilter ? filter.meta.field || '' : Object.keys(filter.query.match)[0];
@ -49,32 +47,17 @@ const getParams = (filter: PhraseFilter, indexPattern?: IndexPattern) => {
key,
params,
type: FILTERS.PHRASE,
value: getFormattedValue(query, key, indexPattern),
value: getFormattedValueFn(query),
};
};
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;
}
export const mapPhrase = (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;
}
};
return getParams(filter);
};

View file

@ -19,7 +19,7 @@
import { Filter, isPhrasesFilter } from '@kbn/es-query';
export const mapPhrases = async (filter: Filter) => {
export const mapPhrases = (filter: Filter) => {
if (!isPhrasesFilter(filter)) {
throw filter;
}

View file

@ -27,7 +27,7 @@ describe('filter manager utilities', () => {
{ query_string: { query: 'foo:bar' } },
'index'
);
const result = await mapQueryString(filter);
const result = mapQueryString(filter);
expect(result).toHaveProperty('key', 'query');
expect(result).toHaveProperty('value', 'foo:bar');
@ -37,7 +37,7 @@ describe('filter manager utilities', () => {
const filter = buildEmptyFilter(true) as QueryStringFilter;
try {
await mapQueryString(filter);
mapQueryString(filter);
} catch (e) {
expect(e).toBe(filter);
done();

View file

@ -18,7 +18,7 @@
*/
import { Filter, FILTERS, isQueryStringFilter } from '@kbn/es-query';
export const mapQueryString = async (filter: Filter) => {
export const mapQueryString = (filter: Filter) => {
if (isQueryStringFilter(filter)) {
return {
type: FILTERS.QUERY_STRING,

View file

@ -18,43 +18,48 @@
*/
import { mapRange } from './map_range';
import { StubIndexPatterns } from '../test_helpers/stub_index_pattern';
import { IndexPatterns } from '../../../index_patterns';
import { RangeFilter, Filter, FilterMeta } from '@kbn/es-query';
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);
const filter = {
meta: { index: 'logstash-*' } as FilterMeta,
range: { bytes: { lt: 2048, gt: 1024 } },
} as RangeFilter;
const result = mapRange(filter);
expect(result).toHaveProperty('key', 'bytes');
expect(result).toHaveProperty('value', '1024 to 2048');
expect(result).toHaveProperty('value');
if (result.value) {
const displayName = result.value();
expect(displayName).toBe('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);
const filter = {
meta: { index: 'logstash-*' } as FilterMeta,
range: { bytes: { lte: 2048, gte: 1024 } },
} as RangeFilter;
const result = mapRange(filter);
expect(result).toHaveProperty('key', 'bytes');
expect(result).toHaveProperty('value', '1024 to 2048');
expect(result).toHaveProperty('value');
if (result.value) {
const displayName = result.value();
expect(displayName).toBe('1024 to 2048');
}
});
test('should return undefined for none matching', async done => {
const filter = {
meta: { index: 'logstash-*' },
query: { query_string: { query: 'foo:bar' } },
};
} as Filter;
try {
await mapRangeFn(filter);
mapRange(filter);
} catch (e) {
expect(e).toBe(filter);

View file

@ -17,15 +17,31 @@
* under the License.
*/
import { Filter, RangeFilter, FILTERS, isRangeFilter, isScriptedRangeFilter } from '@kbn/es-query';
import {
Filter,
RangeFilter,
FILTERS,
isRangeFilter,
isScriptedRangeFilter,
FilterValueFormatter,
} from '@kbn/es-query';
import { get, has } from 'lodash';
import { SavedObjectNotFound } from '../../../../../../../plugins/kibana_utils/public';
import { IndexPatterns, IndexPattern, Field } from '../../../index_patterns';
const getFormattedValueFn = (left: any, right: any) => {
return (formatter?: FilterValueFormatter) => {
let displayValue = `${left} to ${right}`;
if (formatter) {
const convert = formatter.getConverterFor('text');
displayValue = `${convert(left)} to ${convert(right)}`;
}
return displayValue;
};
};
const getFirstRangeKey = (filter: RangeFilter) => filter.range && Object.keys(filter.range)[0];
const getRangeByKey = (filter: RangeFilter, key: string) => get(filter, ['range', key]);
function getParams(filter: RangeFilter, indexPattern?: IndexPattern) {
function getParams(filter: RangeFilter) {
const isScriptedRange = isScriptedRangeFilter(filter);
const key: string = (isScriptedRange ? filter.meta.field : getFirstRangeKey(filter)) || '';
const params: any = isScriptedRange
@ -38,16 +54,7 @@ function getParams(filter: RangeFilter, indexPattern?: 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.
if (key && indexPattern && indexPattern.fields.getByName(key)) {
const convert = (indexPattern.fields.getByName(key) as Field).format.getConverterFor('text');
value = `${convert(left)} to ${convert(right)}`;
}
const value = getFormattedValueFn(left, right);
return { type: FILTERS.RANGE, key, value, params };
}
@ -55,25 +62,10 @@ function getParams(filter: RangeFilter, indexPattern?: IndexPattern) {
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;
}
export const mapRange = (filter: Filter) => {
if (!isMapRangeFilter(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;
}
};
return getParams(filter);
};

View file

@ -17,11 +17,17 @@
* under the License.
*/
import { FilterService, FilterStart } from '.';
import { FilterService, FilterStart, FilterSetup } from '.';
type FilterServiceClientContract = PublicMethodsOf<FilterService>;
const createSetupContract = () => {};
const createSetupContractMock = () => {
const setupContract: jest.Mocked<FilterSetup> = {
filterManager: jest.fn() as any,
};
return setupContract;
};
const createStartContractMock = () => {
const startContract: jest.Mocked<FilterStart> = {
@ -38,13 +44,13 @@ const createMock = () => {
stop: jest.fn(),
};
mocked.setup.mockReturnValue(createSetupContract());
mocked.setup.mockReturnValue(createSetupContractMock());
mocked.start.mockReturnValue(createStartContractMock());
return mocked;
};
export const filterServiceMock = {
create: createMock,
createSetupContract,
createSetupContract: createSetupContractMock,
createStartContract: createStartContractMock,
};

View file

@ -18,7 +18,6 @@
*/
import { UiSettingsClientContract } from 'src/core/public';
import { IndexPatterns } from '../index_patterns';
import { FilterManager } from './filter_manager';
/**
@ -27,18 +26,23 @@ import { FilterManager } from './filter_manager';
*/
export interface FilterServiceDependencies {
indexPatterns: IndexPatterns;
uiSettings: UiSettingsClientContract;
}
export class FilterService {
public setup() {
// Filter service requires index patterns, which are only available in `start`
filterManager!: FilterManager;
public setup({ uiSettings }: FilterServiceDependencies) {
this.filterManager = new FilterManager(uiSettings);
return {
filterManager: this.filterManager,
};
}
public start({ indexPatterns, uiSettings }: FilterServiceDependencies) {
public start() {
return {
filterManager: new FilterManager(indexPatterns, uiSettings),
filterManager: this.filterManager,
};
}
@ -48,4 +52,5 @@ export class FilterService {
}
/** @public */
export type FilterSetup = ReturnType<FilterService['setup']>;
export type FilterStart = ReturnType<FilterService['start']>;

View file

@ -17,7 +17,7 @@
* under the License.
*/
export { FilterService, FilterStart } from './filter_service';
export * from './filter_service';
export { FilterBar } from './filter_bar';

View file

@ -20,7 +20,7 @@
import { CoreSetup, CoreStart, Plugin } from 'kibana/public';
import { SearchService, SearchStart, createSearchBar, StatetfulSearchBarProps } from './search';
import { QueryService, QuerySetup } from './query';
import { FilterService, FilterStart } from './filter';
import { FilterService, FilterSetup, FilterStart } from './filter';
import { TimefilterService, TimefilterSetup } from './timefilter';
import { IndexPatternsService, IndexPatternsSetup, IndexPatternsStart } from './index_patterns';
import {
@ -52,6 +52,7 @@ export interface DataSetup {
query: QuerySetup;
timefilter: TimefilterSetup;
indexPatterns: IndexPatternsSetup;
filter: FilterSetup;
}
/**
@ -100,10 +101,14 @@ export class DataPlugin
uiSettings,
store: __LEGACY.storage,
});
const filterService = this.filter.setup({
uiSettings,
});
this.setupApi = {
indexPatterns: this.indexPatterns.setup(),
query: this.query.setup(),
timefilter: timefilterService,
filter: filterService,
};
return this.setupApi;
@ -119,23 +124,17 @@ export class DataPlugin
notifications,
});
const filterService = this.filter.start({
uiSettings,
indexPatterns: indexPatternsService.indexPatterns,
});
const SearchBar = createSearchBar({
core,
data,
store: __LEGACY.storage,
timefilter: this.setupApi.timefilter,
filterManager: filterService.filterManager,
filterManager: this.setupApi.filter.filterManager,
});
return {
...this.setupApi!,
indexPatterns: indexPatternsService,
filter: filterService,
search: this.search.start(savedObjects.client),
ui: {
SearchBar,

View file

@ -17,6 +17,7 @@
* under the License.
*/
import { compact } from 'lodash';
import { Filter } from '@kbn/es-query';
import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
import classNames from 'classnames';
@ -195,7 +196,12 @@ class SearchBarUI extends Component<SearchBarProps, State> {
}
private shouldRenderFilterBar() {
return this.props.showFilterBar && this.props.filters && this.props.indexPatterns;
return (
this.props.showFilterBar &&
this.props.filters &&
this.props.indexPatterns &&
compact(this.props.indexPatterns).length > 0
);
}
public setFilterBarHeight = () => {

View file

@ -4,4 +4,5 @@
filters="state.filters"
on-cancel="onCancel"
on-submit="onSubmit"
index-patterns="indexPatterns"
></apply-filters-popover-component>

View file

@ -79,7 +79,7 @@ export const initLegacyModule = once((): void => {
.directive('applyFiltersPopoverComponent', (reactDirective: any) =>
reactDirective(wrapInI18nContext(ApplyFiltersPopover))
)
.directive('applyFiltersPopover', (indexPatterns: IndexPatterns) => {
.directive('applyFiltersPopover', () => {
return {
template,
restrict: 'E',
@ -87,6 +87,7 @@ export const initLegacyModule = once((): void => {
filters: '=',
onCancel: '=',
onSubmit: '=',
indexPatterns: '=',
},
link($scope: any) {
$scope.state = {};
@ -94,8 +95,8 @@ export const initLegacyModule = once((): void => {
// Each time the new filters change we want to rebuild (not just re-render) the "apply filters"
// popover, because it has to reset its state whenever the new filters change. Setting a `key`
// property on the component accomplishes this due to how React handles the `key` property.
$scope.$watch('filters', async (filters: any) => {
const mappedFilters: Filter[] = await mapAndFlattenFilters(indexPatterns, filters);
$scope.$watch('filters', (filters: any) => {
const mappedFilters: Filter[] = mapAndFlattenFilters(filters);
$scope.state = {
filters: mappedFilters,
key: Date.now(),
@ -107,7 +108,7 @@ export const initLegacyModule = once((): void => {
const module = uiModules.get('kibana/index_patterns');
let _service: any;
module.service('indexPatterns', function(chrome: any) {
module.service('indexPatterns', function() {
if (!_service)
_service = new IndexPatterns(
npStart.core.uiSettings,

View file

@ -46,6 +46,7 @@
filters="appState.$newFilters"
on-cancel="onCancelApplyFilters"
on-submit="onApplyFilters"
index-patterns="indexPatterns"
></apply-filters-popover>
<div ng-show="getShouldShowEditHelp() || getShouldShowViewHelp()" class="dshStartScreen">

View file

@ -258,7 +258,7 @@ export class DashboardAppController {
updateIndexPatterns(dashboardContainer);
});
inputSubscription = dashboardContainer.getInput$().subscribe(async () => {
inputSubscription = dashboardContainer.getInput$().subscribe(() => {
let dirty = false;
// This has to be first because handleDashboardContainerChanges causes
@ -266,7 +266,7 @@ export class DashboardAppController {
// Add filters modifies the object passed to it, hence the clone deep.
if (!_.isEqual(container.getInput().filters, queryFilter.getFilters())) {
await queryFilter.addFilters(_.cloneDeep(container.getInput().filters));
queryFilter.addFilters(_.cloneDeep(container.getInput().filters));
dashboardStateManager.applyFilters($scope.model.query, container.getInput().filters);
dirty = true;
@ -453,7 +453,6 @@ export class DashboardAppController {
$scope.onClearSavedQuery = () => {
delete $scope.savedQuery;
dashboardStateManager.setSavedQueryId(undefined);
queryFilter.removeAll();
dashboardStateManager.applyFilters(
{
query: '',
@ -462,10 +461,12 @@ export class DashboardAppController {
},
[]
);
// Making this method sync broke the updates.
// Temporary fix, until we fix the complex state in this file.
setTimeout(queryFilter.removeAll, 0);
};
const updateStateFromSavedQuery = (savedQuery: SavedQuery) => {
queryFilter.setFilters(savedQuery.attributes.filters || []);
dashboardStateManager.applyFilters(
savedQuery.attributes.query,
savedQuery.attributes.filters || []
@ -479,6 +480,11 @@ export class DashboardAppController {
timefilter.setRefreshInterval(savedQuery.attributes.timefilter.refreshInterval);
}
}
// Making this method sync broke the updates.
// Temporary fix, until we fix the complex state in this file.
setTimeout(() => {
queryFilter.setFilters(savedQuery.attributes.filters || []);
}, 0);
};
$scope.$watch('savedQuery', (newSavedQuery: SavedQuery) => {

View file

@ -66,6 +66,14 @@ export class FilterUtils {
* @returns {Array.<Object>}
*/
public static cleanFiltersForComparison(filters: Filter[]) {
return _.map(filters, filter => _.omit(filter, ['$$hashKey', '$state']));
return _.map(filters, filter => {
const f: Partial<Filter> = _.omit(filter, ['$$hashKey', '$state']);
if (f.meta) {
// f.meta.value is the value displayed in the filter bar.
// It may also be loaded differently and shouldn't be used in this comparison.
return _.omit(f.meta, ['value']);
}
return f;
});
}
}

View file

@ -77,6 +77,7 @@
filters="state.$newFilters"
on-cancel="onCancelApplyFilters"
on-submit="onApplyFilters"
index-patterns="indexPatterns"
></apply-filters-popover>
<div

View file

@ -264,7 +264,7 @@ export class VegaBaseView {
async addFilterHandler(query, index) {
const indexId = await this._findIndex(index);
const filter = buildQueryFilter(query, indexId);
await this._queryfilter.addFilters(filter);
this._queryfilter.addFilters(filter);
}
/**

View file

@ -781,7 +781,6 @@
"data.filter.filterBar.filterItemBadgeIconAriaLabel": "削除",
"data.filter.filterBar.includeFilterButtonLabel": "結果を含める",
"data.filter.filterBar.indexPatternSelectPlaceholder": "インデックスパターンの選択",
"data.filter.filterBar.moreFilterActionsMessage": "他のフィルターアクションを使用するには選択してください。",
"data.filter.filterBar.negatedFilterPrefix": "NOT ",
"data.filter.filterBar.pinFilterButtonLabel": "すべてのアプリにピン付け",
"data.filter.filterBar.pinnedFilterPrefix": "ピン付け済み",

View file

@ -782,7 +782,6 @@
"data.filter.filterBar.filterItemBadgeIconAriaLabel": "删除",
"data.filter.filterBar.includeFilterButtonLabel": "包括结果",
"data.filter.filterBar.indexPatternSelectPlaceholder": "选择索引模式",
"data.filter.filterBar.moreFilterActionsMessage": "选择更多筛选操作。",
"data.filter.filterBar.negatedFilterPrefix": "非 ",
"data.filter.filterBar.pinFilterButtonLabel": "在所有应用上固定",
"data.filter.filterBar.pinnedFilterPrefix": "已固定",

View file

@ -109,7 +109,7 @@ export default function ({ getPageObjects, getService }) {
it('should update app state with query stored with map', async () => {
const currentUrl = await browser.getCurrentUrl();
const appState = currentUrl.substring(currentUrl.indexOf('_a='));
expect(appState).to.equal('_a=(filters:!((%27$state%27:(store:appState),meta:(alias:!n,disabled:!f,index:c698b940-e149-11e8-a35a-370a8516603a,key:machine.os.raw,negate:!f,params:(query:ios),type:phrase,value:ios),query:(match:(machine.os.raw:(query:ios,type:phrase))))),query:(language:kuery,query:%27%27))'); // eslint-disable-line max-len
expect(appState).to.equal('_a=(filters:!((%27$state%27:(store:appState),meta:(alias:!n,disabled:!f,index:c698b940-e149-11e8-a35a-370a8516603a,key:machine.os.raw,negate:!f,params:(query:ios),type:phrase),query:(match:(machine.os.raw:(query:ios,type:phrase))))),query:(language:kuery,query:%27%27))'); // eslint-disable-line max-len
});
it('should apply query stored with map', async () => {