Skip to content

Commit 30c8f66

Browse files
feat: add bucket selector aggregation (#184)
1 parent 7f6508f commit 30c8f66

File tree

8 files changed

+131
-3
lines changed

8 files changed

+131
-3
lines changed

.eslintrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
"precision_threshold",
2323
"missing_order",
2424
"missing_bucket",
25-
"bucket_script"
25+
"bucket_script",
26+
"bucket_selector"
2627
]
2728
}
2829
]
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export type BucketSelectorAggregation = {
2+
bucket_selector: BucketSelectorAggregationBody
3+
}
4+
5+
export type BucketSelectorAggregationBody = {
6+
buckets_path: Record<string, string>
7+
script: string
8+
}
9+
10+
export const getBucketSelectorAggregation = (script: string, bucketsPath: Record<string, string>): BucketSelectorAggregation => ({
11+
bucket_selector: {
12+
buckets_path: bucketsPath,
13+
script
14+
}
15+
})

src/lib/aggregations/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ export * from './get-stats-bucket'
1717
export * from './get-nested'
1818
export * from './get-filter'
1919
export * from './get-bucket-script'
20+
export * from './get-bucket-selector'

src/lib/aggregations/test/get-bucket-script.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ describe('getBucketScriptAggregation', () => {
6363
value: expect.any(Number)
6464
})
6565
)
66+
67+
expect(bucket.script.value).toEqual(bucket.sum.value / bucket.count.value)
6668
})
6769
})
6870

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { ResponseError } from 'lib/common'
2+
import { HomeDocument } from 'test/module'
3+
import { TEST_ELASTICSEARCH_NODE } from 'test/constants'
4+
import { setupNestApplication } from 'test/toolkit'
5+
import { ElasticsearchModule } from 'module/elasticsearch.module'
6+
import { ElasticsearchService } from 'module/elasticsearch.service'
7+
import { getTermsAggregation } from '../get-terms'
8+
import { getSumAggregation } from '../get-sum'
9+
import { getBucketSelectorAggregation } from '../get-bucket-selector'
10+
11+
describe('getBucketSelectorAggregation', () => {
12+
const { app } = setupNestApplication({
13+
imports: [
14+
ElasticsearchModule.register({
15+
node: TEST_ELASTICSEARCH_NODE
16+
})
17+
]
18+
})
19+
20+
it('accepts object with string values for each field', () => {
21+
const query = getBucketSelectorAggregation('params.myVar1 > 5', {
22+
myVar1: 'theSum'
23+
})
24+
25+
expect(query).toEqual({
26+
bucket_selector: {
27+
buckets_path: {
28+
myVar1: 'theSum'
29+
},
30+
script: 'params.myVar1 > 5'
31+
}
32+
})
33+
})
34+
35+
it('should query elasticsearch for bucket selector aggregation', async () => {
36+
const service = app.get(ElasticsearchService)
37+
38+
const selectorFilterValue = 5
39+
40+
const result = await service.search(HomeDocument, {
41+
size: 0,
42+
aggregations: {
43+
date: {
44+
...getTermsAggregation('contractDate'),
45+
aggregations: {
46+
sum: getSumAggregation('builtInYear'),
47+
selector: getBucketSelectorAggregation(`params.myVar1 > ${selectorFilterValue}`, {
48+
myVar1: 'sum'
49+
})
50+
}
51+
}
52+
}
53+
})
54+
55+
expect(result.aggregations.date.buckets.length).toBeGreaterThan(0)
56+
57+
result.aggregations.date.buckets.forEach(bucket => {
58+
expect(bucket.sum).toEqual(
59+
expect.objectContaining({
60+
value: expect.any(Number)
61+
})
62+
)
63+
64+
expect(bucket.sum.value).toBeGreaterThan(selectorFilterValue)
65+
})
66+
})
67+
68+
it('should return an error if bucket selector aggregation is not inside another aggregation', async () => {
69+
const service = app.get(ElasticsearchService)
70+
71+
await service
72+
.search(HomeDocument, {
73+
size: 0,
74+
aggregations: {
75+
sum: getSumAggregation('builtInYear'),
76+
selector: getBucketSelectorAggregation('params.myVar1 > 5', {
77+
myVar1: 'sum'
78+
})
79+
}
80+
})
81+
.catch(error => {
82+
expect(error).toBeInstanceOf(ResponseError)
83+
expect(error.message).toContain('action_request_validation_exception')
84+
expect(error.message).toContain('bucket_selector aggregation [selector] must be declared inside of another aggregation')
85+
})
86+
})
87+
88+
it(`should return an error if bucket selector tries to use non existing aggregation path`, async () => {
89+
const service = app.get(ElasticsearchService)
90+
91+
await service
92+
.search(HomeDocument, {
93+
size: 0,
94+
aggregations: {
95+
sum: getSumAggregation('builtInYear'),
96+
selector: getBucketSelectorAggregation('params.myVar1 > 5', {
97+
myVar1: 'sumAggregation'
98+
})
99+
}
100+
})
101+
.catch(error => {
102+
expect(error).toBeInstanceOf(ResponseError)
103+
expect(error.message).toContain('action_request_validation_exception')
104+
expect(error.message).toContain('No aggregation found for path [sumAggregation]')
105+
})
106+
})
107+
})

src/lib/aggregations/test/get-filter.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { ElasticsearchModule } from 'module/elasticsearch.module'
77
import { getFilterAggregation } from '../get-filter'
88
import { getSumAggregation } from '../get-sum'
99

10-
describe('getTermsAggregation', () => {
10+
describe('getFilterAggregation', () => {
1111
const { app } = setupNestApplication({
1212
imports: [
1313
ElasticsearchModule.register({

src/lib/aggregations/test/get-terms.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { MissingOrder, Order } from 'lib/enums'
2+
import { ResponseError } from 'lib/common'
23
import { TEST_ELASTICSEARCH_NODE } from 'test/constants'
34
import { setupNestApplication } from 'test/toolkit'
45
import { HomeDocument } from 'test/module'
56
import { ElasticsearchService } from 'module/elasticsearch.service'
67
import { ElasticsearchModule } from 'module/elasticsearch.module'
78
import { getTermsAggregation } from '../get-terms'
8-
import { ResponseError } from 'lib/common'
99
import { getNestedAggregation } from '../get-nested'
1010

1111
describe('getTermsAggregation', () => {

src/lib/aggregations/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { StatsBucketAggregation } from './get-stats-bucket'
1717
import { NestedAggregation } from './get-nested'
1818
import { FilterAggregation } from './get-filter'
1919
import { BucketScriptAggregation } from './get-bucket-script'
20+
import { BucketSelectorAggregation } from './get-bucket-selector'
2021

2122
export type AggregationList<TDocument extends Document> =
2223
| AvgAggregation<TDocument>
@@ -38,6 +39,7 @@ export type AggregationList<TDocument extends Document> =
3839
| NestedAggregation<TDocument>
3940
| FilterAggregation<TDocument>
4041
| BucketScriptAggregation
42+
| BucketSelectorAggregation
4143

4244
export type AggregationsContainer<TDocument extends Document> = Record<string, Aggregations<TDocument>>
4345

0 commit comments

Comments
 (0)