Convert filter_manager/lib to TypeScript / Jest (#45785)

* Convert filter_manager/lib to TypeScript / Jest

Fix: #44952

* Update map_query_string.ts

* remove extra ts-ignore

* formatting

* fix PR comments

* Fix PR comments

* fix PR comments

* fix PR comments

* fix merge conflicts

* revert logic

* Fix PR commnets

* add tests for compare_filters

* fix PR comments
This commit is contained in:
Alexey Antonov 2019-09-27 15:22:29 +03:00 committed by GitHub
parent a99a5d62bf
commit 905d021c5a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
65 changed files with 1663 additions and 1335 deletions

View file

@ -21,6 +21,13 @@ import { Filter, FilterMeta } from './meta_filter';
export type ExistsFilterMeta = FilterMeta;
export interface FilterExistsProperty {
field: any;
}
export type ExistsFilter = Filter & {
meta: ExistsFilterMeta;
exists?: FilterExistsProperty;
};
export const isExistsFilter = (filter: any): filter is ExistsFilter => filter && filter.exists;

View file

@ -28,4 +28,8 @@ export type GeoBoundingBoxFilterMeta = FilterMeta & {
export type GeoBoundingBoxFilter = Filter & {
meta: GeoBoundingBoxFilterMeta;
geo_bounding_box: any;
};
export const isGeoBoundingBoxFilter = (filter: any): filter is GeoBoundingBoxFilter =>
filter && filter.geo_bounding_box;

View file

@ -27,4 +27,8 @@ export type GeoPolygonFilterMeta = FilterMeta & {
export type GeoPolygonFilter = Filter & {
meta: GeoPolygonFilterMeta;
geo_polygon: any;
};
export const isGeoPolygonFilter = (filter: any): filter is GeoPolygonFilter =>
filter && filter.geo_polygon;

View file

@ -22,22 +22,38 @@ export * from './meta_filter';
// The actual filter types
import { CustomFilter } from './custom_filter';
import { ExistsFilter } from './exists_filter';
import { GeoBoundingBoxFilter } from './geo_bounding_box_filter';
import { GeoPolygonFilter } from './geo_polygon_filter';
import { PhraseFilter } from './phrase_filter';
import { PhrasesFilter } from './phrases_filter';
import { QueryStringFilter } from './query_string_filter';
import { RangeFilter } from './range_filter';
import { ExistsFilter, isExistsFilter } from './exists_filter';
import { GeoBoundingBoxFilter, isGeoBoundingBoxFilter } from './geo_bounding_box_filter';
import { GeoPolygonFilter, isGeoPolygonFilter } from './geo_polygon_filter';
import { PhraseFilter, isPhraseFilter, isScriptedPhraseFilter } from './phrase_filter';
import { PhrasesFilter, isPhrasesFilter } from './phrases_filter';
import { QueryStringFilter, isQueryStringFilter } from './query_string_filter';
import { RangeFilter, isRangeFilter, isScriptedRangeFilter } from './range_filter';
import { MatchAllFilter, isMatchAllFilter } from './match_all_filter';
import { MissingFilter, isMissingFilter } from './missing_filter';
export {
CustomFilter,
ExistsFilter,
isExistsFilter,
GeoBoundingBoxFilter,
isGeoBoundingBoxFilter,
GeoPolygonFilter,
isGeoPolygonFilter,
PhraseFilter,
isPhraseFilter,
isScriptedPhraseFilter,
PhrasesFilter,
isPhrasesFilter,
QueryStringFilter,
isQueryStringFilter,
RangeFilter,
isRangeFilter,
isScriptedRangeFilter,
MatchAllFilter,
isMatchAllFilter,
MissingFilter,
isMissingFilter,
};
// Any filter associated with a field (used in the filter bar/editor)
@ -47,4 +63,19 @@ export type FieldFilter =
| GeoPolygonFilter
| PhraseFilter
| PhrasesFilter
| RangeFilter;
| RangeFilter
| MatchAllFilter
| MissingFilter;
export enum FILTERS {
CUSTOM = 'custom',
PHRASES = 'phrases',
PHRASE = 'phrase',
EXISTS = 'exists',
MATCH_ALL = 'match_all',
MISSING = 'missing',
QUERY_STRING = 'query_string',
RANGE = 'range',
GEO_BOUNDING_BOX = 'geo_bounding_box',
GEO_POLYGON = 'geo_polygon',
}

View file

@ -0,0 +1,33 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { Filter, FilterMeta } from './meta_filter';
export interface MatchAllFilterMeta extends FilterMeta {
field: any;
formattedValue: string;
}
export type MatchAllFilter = Filter & {
meta: MatchAllFilterMeta;
match_all: any;
};
export const isMatchAllFilter = (filter: any): filter is MatchAllFilter =>
filter && filter.match_all;

View file

@ -35,12 +35,13 @@ export interface FilterMeta {
alias: string | null;
key?: string;
value?: string;
params?: any;
}
export interface Filter {
$state?: FilterState;
meta: FilterMeta;
query?: object;
query?: any;
}
export interface LatLon {

View file

@ -17,13 +17,13 @@
* under the License.
*/
import _ from 'lodash';
import { mapFilter } from './map_filter';
import { Filter, FilterMeta } from './meta_filter';
export function mapAndFlattenFilters(indexPatterns, filters) {
const flattened = _(filters)
.flatten()
.compact()
.map(item => mapFilter(indexPatterns, item)).value();
return Promise.all(flattened);
}
export type MissingFilterMeta = FilterMeta;
export type MissingFilter = Filter & {
meta: MissingFilterMeta;
missing: any;
};
export const isMissingFilter = (filter: any): filter is MissingFilter => filter && filter.missing;

View file

@ -17,14 +17,27 @@
* under the License.
*/
import { get } from 'lodash';
import { Filter, FilterMeta } from './meta_filter';
export type PhraseFilterMeta = FilterMeta & {
params: {
query: string; // The unformatted value
};
script?: {
script: {
params: any;
};
};
field?: any;
};
export type PhraseFilter = Filter & {
meta: PhraseFilterMeta;
};
export const isPhraseFilter = (filter: any): filter is PhraseFilter =>
filter && (filter.query && filter.query.match);
export const isScriptedPhraseFilter = (filter: any): filter is PhraseFilter =>
Boolean(get(filter, 'script.script.params.value'));

View file

@ -21,8 +21,12 @@ import { Filter, FilterMeta } from './meta_filter';
export type PhrasesFilterMeta = FilterMeta & {
params: string[]; // The unformatted values
field?: string;
};
export type PhrasesFilter = Filter & {
meta: PhrasesFilterMeta;
};
export const isPhrasesFilter = (filter: any): filter is PhrasesFilter =>
filter && filter.meta.type === 'phrases';

View file

@ -23,4 +23,12 @@ export type QueryStringFilterMeta = FilterMeta;
export type QueryStringFilter = Filter & {
meta: QueryStringFilterMeta;
query?: {
query_string: {
query: string;
};
};
};
export const isQueryStringFilter = (filter: any): filter is QueryStringFilter =>
filter && filter.query && filter.query.query_string;

View file

@ -16,20 +16,50 @@
* specific language governing permissions and limitations
* under the License.
*/
import { get, keys } from 'lodash';
import { Filter, FilterMeta } from './meta_filter';
export interface RangeFilterParams {
interface FilterRange {
from?: number | string;
to?: number | string;
}
interface FilterRangeGt {
gt?: number | string;
gte?: number | string;
lte?: number | string;
lt?: number | string;
}
interface FilterRangeGte {
gte?: number | string;
lte?: number | string;
}
export type RangeFilterParams = FilterRange & FilterRangeGt & FilterRangeGte;
export type RangeFilterMeta = FilterMeta & {
params: RangeFilterParams;
field?: any;
};
export type RangeFilter = Filter & {
meta: RangeFilterMeta;
script?: {
script: {
params: any;
};
};
range: { [key: string]: RangeFilterParams };
};
const hasRangeKeys = (params: RangeFilterParams) =>
Boolean(
keys(params).find((key: string) => ['gte', 'gt', 'lte', 'lt', 'from', 'to'].includes(key))
);
export const isRangeFilter = (filter: any): filter is RangeFilter => filter && filter.range;
export const isScriptedRangeFilter = (filter: any): filter is RangeFilter => {
const params: RangeFilterParams = get(filter, 'script.script.params', {});
return hasRangeKeys(params);
};

View file

@ -36,4 +36,5 @@ export const rangeFilter: RangeFilter = {
$state: {
store: FilterStateStore.APP_STATE,
},
range: {},
};

View file

@ -17,19 +17,16 @@
* under the License.
*/
import { Filter, isFilterPinned, FilterStateStore } from '@kbn/es-query';
import { Filter, isFilterPinned, isRangeFilter, FilterStateStore } from '@kbn/es-query';
import _ from 'lodash';
import { Subject } from 'rxjs';
import { UiSettingsClientContract } from 'src/core/public';
// @ts-ignore
import { compareFilters } from './lib/compare_filters';
// @ts-ignore
import { mapAndFlattenFilters } from './lib/map_and_flatten_filters';
// @ts-ignore
import { uniqFilters } from './lib/uniq_filters';
import { compareFilters } from './lib/compare_filters';
import { mapAndFlattenFilters } from './lib/map_and_flatten_filters';
import { uniqFilters } from './lib/uniq_filters';
import { extractTimeFilter } from './lib/extract_time_filter';
import { changeTimeFilter } from './lib/change_time_filter';
import { onlyDisabledFiltersChanged } from './lib/only_disabled';
@ -194,7 +191,10 @@ export class FilterManager {
public async addFiltersAndChangeTimeFilter(filters: Filter[]) {
const timeFilter = await extractTimeFilter(this.indexPatterns, filters);
if (timeFilter) changeTimeFilter(this.timefilter, timeFilter);
if (isRangeFilter(timeFilter)) {
changeTimeFilter(this.timefilter, timeFilter);
}
return this.addFilters(filters.filter(filter => filter !== timeFilter));
}

View file

@ -20,6 +20,5 @@
export { FilterManager } from './filter_manager';
export { FilterStateManager } from './filter_state_manager';
// @ts-ignore
export { uniqFilters } from './lib/uniq_filters';
export { onlyDisabledFiltersChanged } from './lib/only_disabled';

View file

@ -1,68 +0,0 @@
/*
* 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 { dedupFilters } from '../dedup_filters';
import expect from '@kbn/expect';
describe('Filter Bar Directive', function () {
describe('dedupFilters(existing, filters)', function () {
it('should return only filters which are not in the existing', function () {
const existing = [
{ range: { bytes: { from: 0, to: 1024 } } },
{ query: { match: { _term: { query: 'apache', type: 'phrase' } } } }
];
const filters = [
{ range: { bytes: { from: 1024, to: 2048 } } },
{ query: { match: { _term: { query: 'apache', type: 'phrase' } } } }
];
const results = dedupFilters(existing, filters);
expect(results).to.contain(filters[0]);
expect(results).to.not.contain(filters[1]);
});
it('should ignore the disabled attribute when comparing ', function () {
const existing = [
{ range: { bytes: { from: 0, to: 1024 } } },
{ meta: { disabled: true }, query: { match: { _term: { query: 'apache', type: 'phrase' } } } }
];
const filters = [
{ range: { bytes: { from: 1024, to: 2048 } } },
{ query: { match: { _term: { query: 'apache', type: 'phrase' } } } }
];
const results = dedupFilters(existing, filters);
expect(results).to.contain(filters[0]);
expect(results).to.not.contain(filters[1]);
});
it('should ignore $state attribute', function () {
const existing = [
{ range: { bytes: { from: 0, to: 1024 } } },
{ $state: { store: 'appState' }, query: { match: { _term: { query: 'apache', type: 'phrase' } } } }
];
const filters = [
{ range: { bytes: { from: 1024, to: 2048 } } },
{ $state: { store: 'globalState' }, query: { match: { _term: { query: 'apache', type: 'phrase' } } } }
];
const results = dedupFilters(existing, filters);
expect(results).to.contain(filters[0]);
expect(results).to.not.contain(filters[1]);
});
});
});

View file

@ -1,61 +0,0 @@
/*
* 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 expect from '@kbn/expect';
import ngMock from 'ng_mock';
import { extractTimeFilter } from '../extract_time_filter';
import IndexPatternMock from 'fixtures/mock_index_patterns';
describe('Filter Bar Directive', function () {
describe('extractTimeFilter()', function () {
let mockIndexPatterns;
beforeEach(ngMock.module(
'kibana',
'kibana/courier'
));
beforeEach(ngMock.inject(function (Private) {
mockIndexPatterns = Private(IndexPatternMock);
}));
it('should return the matching filter for the default time field', function (done) {
const filters = [
{ meta: { index: 'logstash-*' }, query: { match: { _type: { query: 'apache', type: 'phrase' } } } },
{ meta: { index: 'logstash-*' }, range: { 'time': { gt: 1388559600000, lt: 1388646000000 } } }
];
extractTimeFilter(mockIndexPatterns, filters).then(function (filter) {
expect(filter).to.eql(filters[1]);
done();
});
});
it('should not return the non-matching filter for the default time field', function (done) {
const filters = [
{ meta: { index: 'logstash-*' }, query: { match: { _type: { query: 'apache', type: 'phrase' } } } },
{ meta: { index: 'logstash-*' }, range: { '@timestamp': { gt: 1388559600000, lt: 1388646000000 } } }
];
extractTimeFilter(mockIndexPatterns, filters).then(function (filter) {
expect(filter).to.be(undefined);
done();
});
});
});
});

View file

@ -1,111 +0,0 @@
/*
* 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 sinon from 'sinon';
import expect from '@kbn/expect';
import ngMock from 'ng_mock';
import { generateMappingChain } from '../generate_mapping_chain';
describe('Filter Bar Directive', function () {
describe('generateMappingChain()', function () {
beforeEach(ngMock.module('kibana'));
it('should create a chaining function which calls the next function if the promise is rejected', function (done) {
const filter = {};
const mapping = sinon.stub();
mapping.rejects(filter);
const next = sinon.stub();
next.resolves('good');
const chain = generateMappingChain(mapping, next);
chain(filter).then(function (result) {
expect(result).to.be('good');
sinon.assert.calledOnce(next);
done();
});
});
it('should create a chaining function which DOES NOT call the next function if the result is resolved', function (done) {
const mapping = sinon.stub();
mapping.resolves('good');
const next = sinon.stub();
next.resolves('bad');
const chain = generateMappingChain(mapping, next);
chain({}).then(function (result) {
expect(result).to.be('good');
sinon.assert.notCalled(next);
done();
});
});
it('should resolve result for the mapping function', function (done) {
const mapping = sinon.stub();
mapping.resolves({ key: 'test', value: 'example' });
const next = sinon.stub();
const chain = generateMappingChain(mapping, next);
chain({}).then(function (result) {
sinon.assert.notCalled(next);
expect(result).to.eql({ key: 'test', value: 'example' });
done();
});
});
it('should call the mapping function with the argument to the chain', function (done) {
const mapping = sinon.stub();
mapping.resolves({ key: 'test', value: 'example' });
const next = sinon.stub();
const chain = generateMappingChain(mapping, next);
chain({ test: 'example' }).then(function (result) {
sinon.assert.calledOnce(mapping);
expect(mapping.args[0][0]).to.eql({ test: 'example' });
sinon.assert.notCalled(next);
expect(result).to.eql({ key: 'test', value: 'example' });
done();
});
});
it('should resolve result for the next function', function (done) {
const filter = {};
const mapping = sinon.stub();
mapping.rejects(filter);
const next = sinon.stub();
next.resolves({ key: 'test', value: 'example' });
const chain = generateMappingChain(mapping, next);
chain(filter).then(function (result) {
sinon.assert.calledOnce(mapping);
sinon.assert.calledOnce(next);
expect(result).to.eql({ key: 'test', value: 'example' });
done();
});
});
it('should reject with an error if no functions match', function (done) {
const filter = {};
const mapping = sinon.stub();
mapping.rejects(filter);
const chain = generateMappingChain(mapping);
chain(filter).catch(function (err) {
expect(err).to.be.an(Error);
expect(err.message).to.be('No mappings have been found for filter.');
done();
});
});
});
});

View file

@ -1,72 +0,0 @@
/*
* 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 expect from '@kbn/expect';
import ngMock from 'ng_mock';
import { mapAndFlattenFilters } from '../map_and_flatten_filters';
import IndexPatternMock from 'fixtures/mock_index_patterns';
describe('Filter Bar Directive', function () {
describe('mapAndFlattenFilters()', function () {
let mockIndexPatterns;
beforeEach(ngMock.module(
'kibana',
'kibana/courier'
));
beforeEach(ngMock.inject(function (Private) {
mockIndexPatterns = Private(IndexPatternMock);
}));
const filters = [
null,
[
{ meta: { index: 'logstash-*' }, exists: { field: '_type' } },
{ meta: { index: 'logstash-*' }, missing: { field: '_type' } }
],
{ meta: { index: 'logstash-*' }, query: { query_string: { query: 'foo:bar' } } },
{ meta: { index: 'logstash-*' }, range: { bytes: { lt: 2048, gt: 1024 } } },
{ meta: { index: 'logstash-*' }, query: { match: { _type: { query: 'apache', type: 'phrase' } } } }
];
it('should map and flatten the filters', function (done) {
mapAndFlattenFilters(mockIndexPatterns, filters).then(function (results) {
expect(results).to.have.length(5);
expect(results[0]).to.have.property('meta');
expect(results[1]).to.have.property('meta');
expect(results[2]).to.have.property('meta');
expect(results[3]).to.have.property('meta');
expect(results[4]).to.have.property('meta');
expect(results[0].meta).to.have.property('key', '_type');
expect(results[0].meta).to.have.property('value', 'exists');
expect(results[1].meta).to.have.property('key', '_type');
expect(results[1].meta).to.have.property('value', 'missing');
expect(results[2].meta).to.have.property('key', 'query');
expect(results[2].meta).to.have.property('value', 'foo:bar');
expect(results[3].meta).to.have.property('key', 'bytes');
expect(results[3].meta).to.have.property('value', '1,024 to 2,048');
expect(results[4].meta).to.have.property('key', '_type');
expect(results[4].meta).to.have.property('value', 'apache');
done();
});
});
});
});

View file

@ -1,62 +0,0 @@
/*
* 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 expect from '@kbn/expect';
import { mapDefault } from '../map_default';
describe('Filter Bar Directive', function () {
describe('mapDefault()', function () {
it('should return the key and value for matching filters', function (done) {
const filter = { query: { match_all: {} } };
mapDefault(filter).then(function (result) {
expect(result).to.have.property('key', 'query');
expect(result).to.have.property('value', '{"match_all":{}}');
done();
});
});
it('should work with undefined filter types', function (done) {
const filter = {
'bool': {
'must': {
'term': {
'geo.src': 'US'
}
}
}
};
mapDefault(filter).then(function (result) {
expect(result).to.have.property('key', 'bool');
expect(result).to.have.property('value', JSON.stringify(filter.bool));
done();
});
});
it('should return undefined if there is no valid key', function (done) {
const filter = { meta: {} };
mapDefault(filter).catch(function (result) {
expect(result).to.be(filter);
done();
});
});
});
});

View file

@ -1,97 +0,0 @@
/*
* 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 expect from '@kbn/expect';
import ngMock from 'ng_mock';
import { mapFilter } from '../map_filter';
import IndexPatternMock from 'fixtures/mock_index_patterns';
describe('Filter Bar Directive', function () {
let mockIndexPatterns;
beforeEach(ngMock.module(
'kibana',
'kibana/courier'
));
beforeEach(ngMock.inject(function (Private) {
mockIndexPatterns = Private(IndexPatternMock);
}));
describe('mapFilter()', function () {
it('should map query filters', function (done) {
const before = { meta: { index: 'logstash-*' }, query: { match: { '_type': { query: 'apache' } } } };
mapFilter(mockIndexPatterns, before).then(function (after) {
expect(after).to.have.property('meta');
expect(after.meta).to.have.property('key', '_type');
expect(after.meta).to.have.property('value', 'apache');
expect(after.meta).to.have.property('disabled', false);
expect(after.meta).to.have.property('negate', false);
done();
});
});
it('should map exists filters', function (done) {
const before = { meta: { index: 'logstash-*' }, exists: { field: '@timestamp' } };
mapFilter(mockIndexPatterns, before).then(function (after) {
expect(after).to.have.property('meta');
expect(after.meta).to.have.property('key', '@timestamp');
expect(after.meta).to.have.property('value', 'exists');
expect(after.meta).to.have.property('disabled', false);
expect(after.meta).to.have.property('negate', false);
done();
});
});
it('should map missing filters', function (done) {
const before = { meta: { index: 'logstash-*' }, missing: { field: '@timestamp' } };
mapFilter(mockIndexPatterns, before).then(function (after) {
expect(after).to.have.property('meta');
expect(after.meta).to.have.property('key', '@timestamp');
expect(after.meta).to.have.property('value', 'missing');
expect(after.meta).to.have.property('disabled', false);
expect(after.meta).to.have.property('negate', false);
done();
});
});
it('should map json filter', function (done) {
const before = { meta: { index: 'logstash-*' }, query: { match_all: {} } };
mapFilter(mockIndexPatterns, before).then(function (after) {
expect(after).to.have.property('meta');
expect(after.meta).to.have.property('key', 'query');
expect(after.meta).to.have.property('value', '{"match_all":{}}');
expect(after.meta).to.have.property('disabled', false);
expect(after.meta).to.have.property('negate', false);
done();
});
});
it('should finish with a catch', function (done) {
const before = { meta: { index: 'logstash-*' } };
mapFilter(mockIndexPatterns, before).catch(function (error) {
expect(error).to.be.an(Error);
expect(error.message).to.be('No mappings have been found for filter.');
done();
});
});
});
});

View file

@ -1,92 +0,0 @@
/*
* 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 expect from '@kbn/expect';
import ngMock from 'ng_mock';
import { mapGeoBoundingBox } from '../map_geo_bounding_box';
import IndexPatternMock from 'fixtures/mock_index_patterns';
describe('Filter Bar Directive', function () {
describe('mapGeoBoundingBox()', function () {
let mapGeoBoundingBoxFn;
let mockIndexPatterns;
beforeEach(ngMock.module(
'kibana',
'kibana/courier'
));
beforeEach(ngMock.inject(function (Private) {
mockIndexPatterns = Private(IndexPatternMock);
mapGeoBoundingBoxFn = mapGeoBoundingBox(mockIndexPatterns);
}));
it('should return the key and value for matching filters with bounds', function (done) {
const filter = {
meta: {
index: 'logstash-*'
},
geo_bounding_box: {
point: { // field name
top_left: { lat: 5, lon: 10 },
bottom_right: { lat: 15, lon: 20 }
}
}
};
mapGeoBoundingBoxFn(filter).then(function (result) {
expect(result).to.have.property('key', 'point');
expect(result).to.have.property('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, '')).to.be('lat5lon10tolat15lon20');
done();
});
});
it('should return undefined for none matching', function (done) {
const filter = { meta: { index: 'logstash-*' }, query: { query_string: { query: 'foo:bar' } } };
mapGeoBoundingBoxFn(filter).catch(function (result) {
expect(result).to.be(filter);
done();
});
});
it('should return the key and value even when using ignore_unmapped', function (done) {
const filter = {
meta: {
index: 'logstash-*'
},
geo_bounding_box: {
ignore_unmapped: true,
point: { // field name
top_left: { lat: 5, lon: 10 },
bottom_right: { lat: 15, lon: 20 }
}
}
};
mapGeoBoundingBoxFn(filter).then(function (result) {
expect(result).to.have.property('key', 'point');
expect(result).to.have.property('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, '')).to.be('lat5lon10tolat15lon20');
done();
});
});
});
});

View file

@ -1,96 +0,0 @@
/*
* 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 expect from '@kbn/expect';
import ngMock from 'ng_mock';
import { mapGeoPolygon } from '../map_geo_polygon';
import IndexPatternMock from 'fixtures/mock_index_patterns';
describe('Filter Bar Directive', function () {
describe('mapGeoPolygon()', function () {
let mapGeoPolygonFn;
let mockIndexPatterns;
beforeEach(ngMock.module(
'kibana',
'kibana/courier'
));
beforeEach(ngMock.inject(function (Private) {
mockIndexPatterns = Private(IndexPatternMock);
mapGeoPolygonFn = mapGeoPolygon(mockIndexPatterns);
}));
it('should return the key and value for matching filters with bounds', function (done) {
const filter = {
meta: {
index: 'logstash-*'
},
geo_polygon: {
point: { // field name
points: [
{ lat: 5, lon: 10 },
{ lat: 15, lon: 20 }
]
}
}
};
mapGeoPolygonFn(filter).then(function (result) {
expect(result).to.have.property('key', 'point');
expect(result).to.have.property('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, '')).to.be('lat5lon10lat15lon20');
done();
});
});
it('should return undefined for none matching', function (done) {
const filter = { meta: { index: 'logstash-*' }, query: { query_string: { query: 'foo:bar' } } };
mapGeoPolygonFn(filter).catch(function (result) {
expect(result).to.be(filter);
done();
});
});
it('should return the key and value even when using ignore_unmapped', function (done) {
const filter = {
meta: {
index: 'logstash-*'
},
geo_polygon: {
ignore_unmapped: true,
point: { // field name
points: [
{ lat: 5, lon: 10 },
{ lat: 15, lon: 20 }
]
}
}
};
mapGeoPolygonFn(filter).then(function (result) {
expect(result).to.have.property('key', 'point');
expect(result).to.have.property('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, '')).to.be('lat5lon10lat15lon20');
done();
});
});
});
});

View file

@ -1,68 +0,0 @@
/*
* 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 expect from '@kbn/expect';
import ngMock from 'ng_mock';
import { mapMatchAll } from '../map_match_all';
describe('filter_manager/lib', function () {
describe('mapMatchAll()', function () {
let filter;
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function () {
filter = {
match_all: {},
meta: {
field: 'foo',
formattedValue: 'bar'
}
};
}));
describe('when given a filter that is not match_all', function () {
it('filter is rejected', function (done) {
delete filter.match_all;
mapMatchAll(filter).catch(result => {
expect(result).to.be(filter);
done();
});
});
});
describe('when given a match_all filter', function () {
let result;
beforeEach(function (done) {
mapMatchAll(filter).then(r => {
result = r;
done();
});
});
it('key is set to meta field', function () {
expect(result).to.have.property('key', filter.meta.field);
});
it('value is set to meta formattedValue', function () {
expect(result).to.have.property('value', filter.meta.formattedValue);
});
});
});
});

View file

@ -1,58 +0,0 @@
/*
* 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 expect from '@kbn/expect';
import ngMock from 'ng_mock';
import { mapPhrase } from '../map_phrase';
import IndexPatternMock from 'fixtures/mock_index_patterns';
describe('Filter Bar Directive', function () {
describe('mapPhrase()', function () {
let mapPhraseFn;
let mockIndexPatterns;
beforeEach(ngMock.module(
'kibana',
'kibana/courier'
));
beforeEach(ngMock.inject(function (Private) {
mockIndexPatterns = Private(IndexPatternMock);
mapPhraseFn = mapPhrase(mockIndexPatterns);
}));
it('should return the key and value for matching filters', function (done) {
const filter = { meta: { index: 'logstash-*' }, query: { match: { _type: { query: 'apache', type: 'phrase' } } } };
mapPhraseFn(filter).then(function (result) {
expect(result).to.have.property('key', '_type');
expect(result).to.have.property('value', 'apache');
done();
});
});
it('should return undefined for none matching', function (done) {
const filter = { meta: { index: 'logstash-*' }, query: { query_string: { query: 'foo:bar' } } };
mapPhraseFn(filter).catch(function (result) {
expect(result).to.be(filter);
done();
});
});
});
});

View file

@ -1,67 +0,0 @@
/*
* 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 expect from '@kbn/expect';
import ngMock from 'ng_mock';
import { mapRange } from '../map_range';
import IndexPatternMock from 'fixtures/mock_index_patterns';
describe('Filter Bar Directive', function () {
describe('mapRange()', function () {
let mapRangeFn;
let mockIndexPatterns;
beforeEach(ngMock.module(
'kibana',
'kibana/courier'
));
beforeEach(ngMock.inject(function (Private) {
mockIndexPatterns = Private(IndexPatternMock);
mapRangeFn = mapRange(mockIndexPatterns);
}));
it('should return the key and value for matching filters with gt/lt', function (done) {
const filter = { meta: { index: 'logstash-*' }, range: { bytes: { lt: 2048, gt: 1024 } } };
mapRangeFn(filter).then(function (result) {
expect(result).to.have.property('key', 'bytes');
expect(result).to.have.property('value', '1,024 to 2,048');
done();
});
});
it('should return the key and value for matching filters with gte/lte', function (done) {
const filter = { meta: { index: 'logstash-*' }, range: { bytes: { lte: 2048, gte: 1024 } } };
mapRangeFn(filter).then(function (result) {
expect(result).to.have.property('key', 'bytes');
expect(result).to.have.property('value', '1,024 to 2,048');
done();
});
});
it('should return undefined for none matching', function (done) {
const filter = { meta: { index: 'logstash-*' }, query: { query_string: { query: 'foo:bar' } } };
mapRangeFn(filter).catch(function (result) {
expect(result).to.be(filter);
done();
});
});
});
});

View file

@ -1,65 +0,0 @@
/*
* 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 { uniqFilters } from '../uniq_filters';
import expect from '@kbn/expect';
describe('Filter Bar Directive', function () {
describe('uniqFilter', function () {
it('should filter out dups', function () {
const before = [
{ query: { _type: { match: { query: 'apache', type: 'phrase' } } } },
{ query: { _type: { match: { query: 'apache', type: 'phrase' } } } }
];
const results = uniqFilters(before);
expect(results).to.have.length(1);
});
it('should filter out duplicates, ignoring meta attributes', function () {
const before = [
{
meta: { negate: true },
query: { _type: { match: { query: 'apache', type: 'phrase' } } }
},
{
meta: { negate: false },
query: { _type: { match: { query: 'apache', type: 'phrase' } } }
}
];
const results = uniqFilters(before);
expect(results).to.have.length(1);
});
it('should filter out duplicates, ignoring $state attributes', function () {
const before = [
{
$state: { store: 'appState' },
query: { _type: { match: { query: 'apache', type: 'phrase' } } }
},
{
$state: { store: 'globalState' },
query: { _type: { match: { query: 'apache', type: 'phrase' } } }
}
];
const results = uniqFilters(before);
expect(results).to.have.length(1);
});
});
});

View file

@ -16,15 +16,16 @@
* specific language governing permissions and limitations
* under the License.
*/
import expect from '@kbn/expect';
import { changeTimeFilter } from '../change_time_filter';
import { RangeFilter } from '@kbn/es-query';
import { changeTimeFilter } from './change_time_filter';
import { TimeRange } from 'src/plugins/data/public';
import { timefilterServiceMock } from '../../../timefilter/timefilter_service.mock';
import { timefilterServiceMock } from '../../../../timefilter/timefilter_service.mock';
const timefilterMock = timefilterServiceMock.createSetupContract();
const timefilter = timefilterMock.timefilter;
let _time: TimeRange | undefined;
timefilter.setTime.mockImplementation((time: any) => {
_time = {
from: time.from.toISOString(),
@ -35,23 +36,27 @@ timefilter.getTime.mockImplementation(() => {
return _time!;
});
describe('changeTimeFilter()', function() {
describe('changeTimeFilter()', () => {
const gt = 1388559600000;
const lt = 1388646000000;
test('should change the timefilter to match the range gt/lt', function() {
const filter = { range: { '@timestamp': { gt, lt } } };
changeTimeFilter(timefilter, filter);
test('should change the timefilter to match the range gt/lt', () => {
const filter: any = { range: { '@timestamp': { gt, lt } } };
changeTimeFilter(timefilter, filter as RangeFilter);
const { to, from } = timefilter.getTime();
expect(to).to.be(new Date(lt).toISOString());
expect(from).to.be(new Date(gt).toISOString());
expect(to).toBe(new Date(lt).toISOString());
expect(from).toBe(new Date(gt).toISOString());
});
test('should change the timefilter to match the range gte/lte', function() {
const filter = { range: { '@timestamp': { gte: gt, lte: lt } } };
changeTimeFilter(timefilter, filter);
test('should change the timefilter to match the range gte/lte', () => {
const filter: any = { range: { '@timestamp': { gte: gt, lte: lt } } };
changeTimeFilter(timefilter, filter as RangeFilter);
const { to, from } = timefilter.getTime();
expect(to).to.be(new Date(lt).toISOString());
expect(from).to.be(new Date(gt).toISOString());
expect(to).toBe(new Date(lt).toISOString());
expect(from).toBe(new Date(gt).toISOString());
});
});

View file

@ -18,14 +18,18 @@
*/
import moment from 'moment';
import _ from 'lodash';
import { keys } from 'lodash';
import { RangeFilter, isRangeFilter } from '@kbn/es-query';
import { TimefilterContract } from '../../../timefilter';
export function changeTimeFilter(timefilter: TimefilterContract, filter: any) {
const key = _.keys(filter.range)[0];
const values = filter.range[key];
timefilter.setTime({
from: moment(values.gt || values.gte),
to: moment(values.lt || values.lte),
});
export function changeTimeFilter(timeFilter: TimefilterContract, filter: RangeFilter) {
if (isRangeFilter(filter)) {
const key = keys(filter.range)[0];
const values = filter.range[key];
timeFilter.setTime({
from: moment(values.gt || values.gte),
to: moment(values.lt || values.lte),
});
}
}

View file

@ -0,0 +1,73 @@
/*
* 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 { buildQueryFilter, buildEmptyFilter, FilterStateStore } from '@kbn/es-query';
import { compareFilters } from './compare_filters';
describe('filter manager utilities', () => {
describe('compare filters', () => {
test('should compare filters', () => {
const f1 = buildQueryFilter(
{ _type: { match: { query: 'apache', type: 'phrase' } } },
'index'
);
const f2 = buildEmptyFilter(true);
expect(compareFilters(f1, f2)).toBeFalsy();
});
test('should compare duplicates', () => {
const f1 = buildQueryFilter(
{ _type: { match: { query: 'apache', type: 'phrase' } } },
'index'
);
const f2 = buildQueryFilter(
{ _type: { match: { query: 'apache', type: 'phrase' } } },
'index'
);
expect(compareFilters(f1, f2)).toBeTruthy();
});
test('should compare duplicates, ignoring meta attributes', () => {
const f1 = buildQueryFilter(
{ _type: { match: { query: 'apache', type: 'phrase' } } },
'index1'
);
const f2 = buildQueryFilter(
{ _type: { match: { query: 'apache', type: 'phrase' } } },
'index2'
);
expect(compareFilters(f1, f2)).toBeTruthy();
});
test('should compare duplicates, ignoring $state attributes', () => {
const f1 = {
$state: { store: FilterStateStore.APP_STATE },
...buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index'),
};
const f2 = {
$state: { store: FilterStateStore.GLOBAL_STATE },
...buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index'),
};
expect(compareFilters(f1, f2)).toBeTruthy();
});
});
});

View file

@ -17,20 +17,31 @@
* under the License.
*/
import _ from 'lodash';
let excludedAttributes;
let comparators;
import { Filter, FilterMeta } from '@kbn/es-query';
import { defaults, isEqual, omit } from 'lodash';
/**
* Compare two filters to see if they match
*
* @param {object} first The first filter to compare
* @param {object} second The second filter to compare
* @param {object} comparatorOptions Parameters to use for comparison
*
* @returns {bool} Filters are the same
*/
export function compareFilters(first, second, comparatorOptions) {
excludedAttributes = ['$$hashKey', 'meta'];
comparators = _.defaults(comparatorOptions || {}, {
export const compareFilters = (first: Filter, second: Filter, comparatorOptions: any = {}) => {
let comparators: any = {};
const mapFilter = (filter: Filter) => {
const cleaned: FilterMeta = omit(filter, excludedAttributes);
if (comparators.negate) cleaned.negate = filter.meta && Boolean(filter.meta.negate);
if (comparators.disabled) cleaned.disabled = filter.meta && Boolean(filter.meta.disabled);
return cleaned;
};
const excludedAttributes: string[] = ['$$hashKey', 'meta'];
comparators = defaults(comparatorOptions || {}, {
state: false,
negate: false,
disabled: false,
@ -38,12 +49,5 @@ export function compareFilters(first, second, comparatorOptions) {
if (!comparators.state) excludedAttributes.push('$state');
return _.isEqual(mapFilter(first), mapFilter(second));
}
function mapFilter(filter) {
const cleaned = _.omit(filter, excludedAttributes);
if (comparators.negate) cleaned.negate = filter.meta && !!filter.meta.negate;
if (comparators.disabled) cleaned.disabled = filter.meta && !!filter.meta.disabled;
return cleaned;
}
return isEqual(mapFilter(first), mapFilter(second));
};

View file

@ -0,0 +1,79 @@
/*
* 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, buildRangeFilter, FilterStateStore, buildQueryFilter } from '@kbn/es-query';
import { dedupFilters } from './dedup_filters';
describe('filter manager utilities', () => {
describe('dedupFilters(existing, filters)', () => {
test('should return only filters which are not in the existing', () => {
const existing: Filter[] = [
buildRangeFilter({ name: 'bytes' }, { from: 0, to: 1024 }, 'index'),
buildQueryFilter({ match: { _term: { query: 'apache', type: 'phrase' } } }, 'index'),
];
const filters: Filter[] = [
buildRangeFilter({ name: 'bytes' }, { from: 1024, to: 2048 }, 'index'),
buildQueryFilter({ match: { _term: { query: 'apache', type: 'phrase' } } }, 'index'),
];
const results = dedupFilters(existing, filters);
expect(results).toContain(filters[0]);
expect(results).not.toContain(filters[1]);
});
test('should ignore the disabled attribute when comparing ', () => {
const existing: Filter[] = [
buildRangeFilter({ name: 'bytes' }, { from: 0, to: 1024 }, 'index'),
{
...buildQueryFilter({ match: { _term: { query: 'apache', type: 'phrase' } } }, 'index'),
meta: { disabled: true, negate: false, alias: null },
},
];
const filters: Filter[] = [
buildRangeFilter({ name: 'bytes' }, { from: 1024, to: 2048 }, 'index'),
buildQueryFilter({ match: { _term: { query: 'apache', type: 'phrase' } } }, 'index'),
];
const results = dedupFilters(existing, filters);
expect(results).toContain(filters[0]);
expect(results).not.toContain(filters[1]);
});
test('should ignore $state attribute', () => {
const existing: Filter[] = [
buildRangeFilter({ name: 'bytes' }, { from: 0, to: 1024 }, 'index'),
{
...buildQueryFilter({ match: { _term: { query: 'apache', type: 'phrase' } } }, 'index'),
$state: { store: FilterStateStore.APP_STATE },
},
];
const filters: Filter[] = [
buildRangeFilter({ name: 'bytes' }, { from: 1024, to: 2048 }, 'index'),
{
...buildQueryFilter({ match: { _term: { query: 'apache', type: 'phrase' } } }, 'index'),
$state: { store: FilterStateStore.GLOBAL_STATE },
},
];
const results = dedupFilters(existing, filters);
expect(results).toContain(filters[0]);
expect(results).not.toContain(filters[1]);
});
});
});

View file

@ -17,22 +17,33 @@
* under the License.
*/
import _ from 'lodash';
import { Filter } from '@kbn/es-query';
import { filter, find } from 'lodash';
import { compareFilters } from './compare_filters';
/**
* Combine 2 filter collections, removing duplicates
* @param {object} existing The filters to compare to
* @param {object} filters The filters being added
* @param {object} comparatorOptions Parameters to use for comparison
*
* @param {object} existingFilters - The filters to compare to
* @param {object} filters - The filters being added
* @param {object} comparatorOptions - Parameters to use for comparison
*
* @returns {object} An array of filters that were not in existing
*/
export function dedupFilters(existingFilters, filters, comparatorOptions) {
if (!Array.isArray(filters)) filters = [filters];
export const dedupFilters = (
existingFilters: Filter[],
filters: Filter[],
comparatorOptions: any = {}
) => {
if (!Array.isArray(filters)) {
filters = [filters];
}
return _.filter(filters, function (filter) {
return !_.find(existingFilters, function (existingFilter) {
return compareFilters(existingFilter, filter, comparatorOptions);
});
});
}
return filter(
filters,
(f: Filter) =>
!find(existingFilters, (existingFilter: Filter) =>
compareFilters(existingFilter, f, comparatorOptions)
)
);
};

View file

@ -0,0 +1,57 @@
/*
* 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, buildRangeFilter, buildQueryFilter } from '@kbn/es-query';
import { extractTimeFilter } from './extract_time_filter';
import { IndexPatterns } from '../../../index_patterns';
const mockIndexPatterns = jest.fn(
() =>
({
get: () => ({
timeFieldName: 'time',
}),
} as any)
);
describe('filter manager utilities', () => {
describe('extractTimeFilter()', () => {
const indexPatterns = mockIndexPatterns() as IndexPatterns;
test('should return the matching filter for the default time field', async () => {
const filters: Filter[] = [
buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'logstash-*'),
buildRangeFilter({ name: 'time' }, { gt: 1388559600000, lt: 1388646000000 }, 'logstash-*'),
];
const result = await extractTimeFilter(indexPatterns, filters);
expect(result).toEqual(filters[1]);
});
test('should not return the non-matching filter for the default time field', async () => {
const filters: Filter[] = [
buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'logstash-*'),
buildRangeFilter({ name: '@timestamp' }, { from: 1, to: 2 }, 'logstash-*'),
];
const result = await extractTimeFilter(indexPatterns, filters);
expect(result).toBeUndefined();
});
});
});

View file

@ -17,22 +17,25 @@
* under the License.
*/
import _ from 'lodash';
import { keys, find, get } from 'lodash';
import { Filter, isRangeFilter } from '@kbn/es-query';
import { IndexPatterns } from '../../../index_patterns';
export async function extractTimeFilter(indexPatterns: IndexPatterns, filters: any) {
export async function extractTimeFilter(indexPatterns: IndexPatterns, filters: Filter[]) {
// Assume all the index patterns are the same since they will be added
// from the same visualization.
const id: string = _.get(filters, '[0].meta.index');
const id: string = get(filters, '[0].meta.index');
if (id == null) return;
const indexPattern = await indexPatterns.get(id);
const filter = _.find(filters, function(obj: any) {
const key = _.keys(obj.range)[0];
return key === indexPattern.timeFieldName;
return find(filters, (obj: Filter) => {
let key;
if (isRangeFilter(obj)) {
key = keys(obj.range)[0];
}
return Boolean(key && key === indexPattern.timeFieldName);
});
if (filter && filter.range) {
return filter;
}
}

View file

@ -0,0 +1,114 @@
/*
* 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 sinon from 'sinon';
import { Filter, buildEmptyFilter } from '@kbn/es-query';
import { generateMappingChain } from './generate_mapping_chain';
describe('filter manager utilities', () => {
let mapping: any;
let next: any;
beforeEach(() => {
mapping = sinon.stub();
next = sinon.stub();
});
describe('generateMappingChain()', () => {
test('should create a chaining function which calls the next function if the promise is rejected', async () => {
const filter: Filter = buildEmptyFilter(true);
mapping.rejects(filter);
next.resolves('good');
const chain = generateMappingChain(mapping, next);
const result = await 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 () => {
const filter: Filter = buildEmptyFilter(true);
mapping.resolves('good');
next.resolves('bad');
const chain = generateMappingChain(mapping, next);
const result = await chain(filter);
expect(result).toBe('good');
});
test('should resolve result for the mapping function', async () => {
const filter: Filter = buildEmptyFilter(true);
mapping.resolves({ key: 'test', value: 'example' });
const chain = generateMappingChain(mapping, next);
const result = await chain(filter);
sinon.assert.notCalled(next);
expect(result).toEqual({ key: 'test', value: 'example' });
});
test('should call the mapping function with the argument to the chain', async () => {
// @ts-ignore
const filter: Filter = { test: 'example' };
mapping.resolves({ key: 'test', value: 'example' });
const chain = generateMappingChain(mapping, next);
const result = await chain(filter);
sinon.assert.calledOnce(mapping);
expect(mapping.args[0][0]).toEqual({ test: 'example' });
sinon.assert.notCalled(next);
expect(result).toEqual({ key: 'test', value: 'example' });
});
test('should resolve result for the next function', async () => {
const filter: Filter = buildEmptyFilter(true);
mapping.rejects(filter);
next.resolves({ key: 'test', value: 'example' });
const chain = generateMappingChain(mapping, next);
const result = await 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 => {
const filter: Filter = buildEmptyFilter(true);
mapping.rejects(filter);
const chain = generateMappingChain(mapping);
chain(filter).catch(err => {
expect(err).toBeInstanceOf(Error);
expect(err.message).toBe('No mappings have been found for filter.');
done();
});
});
});
});

View file

@ -16,19 +16,19 @@
* specific language governing permissions and limitations
* under the License.
*/
import { Filter } from '@kbn/es-query';
export function generateMappingChain(fn, next) {
const noop = function () {
throw new Error('No mappings have been found for filter.');
};
const noop = () => {
throw new Error('No mappings have been found for filter.');
};
next = next || noop;
return async function (filter) {
return await fn(filter).catch(function (result) {
export const generateMappingChain = (fn: Function, next: Function = noop) => {
return async (filter: Filter) => {
return await fn(filter).catch((result: any) => {
if (result === filter) {
return next(filter);
}
throw result;
});
};
}
};

View file

@ -0,0 +1,71 @@
/*
* 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 { 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;
beforeEach(() => {
mockIndexPatterns = new StubIndexPatterns();
filters = [
null,
[
{ meta: { index: 'logstash-*' }, exists: { field: '_type' } },
{ meta: { index: 'logstash-*' }, missing: { field: '_type' } },
],
{ meta: { index: 'logstash-*' }, query: { query_string: { query: 'foo:bar' } } },
{ meta: { index: 'logstash-*' }, range: { bytes: { lt: 2048, gt: 1024 } } },
{
meta: { index: 'logstash-*' },
query: { match: { _type: { query: 'apache', type: 'phrase' } } },
},
];
});
test('should map and flatten the filters', async () => {
const results = await mapAndFlattenFilters(
mockIndexPatterns as IndexPatterns,
filters as Filter[]
);
expect(results).toHaveLength(5);
expect(results[0]).toHaveProperty('meta');
expect(results[1]).toHaveProperty('meta');
expect(results[2]).toHaveProperty('meta');
expect(results[3]).toHaveProperty('meta');
expect(results[4]).toHaveProperty('meta');
expect(results[0].meta).toHaveProperty('key', '_type');
expect(results[0].meta).toHaveProperty('value', 'exists');
expect(results[1].meta).toHaveProperty('key', '_type');
expect(results[1].meta).toHaveProperty('value', 'missing');
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[4].meta).toHaveProperty('key', '_type');
expect(results[4].meta).toHaveProperty('value', 'apache');
});
});
});

View file

@ -0,0 +1,29 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { 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);
};

View file

@ -16,29 +16,27 @@
* specific language governing permissions and limitations
* under the License.
*/
import { CustomFilter, buildEmptyFilter, buildQueryFilter } from '@kbn/es-query';
import { mapDefault } from './map_default';
import expect from '@kbn/expect';
import { mapMissing } from '../map_missing';
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);
describe('Filter Bar Directive', function () {
describe('mapMissing()', function () {
it('should return the key and value for matching filters', function (done) {
const filter = { missing: { field: '_type' } };
mapMissing(filter).then(function (result) {
expect(result).to.have.property('key', '_type');
expect(result).to.have.property('value', 'missing');
done();
});
expect(result).toHaveProperty('key', 'query');
expect(result).toHaveProperty('value', '{"match_all":{}}');
});
it('should return undefined for none matching', function (done) {
const filter = { query: { match: { query: 'foo' } } };
mapMissing(filter).catch(function (result) {
expect(result).to.be(filter);
done();
});
});
test('should return undefined if there is no valid key', async () => {
const filter = buildEmptyFilter(true) as CustomFilter;
try {
await mapDefault(filter);
} catch (e) {
expect(e).toBe(filter);
}
});
});
});

View file

@ -17,21 +17,19 @@
* under the License.
*/
import angular from 'angular';
import _ from 'lodash';
import { Filter, FILTERS } from '@kbn/es-query';
import { find, keys, get } from 'lodash';
export async function mapDefault(filter) {
export const mapDefault = async (filter: Filter) => {
const metaProperty = /(^\$|meta)/;
const key = _.find(_.keys(filter), function (key) {
return !key.match(metaProperty);
});
const key = find(keys(filter), item => !item.match(metaProperty));
if (key) {
const type = 'custom';
const value = angular.toJson(filter[key]);
const type = FILTERS.CUSTOM;
const value = JSON.stringify(get(filter, key, {}));
return { type, key, value };
}
throw filter;
}
};

View file

@ -16,29 +16,29 @@
* specific language governing permissions and limitations
* under the License.
*/
import { ExistsFilter, buildEmptyFilter, buildExistsFilter } from '@kbn/es-query';
import { mapExists } from './map_exists';
import { mapQueryString } from './map_query_string';
import expect from '@kbn/expect';
import { mapQueryString } from '../map_query_string';
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);
describe('Filter Bar Directive', function () {
describe('mapQueryString()', function () {
it('should return the key and value for matching filters', function (done) {
const filter = { query: { query_string: { query: 'foo:bar' } } };
mapQueryString(filter).then(function (result) {
expect(result).to.have.property('key', 'query');
expect(result).to.have.property('value', 'foo:bar');
done();
});
expect(result).toHaveProperty('key', '_type');
expect(result).toHaveProperty('value', 'exists');
});
it('should return undefined for none matching', function (done) {
const filter = { query: { match: { query: 'foo' } } };
mapQueryString(filter).catch(function (result) {
expect(result).to.be(filter);
done();
});
});
test('should return undefined for none matching', async done => {
const filter = buildEmptyFilter(true) as ExistsFilter;
try {
await mapQueryString(filter);
} catch (e) {
expect(e).toBe(filter);
done();
}
});
});
});

View file

@ -17,12 +17,16 @@
* under the License.
*/
export async function mapQueryString(filter) {
if (filter.query && filter.query.query_string) {
const type = 'query_string';
const key = 'query';
const value = filter.query.query_string.query;
return { type, key, value };
import { Filter, isExistsFilter, FILTERS } from '@kbn/es-query';
import { get } from 'lodash';
export const mapExists = async (filter: Filter) => {
if (isExistsFilter(filter)) {
return {
type: FILTERS.EXISTS,
value: FILTERS.EXISTS,
key: get(filter, 'exists.field'),
};
}
throw filter;
}
};

View file

@ -0,0 +1,95 @@
/*
* 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 { 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;
});
describe('mapFilter()', () => {
test('should map query filters', async () => {
const before = {
meta: { index: 'logstash-*' },
query: { match: { _type: { query: 'apache' } } },
};
const after = await mapFilter(indexPatterns, before as Filter);
expect(after).toHaveProperty('meta');
expect(after.meta).toHaveProperty('key', '_type');
expect(after.meta).toHaveProperty('value', '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);
expect(after).toHaveProperty('meta');
expect(after.meta).toHaveProperty('key', '@timestamp');
expect(after.meta).toHaveProperty('value', '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);
expect(after).toHaveProperty('meta');
expect(after.meta).toHaveProperty('key', '@timestamp');
expect(after.meta).toHaveProperty('value', '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);
expect(after).toHaveProperty('meta');
expect(after.meta).toHaveProperty('key', 'query');
expect(after.meta).toHaveProperty('value', '{"match_all":{}}');
expect(after.meta).toHaveProperty('disabled', false);
expect(after.meta).toHaveProperty('negate', false);
});
test('should finish with a catch', async done => {
const before: any = { meta: { index: 'logstash-*' } };
try {
await mapFilter(indexPatterns, before as Filter);
} catch (e) {
expect(e).toBeInstanceOf(Error);
expect(e.message).toBe('No mappings have been found for filter.');
done();
}
});
});
});

View file

@ -17,7 +17,10 @@
* under the License.
*/
import _ from 'lodash';
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';
import { mapPhrases } from './map_phrases';
@ -30,7 +33,7 @@ import { mapGeoPolygon } from './map_geo_polygon';
import { mapDefault } from './map_default';
import { generateMappingChain } from './generate_mapping_chain';
export async function mapFilter(indexPatterns, filter) {
export async function mapFilter(indexPatterns: IndexPatterns, filter: Filter) {
/** Mappers **/
// Each mapper is a simple promise function that test if the mapper can
@ -60,16 +63,17 @@ export async function mapFilter(indexPatterns, filter) {
mapDefault,
];
const noop = function () {
const noop = () => {
throw new Error('No mappings have been found for filter.');
};
// Create a chain of responsibility by reducing all the
// mappers down into one function.
const mapFn = _.reduceRight(mappers, function (memo, map) {
return generateMappingChain(map, memo);
}, noop);
const mapFn = reduceRight<Function, Function>(
mappers,
(memo, map) => generateMappingChain(map, memo),
noop
);
const mapped = await mapFn(filter);
// Map the filter into an object with the key and value exposed so it's
@ -79,8 +83,8 @@ export async function mapFilter(indexPatterns, filter) {
filter.meta.key = mapped.key;
filter.meta.value = mapped.value;
filter.meta.params = mapped.params;
filter.meta.disabled = !!(filter.meta.disabled);
filter.meta.negate = !!(filter.meta.negate);
filter.meta.disabled = Boolean(filter.meta.disabled);
filter.meta.negate = Boolean(filter.meta.negate);
filter.meta.alias = filter.meta.alias || null;
return filter;

View file

@ -1,58 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import _ from 'lodash';
import { SavedObjectNotFound } from '../../../../../../../plugins/kibana_utils/public';
function getParams(filter, indexPattern) {
const type = 'geo_bounding_box';
const key = _.keys(filter.geo_bounding_box)
.filter(key => key !== 'ignore_unmapped')[0];
const params = filter.geo_bounding_box[key];
// 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.
const topLeft = indexPattern
? indexPattern.fields.byName[key].format.convert(params.top_left)
: JSON.stringify(params.top_left);
const bottomRight = indexPattern
? indexPattern.fields.byName[key].format.convert(params.bottom_right)
: JSON.stringify(params.bottom_right);
const value = topLeft + ' to ' + bottomRight;
return { type, key, value, params };
}
export function mapGeoBoundingBox(indexPatterns) {
return async function (filter) {
if (!filter.geo_bounding_box) {
throw filter;
}
try {
const indexPattern = await indexPatterns.get(filter.meta.index);
return getParams(filter, indexPattern);
} catch (error) {
if (error instanceof SavedObjectNotFound) {
return getParams(filter);
}
throw error;
}
};
}

View file

@ -0,0 +1,96 @@
/*
* 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 { mapGeoBoundingBox } from './map_geo_bounding_box';
import { StubIndexPatterns } from '../test_helpers/stub_index_pattern';
import { IndexPatterns } from '../../../index_patterns';
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: {
index: 'logstash-*',
},
geo_bounding_box: {
point: {
// field name
top_left: { lat: 5, lon: 10 },
bottom_right: { lat: 15, lon: 20 },
},
},
};
const result = await mapGeoBoundingBoxFn(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'
);
});
test('should return the key and value even when using ignore_unmapped', async () => {
const filter = {
meta: {
index: 'logstash-*',
},
geo_bounding_box: {
ignore_unmapped: true,
point: {
// field name
top_left: { lat: 5, lon: 10 },
bottom_right: { lat: 15, lon: 20 },
},
},
};
const result = await mapGeoBoundingBoxFn(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'
);
});
test('should return undefined for none matching', async done => {
const filter = {
meta: { index: 'logstash-*' },
query: { query_string: { query: 'foo:bar' } },
};
try {
await mapGeoBoundingBoxFn(filter);
} catch (e) {
expect(e).toBe(filter);
done();
}
});
});
});

View file

@ -0,0 +1,73 @@
/*
* 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 { 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';
const getFormattedValue = (params: any, key: string, indexPattern?: IndexPattern) => {
const formatter: any =
indexPattern && key && get(indexPattern, ['fields', 'byName', key, 'format']);
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),
};
};
const getParams = (filter: GeoBoundingBoxFilter, indexPattern?: IndexPattern) => {
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,
};
};
export const mapGeoBoundingBox = (indexPatterns: IndexPatterns) => {
return async (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;
}
};
};

View file

@ -0,0 +1,94 @@
/*
* 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 { mapGeoPolygon } from './map_geo_polygon';
import { StubIndexPatterns } from '../test_helpers/stub_index_pattern';
import { IndexPatterns } from '../../../index_patterns';
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: {
index: 'logstash-*',
},
geo_polygon: {
point: {
points: [{ lat: 5, lon: 10 }, { lat: 15, lon: 20 }],
},
},
};
const result = await mapGeoPolygonFn(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'
);
});
test('should return the key and value even when using ignore_unmapped', async () => {
const filter = {
meta: {
index: 'logstash-*',
},
geo_polygon: {
ignore_unmapped: true,
point: {
points: [{ lat: 5, lon: 10 }, { lat: 15, lon: 20 }],
},
},
};
const result = await mapGeoPolygonFn(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'
);
});
test('should return undefined for none matching', async done => {
const filter = {
meta: { index: 'logstash-*' },
query: { query_string: { query: 'foo:bar' } },
};
try {
await mapGeoPolygonFn(filter);
} catch (e) {
expect(e).toBe(filter);
done();
}
});
});
});

View file

@ -16,37 +16,47 @@
* specific language governing permissions and limitations
* under the License.
*/
import _ from 'lodash';
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';
function getParams(filter, indexPattern) {
const type = 'geo_polygon';
const key = _.keys(filter.geo_polygon)
.filter(key => key !== 'ignore_unmapped')[0];
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);
};
function getParams(filter: GeoPolygonFilter, indexPattern?: IndexPattern) {
const key = Object.keys(filter.geo_polygon).filter(k => k !== 'ignore_unmapped')[0];
const params = filter.geo_polygon[key];
// 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.
const points = params.points.map((point) => {
return indexPattern
? indexPattern.fields.byName[key].format.convert(point)
: JSON.stringify(point);
});
const value = points.join(', ');
return { type, key, value, params };
return {
key,
params,
type: FILTERS.GEO_POLYGON,
value: (params.points || [])
.map((point: string) => getFormattedValue(point, key, indexPattern))
.join(POINTS_SEPARATOR),
};
}
export function mapGeoPolygon(indexPatterns) {
return async function (filter) {
if (!filter.geo_polygon) {
export function mapGeoPolygon(indexPatterns: IndexPatterns) {
return async function(filter: Filter) {
if (!isGeoPolygonFilter(filter)) {
throw filter;
}
try {
const indexPattern = await indexPatterns.get(filter.meta.index);
let indexPattern;
if (filter.meta.index) {
indexPattern = await indexPatterns.get(filter.meta.index);
}
return getParams(filter, indexPattern);
} catch (error) {
if (error instanceof SavedObjectNotFound) {

View file

@ -0,0 +1,66 @@
/*
* 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 { MatchAllFilter } from '@kbn/es-query';
import { mapMatchAll } from './map_match_all';
describe('filter_manager/lib', () => {
describe('mapMatchAll()', () => {
let filter: MatchAllFilter;
beforeEach(() => {
filter = {
match_all: {},
meta: {
alias: null,
negate: true,
disabled: false,
field: 'foo',
formattedValue: 'bar',
},
};
});
describe('when given a filter that is not match_all', () => {
test('filter is rejected', async done => {
delete filter.match_all;
try {
await mapMatchAll(filter);
} catch (e) {
expect(e).toBe(filter);
done();
}
});
});
describe('when given a match_all filter', () => {
test('key is set to meta field', async () => {
const result = await mapMatchAll(filter);
expect(result).toHaveProperty('key', filter.meta.field);
});
test('value is set to meta formattedValue', async () => {
const result = await mapMatchAll(filter);
expect(result).toHaveProperty('value', filter.meta.formattedValue);
});
});
});
});

View file

@ -16,13 +16,15 @@
* specific language governing permissions and limitations
* under the License.
*/
import { Filter, FILTERS, isMatchAllFilter } from '@kbn/es-query';
export async function mapMatchAll(filter) {
if (filter.match_all) {
const type = 'match_all';
const key = filter.meta.field;
const value = filter.meta.formattedValue || 'all';
return { type, key, value };
export const mapMatchAll = async (filter: Filter) => {
if (isMatchAllFilter(filter)) {
return {
type: FILTERS.MATCH_ALL,
key: filter.meta.field,
value: filter.meta.formattedValue || 'all',
};
}
throw filter;
}
};

View file

@ -16,32 +16,31 @@
* specific language governing permissions and limitations
* under the License.
*/
import { MissingFilter, buildEmptyFilter, ExistsFilter } from '@kbn/es-query';
import { mapMissing } from './map_missing';
import expect from '@kbn/expect';
import ngMock from 'ng_mock';
import { mapExists } from '../map_exists';
describe('filter manager utilities', () => {
describe('mapMissing()', () => {
test('should return the key and value for matching filters', async () => {
const filter: MissingFilter = {
missing: { field: '_type' },
...buildEmptyFilter(true),
};
const result = await mapMissing(filter);
describe('Filter Bar Directive', function () {
describe('mapExists()', function () {
beforeEach(ngMock.module('kibana'));
it('should return the key and value for matching filters', function (done) {
const filter = { exists: { field: '_type' } };
mapExists(filter).then(function (result) {
expect(result).to.have.property('key', '_type');
expect(result).to.have.property('value', 'exists');
done();
});
expect(result).toHaveProperty('key', '_type');
expect(result).toHaveProperty('value', 'missing');
});
it('should return undefined for none matching', function (done) {
const filter = { query: { match: { query: 'foo' } } };
mapExists(filter).catch(function (result) {
expect(result).to.be(filter);
done();
});
});
test('should return undefined for none matching', async done => {
const filter = buildEmptyFilter(true) as ExistsFilter;
try {
await mapMissing(filter);
} catch (e) {
expect(e).toBe(filter);
done();
}
});
});
});

View file

@ -16,13 +16,16 @@
* specific language governing permissions and limitations
* under the License.
*/
import { Filter, FILTERS, isMissingFilter } from '@kbn/es-query';
export async function mapMissing(filter) {
if (filter.missing) {
const type = 'missing';
const key = filter.missing.field;
const value = type;
return { type, key, value };
export const mapMissing = async (filter: Filter) => {
if (isMissingFilter(filter)) {
return {
type: FILTERS.MISSING,
value: FILTERS.MISSING,
key: filter.missing.field,
};
}
throw filter;
}
};

View file

@ -1,64 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import _ from 'lodash';
import { SavedObjectNotFound } from '../../../../../../../plugins/kibana_utils/public';
function isScriptedPhrase(filter) {
const value = _.get(filter, ['script', 'script', 'params', 'value']);
return typeof value !== 'undefined';
}
function getParams(filter, indexPattern) {
const isScriptedPhraseFilter = isScriptedPhrase(filter);
const type = 'phrase';
const key = isScriptedPhraseFilter ? filter.meta.field : Object.keys(filter.query.match)[0];
const query = isScriptedPhraseFilter ? filter.script.script.params.value : filter.query.match[key].query;
const params = { query };
// Sometimes a filter will end up with an invalid index or field 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 or field is invalid.
const value = (
indexPattern &&
indexPattern.fields &&
indexPattern.fields.byName[key]
) ? indexPattern.fields.byName[key].format.convert(query) : query;
return { type, key, value, params };
}
export function mapPhrase(indexPatterns) {
return async function (filter) {
const isScriptedPhraseFilter = isScriptedPhrase(filter);
if (!_.has(filter, ['query', 'match']) && !isScriptedPhraseFilter) {
throw filter;
}
try {
const indexPattern = await indexPatterns.get(filter.meta.index);
return getParams(filter, indexPattern);
} catch (error) {
if (error instanceof SavedObjectNotFound) {
return getParams(filter);
}
throw error;
}
};
}

View file

@ -0,0 +1,58 @@
/*
* 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 { mapPhrase } from './map_phrase';
import { StubIndexPatterns } from '../test_helpers/stub_index_pattern';
import { IndexPatterns } from '../../../index_patterns';
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);
expect(result).toHaveProperty('key', '_type');
expect(result).toHaveProperty('value', 'apache');
});
test('should return undefined for none matching', async done => {
const filter = {
meta: { index: 'logstash-*' },
query: { query_string: { query: 'foo:bar' } },
};
try {
await mapPhraseFn(filter);
} catch (e) {
expect(e).toBe(filter);
done();
}
});
});
});

View file

@ -0,0 +1,80 @@
/*
* 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 { get } from 'lodash';
import {
PhraseFilter,
Filter,
FILTERS,
isPhraseFilter,
isScriptedPhraseFilter,
} 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 getParams = (filter: PhraseFilter, indexPattern?: IndexPattern) => {
const scriptedPhraseValue = getScriptedPhraseValue(filter);
const isScriptedFilter = Boolean(scriptedPhraseValue);
const key = isScriptedFilter ? filter.meta.field || '' : Object.keys(filter.query.match)[0];
const query = scriptedPhraseValue || get(filter, ['query', 'match', key, 'query']);
const params = { query };
return {
key,
params,
type: FILTERS.PHRASE,
value: getFormattedValue(query, key, indexPattern),
};
};
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;
}
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;
}
};
};

View file

@ -17,11 +17,14 @@
* under the License.
*/
export async function mapPhrases(filter) {
const { type, key, value, params } = filter.meta;
if (type !== 'phrases') {
import { Filter, isPhrasesFilter } from '@kbn/es-query';
export const mapPhrases = async (filter: Filter) => {
if (!isPhrasesFilter(filter)) {
throw filter;
} else {
return { type, key, value, params };
}
}
const { type, key, value, params } = filter.meta;
return { type, key, value, params };
};

View file

@ -0,0 +1,47 @@
/*
* 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 { QueryStringFilter, buildQueryFilter, buildEmptyFilter } from '@kbn/es-query';
import { mapQueryString } from './map_query_string';
describe('filter manager utilities', () => {
describe('mapQueryString()', () => {
test('should return the key and value for matching filters', async () => {
const filter: QueryStringFilter = buildQueryFilter(
{ query_string: { query: 'foo:bar' } },
'index'
);
const result = await mapQueryString(filter);
expect(result).toHaveProperty('key', 'query');
expect(result).toHaveProperty('value', 'foo:bar');
});
test('should return undefined for none matching', async done => {
const filter = buildEmptyFilter(true) as QueryStringFilter;
try {
await mapQueryString(filter);
} catch (e) {
expect(e).toBe(filter);
done();
}
});
});
});

View file

@ -16,13 +16,16 @@
* specific language governing permissions and limitations
* under the License.
*/
import { Filter, FILTERS, isQueryStringFilter } from '@kbn/es-query';
export async function mapExists(filter) {
if (filter.exists) {
const type = 'exists';
const key = filter.exists.field;
const value = type;
return { type, key, value };
export const mapQueryString = async (filter: Filter) => {
if (isQueryStringFilter(filter)) {
return {
type: FILTERS.QUERY_STRING,
key: 'query',
value: filter.query.query_string.query,
};
}
throw filter;
}
};

View file

@ -0,0 +1,65 @@
/*
* 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 { mapRange } from './map_range';
import { StubIndexPatterns } from '../test_helpers/stub_index_pattern';
import { IndexPatterns } from '../../../index_patterns';
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);
expect(result).toHaveProperty('key', 'bytes');
expect(result).toHaveProperty('value', '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);
expect(result).toHaveProperty('key', 'bytes');
expect(result).toHaveProperty('value', '1024 to 2048');
});
test('should return undefined for none matching', async done => {
const filter = {
meta: { index: 'logstash-*' },
query: { query_string: { query: 'foo:bar' } },
};
try {
await mapRangeFn(filter);
} catch (e) {
expect(e).toBe(filter);
done();
}
});
});
});

View file

@ -17,20 +17,20 @@
* under the License.
*/
import { has, get } from 'lodash';
import { Filter, RangeFilter, FILTERS, isRangeFilter, isScriptedRangeFilter } from '@kbn/es-query';
import { get, has } from 'lodash';
import { SavedObjectNotFound } from '../../../../../../../plugins/kibana_utils/public';
import { IndexPatterns, IndexPattern } from '../../../index_patterns';
const getFirstRangeKey = (filter: RangeFilter) => filter.range && Object.keys(filter.range)[0];
const getRangeByKey = (filter: RangeFilter, key: string) => get(filter, ['range', key]);
function isScriptedRange(filter) {
const params = get(filter, ['script', 'script', 'params']);
return params && Object.keys(params).find(key => ['gte', 'gt', 'lte', 'lt'].includes(key));
}
function getParams(filter, indexPattern) {
const isScriptedRangeFilter = isScriptedRange(filter);
const type = 'range';
const key = isScriptedRangeFilter ? filter.meta.field : Object.keys(filter.range)[0];
const params = isScriptedRangeFilter ? filter.script.script.params : filter.range[key];
function getParams(filter: RangeFilter, indexPattern?: IndexPattern) {
const isScriptedRange = isScriptedRangeFilter(filter);
const key: string = (isScriptedRange ? filter.meta.field : getFirstRangeKey(filter)) || '';
const params: any = isScriptedRange
? get(filter, 'script.script.params')
: getRangeByKey(filter, key);
let left = has(params, 'gte') ? params.gte : params.gt;
if (left == null) left = -Infinity;
@ -38,28 +38,37 @@ function getParams(filter, 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.
let value = `${left} to ${right}`;
if (indexPattern && indexPattern.fields.byName[key]) {
if (key && indexPattern && indexPattern.fields.byName[key]) {
const convert = indexPattern.fields.byName[key].format.getConverterFor('text');
value = `${convert(left)} to ${convert(right)}`;
}
return { type, key, value, params };
return { type: FILTERS.RANGE, key, value, params };
}
export function mapRange(indexPatterns) {
return async function (filter) {
const isScriptedRangeFilter = isScriptedRange(filter);
if (!filter.range && !isScriptedRangeFilter) {
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;
}
try {
const indexPattern = await indexPatterns.get(filter.meta.index);
let indexPattern;
if (filter.meta.index) {
indexPattern = await indexPatterns.get(filter.meta.index);
}
return getParams(filter, indexPattern);
} catch (error) {
if (error instanceof SavedObjectNotFound) {
@ -68,4 +77,4 @@ export function mapRange(indexPatterns) {
throw error;
}
};
}
};

View file

@ -18,83 +18,89 @@
*/
import { Filter } from '@kbn/es-query';
import { onlyDisabledFiltersChanged } from './only_disabled';
import expect from '@kbn/expect';
describe('Filter Bar Directive', function() {
describe('onlyDisabledFiltersChanged()', function() {
it('should return true if all filters are disabled', function() {
describe('filter manager utilities', () => {
describe('onlyDisabledFiltersChanged()', () => {
test('should return true if all filters are disabled', () => {
const filters = [
{ meta: { disabled: true } },
{ meta: { disabled: true } },
{ meta: { disabled: true } },
] as Filter[];
const newFilters = [{ meta: { disabled: true } }] as Filter[];
expect(onlyDisabledFiltersChanged(newFilters, filters)).to.be(true);
expect(onlyDisabledFiltersChanged(newFilters, filters)).toBe(true);
});
it('should return false if there are no old filters', function() {
test('should return false if there are no old filters', () => {
const newFilters = [{ meta: { disabled: false } }] as Filter[];
expect(onlyDisabledFiltersChanged(newFilters, undefined)).to.be(false);
expect(onlyDisabledFiltersChanged(newFilters, undefined)).toBe(false);
});
it('should return false if there are no new filters', function() {
test('should return false if there are no new filters', () => {
const filters = [{ meta: { disabled: false } }] as Filter[];
expect(onlyDisabledFiltersChanged(undefined, filters)).to.be(false);
expect(onlyDisabledFiltersChanged(undefined, filters)).toBe(false);
});
it('should return false if all filters are not disabled', function() {
test('should return false if all filters are not disabled', () => {
const filters = [
{ meta: { disabled: false } },
{ meta: { disabled: false } },
{ meta: { disabled: false } },
] as Filter[];
const newFilters = [{ meta: { disabled: false } }] as Filter[];
expect(onlyDisabledFiltersChanged(newFilters, filters)).to.be(false);
expect(onlyDisabledFiltersChanged(newFilters, filters)).toBe(false);
});
it('should return false if only old filters are disabled', function() {
test('should return false if only old filters are disabled', () => {
const filters = [
{ meta: { disabled: true } },
{ meta: { disabled: true } },
{ meta: { disabled: true } },
] as Filter[];
const newFilters = [{ meta: { disabled: false } }] as Filter[];
expect(onlyDisabledFiltersChanged(newFilters, filters)).to.be(false);
expect(onlyDisabledFiltersChanged(newFilters, filters)).toBe(false);
});
it('should return false if new filters are not disabled', function() {
test('should return false if new filters are not disabled', () => {
const filters = [
{ meta: { disabled: false } },
{ meta: { disabled: false } },
{ meta: { disabled: false } },
] as Filter[];
const newFilters = [{ meta: { disabled: true } }] as Filter[];
expect(onlyDisabledFiltersChanged(newFilters, filters)).to.be(false);
expect(onlyDisabledFiltersChanged(newFilters, filters)).toBe(false);
});
it('should return true when all removed filters were disabled', function() {
test('should return true when all removed filters were disabled', () => {
const filters = [
{ meta: { disabled: true } },
{ meta: { disabled: true } },
{ meta: { disabled: true } },
] as Filter[];
const newFilters = [] as Filter[];
expect(onlyDisabledFiltersChanged(newFilters, filters)).to.be(true);
expect(onlyDisabledFiltersChanged(newFilters, filters)).toBe(true);
});
it('should return false when all removed filters were not disabled', function() {
test('should return false when all removed filters were not disabled', () => {
const filters = [
{ meta: { disabled: false } },
{ meta: { disabled: false } },
{ meta: { disabled: false } },
] as Filter[];
const newFilters = [] as Filter[];
expect(onlyDisabledFiltersChanged(newFilters, filters)).to.be(false);
expect(onlyDisabledFiltersChanged(newFilters, filters)).toBe(false);
});
it('should return true if all changed filters are disabled', function() {
test('should return true if all changed filters are disabled', () => {
const filters = [
{ meta: { disabled: true, negate: false } },
{ meta: { disabled: true, negate: false } },
@ -103,35 +109,39 @@ describe('Filter Bar Directive', function() {
{ meta: { disabled: true, negate: true } },
{ meta: { disabled: true, negate: true } },
] as Filter[];
expect(onlyDisabledFiltersChanged(newFilters, filters)).to.be(true);
expect(onlyDisabledFiltersChanged(newFilters, filters)).toBe(true);
});
it('should return false if all filters remove were not disabled', function() {
test('should return false if all filters remove were not disabled', () => {
const filters = [
{ meta: { disabled: false } },
{ meta: { disabled: false } },
{ meta: { disabled: true } },
] as Filter[];
const newFilters = [{ meta: { disabled: false } }] as Filter[];
expect(onlyDisabledFiltersChanged(newFilters, filters)).to.be(false);
expect(onlyDisabledFiltersChanged(newFilters, filters)).toBe(false);
});
it('should return false when all removed filters are not disabled', function() {
test('should return false when all removed filters are not disabled', () => {
const filters = [
{ meta: { disabled: true } },
{ meta: { disabled: false } },
{ meta: { disabled: true } },
] as Filter[];
const newFilters = [] as Filter[];
expect(onlyDisabledFiltersChanged(newFilters, filters)).to.be(false);
expect(onlyDisabledFiltersChanged(newFilters, filters)).toBe(false);
});
it('should not throw with null filters', function() {
test('should not throw with null filters', () => {
const filters = [null, { meta: { disabled: true } }] as Filter[];
const newFilters = [] as Filter[];
expect(function() {
expect(() => {
onlyDisabledFiltersChanged(newFilters, filters);
}).to.not.throwError();
}).not.toThrowError();
});
});
});

View file

@ -17,20 +17,20 @@
* under the License.
*/
import _ from 'lodash';
import { Filter } from '@kbn/es-query';
import { filter, isEqual } from 'lodash';
const isEnabled = (f: Filter) => f && f.meta && !f.meta.disabled;
const isEnabled = function(filter: Filter) {
return filter && filter.meta && !filter.meta.disabled;
};
/**
* Checks to see if only disabled filters have been changed
*
* @returns {bool} Only disabled filters
*/
export function onlyDisabledFiltersChanged(newFilters?: Filter[], oldFilters?: Filter[]) {
export const onlyDisabledFiltersChanged = (newFilters?: Filter[], oldFilters?: Filter[]) => {
// If it's the same - compare only enabled filters
const newEnabledFilters = _.filter(newFilters || [], isEnabled);
const oldEnabledFilters = _.filter(oldFilters || [], isEnabled);
const newEnabledFilters = filter(newFilters || [], isEnabled);
const oldEnabledFilters = filter(oldFilters || [], isEnabled);
return _.isEqual(oldEnabledFilters, newEnabledFilters);
}
return isEqual(oldEnabledFilters, newEnabledFilters);
};

View file

@ -0,0 +1,60 @@
/*
* 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, buildQueryFilter, FilterStateStore } from '@kbn/es-query';
import { uniqFilters } from './uniq_filters';
describe('filter manager utilities', () => {
describe('niqFilter', () => {
test('should filter out dups', () => {
const before: Filter[] = [
buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index'),
buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index'),
];
const results = uniqFilters(before);
expect(results).toHaveLength(1);
});
test('should filter out duplicates, ignoring meta attributes', () => {
const before: Filter[] = [
buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index1'),
buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index2'),
];
const results = uniqFilters(before);
expect(results).toHaveLength(1);
});
test('should filter out duplicates, ignoring $state attributes', () => {
const before: Filter[] = [
{
$state: { store: FilterStateStore.APP_STATE },
...buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index'),
},
{
$state: { store: FilterStateStore.GLOBAL_STATE },
...buildQueryFilter({ _type: { match: { query: 'apache', type: 'phrase' } } }, 'index'),
},
];
const results = uniqFilters(before);
expect(results).toHaveLength(1);
});
});
});

View file

@ -16,19 +16,24 @@
* specific language governing permissions and limitations
* under the License.
*/
import _ from 'lodash';
import { Filter } from '@kbn/es-query';
import { each, union } from 'lodash';
import { dedupFilters } from './dedup_filters';
/**
* Remove duplicate filters from an array of filters
*
* @param {array} filters The filters to remove duplicates from
* @param {object} comparatorOptions - Parameters to use for comparison
* @returns {object} The original filters array with duplicates removed
*/
export function uniqFilters(filters, comparatorOptions) {
let results = [];
_.each(filters, function (filter) {
results = _.union(results, dedupFilters(results, [filter], comparatorOptions));
export const uniqFilters = (filters: Filter[], comparatorOptions: any = {}) => {
let results: Filter[] = [];
each(filters, (filter: Filter) => {
results = union(results, dedupFilters(results, [filter]), comparatorOptions);
});
return results;
}
};