From 0758f011209a598d16d5caa9b93851ebda3ac758 Mon Sep 17 00:00:00 2001 From: Elliot DeNolf Date: Fri, 16 Oct 2020 17:13:52 -0400 Subject: [PATCH 1/2] wip: exists query --- .../elements/WhereBuilder/field-types.js | 4 ++ src/graphql/schema/buildWhereInputType.js | 57 +++++++++++-------- src/graphql/schema/withOperators.js | 23 ++++---- src/mongoose/buildQuery.js | 6 +- 4 files changed, 54 insertions(+), 36 deletions(-) diff --git a/src/admin/components/elements/WhereBuilder/field-types.js b/src/admin/components/elements/WhereBuilder/field-types.js index 575e7b9387..1cdf649d8e 100644 --- a/src/admin/components/elements/WhereBuilder/field-types.js +++ b/src/admin/components/elements/WhereBuilder/field-types.js @@ -19,6 +19,10 @@ const base = [ label: 'is not in', value: 'not_in', }, + { + label: 'exists', + value: 'exists', + }, ]; const numeric = [ diff --git a/src/graphql/schema/buildWhereInputType.js b/src/graphql/schema/buildWhereInputType.js index b542e2d894..c98310ea5c 100644 --- a/src/graphql/schema/buildWhereInputType.js +++ b/src/graphql/schema/buildWhereInputType.js @@ -62,15 +62,21 @@ const buildWhereInputType = (name, fields, parentName) => { return nestedPaths; }; + const operators = { + equality: ['equals', 'not_equals'], + contains: ['in', 'not_in', 'all'], + comparison: ['greater_than_equal', 'greater_than', 'less_than_equal', 'less_than'], + }; + const fieldToSchemaMap = { number: (field) => { const type = GraphQLFloat; return { type: withOperators( - field.name, + field, type, parentName, - ['equals', 'greater_than_equal', 'greater_than', 'less_than_equal', 'less_than', 'not_equals'], + [...operators.equality, ...operators.comparison], ), }; }, @@ -78,10 +84,11 @@ const buildWhereInputType = (name, fields, parentName) => { const type = GraphQLString; return { type: withOperators( - field.name, + field, type, parentName, ['equals', 'like', 'not_equals'], + [...operators.equality, 'like'], ), }; }, @@ -89,10 +96,10 @@ const buildWhereInputType = (name, fields, parentName) => { const type = EmailAddressResolver; return { type: withOperators( - field.name, + field, type, parentName, - ['equals', 'like', 'not_equals'], + [...operators.equality, 'like'], ), }; }, @@ -100,10 +107,10 @@ const buildWhereInputType = (name, fields, parentName) => { const type = GraphQLString; return { type: withOperators( - field.name, + field, type, parentName, - ['equals', 'like', 'not_equals'], + [...operators.equality, 'like'], ), }; }, @@ -111,10 +118,10 @@ const buildWhereInputType = (name, fields, parentName) => { const type = GraphQLJSON; return { type: withOperators( - field.name, + field, type, parentName, - ['equals', 'like', 'not_equals'], + [...operators.equality, 'like'], ), }; }, @@ -122,16 +129,16 @@ const buildWhereInputType = (name, fields, parentName) => { const type = GraphQLString; return { type: withOperators( - field.name, + field, type, parentName, - ['equals', 'like', 'not_equals'], + [...operators.equality, 'like'], ), }; }, radio: (field) => ({ type: withOperators( - field.name, + field, new GraphQLEnumType({ name: `${combineParentName(parentName, field.name)}_Input`, values: field.options.reduce((values, option) => ({ @@ -142,26 +149,26 @@ const buildWhereInputType = (name, fields, parentName) => { }), {}), }), parentName, - ['like', 'equals', 'not_equals'], + [...operators.equality, 'like'], ), }), date: (field) => { const type = DateTimeResolver; return { type: withOperators( - field.name, + field, type, parentName, - ['equals', 'like', 'not_equals', 'greater_than_equal', 'greater_than', 'less_than_equal', 'less_than'], + [...operators.equality, ...operators.comparison, 'like'], ), }; }, relationship: (field) => { let type = withOperators( - field.name, + field, GraphQLString, parentName, - ['in', 'not_in', 'all', 'equals', 'not_equals'], + [...operators.equality, ...operators.contains], ); if (Array.isArray(field.relationTo)) { @@ -194,23 +201,23 @@ const buildWhereInputType = (name, fields, parentName) => { }, upload: (field) => ({ type: withOperators( - field.name, + field, GraphQLString, parentName, - ['equals', 'not_equals'], + [...operators.equality], ), }), checkbox: (field) => ({ type: withOperators( - field.name, + field, GraphQLBoolean, parentName, - ['equals', 'not_equals'], + [...operators.equality], ), }), select: (field) => ({ type: withOperators( - field.name, + field, new GraphQLEnumType({ name: `${combineParentName(parentName, field.name)}_Input`, values: field.options.reduce((values, option) => { @@ -236,7 +243,7 @@ const buildWhereInputType = (name, fields, parentName) => { }, {}), }), parentName, - ['in', 'not_in', 'all', 'equals', 'not_equals'], + [...operators.equality, ...operators.contains], ), }), array: (field) => recursivelyBuildNestedPaths(field), @@ -294,10 +301,10 @@ const buildWhereInputType = (name, fields, parentName) => { fieldTypes.id = { type: withOperators( - 'id', + { name: 'id' }, GraphQLString, parentName, - ['equals', 'not_equals', 'in', 'not_in'], + [...operators.equality, ...operators.contains], ), }; diff --git a/src/graphql/schema/withOperators.js b/src/graphql/schema/withOperators.js index 1b55fa988b..6ee8fba886 100644 --- a/src/graphql/schema/withOperators.js +++ b/src/graphql/schema/withOperators.js @@ -1,20 +1,23 @@ const { GraphQLList, GraphQLInputObjectType } = require('graphql'); const combineParentName = require('../utilities/combineParentName'); -const withOperators = (fieldName, type, parent, operators) => { - const name = `${combineParentName(parent, fieldName)}_operator`; +const withOperators = (field, type, parent, operators) => { + const name = `${combineParentName(parent, field.name)}_operator`; const listOperators = ['in', 'not_in', 'all']; + if (!field.required) { + console.log('name', field.name); + operators.push('exists'); + } + return new GraphQLInputObjectType({ name, - fields: operators.reduce((fields, operator) => { - return { - ...fields, - [operator]: { - type: listOperators.indexOf(operator) > -1 ? new GraphQLList(type) : type, - }, - }; - }, {}), + fields: operators.reduce((fields, operator) => ({ + ...fields, + [operator]: { + type: listOperators.indexOf(operator) > -1 ? new GraphQLList(type) : type, + }, + }), {}), }); }; diff --git a/src/mongoose/buildQuery.js b/src/mongoose/buildQuery.js index 4ca743ab06..10ec12ff8f 100644 --- a/src/mongoose/buildQuery.js +++ b/src/mongoose/buildQuery.js @@ -2,7 +2,7 @@ /* eslint-disable no-restricted-syntax */ const mongoose = require('mongoose'); -const validOperators = ['like', 'in', 'all', 'not_in', 'greater_than_equal', 'greater_than', 'less_than_equal', 'less_than', 'not_equals', 'equals']; +const validOperators = ['like', 'in', 'all', 'not_in', 'greater_than_equal', 'greater_than', 'less_than_equal', 'less_than', 'not_equals', 'equals', 'exists']; function addSearchParam(key, value, searchParams) { return { @@ -216,6 +216,10 @@ class ParamParser { break; + case 'exists': + formattedValue = { $exists: (val === 'true' || val === true) }; + break; + default: formattedValue = val; break; From 215df7ff9eedb2c2d78dff38d3d449b5cb21e01f Mon Sep 17 00:00:00 2001 From: Elliot DeNolf Date: Fri, 16 Oct 2020 21:42:55 -0400 Subject: [PATCH 2/2] handle graphql exists type --- .../graphql/resolvers/resolvers.spec.js | 73 +++++++++++++++++++ src/graphql/schema/withOperators.js | 29 +++++--- 2 files changed, 91 insertions(+), 11 deletions(-) diff --git a/src/collections/graphql/resolvers/resolvers.spec.js b/src/collections/graphql/resolvers/resolvers.spec.js index c812cfd96f..f73c494d12 100644 --- a/src/collections/graphql/resolvers/resolvers.spec.js +++ b/src/collections/graphql/resolvers/resolvers.spec.js @@ -97,6 +97,79 @@ describe('GrahpQL Resolvers', () => { expect(retrievedId).toStrictEqual(id); }); + + it('should query exists - true', async () => { + const title = 'gql read'; + const description = 'description'; + const summary = 'summary'; + + // language=graphQL + const query = `mutation { + createLocalizedPost(data: {title: "${title}", description: "${description}", summary: "${summary}", priority: 10}) { + id + title + description + priority + createdAt + updatedAt + } + }`; + + const response = await client.request(query); + + const { id } = response.createLocalizedPost; + // language=graphQL + const readQuery = `query { + LocalizedPosts(where: { summary: { exists: true }}) { + docs { + id + description + summary + } + } +}`; + const readResponse = await client.request(readQuery); + const retrievedId = readResponse.LocalizedPosts.docs[0].id; + + expect(readResponse.LocalizedPosts.docs).toHaveLength(1); + expect(retrievedId).toStrictEqual(id); + }); + + it('should query exists - false', async () => { + const title = 'gql read'; + const description = 'description'; + + // language=graphQL + const query = `mutation { + createLocalizedPost(data: {title: "${title}", description: "${description}", priority: 10}) { + id + title + description + priority + createdAt + updatedAt + } + }`; + + const response = await client.request(query); + + const { id } = response.createLocalizedPost; + // language=graphQL + const readQuery = `query { + LocalizedPosts(where: { summary: { exists: false }}) { + docs { + id + summary + } + } +}`; + const readResponse = await client.request(readQuery); + const retrievedDoc = readResponse.LocalizedPosts.docs[0]; + + expect(readResponse.LocalizedPosts.docs.length).toBeGreaterThan(0); + expect(retrievedDoc.id).toStrictEqual(id); + expect(retrievedDoc.summary).toBeNull(); + }); }); describe('Update', () => { diff --git a/src/graphql/schema/withOperators.js b/src/graphql/schema/withOperators.js index 6ee8fba886..b1d3b750ee 100644 --- a/src/graphql/schema/withOperators.js +++ b/src/graphql/schema/withOperators.js @@ -1,23 +1,30 @@ -const { GraphQLList, GraphQLInputObjectType } = require('graphql'); +const { GraphQLList, GraphQLInputObjectType, GraphQLBoolean } = require('graphql'); const combineParentName = require('../utilities/combineParentName'); const withOperators = (field, type, parent, operators) => { const name = `${combineParentName(parent, field.name)}_operator`; const listOperators = ['in', 'not_in', 'all']; - if (!field.required) { - console.log('name', field.name); - operators.push('exists'); - } + if (!field.required) operators.push('exists'); return new GraphQLInputObjectType({ name, - fields: operators.reduce((fields, operator) => ({ - ...fields, - [operator]: { - type: listOperators.indexOf(operator) > -1 ? new GraphQLList(type) : type, - }, - }), {}), + fields: operators.reduce((fields, operator) => { + let gqlType; + if (listOperators.indexOf(operator) > -1) { + gqlType = new GraphQLList(type); + } else if (operator === 'exists') { + gqlType = GraphQLBoolean; + } else { + gqlType = type; + } + return { + ...fields, + [operator]: { + type: gqlType, + }, + }; + }, {}), }); };