Skip to content

Commit 9cc9e57

Browse files
committed
feat(QueryDSL): Add basic input types for search body
1 parent f36c827 commit 9cc9e57

12 files changed

+249
-34
lines changed

.eslintrc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,12 @@
1919
"functions": "ignore",
2020
}],
2121
"no-plusplus": 0,
22-
"import/no-extraneous-dependencies": 0
22+
"import/no-extraneous-dependencies": 0,
23+
"import/prefer-default-export": 0,
2324
},
2425
"env": {
2526
"jasmine": true,
26-
"jest": true
27+
"jest": true,
2728
},
2829
"plugins": [
2930
"flowtype",

package.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,24 +35,24 @@
3535
"babel-eslint": "^7.1.1",
3636
"babel-plugin-transform-flow-strip-types": "^6.22.0",
3737
"babel-plugin-transform-object-rest-spread": "^6.22.0",
38-
"babel-preset-env": "^1.1.8",
38+
"babel-preset-env": "^1.2.1",
3939
"cz-conventional-changelog": "^2.0.0",
4040
"elasticsearch": "^12.1.3",
41-
"eslint": "^3.14.1",
41+
"eslint": "^3.17.1",
4242
"eslint-config-airbnb-base": "^11.0.1",
4343
"eslint-config-prettier": "^1.0.2",
44-
"eslint-plugin-flowtype": "^2.30.0",
44+
"eslint-plugin-flowtype": "^2.30.3",
4545
"eslint-plugin-import": "^2.2.0",
4646
"eslint-plugin-prettier": "^2.0.0",
4747
"express": "^4.15.2",
4848
"express-graphql": "^0.6.3",
4949
"flow-bin": "^0.41.0",
5050
"graphql": "^0.9.1",
51-
"graphql-compose": "^1.14.0",
51+
"graphql-compose": "^1.15.0",
5252
"jest": "^19.0.2",
5353
"jest-babel": "^1.0.1",
5454
"npm-run-all": "^4.0.1",
55-
"prettier": "^0.21.0",
55+
"prettier": "^0.22.0",
5656
"rimraf": "^2.5.4",
5757
"semantic-release": "^6.3.2"
5858
},
@@ -70,9 +70,9 @@
7070
"build": "npm-run-all build:*",
7171
"build:lib": "rimraf lib && babel src --ignore __tests__,__mocks__ -d lib",
7272
"build:flow": "find ./src -name '*.js' -not -path '*/__*' | while read filepath; do cp $filepath `echo $filepath | sed 's/\\/src\\//\\/lib\\//g'`.flow; done",
73-
"example": "nodemon -e js --exec ./node_modules/.bin/babel-node ./examples/elastic50/index.js",
73+
"example": "nodemon -e js --ignore *test* --exec ./node_modules/.bin/babel-node ./examples/elastic50/index.js",
7474
"coverage": "jest --coverage",
75-
"lint": "eslint src test *.js",
75+
"lint": "eslint --ext .js ./src",
7676
"test": "jest",
7777
"test:watch": "jest --watch",
7878
"watch": "npm run test:watch",

src/ElasticApiParser.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@ import {
1313
GraphQLNonNull,
1414
} from 'graphql';
1515
import { GraphQLJSON, upperFirst, TypeComposer } from 'graphql-compose';
16+
import { getSearchBodyITC } from './ElasticDSL/SearchBody';
1617

1718
import type {
1819
GraphQLArgumentConfig,
1920
GraphQLFieldConfigArgumentMap,
2021
GraphQLFieldMap,
2122
GraphQLInputType,
22-
} from 'graphql/type/definition';
23+
} from 'graphql/type/definition'; // eslint-disable-line
2324

2425
export type ElasticApiParserOptsT = {
2526
version?:
@@ -125,6 +126,12 @@ export default class ElasticApiParser {
125126

126127
const elasticMethod = this.getMethodName(item.ctx.string);
127128

129+
if (elasticMethod === 'search') {
130+
argMap.body.type = getSearchBodyITC({
131+
prefix: this.prefix,
132+
}).getType();
133+
}
134+
128135
result[item.ctx.string] = {
129136
type: GraphQLJSON,
130137
description,
@@ -253,8 +260,12 @@ export default class ElasticApiParser {
253260
return this.getEnumType(fieldName, paramCfg.options);
254261
}
255262
return GraphQLString;
263+
case undefined:
264+
// some fields may not have type definition in API file,
265+
// eg '@param {anything} params.operationThreading - ?'
266+
return GraphQLJSON;
256267
default:
257-
console.log(`New type '${paramCfg.type}' in elastic params setting.`); // eslint-disable-line
268+
console.log(`New type '${paramCfg.type}' in elastic params setting for field ${fieldName}.`); // eslint-disable-line
258269
return GraphQLJSON;
259270
}
260271
}

src/ElasticDSL/Query/Bool.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/* @flow */
2+
3+
import { InputTypeComposer } from 'graphql-compose';
4+
import { getQueryITC } from './Query';
5+
import { getTypeName, getOrSetType } from "../../utils";
6+
7+
export function getBoolITC(opts = {}): InputTypeComposer {
8+
const typeName = getTypeName('QueryBool', opts);
9+
10+
return getOrSetType(typeName, () => {
11+
const BoolITC = InputTypeComposer.create(typeName);
12+
BoolITC.setDescription('A query that matches documents matching boolean combinations of other queries. The bool query maps to Lucene BooleanQuery. It is built using one or more boolean clauses, each clause with a typed occurrence. ');
13+
BoolITC.setFields({
14+
must: {
15+
type: () => getQueryITC(opts),
16+
description: 'The clause (query) must appear in matching documents and will contribute to the score.',
17+
},
18+
filter: {
19+
type: () => getQueryITC(opts),
20+
description: 'The clause (query) must appear in matching documents. However unlike must the score of the query will be ignored. Filter clauses are executed in filter context, meaning that scoring is ignored and clauses are considered for caching.',
21+
},
22+
should: {
23+
type: () => getQueryITC(opts),
24+
description: 'The clause (query) should appear in the matching document. In a boolean query with no must or filter clauses, one or more should clauses must match a document. The minimum number of should clauses to match can be set using the minimum_should_match parameter.',
25+
},
26+
minimum_should_match: {
27+
type: 'Int',
28+
description: 'The minimum number of should clauses to match can be set using the minimum_should_match parameter.',
29+
},
30+
must_not: {
31+
type: () => getQueryITC(opts),
32+
description: 'The clause (query) must not appear in the matching documents. Clauses are executed in filter context meaning that scoring is ignored and clauses are considered for caching. Because scoring is ignored, a score of 0 for all documents is returned.',
33+
},
34+
boost: {
35+
type: 'Float',
36+
},
37+
});
38+
39+
return BoolITC;
40+
});
41+
}

src/ElasticDSL/Query/MatchAll.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/* @flow */
2+
3+
import { InputTypeComposer } from 'graphql-compose';
4+
import { getTypeName, getOrSetType } from '../../utils';
5+
6+
export function getMatchAllITC(opts = {}): InputTypeComposer {
7+
const typeName = getTypeName('QueryMatchAll', opts);
8+
9+
return getOrSetType(typeName, () => {
10+
const MatchAllITC = InputTypeComposer.create(typeName);
11+
MatchAllITC.setDescription('The most simple query, which matches all documents, giving them all a _score of 1.0.');
12+
MatchAllITC.setFields({
13+
boost: {
14+
type: 'Float',
15+
},
16+
});
17+
18+
return MatchAllITC;
19+
});
20+
}

src/ElasticDSL/Query/Query.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/* @flow */
2+
3+
import { InputTypeComposer } from 'graphql-compose';
4+
import { getBoolITC } from './Bool';
5+
import { getMatchAllITC } from './MatchAll';
6+
import { getTypeName, getOrSetType } from '../../utils';
7+
8+
export function getQueryITC(opts = {}): InputTypeComposer {
9+
const typeName = getTypeName('Query', opts);
10+
11+
return getOrSetType(typeName, () => {
12+
const QueryITC = InputTypeComposer.create(typeName);
13+
QueryITC.setFields({
14+
bool: () => getBoolITC(opts),
15+
match_all: () => getMatchAllITC(opts),
16+
term: {
17+
type: 'JSON',
18+
description: 'The term query finds documents that contain the exact term specified in the inverted index. { fieldName: value } or { fieldName: { value: value, boost: 2.0 } }',
19+
},
20+
terms: {
21+
type: 'JSON',
22+
description: 'Filters documents that have fields that match any of the provided terms (not analyzed). { fieldName: [values] }',
23+
},
24+
match: {
25+
type: 'JSON',
26+
description: '',
27+
},
28+
});
29+
30+
return QueryITC;
31+
});
32+
}

src/ElasticDSL/SearchBody.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/* @flow */
2+
3+
import { InputTypeComposer } from 'graphql-compose';
4+
import { getQueryITC } from './Query/Query';
5+
import { getTypeName, getOrSetType } from '../utils';
6+
7+
export function getSearchBodyITC(opts = {}): InputTypeComposer {
8+
const typeName = getTypeName('SearchBody', opts);
9+
10+
return getOrSetType(typeName, () => {
11+
const SearchBodyITC = InputTypeComposer.create(typeName);
12+
SearchBodyITC.setDescription(
13+
'A query that matches documents matching boolean combinations of other queries. The bool query maps to Lucene BooleanQuery. It is built using one or more boolean clauses, each clause with a typed occurrence. '
14+
);
15+
SearchBodyITC.setFields({
16+
query: () => getQueryITC(opts),
17+
});
18+
19+
return SearchBodyITC;
20+
});
21+
}

src/__tests__/__snapshots__/ElasticApiParser-test.js.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Object {
3333
"type": "String",
3434
},
3535
"body": Object {
36-
"type": "JSON",
36+
"type": "ElasticSearchBody",
3737
},
3838
"defaultOperator": Object {
3939
"defaultValue": "OR",

src/__tests__/typeStorage-test.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/* @flow */
2+
3+
import typeStorage from '../typeStorage';
4+
5+
const GraphQLType = 'SomeTypeInstance';
6+
7+
describe('typeStorage', () => {
8+
beforeEach(() => {
9+
typeStorage.clear();
10+
});
11+
12+
it('should be instance of Map', () => {
13+
expect(typeStorage).toBeInstanceOf(Map);
14+
});
15+
16+
it('should work `get`, `set`, `has`, `clear` methods and `size` property', () => {
17+
expect(typeStorage.size).toEqual(0);
18+
typeStorage.set('MyType', GraphQLType);
19+
expect(typeStorage.get('MyType')).toEqual(GraphQLType);
20+
expect(typeStorage.has('MyType')).toEqual(true);
21+
expect(typeStorage.size).toEqual(1);
22+
typeStorage.clear();
23+
expect(typeStorage.size).toEqual(0);
24+
});
25+
26+
describe('getOrSet() method', () => {
27+
it('should return existed value', () => {
28+
typeStorage.set('MyType', GraphQLType);
29+
expect(typeStorage.getOrSet('MyType', () => 'any')).toEqual(GraphQLType);
30+
});
31+
32+
it('should set new type as function and return type, if key not exists', () => {
33+
expect(typeStorage.getOrSet('MyType', () => GraphQLType)).toEqual(GraphQLType);
34+
expect(typeStorage.get('MyType')).toEqual(GraphQLType);
35+
});
36+
37+
it('should set new type and return it, if key not exists', () => {
38+
expect(typeStorage.getOrSet('MyType', GraphQLType)).toEqual(GraphQLType);
39+
expect(typeStorage.get('MyType')).toEqual(GraphQLType);
40+
});
41+
42+
it('should not set new value if it is empty', () => {
43+
expect(typeStorage.getOrSet('MyType', () => null)).toEqual(null);
44+
expect(typeStorage.has('MyType')).toEqual(false);
45+
});
46+
});
47+
});

src/typeStorage.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/* @flow */
2+
import { isFunction } from 'graphql-compose';
3+
4+
class TypeStorage extends Map {
5+
// this `create` hack due TypeError:
6+
// Constructor Map requires 'new' at TypeStorage.Map (native)
7+
static create(array?:any[]): TypeStorage {
8+
const inst = new Map(array);
9+
// $FlowFixMe
10+
inst.__proto__ = TypeStorage.prototype; // eslint-disable-line
11+
// $FlowFixMe
12+
return inst;
13+
}
14+
15+
getOrSet<T>(typeName: string, typeOrThunk: T | () => T): ?T {
16+
if (this.has(typeName)) {
17+
// $FlowFixMe
18+
return this.get(typeName);
19+
}
20+
21+
22+
// $FlowFixMe
23+
const gqType: T = isFunction(typeOrThunk) ? typeOrThunk() : typeOrThunk;
24+
if (gqType) {
25+
this.set(typeName, gqType);
26+
}
27+
28+
return gqType;
29+
}
30+
}
31+
32+
const typeStorage = TypeStorage.create();
33+
export default typeStorage;

src/utils.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import typeStorage from './typeStorage';
2+
3+
export function getTypeName(name: string, opts: any): string {
4+
return `${opts && opts.prefix || 'Elastic'}${name}${opts && opts.postfix || ''}`;
5+
}
6+
7+
export function getOrSetType<T>(typeName: string, typeOrThunk: T | () => T): T {
8+
return typeStorage.getOrSet(typeName, typeOrThunk);
9+
}

0 commit comments

Comments
 (0)