From fef17f0dc5ce0a1e3dff3914f588d1931c3df1f1 Mon Sep 17 00:00:00 2001 From: nodkz Date: Tue, 21 Mar 2017 22:49:48 +0600 Subject: [PATCH] feat(SearchResolver): Add SearchResolver generated from mapping --- package.json | 4 +- src/elasticDSL/Commons/FieldNames.js | 45 +++++++++++--------- src/elasticDSL/SearchBody.js | 3 +- src/elasticDSL/Sort.js | 62 ++++++++++++++++++++++++++++ src/resolvers/search.js | 18 +++++--- src/resolvers/searchConnection.js | 21 ++++++++-- yarn.lock | 36 +++++++--------- 7 files changed, 139 insertions(+), 50 deletions(-) create mode 100644 src/elasticDSL/Sort.js diff --git a/package.json b/package.json index 0bd1c26..b228472 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ }, "devDependencies": { "babel-cli": "^6.24.0", - "babel-eslint": "^7.1.1", + "babel-eslint": "^7.2.0", "babel-plugin-transform-flow-strip-types": "^6.22.0", "babel-plugin-transform-object-rest-spread": "^6.22.0", "babel-preset-env": "^1.2.2", @@ -48,7 +48,7 @@ "express-graphql": "^0.6.3", "flow-bin": "^0.42.0", "graphql": "^0.9.1", - "graphql-compose": "^1.17.3", + "graphql-compose": "^1.18.0", "jest": "^19.0.2", "jest-babel": "^1.0.1", "npm-run-all": "^4.0.1", diff --git a/src/elasticDSL/Commons/FieldNames.js b/src/elasticDSL/Commons/FieldNames.js index 2a90d77..834e174 100644 --- a/src/elasticDSL/Commons/FieldNames.js +++ b/src/elasticDSL/Commons/FieldNames.js @@ -89,6 +89,21 @@ export function getAllAsFieldConfigMap(opts: mixed, fc: mixed) { return getFieldConfigMap(opts, ['_all'], fc); } +export function getFieldNamesByElasticType( + fieldMap: any, + types: ElasticDataType[] +): string[] { + const fieldNames = []; + types.forEach(type => { + if (typeof fieldMap[type] === 'object') { + Object.keys(fieldMap[type]).forEach(fieldName => { + fieldNames.push(fieldName); + }); + } + }); + return fieldNames; +} + export function getFieldNamesType( opts: mixed, types: ElasticDataType[], @@ -134,19 +149,15 @@ function getEnumValues( addAll: boolean = false ): GraphQLEnumValueConfigMap { const values = {}; - types.forEach(type => { - if (addAll) { - values._all = { - value: '_all', - }; - } - if (typeof fieldMap[type] === 'object') { - Object.keys(fieldMap[type]).forEach(fieldName => { - values[fieldName] = { - value: fieldName.replace('__', '.'), - }; - }); - } + if (addAll) { + values._all = { + value: '_all', + }; + } + getFieldNamesByElasticType(fieldMap, types).forEach(fieldName => { + values[fieldName] = { + value: fieldName.replace('__', '.'), + }; }); return values; } @@ -175,12 +186,8 @@ export function getFieldConfigMap( // $FlowFixMe fcMap._all = fc; } - types.forEach(type => { - if (typeof opts.fieldMap[type] === 'object') { - Object.keys(opts.fieldMap[type]).forEach(fieldName => { - fcMap[fieldName] = fc; - }); - } + getFieldNamesByElasticType(opts.fieldMap, types).forEach(fieldName => { + fcMap[fieldName] = fc; }); if (Object.keys(fcMap).length === 0) { diff --git a/src/elasticDSL/SearchBody.js b/src/elasticDSL/SearchBody.js index abd4da6..84a7930 100644 --- a/src/elasticDSL/SearchBody.js +++ b/src/elasticDSL/SearchBody.js @@ -4,6 +4,7 @@ import { InputTypeComposer } from 'graphql-compose'; import type { FieldsMapByElasticType } from '../mappingConverter'; import { getQueryITC, prepareQueryInResolve } from './Query/Query'; import { getAggsITC, prepareAggsInResolve } from './Aggs/Aggs'; +import { getSortITC } from './Sort'; import { getTypeName, getOrSetType, desc } from '../utils'; export type SearchOptsT = { @@ -31,7 +32,7 @@ export function getSearchBodyITC(opts: SearchOptsT = {}): InputTypeComposer { aggs: () => getAggsITC(opts), size: 'Int', from: 'Int', - sort: 'JSON', + sort: () => [getSortITC(opts)], _source: 'JSON', script_fields: 'JSON', post_filter: () => getQueryITC(opts), diff --git a/src/elasticDSL/Sort.js b/src/elasticDSL/Sort.js new file mode 100644 index 0000000..3c22757 --- /dev/null +++ b/src/elasticDSL/Sort.js @@ -0,0 +1,62 @@ +/* @flow */ + +import { InputTypeComposer } from 'graphql-compose'; +import { GraphQLEnumType } from 'graphql'; +import { getTypeName, getOrSetType } from '../utils'; +import { getFieldNamesByElasticType } from './Commons/FieldNames'; + +const sortableTypes = [ + 'byte', + 'short', + 'integer', + 'long', + 'double', + 'float', + 'half_float', + 'scaled_float', + 'token_count', + 'date', + 'boolean', + 'ip', + 'keyword', +]; + +export function getSortITC(opts: any = {}): InputTypeComposer | string { + const name = getTypeName('SortEnum', opts); + const description = 'Sortable fields from mapping'; + + if (!opts.fieldMap) { + return 'JSON'; + } + + return getOrSetType(name, () => { + const sortableFields = getFieldNamesByElasticType( + opts.fieldMap, + sortableTypes + ); + if (sortableFields.length === 0) { + return 'JSON'; + } + + const values = { + _score: { + value: '_score', + }, + }; + sortableFields.forEach(fieldName => { + const dottedName = fieldName.replace('__', '.'); + values[`${fieldName}__asc`] = { + value: { [dottedName]: 'asc' }, + }; + values[`${fieldName}__desc`] = { + value: { [dottedName]: 'desc' }, + }; + }); + + return new GraphQLEnumType({ + name, + description, + values, + }); + }); +} diff --git a/src/resolvers/search.js b/src/resolvers/search.js index ec61000..94f62fa 100644 --- a/src/resolvers/search.js +++ b/src/resolvers/search.js @@ -89,16 +89,18 @@ export default function createSearchResolver( const bodyITC = InputTypeComposer.create(argsConfigMap.body.type); argsConfigMap.query = bodyITC.getField('query'); argsConfigMap.aggs = bodyITC.getField('aggs'); + argsConfigMap.sort = bodyITC.getField('sort'); argsConfigMap.highlight = bodyITC.getField('highlight'); const topLevelArgs = [ - 'limit', - 'skip', 'q', - 'opts', 'query', + 'sort', + 'limit', + 'skip', 'aggs', 'highlight', + 'opts', ]; argsConfigMap.opts = InputTypeComposer.create({ name: `${sourceTC.getTypeName()}Opts`, @@ -111,6 +113,7 @@ export default function createSearchResolver( }); const type = getSearchOutputTC({ prefix, fieldMap, sourceTC }); + const hitsType = type.get('hits.hits'); type .addFields({ // $FlowFixMe @@ -118,7 +121,7 @@ export default function createSearchResolver( // $FlowFixMe max_score: 'Float', // $FlowFixMe - hits: [type.get('hits.hits')], + hits: hitsType ? [hitsType] : 'JSON', }) .reorderFields([ 'hits', @@ -185,6 +188,11 @@ export default function createSearchResolver( delete args.aggs; } + if (args.sort) { + args.body.sort = args.sort; + delete args.sort; + } + if (args.opts) { args = { ...args.opts, @@ -212,7 +220,7 @@ export default function createSearchResolver( return res; }, - }).reorderArgs(['q', 'query', 'aggs', 'limit', 'skip']); + }).reorderArgs(['q', 'query', 'sort', 'limit', 'skip', 'aggs']); } export function toDottedList( diff --git a/src/resolvers/searchConnection.js b/src/resolvers/searchConnection.js index 765c0d4..40ff13d 100644 --- a/src/resolvers/searchConnection.js +++ b/src/resolvers/searchConnection.js @@ -19,7 +19,16 @@ export default function createSearchConnectionResolver( before: 'String', }) .removeArg(['limit', 'skip']) - .reorderArgs(['q', 'query', 'aggs', 'first', 'after', 'last', 'before']); + .reorderArgs([ + 'q', + 'query', + 'sort', + 'aggs', + 'first', + 'after', + 'last', + 'before', + ]); const searchType = searchResolver.getTypeComposer(); const typeName = searchType.getTypeName(); @@ -43,7 +52,7 @@ export default function createSearchConnectionResolver( ); resolver.resolve = async rp => { - const { args = {} } = rp; + const { args = {}, projection = {} } = rp; const first = parseInt(args.first, 10) || 0; if (first < 0) { @@ -75,9 +84,15 @@ export default function createSearchConnectionResolver( args.limit = limit + 1; // +1 document, to check next page presence args.skip = skip; + if (projection.edges) { + projection.hits = projection.edges.node; + delete projection.edges; + } + const res = await searchResolver.resolve(rp); - let list = res.hits.hits; + let list = res.hits || []; + const hasExtraRecords = list.length > limit; if (hasExtraRecords) list = list.slice(0, limit); const edges = list.map(node => ({ node, cursor: dataToCursor(node.sort) })); diff --git a/yarn.lock b/yarn.lock index bdf5d45..f21290a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -314,15 +314,15 @@ babel-core@^6.0.0, babel-core@^6.24.0: slash "^1.0.0" source-map "^0.5.0" -babel-eslint@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-7.1.1.tgz#8a6a884f085aa7060af69cfc77341c2f99370fb2" +babel-eslint@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-7.2.0.tgz#8941514b9dead06f0df71b29d5d5b193a92ee0ae" dependencies: - babel-code-frame "^6.16.0" - babel-traverse "^6.15.0" - babel-types "^6.15.0" - babylon "^6.13.0" - lodash.pickby "^4.6.0" + babel-code-frame "^6.22.0" + babel-traverse "^6.23.1" + babel-types "^6.23.0" + babylon "^6.16.1" + lodash "^4.17.4" babel-generator@^6.18.0, babel-generator@^6.24.0: version "6.24.0" @@ -796,7 +796,7 @@ babel-template@^6.16.0, babel-template@^6.22.0, babel-template@^6.23.0: babylon "^6.11.0" lodash "^4.2.0" -babel-traverse@^6.15.0, babel-traverse@^6.18.0, babel-traverse@^6.22.0, babel-traverse@^6.23.0, babel-traverse@^6.23.1: +babel-traverse@^6.18.0, babel-traverse@^6.22.0, babel-traverse@^6.23.0, babel-traverse@^6.23.1: version "6.23.1" resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.23.1.tgz#d3cb59010ecd06a97d81310065f966b699e14f48" dependencies: @@ -810,7 +810,7 @@ babel-traverse@^6.15.0, babel-traverse@^6.18.0, babel-traverse@^6.22.0, babel-tr invariant "^2.2.0" lodash "^4.2.0" -babel-types@^6.15.0, babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.22.0, babel-types@^6.23.0: +babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.22.0, babel-types@^6.23.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.23.0.tgz#bb17179d7538bad38cd0c9e115d340f77e7e9acf" dependencies: @@ -819,11 +819,11 @@ babel-types@^6.15.0, babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.22 lodash "^4.2.0" to-fast-properties "^1.0.1" -babylon@6.15.0: +babylon@6.15.0, babylon@^6.11.0, babylon@^6.13.0, babylon@^6.15.0: version "6.15.0" resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.15.0.tgz#ba65cfa1a80e1759b0e89fb562e27dccae70348e" -babylon@^6.11.0, babylon@^6.13.0, babylon@^6.15.0: +babylon@^6.16.1: version "6.16.1" resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.16.1.tgz#30c5a22f481978a9e7f8cdfdf496b11d94b404d3" @@ -2001,9 +2001,9 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.4, graceful-fs@^4.1.6: version "1.0.1" resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" -graphql-compose@^1.17.3: - version "1.17.3" - resolved "https://registry.yarnpkg.com/graphql-compose/-/graphql-compose-1.17.3.tgz#ad4f19570aedc6647d6addb43d6f52d3804029b9" +graphql-compose@^1.18.0: + version "1.18.0" + resolved "https://registry.yarnpkg.com/graphql-compose/-/graphql-compose-1.18.0.tgz#bb28c2336bb0e4fdd87efeb92747e781a8691752" dependencies: babel-runtime "^6.23.0" object-path "^0.11.4" @@ -2865,10 +2865,6 @@ lodash.padstart@^4.1.0: version "4.6.1" resolved "https://registry.yarnpkg.com/lodash.padstart/-/lodash.padstart-4.6.1.tgz#d2e3eebff0d9d39ad50f5cbd1b52a7bce6bb611b" -lodash.pickby@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.pickby/-/lodash.pickby-4.6.0.tgz#7dea21d8c18d7703a27c704c15d3b84a67e33aff" - lodash.restparam@^3.0.0: version "3.6.1" resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" @@ -2877,7 +2873,7 @@ lodash@^3.6.0, lodash@^3.7.0: version "3.10.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" -lodash@^4.0.0, lodash@^4.12.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.2.0, lodash@^4.3.0: +lodash@^4.0.0, lodash@^4.12.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"