mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
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:
parent
27dbcb2796
commit
338851f5d3
49 changed files with 499 additions and 469 deletions
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)}
|
||||
/>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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)}`;
|
||||
}
|
||||
}
|
|
@ -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}
|
||||
|
|
|
@ -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)}`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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));
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.');
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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']>;
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export { FilterService, FilterStart } from './filter_service';
|
||||
export * from './filter_service';
|
||||
|
||||
export { FilterBar } from './filter_bar';
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 = () => {
|
||||
|
|
|
@ -4,4 +4,5 @@
|
|||
filters="state.filters"
|
||||
on-cancel="onCancel"
|
||||
on-submit="onSubmit"
|
||||
index-patterns="indexPatterns"
|
||||
></apply-filters-popover-component>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,6 +77,7 @@
|
|||
filters="state.$newFilters"
|
||||
on-cancel="onCancelApplyFilters"
|
||||
on-submit="onApplyFilters"
|
||||
index-patterns="indexPatterns"
|
||||
></apply-filters-popover>
|
||||
|
||||
<div
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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": "ピン付け済み",
|
||||
|
|
|
@ -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": "已固定",
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue