Skip to content

Commit 42f5822

Browse files
committed
feat(Aggs): Start describing body.aggs input for searchTyped
1 parent 56c815a commit 42f5822

File tree

8 files changed

+292
-2
lines changed

8 files changed

+292
-2
lines changed

src/ElasticDSL/Aggs/AggBlock.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/* @flow */
2+
3+
import { InputTypeComposer } from 'graphql-compose';
4+
import { getTypeName, getOrSetType, desc } from '../../utils';
5+
import { getAggRulesITC } from './AggRules';
6+
7+
export function getAggBlockITC(opts: mixed = {}): InputTypeComposer {
8+
const name = getTypeName('AggBlock', opts);
9+
const description = desc(`
10+
The aggregations framework helps provide aggregated data based on
11+
a search query. It is based on simple building blocks called aggregations,
12+
that can be composed in order to build complex summaries of the data.
13+
[Documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html)
14+
`);
15+
16+
return getOrSetType(name, () =>
17+
// $FlowFixMe
18+
InputTypeComposer.create({
19+
name,
20+
description,
21+
fields: {
22+
key: {
23+
type: 'String',
24+
description: 'FieldName in response for aggregation result',
25+
},
26+
value: {
27+
type: () => getAggRulesITC(opts),
28+
description: 'Aggregation rules',
29+
},
30+
},
31+
})
32+
);
33+
}

src/ElasticDSL/Aggs/AggRules.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/* @flow */
2+
3+
import { InputTypeComposer } from 'graphql-compose';
4+
import { getTypeName, getOrSetType, desc } from '../../utils';
5+
import { getAggBlockITC } from './AggBlock';
6+
7+
import { getAvgITC } from './Metrics/Avg';
8+
import { getCardinalityITC } from './Metrics/Cardinality';
9+
import { getExtendedStatsITC } from './Metrics/ExtendedStats';
10+
11+
export function getAggRulesITC(opts: mixed = {}): InputTypeComposer {
12+
const name = getTypeName('AggRules', opts);
13+
const description = desc(
14+
`
15+
The aggregations framework helps provide aggregated data based on
16+
a search query. It is based on simple building blocks called aggregations,
17+
that can be composed in order to build complex summaries of the data.
18+
[Documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html)
19+
`
20+
);
21+
22+
return getOrSetType(name, () =>
23+
// $FlowFixMe
24+
InputTypeComposer.create({
25+
name,
26+
description,
27+
fields: {
28+
avg: () => getAvgITC(opts),
29+
cardinality: () => getCardinalityITC(opts),
30+
extended_stats: () => getExtendedStatsITC(opts),
31+
32+
aggs: {
33+
type: () => [getAggBlockITC(opts)],
34+
description: 'Aggregation block',
35+
},
36+
},
37+
}));
38+
}

