mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-11 12:12:55 +00:00
feat: add support for range filters
This commit is contained in:
committed by
Rainer Killinger
parent
785813c3fb
commit
dcf7906f79
@@ -15,16 +15,18 @@
|
||||
*/
|
||||
import {
|
||||
SCConfigFile,
|
||||
SCSearchBooleanFilter,
|
||||
SCSearchFilter,
|
||||
SCSearchBooleanFilter, SCSearchDateRangeFilter,
|
||||
SCSearchFilter, SCSearchNumericRangeFilter,
|
||||
SCSearchQuery,
|
||||
SCSearchSort,
|
||||
SCThingType
|
||||
} from '@openstapps/core';
|
||||
import { expect } from 'chai';
|
||||
import {expect} from 'chai';
|
||||
import {ESDateRangeFilter} from '../../../src/storage/elasticsearch/common';
|
||||
import {ESNumericRangeFilter} from '../../../src/storage/elasticsearch/common';
|
||||
import {configFile} from '../../../src/common';
|
||||
import {
|
||||
ElasticsearchConfig, ESBooleanFilter, ESDucetSort, ESGeoDistanceFilter,
|
||||
ElasticsearchConfig, ESBooleanFilter, ESGenericSort, ESGeoDistanceFilter,
|
||||
ESGeoDistanceSort,
|
||||
ESTermFilter,
|
||||
ScriptSort
|
||||
@@ -55,7 +57,7 @@ describe('Query', function () {
|
||||
},
|
||||
type: 'boolean'
|
||||
};
|
||||
const booleanFilters: {[key: string]: SCSearchBooleanFilter} = {
|
||||
const booleanFilters: { [key: string]: SCSearchBooleanFilter } = {
|
||||
and: booleanFilter,
|
||||
or: {...booleanFilter, arguments: {...booleanFilter.arguments, operation: 'or'}},
|
||||
not: {...booleanFilter, arguments: {...booleanFilter.arguments, operation: 'not'}},
|
||||
@@ -95,34 +97,34 @@ describe('Query', function () {
|
||||
|
||||
describe('buildQuery', function () {
|
||||
const params: SCSearchQuery = {
|
||||
query: 'mathematics',
|
||||
from: 30,
|
||||
size: 5,
|
||||
sort: [
|
||||
{
|
||||
type: 'ducet',
|
||||
order: 'desc',
|
||||
arguments:
|
||||
{
|
||||
field: 'name'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'ducet',
|
||||
order: 'desc',
|
||||
arguments:
|
||||
{
|
||||
field: 'categories'
|
||||
}
|
||||
},
|
||||
],
|
||||
filter: {
|
||||
type: 'value',
|
||||
arguments: {
|
||||
field: 'type',
|
||||
value: SCThingType.AcademicEvent
|
||||
}
|
||||
}
|
||||
query: 'mathematics',
|
||||
from: 30,
|
||||
size: 5,
|
||||
sort: [
|
||||
{
|
||||
type: 'ducet',
|
||||
order: 'desc',
|
||||
arguments:
|
||||
{
|
||||
field: 'name'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'ducet',
|
||||
order: 'desc',
|
||||
arguments:
|
||||
{
|
||||
field: 'categories'
|
||||
}
|
||||
},
|
||||
],
|
||||
filter: {
|
||||
type: 'value',
|
||||
arguments: {
|
||||
field: 'type',
|
||||
value: SCThingType.AcademicEvent
|
||||
}
|
||||
}
|
||||
};
|
||||
let esConfig: ElasticsearchConfig = {
|
||||
name: 'elasticsearch',
|
||||
@@ -143,7 +145,7 @@ describe('Query', function () {
|
||||
fuzziness: 'AUTO',
|
||||
cutoffFrequency: 0.0,
|
||||
tieBreaker: 0,
|
||||
}
|
||||
};
|
||||
const config: SCConfigFile = {
|
||||
...configFile
|
||||
};
|
||||
@@ -187,7 +189,7 @@ describe('Query', function () {
|
||||
});
|
||||
|
||||
describe('buildFilter', function () {
|
||||
const searchFilters: {[key: string]: SCSearchFilter} = {
|
||||
const searchFilters: { [key: string]: SCSearchFilter } = {
|
||||
value: {
|
||||
type: 'value',
|
||||
arguments: {
|
||||
@@ -246,6 +248,107 @@ describe('Query', function () {
|
||||
expect(filter).to.be.eql(expectedFilter);
|
||||
});
|
||||
|
||||
it('should build numeric range filters', function () {
|
||||
for (const upperMode of ['inclusive', 'exclusive', null]) {
|
||||
for (const lowerMode of ['inclusive', 'exclusive', null]) {
|
||||
const expectedFilter: ESNumericRangeFilter = {
|
||||
range: {
|
||||
price: {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const rawFilter: SCSearchNumericRangeFilter = {
|
||||
type: 'numeric range',
|
||||
arguments: {
|
||||
bounds: {},
|
||||
field: 'price'
|
||||
}
|
||||
};
|
||||
|
||||
const setBound = (location: 'upperBound' | 'lowerBound', bound: string | null) => {
|
||||
let out: number | null = null;
|
||||
if (bound != null) {
|
||||
out = Math.random();
|
||||
rawFilter.arguments.bounds[location] = {
|
||||
mode: bound as 'inclusive' | 'exclusive',
|
||||
limit: out,
|
||||
}
|
||||
// @ts-ignore implicit any
|
||||
expectedFilter.range.price[`${location === 'upperBound' ? 'g' : 'l'}${bound === 'inclusive' ? 'te' : 't'}`] = out;
|
||||
}
|
||||
}
|
||||
setBound('upperBound', upperMode);
|
||||
setBound('lowerBound', lowerMode);
|
||||
|
||||
const filter = buildFilter(rawFilter) as ESNumericRangeFilter;
|
||||
expect(filter).to.deep.equal(expectedFilter);
|
||||
for (const bound of ['g', 'l']) {
|
||||
// @ts-ignore implicit any
|
||||
const inclusiveExists = typeof filter.range.price[`${bound}t`] !== 'undefined';
|
||||
// @ts-ignore implicit any
|
||||
const exclusiveExists = typeof filter.range.price[`${bound}te`] !== 'undefined';
|
||||
|
||||
// only one should exist at the same time
|
||||
expect(inclusiveExists && exclusiveExists).to.be.false
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should build date range filters', function () {
|
||||
for (const upperMode of ['inclusive', 'exclusive', null]) {
|
||||
for (const lowerMode of ['inclusive', 'exclusive', null]) {
|
||||
const expectedFilter: ESDateRangeFilter = {
|
||||
range: {
|
||||
price: {
|
||||
format: 'thisIsADummyFormat',
|
||||
time_zone: 'thisIsADummyTimeZone',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const rawFilter: SCSearchDateRangeFilter = {
|
||||
type: 'date range',
|
||||
arguments: {
|
||||
bounds: {},
|
||||
field: 'price',
|
||||
format: 'thisIsADummyFormat',
|
||||
timeZone: 'thisIsADummyTimeZone',
|
||||
}
|
||||
};
|
||||
|
||||
const setBound = (location: 'upperBound' | 'lowerBound', bound: string | null) => {
|
||||
let out: string | null = null;
|
||||
if (bound != null) {
|
||||
out = `${location} ${bound} ${upperMode} ${lowerMode}`;
|
||||
rawFilter.arguments.bounds[location] = {
|
||||
mode: bound as 'inclusive' | 'exclusive',
|
||||
limit: out,
|
||||
}
|
||||
// @ts-ignore implicit any
|
||||
expectedFilter.range.price[`${location === 'upperBound' ? 'g' : 'l'}${bound === 'inclusive' ? 'te' : 't'}`] = out;
|
||||
}
|
||||
}
|
||||
setBound('upperBound', upperMode);
|
||||
setBound('lowerBound', lowerMode);
|
||||
|
||||
const filter = buildFilter(rawFilter) as ESNumericRangeFilter;
|
||||
expect(filter).to.deep.equal(expectedFilter);
|
||||
for (const bound of ['g', 'l']) {
|
||||
// @ts-ignore implicit any
|
||||
const inclusiveExists = typeof filter.range.price[`${bound}t`] !== 'undefined';
|
||||
// @ts-ignore implicit any
|
||||
const exclusiveExists = typeof filter.range.price[`${bound}te`] !== 'undefined';
|
||||
|
||||
// only one should exist at the same time
|
||||
expect(inclusiveExists && exclusiveExists).to.be.false
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should build availability filter', function () {
|
||||
const filter = buildFilter(searchFilters.availability);
|
||||
const expectedFilter: ESBooleanFilter<any> = {
|
||||
@@ -366,7 +469,7 @@ describe('Query', function () {
|
||||
must_not: [],
|
||||
should: []
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
expect(filter).to.be.eql(expectedFilter);
|
||||
});
|
||||
@@ -381,6 +484,13 @@ describe('Query', function () {
|
||||
field: 'name'
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'generic',
|
||||
order: 'desc',
|
||||
arguments: {
|
||||
field: 'name',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'distance',
|
||||
order: 'desc',
|
||||
@@ -398,11 +508,14 @@ describe('Query', function () {
|
||||
}
|
||||
},
|
||||
];
|
||||
let sorts: Array<ESDucetSort | ESGeoDistanceSort | ScriptSort> = [];
|
||||
const expectedSorts: {[key: string]: ESDucetSort | ESGeoDistanceSort | ScriptSort} = {
|
||||
let sorts: Array<ESGenericSort | ESGeoDistanceSort | ScriptSort> = [];
|
||||
const expectedSorts: { [key: string]: ESGenericSort | ESGeoDistanceSort | ScriptSort } = {
|
||||
ducet: {
|
||||
'name.sort': 'desc'
|
||||
},
|
||||
generic: {
|
||||
'name': 'desc'
|
||||
},
|
||||
distance: {
|
||||
_geo_distance: {
|
||||
mode: 'avg',
|
||||
@@ -430,12 +543,19 @@ describe('Query', function () {
|
||||
expect(sorts[0]).to.be.eql(expectedSorts.ducet);
|
||||
});
|
||||
|
||||
it('should build generic sort', function () {
|
||||
expect(sorts[1]).to.be.eql(expectedSorts.generic);
|
||||
})
|
||||
|
||||
it('should build distance sort', function () {
|
||||
expect(sorts[1]).to.be.eql(expectedSorts.distance);
|
||||
expect(sorts[2]).to.be.eql(expectedSorts.distance);
|
||||
});
|
||||
|
||||
it('should build price sort', function () {
|
||||
const priceSortNoScript = {...sorts[2], _script: {...(sorts[2] as ScriptSort)._script, script: (expectedSorts.price as ScriptSort)._script.script}}
|
||||
const priceSortNoScript = {
|
||||
...sorts[3],
|
||||
_script: {...(sorts[3] as ScriptSort)._script, script: (expectedSorts.price as ScriptSort)._script.script}
|
||||
};
|
||||
expect(priceSortNoScript).to.be.eql(expectedSorts.price);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user