Skip to content

Commit

Permalink
feat(Aggs): Start describing body.aggs input for searchTyped
Browse files Browse the repository at this point in the history
  • Loading branch information
nodkz committed Mar 15, 2017
1 parent 56c815a commit 42f5822
Show file tree
Hide file tree
Showing 8 changed files with 292 additions and 2 deletions.
33 changes: 33 additions & 0 deletions src/ElasticDSL/Aggs/AggBlock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/* @flow */

import { InputTypeComposer } from 'graphql-compose';
import { getTypeName, getOrSetType, desc } from '../../utils';
import { getAggRulesITC } from './AggRules';

export function getAggBlockITC(opts: mixed = {}): InputTypeComposer {
const name = getTypeName('AggBlock', opts);
const description = desc(`
The aggregations framework helps provide aggregated data based on
a search query. It is based on simple building blocks called aggregations,
that can be composed in order to build complex summaries of the data.
[Documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html)
`);

return getOrSetType(name, () =>
// $FlowFixMe
InputTypeComposer.create({
name,
description,
fields: {
key: {
type: 'String',
description: 'FieldName in response for aggregation result',
},
value: {
type: () => getAggRulesITC(opts),
description: 'Aggregation rules',
},
},
})
);
}
38 changes: 38 additions & 0 deletions src/ElasticDSL/Aggs/AggRules.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/* @flow */

import { InputTypeComposer } from 'graphql-compose';
import { getTypeName, getOrSetType, desc } from '../../utils';
import { getAggBlockITC } from './AggBlock';

import { getAvgITC } from './Metrics/Avg';
import { getCardinalityITC } from './Metrics/Cardinality';
import { getExtendedStatsITC } from './Metrics/ExtendedStats';

export function getAggRulesITC(opts: mixed = {}): InputTypeComposer {
const name = getTypeName('AggRules', opts);
const description = desc(
`
The aggregations framework helps provide aggregated data based on
a search query. It is based on simple building blocks called aggregations,
that can be composed in order to build complex summaries of the data.
[Documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html)
`
);

return getOrSetType(name, () =>
// $FlowFixMe
InputTypeComposer.create({
name,
description,
fields: {
avg: () => getAvgITC(opts),
cardinality: () => getCardinalityITC(opts),
extended_stats: () => getExtendedStatsITC(opts),

aggs: {
type: () => [getAggBlockITC(opts)],
description: 'Aggregation block',
},
},
}));
}
29 changes: 29 additions & 0 deletions src/ElasticDSL/Aggs/Metrics/Avg.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/* @flow */

import { InputTypeComposer } from 'graphql-compose';
import { getTypeName, getOrSetType, desc } from '../../../utils';
import { getCommonsScriptITC } from '../../Commons/Script';

export function getAvgITC(opts: mixed = {}): InputTypeComposer {
const name = getTypeName('AggsAvg', opts);
const description = desc(`
A single-value metrics aggregation that computes the average
of numeric values that are extracted from the aggregated documents.
These values can be extracted either from specific numeric fields
in the documents, or be generated by a provided script.
[Documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-avg-aggregation.html)
`);

return getOrSetType(name, () =>
// $FlowFixMe
InputTypeComposer.create({
name,
description,
fields: {
field: 'String',
missing: 'Float',
script: () => getCommonsScriptITC(),
},
})
);
}
39 changes: 39 additions & 0 deletions src/ElasticDSL/Aggs/Metrics/Cardinality.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/* @flow */

import { InputTypeComposer } from 'graphql-compose';
import { getTypeName, getOrSetType, desc } from '../../../utils';
import { getCommonsScriptITC } from '../../Commons/Script';

export function getCardinalityITC(opts: mixed = {}): InputTypeComposer {
const name = getTypeName('AggsCardinality', opts);
const description = desc(
`
A single-value metrics aggregation that calculates an approximate count
of distinct values. Values can be extracted either from specific fields
in the document or generated by a script.
[Documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-cardinality-aggregation.html)
`
);

return getOrSetType(name, () =>
// $FlowFixMe
InputTypeComposer.create({
name,
description,
fields: {
field: 'String',
precision_threshold: {
type: 'Int',
defaultValue: 3000,
description: desc(
`
Allows to trade memory for accuracy, and defines a unique count
below which counts are expected to be close to accurate.
`
),
},
missing: 'String',
script: () => getCommonsScriptITC(),
},
}));
}
31 changes: 31 additions & 0 deletions src/ElasticDSL/Aggs/Metrics/ExtendedStats.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/* @flow */

import { InputTypeComposer } from 'graphql-compose';
import { getTypeName, getOrSetType, desc } from '../../../utils';
import { getCommonsScriptITC } from '../../Commons/Script';