src/ElasticDSL/Aggs/Metrics/Avg.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/* @flow */
2+
3+
import { InputTypeComposer } from 'graphql-compose';
4+
import { getTypeName, getOrSetType, desc } from '../../../utils';
5+
import { getCommonsScriptITC } from '../../Commons/Script';
6+
7+
export function getAvgITC(opts: mixed = {}): InputTypeComposer {
8+
const name = getTypeName('AggsAvg', opts);
9+
const description = desc(`
10+
A single-value metrics aggregation that computes the average
11+
of numeric values that are extracted from the aggregated documents.
12+
These values can be extracted either from specific numeric fields
13+
in the documents, or be generated by a provided script.
14+
[Documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-avg-aggregation.html)
15+
`);
16+
17+
return getOrSetType(name, () =>
18+
// $FlowFixMe
19+
InputTypeComposer.create({
20+
name,
21+
description,
22+
fields: {
23+
field: 'String',
24+
missing: 'Float',
25+
script: () => getCommonsScriptITC(),
26+
},
27+
})
28+
);
29+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/* @flow */
2+
3+
import { InputTypeComposer } from 'graphql-compose';
4+
import { getTypeName, getOrSetType, desc } from '../../../utils';
5+
import { getCommonsScriptITC } from '../../Commons/Script';
6+
7+
export function getCardinalityITC(opts: mixed = {}): InputTypeComposer {
8+
const name = getTypeName('AggsCardinality', opts);
9+
const description = desc(
10+
`
11+
A single-value metrics aggregation that calculates an approximate count
12+
of distinct values. Values can be extracted either from specific fields
13+
in the document or generated by a script.
14+
[Documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-cardinality-aggregation.html)
15+
`
16+
);
17+
18+
return getOrSetType(name, () =>
19+
// $FlowFixMe
20+
InputTypeComposer.create({
21+
name,
22+
description,
23+
fields: {
24+
field: 'String',
25+
precision_threshold: {
26+
type: 'Int',
27+
defaultValue: 3000,
28+
description: desc(
29+
`
30+
Allows to trade memory for accuracy, and defines a unique count
31+
below which counts are expected to be close to accurate.
32+
`
33+
),
34+
},
35+
missing: 'String',
36+
script: () => getCommonsScriptITC(),
37+
},
38+
}));
39+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/* @flow */
2+
3+
import { InputTypeComposer } from 'graphql-compose';
4+
import { getTypeName, getOrSetType, desc } from '../../../utils';
5+
import { getCommonsScriptITC } from '../../Commons/Script';
6+
7+
export function getExtendedStatsITC(opts: mixed = {}): InputTypeComposer {
8+
const name = getTypeName('AggsExtendedStats', opts);
9+
const description = desc(
10+
`
11+
A multi-value metrics aggregation that computes stats over numeric values
12+
extracted from the aggregated documents. These values can be extracted
13+
either from specific numeric fields in the documents, or be generated
14+
by a provided script.
15+
[Documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-extendedstats-aggregation.html)
16+
`
17+
);
18+
19+
return getOrSetType(name, () =>
20+
// $FlowFixMe
21+
InputTypeComposer.create({
22+
name,
23+
description,
24+
fields: {
25+
field: 'String',
26+
sigma: 'Float',
27+
missing: 'Float',
28+
script: () => getCommonsScriptITC(),
29+
},
30+
}));
31+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import argsBlockConverter, {
2+
convertAggsBlocks,
3+
convertAggsRules,
4+
} from '../converter';
5+
6+
describe('AGGS args converter', () => {
7+
it('convertAggsRules()', () => {
8+
expect(convertAggsRules({ some: { field: 1 } })).toEqual({
9+
some: { field: 1 },
10+
});
11+
});
12+
13+
it('convertAggsBlocks()', () => {
14+
expect(
15+
convertAggsBlocks([
16+
{ key: 'field1', value: {} },
17+
{ key: 'field2', value: {} },
18+
])
19+
).toEqual({
20+
field1: {},
21+
field2: {},
22+
});
23+
});
24+
25+
it('should convert recursively aggs', () => {
26+
expect(
27+
convertAggsBlocks([
28+
{ key: 'field1', value: { aggs: [{ key: 'field2', value: {} }] } },
29+
])
30+
).toEqual({ field1: { aggs: { field2: {} } } });
31+
});
32+
33+
it('argsBlockConverter()', () => {
34+
expect.assertions(4);
35+
const mockResolve = (source, args, context, info) => {
36+
expect(args.body.aggs).toEqual({
37+
field1: {},
38+
field2: {},
39+
});
40+
expect(source).toEqual('source');
41+
expect(context).toEqual('context');
42+
expect(info).toEqual('info');
43+
};
44+
45+
const args = {
46+
body: {
47+
aggs: [{ key: 'field1', value: {} }, { key: 'field2', value: {} }],
48+
},
49+
};
50+
argsBlockConverter(mockResolve)('source', args, 'context', 'info');
51+
});
52+
});

src/ElasticDSL/Aggs/converter.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import type { GraphQLFieldResolver } from 'graphql/type/definition';
2+
3+
export type ElasticAggsT = {
4+
[outputFieldName: string]: ElasticAggsRulesT,
5+
};
6+
7+
export type ElasticAggsRulesT = {
8+
[aggOperationName: string]: mixed,
9+
aggs: ElasticAggsT,
10+
};
11+
12+
export type GqlAggBlock = {
13+
key: string,
14+
value: GqlAggRules,
15+
};
16+
17+
export type GqlAggRules = {
18+
[aggOperationName: string]: mixed,
19+
aggs: GqlAggBlock,
20+
};
21+
22+
export default function argsBlockConverter(
23+
resolve: GraphQLFieldResolver<*, *>
24+
): GraphQLFieldResolver<*, *> {
25+
return (source, args, context, info) => {
26+
if (args.body && Array.isArray(args.body.aggs)) {
27+
const aggs: GqlAggBlock[] = args.body.aggs;
28+
args.body.aggs = convertAggsBlocks(aggs); // eslint-disable-line
29+
}
30+
31+
return resolve(source, args, context, info);
32+
};
33+
}
34+
35+
export function convertAggsBlocks(blockList: GqlAggBlock[]): ElasticAggsT {
36+
const result = {};
37+
blockList.forEach(block => {
38+
if (block.key && block.value) {
39+
result[block.key] = convertAggsRules(block.value);
40+
}
41+
});
42+
return result;
43+
}
44+
45+
export function convertAggsRules(rules: GqlAggRules): ElasticAggsRulesT {
46+
const result = {};
47+
Object.keys(rules).forEach(key => {
48+
if (key === 'aggs' && rules.aggs) {
49+
result.aggs = convertAggsBlocks(rules.aggs);
50+
} else {
51+
result[key] = rules[key];
52+
}
53+
});
54+
return result;
55+
}

src/ElasticDSL/SearchBody.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
/* @flow */
22

33
import { InputTypeComposer } from 'graphql-compose';
4+
import type { GraphQLFieldResolver } from 'graphql/type/definition';
45
import { getQueryITC } from './Query/Query';
6+
import { getAggBlockITC } from './Aggs/AggBlock';
7+
import prepareArgAggs from './Aggs/converter';
58
import { getTypeName, getOrSetType, desc } from '../utils';
69

710
export function getSearchBodyITC(opts: mixed = {}): InputTypeComposer {
811
const name = getTypeName('SearchBody', opts);
9-
const description = desc(`
12+
const description = desc(
13+
`
1014
The search request can be executed with a search DSL, which includes
1115
the [Query DSL](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html),
1216
within its body.
13-
`);
17+
`
18+
);
1419

1520
return getOrSetType(name, () =>
1621
// $FlowFixMe
@@ -19,6 +24,14 @@ export function getSearchBodyITC(opts: mixed = {}): InputTypeComposer {
1924
description,
2025
fields: {
2126
query: () => getQueryITC(opts),
27+
aggs: () => [getAggBlockITC(opts)],
28+
size: 'Int',
2229
},
2330
}));
2431
}
32+
33+
export function prepareSearchArgs(
34+
resolve: GraphQLFieldResolver<*, *>
35+
): GraphQLFieldResolver<*, *> {
36+
return prepareArgAggs(resolve);
37+
}

0 commit comments

Comments
 (0)