From ec91757257ed062c7743fca3d07d1b6af21cacb4 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 5 Apr 2022 16:22:57 -0400 Subject: [PATCH] feat: allows like to search by many words, adds contain to match exact strings --- docs/queries/overview.mdx | 3 ++- .../elements/WhereBuilder/field-types.tsx | 21 ++++++++++++------- src/graphql/schema/fieldToSchemaMap.ts | 12 +++++------ src/mongoose/buildQuery.ts | 2 +- src/mongoose/sanitizeFormattedValue.ts | 15 +++++++++++-- 5 files changed, 35 insertions(+), 18 deletions(-) diff --git a/docs/queries/overview.mdx b/docs/queries/overview.mdx index 7545a4752..b6112939b 100644 --- a/docs/queries/overview.mdx +++ b/docs/queries/overview.mdx @@ -59,7 +59,8 @@ The above example demonstrates a simple query but you can get much more complex. | `greater_than_equal` | For numeric or date-based fields. | | `less_than` | For numeric or date-based fields. | | `less_than_equal` | For numeric or date-based fields. | -| `like` | The value must partially match. | +| `like` | Case-insensitive string must be present. If string of words, all words must be present, in any order. | +| `contains` | Must contain the value entered, case-insensitive. | | `in` | The value must be found within the provided comma-delimited list of values. | | `not_in` | The value must NOT be within the provided comma-delimited list of values. | | `exists` | Only return documents where the value either exists (`true`) or does not exist (`false`). | diff --git a/src/admin/components/elements/WhereBuilder/field-types.tsx b/src/admin/components/elements/WhereBuilder/field-types.tsx index 92c6b6c52..170e18d6f 100644 --- a/src/admin/components/elements/WhereBuilder/field-types.tsx +++ b/src/admin/components/elements/WhereBuilder/field-types.tsx @@ -62,26 +62,31 @@ const like = { value: 'like', }; +const contains = { + label: 'contains', + value: 'contains', +}; + const fieldTypeConditions = { text: { component: 'Text', - operators: [...base, like], + operators: [...base, like, contains], }, email: { component: 'Text', - operators: [...base, like], + operators: [...base, contains], }, textarea: { component: 'Text', - operators: [...base, like], - }, - wysiwyg: { - component: 'Text', - operators: [...base, like], + operators: [...base, like, contains], }, code: { component: 'Text', - operators: [...base, like], + operators: [...base, like, contains], + }, + richText: { + component: 'Text', + operators: [...base, like, contains], }, number: { component: 'Number', diff --git a/src/graphql/schema/fieldToSchemaMap.ts b/src/graphql/schema/fieldToSchemaMap.ts index 7c9d5a1cc..10b9040f6 100644 --- a/src/graphql/schema/fieldToSchemaMap.ts +++ b/src/graphql/schema/fieldToSchemaMap.ts @@ -44,7 +44,7 @@ const fieldToSchemaMap: (parentName: string) => any = (parentName: string) => ({ field, type, parentName, - [...operators.equality, 'like'], + [...operators.equality, 'like', 'contains'], ), }; }, @@ -55,7 +55,7 @@ const fieldToSchemaMap: (parentName: string) => any = (parentName: string) => ({ field, type, parentName, - [...operators.equality, 'like'], + [...operators.equality, 'like', 'contains'], ), }; }, @@ -66,7 +66,7 @@ const fieldToSchemaMap: (parentName: string) => any = (parentName: string) => ({ field, type, parentName, - [...operators.equality, 'like'], + [...operators.equality, 'like', 'contains'], ), }; }, @@ -77,7 +77,7 @@ const fieldToSchemaMap: (parentName: string) => any = (parentName: string) => ({ field, type, parentName, - [...operators.equality, 'like'], + [...operators.equality, 'like', 'contains'], ), }; }, @@ -88,7 +88,7 @@ const fieldToSchemaMap: (parentName: string) => any = (parentName: string) => ({ field, type, parentName, - [...operators.equality, 'like'], + [...operators.equality, 'like', 'contains'], ), }; }, @@ -116,7 +116,7 @@ const fieldToSchemaMap: (parentName: string) => any = (parentName: string) => ({ }, {}), }), parentName, - [...operators.equality, 'like'], + [...operators.equality, 'like', 'contains'], ), }), date: (field: DateField) => { diff --git a/src/mongoose/buildQuery.ts b/src/mongoose/buildQuery.ts index 75746bb19..1396bf69c 100644 --- a/src/mongoose/buildQuery.ts +++ b/src/mongoose/buildQuery.ts @@ -8,7 +8,7 @@ import { getSchemaTypeOptions } from './getSchemaTypeOptions'; import { operatorMap } from './operatorMap'; import { sanitizeQueryValue } from './sanitizeFormattedValue'; -const validOperators = ['like', 'in', 'all', 'not_in', 'greater_than_equal', 'greater_than', 'less_than_equal', 'less_than', 'not_equals', 'equals', 'exists', 'near']; +const validOperators = ['like', 'contains', 'in', 'all', 'not_in', 'greater_than_equal', 'greater_than', 'less_than_equal', 'less_than', 'not_equals', 'equals', 'exists', 'near']; const subQueryOptions = { limit: 50, diff --git a/src/mongoose/sanitizeFormattedValue.ts b/src/mongoose/sanitizeFormattedValue.ts index f807dd119..e49fd0791 100644 --- a/src/mongoose/sanitizeFormattedValue.ts +++ b/src/mongoose/sanitizeFormattedValue.ts @@ -90,8 +90,19 @@ export const sanitizeQueryValue = (schemaType: SchemaType, path: string, operato } } - if (operator === 'like' && path !== '_id') { - formattedValue = { $regex: formattedValue, $options: 'i' }; + if (path !== '_id') { + if (operator === 'contains') { + formattedValue = { $regex: formattedValue, $options: 'i' }; + } + + if (operator === 'like' && typeof formattedValue === 'string') { + const words = formattedValue.split(' '); + const regex = words.reduce((pattern, word, i) => { + return `${pattern}(?=.*\\b${word}\\b)${i + 1 === words.length ? '.+' : ''}`; + }, ''); + + formattedValue = { $regex: new RegExp(regex), $options: 'i' }; + } } if (operator === 'exists') {