Handle infinite ranges in filters

When only one side of the range is infinite, we never add a condition to
the script filter for that half of the range, so elasticsearch treats it
as infinite.

When both sides of the range are infinite, we use a match_all filter
instead of a conditional script to define the range.
This commit is contained in:
Court Ewing 2015-09-15 11:34:43 -04:00
parent 89d0b168ca
commit 6761e01053
5 changed files with 138 additions and 8 deletions

View file

@ -0,0 +1,51 @@
describe('ui/filter_bar/lib', function () {
describe('mapMatchAll()', function () {
const expect = require('expect.js');
const ngMock = require('ngMock');
let resolvePromises;
let mapMatchAll;
let filter;
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private, $rootScope) {
resolvePromises = () => $rootScope.$apply();
mapMatchAll = Private(require('ui/filter_bar/lib/mapMatchAll'));
filter = {
match_all: {},
meta: {
field: 'foo',
formattedValue: 'bar'
}
};
}));
context('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();
});
resolvePromises();
});
});
context('when given a match_all filter', function () {
let result;
beforeEach(function () {
mapMatchAll(filter).then(r => result = r);
resolvePromises();
});
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

@ -21,6 +21,7 @@ define(function (require) {
// that either handles the mapping operation or not
// and add it here. ProTip: These are executed in order listed
var mappers = [
Private(require('./mapMatchAll')),
Private(require('./mapTerms')),
Private(require('./mapRange')),
Private(require('./mapExists')),

View file

@ -0,0 +1,12 @@
define(function (require) {
return function mapMatchAllProvider(Promise) {
return function (filter) {
if (filter.match_all) {
const key = filter.meta.field;
const value = filter.meta.formattedValue || 'all';
return Promise.resolve({ key, value });
}
return Promise.reject(filter);
};
};
});

View file

@ -63,5 +63,53 @@ describe('Filter Manager', function () {
});
});
context('when given params where one side is infinite', function () {
let filter;
beforeEach(function () {
filter = fn(indexPattern.fields.byName['script number'], { gte: 0, lt: Infinity }, indexPattern);
});
describe('returned filter', function () {
it('is a script filter', function () {
expect(filter).to.have.property('script');
});
it('contain a param for the finite side', function () {
expect(filter.script.params).to.have.property('gte', 0);
});
it('does not contain a param for the infinite side', function () {
expect(filter.script.params).not.to.have.property('lt');
});
it('does not contain a script condition for the infinite side', function () {
const script = indexPattern.fields.byName['script number'].script;
expect(filter.script.script).to.equal(`(${script})>=gte`);
});
});
});
context('when given params where both sides are infinite', function () {
let filter;
beforeEach(function () {
filter = fn(indexPattern.fields.byName['script number'], { gte: -Infinity, lt: Infinity }, indexPattern);
});
describe('returned filter', function () {
it('is a match_all filter', function () {
expect(filter).not.to.have.property('script');
expect(filter).to.have.property('match_all');
});
it('does not contain params', function () {
expect(filter).not.to.have.property('params');
});
it('meta field is set to field name', function () {
expect(filter.meta.field).to.equal('script number');
});
});
});
});
});

View file

@ -1,27 +1,44 @@
define(function (require) {
var _ = require('lodash');
const _ = require('lodash');
const OPERANDS_IN_RANGE = 2;
return function buildRangeFilter(field, params, indexPattern, formattedValue) {
var filter = { meta: { index: indexPattern.id } };
const filter = { meta: { index: indexPattern.id } };
if (formattedValue) filter.meta.formattedValue = formattedValue;
params = _.clone(params);
if (params.gte && params.gt) throw new Error('gte and gt are mutually exclusive');
if (params.lte && params.lt) throw new Error('lte and lt are mutually exclusive');
if ('gte' in params && 'gt' in params) throw new Error('gte and gt are mutually exclusive');
if ('lte' in params && 'lt' in params) throw new Error('lte and lt are mutually exclusive');
if (field.scripted) {
var operators = {
const totalInfinite = ['gt', 'lt'].reduce((totalInfinite, op) => {
const key = op in params ? op : `${op}e`;
const isInfinite = Math.abs(params[key]) === Infinity;
if (isInfinite) {
totalInfinite++;
delete params[key];
}
return totalInfinite;
}, 0);
if (totalInfinite === OPERANDS_IN_RANGE) {
filter.match_all = {};
filter.meta.field = field.name;
} else if (field.scripted) {
const operators = {
gt: '>',
gte: '>=',
lte: '<=',
lt: '<',
};
var script = _.map(params, function (val, key) {
const script = _.map(params, function (val, key) {
return '(' + field.script + ')' + operators[key] + key;
}).join(' && ');
var value = _.map(params, function (val, key) {
const value = _.map(params, function (val, key) {
return operators[key] + field.format.convert(val);
}).join(' ');
@ -32,6 +49,7 @@ define(function (require) {
filter.range = {};
filter.range[field.name] = params;
}
return filter;
};
});