export function getExtendedStatsITC(opts: mixed = {}): InputTypeComposer {
const name = getTypeName('AggsExtendedStats', opts);
const description = desc(
`
A multi-value metrics aggregation that computes stats over numeric values
extracted from the aggregated documents. These values can be extracted
either from specific numeric fields in the documents, or be generated
by a provided script.
[Documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-extendedstats-aggregation.html)
`
);

return getOrSetType(name, () =>
// $FlowFixMe
InputTypeComposer.create({
name,
description,
fields: {
field: 'String',
sigma: 'Float',
missing: 'Float',
script: () => getCommonsScriptITC(),
},
}));
}
52 changes: 52 additions & 0 deletions src/ElasticDSL/Aggs/__tests__/converter-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import argsBlockConverter, {
convertAggsBlocks,
convertAggsRules,
} from '../converter';

describe('AGGS args converter', () => {
it('convertAggsRules()', () => {
expect(convertAggsRules({ some: { field: 1 } })).toEqual({
some: { field: 1 },
});
});

it('convertAggsBlocks()', () => {
expect(
convertAggsBlocks([
{ key: 'field1', value: {} },
{ key: 'field2', value: {} },
])
).toEqual({
field1: {},
field2: {},
});
});

it('should convert recursively aggs', () => {
expect(
convertAggsBlocks([
{ key: 'field1', value: { aggs: [{ key: 'field2', value: {} }] } },
])
).toEqual({ field1: { aggs: { field2: {} } } });
});

it('argsBlockConverter()', () => {
expect.assertions(4);
const mockResolve = (source, args, context, info) => {
expect(args.body.aggs).toEqual({
field1: {},
field2: {},
});
expect(source).toEqual('source');
expect(context).toEqual('context');
expect(info).toEqual('info');
};

const args = {
body: {
aggs: [{ key: 'field1', value: {} }, { key: 'field2', value: {} }],
},
};
argsBlockConverter(mockResolve)('source', args, 'context', 'info');
});
});
55 changes: 55 additions & 0 deletions src/ElasticDSL/Aggs/converter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import type { GraphQLFieldResolver } from 'graphql/type/definition';

export type ElasticAggsT = {
[outputFieldName: string]: ElasticAggsRulesT,
};

export type ElasticAggsRulesT = {
[aggOperationName: string]: mixed,
aggs: ElasticAggsT,
};

export type GqlAggBlock = {
key: string,
value: GqlAggRules,
};

export type GqlAggRules = {
[aggOperationName: string]: mixed,
aggs: GqlAggBlock,
};

export default function argsBlockConverter(
resolve: GraphQLFieldResolver<*, *>
): GraphQLFieldResolver<*, *> {
return (source, args, context, info) => {
if (args.body && Array.isArray(args.body.aggs)) {
const aggs: GqlAggBlock[] = args.body.aggs;
args.body.aggs = convertAggsBlocks(aggs); // eslint-disable-line
}

return resolve(source, args, context, info);
};
}

export function convertAggsBlocks(blockList: GqlAggBlock[]): ElasticAggsT {
const result = {};
blockList.forEach(block => {
if (block.key && block.value) {
result[block.key] = convertAggsRules(block.value);
}
});
return result;
}

export function convertAggsRules(rules: GqlAggRules): ElasticAggsRulesT {
const result = {};
Object.keys(rules).forEach(key => {
if (key === 'aggs' && rules.aggs) {
result.aggs = convertAggsBlocks(rules.aggs);
} else {
result[key] = rules[key];
}
});
return result;
}
17 changes: 15 additions & 2 deletions src/ElasticDSL/SearchBody.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
/* @flow */

import { InputTypeComposer } from 'graphql-compose';
import type { GraphQLFieldResolver } from 'graphql/type/definition';
import { getQueryITC } from './Query/Query';
import { getAggBlockITC } from './Aggs/AggBlock';
import prepareArgAggs from './Aggs/converter';
import { getTypeName, getOrSetType, desc } from '../utils';

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

return getOrSetType(name, () =>
// $FlowFixMe
Expand All @@ -19,6 +24,14 @@ export function getSearchBodyITC(opts: mixed = {}): InputTypeComposer {
description,
fields: {
query: () => getQueryITC(opts),
aggs: () => [getAggBlockITC(opts)],
size: 'Int',
},
}));
}

export function prepareSearchArgs(
resolve: GraphQLFieldResolver<*, *>
): GraphQLFieldResolver<*, *> {
return prepareArgAggs(resolve);
}

0 comments on commit 42f5822

Please sign in to comment.