From 4d0122ecec8d9ccea7f26cb9b55fbd707e77b627 Mon Sep 17 00:00:00 2001 From: Nick Redmark Date: Thu, 8 Feb 2024 16:17:56 +0100 Subject: [PATCH] break: Extend query builders again (#135) * break: Extend query builders again * fixes --------- Co-authored-by: Nicola Marcacci Rossi --- src/client/queries.ts | 57 ++++++++++++------- src/models/utils.ts | 14 +++-- tests/unit/__snapshots__/queries.spec.ts.snap | 5 +- tests/unit/queries.spec.ts | 5 +- 4 files changed, 49 insertions(+), 32 deletions(-) diff --git a/src/client/queries.ts b/src/client/queries.ts index d35b8ff..a84e647 100644 --- a/src/client/queries.ts +++ b/src/client/queries.ts @@ -1,9 +1,8 @@ -import upperFirst from 'lodash/upperFirst'; import { ManyToManyRelation } from '..'; import { EntityModel, Model, Models, Relation } from '../models/models'; import { - actionableRelations, and, + getActionableRelations, isQueriableBy, isRelation, isSimpleField, @@ -27,41 +26,55 @@ export const getUpdateEntityQuery = ( .filter(isUpdatableBy(role)) .map(({ name }) => name) .join(' ')} - ${actionableRelations(model, 'update') - .filter(({ name }) => !fields || fields.includes(name)) - .map(({ name }) => `${name} { id }`)} + ${getActionableRelations(model, 'update') + .filter((name) => !fields || fields.includes(name)) + .map((name) => `${name} { id }`)} ${additionalFields} } }`; -export const getEditEntityRelationsQuery = ( - model: EntityModel, - action: 'create' | 'update' | 'filter', - fields?: string[], - ignoreFields?: string[], - additionalFields: Record = {} -) => { - const relations = actionableRelations(model, action).filter( - ({ name }) => (!fields || fields.includes(name)) && (!ignoreFields || !ignoreFields.includes(name)) - ); +export type RelationConstraints = Record any>; + +export const fieldIsSearchable = (model: EntityModel, fieldName: string) => { + const relation = model.getRelation(fieldName); + const targetModel = relation.targetModel; + const displayField = targetModel.getField(targetModel.displayField || 'id'); + return displayField.searchable; +}; + +export const getSelectEntityRelationsQuery = (model: EntityModel, relationNames: string[]) => { + const relations = relationNames.map((name) => model.getRelation(name)); return ( !!relations.length && - `query ${upperFirst(action)}${model.name}Relations { + `query Select${model.name}Relations(${relations + .map( + (relation) => + `$${relation.name}Where: ${relation.targetModel.name}Where, $${relation.name}Limit: Int${ + fieldIsSearchable(model, relation.name) ? `, $${relation.name}Search: String` : '' + }` + ) + .join(', ')}) { ${relations .map((relation) => { let filters = ''; + if (relation.targetModel.displayField) { const displayField = relation.targetModel.fieldsByName[relation.targetModel.displayField]; - if (displayField.orderable) { - filters = `(orderBy: [{ ${relation.targetModel.displayField}: ASC }])`; + if ('orderable' in displayField && displayField.orderable) { + filters += `, orderBy: [{ ${relation.targetModel.displayField}: ASC }]`; } } - return `${relation.name}: ${relation.targetModel.pluralField}${filters} { + if (fieldIsSearchable(model, relation.name)) { + filters += `, search: $${relation.name}Search`; + } + + return `${relation.name}: ${relation.targetModel.pluralField}(where: $${relation.name}Where, limit: $${ + relation.name + }Limit${filters}) { id display: ${relation.targetModel.displayField || 'id'} - ${additionalFields[relation.name] || ''} }`; }) .join(' ')} @@ -160,11 +173,11 @@ export const getEntityListQuery = ( ${root ? '$id: ID!,' : ''} $limit: Int!, $where: ${model.name}Where!, - ${model.fields.some(({ searchable }) => searchable) ? '$search: String,' : ''} + ${model.relations.some(({ field: { searchable } }) => searchable) ? '$search: String,' : ''} ) { ${root ? `root: ${typeToField(root.model.name)}(where: { id: $id }) {` : ''} data: ${root ? root.reverseRelationName : model.pluralField}(limit: $limit, where: $where, ${ - model.fields.some(({ searchable }) => searchable) ? ', search: $search' : '' + model.relations.some(({ field: { searchable } }) => searchable) ? ', search: $search' : '' }) { ${displayField(model)} ${model.fields.filter(and(isSimpleField, isQueriableBy(role))).map(({ name }) => name)} diff --git a/src/models/utils.ts b/src/models/utils.ts index f926fb5..d7bb060 100644 --- a/src/models/utils.ts +++ b/src/models/utils.ts @@ -104,11 +104,15 @@ export const isUpdatableBy = (role: string) => (field: EntityField) => export const isCreatableBy = (role: string) => (field: EntityField) => field.creatable && (field.creatable === true || !field.creatable.roles || field.creatable.roles.includes(role)); -export const actionableRelations = (model: EntityModel, action: 'create' | 'update' | 'filter') => - model.relations.filter( - (relation) => - relation.field[`${action === 'filter' ? action : action.slice(0, -1)}able` as 'filterable' | 'creatable' | 'updatable'] - ); +export const getActionableRelations = (model: EntityModel, action: 'create' | 'update' | 'filter') => + model.relations + .filter( + (relation) => + relation.field[ + `${action === 'filter' ? action : action.slice(0, -1)}able` as 'filterable' | 'creatable' | 'updatable' + ] + ) + .map(({ name }) => name); export const summonByName = (array: T[], value: string) => summonByKey(array, 'name', value); diff --git a/tests/unit/__snapshots__/queries.spec.ts.snap b/tests/unit/__snapshots__/queries.spec.ts.snap index b20d5b6..aaf904b 100644 --- a/tests/unit/__snapshots__/queries.spec.ts.snap +++ b/tests/unit/__snapshots__/queries.spec.ts.snap @@ -1,11 +1,10 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`queries getEntityRelationsQuery applies filters 1`] = ` -"query UpdateSomeObjectRelations { - another: anotherObjects(orderBy: [{ name: ASC }]) { +"query SelectSomeObjectRelations($anotherWhere: AnotherObjectWhere, $anotherLimit: Int) { + another: anotherObjects(where: $anotherWhere, limit: $anotherLimit, orderBy: [{ name: ASC }]) { id display: name - } }" `; diff --git a/tests/unit/queries.spec.ts b/tests/unit/queries.spec.ts index 60533ad..00ce2af 100644 --- a/tests/unit/queries.spec.ts +++ b/tests/unit/queries.spec.ts @@ -1,10 +1,11 @@ -import { getEditEntityRelationsQuery } from '../../src'; +import { getActionableRelations, getSelectEntityRelationsQuery } from '../../src'; import { models } from '../utils/models'; describe('queries', () => { describe('getEntityRelationsQuery', () => { it('applies filters', () => { - expect(getEditEntityRelationsQuery(models.getModel('SomeObject', 'entity'), 'update')).toMatchSnapshot(); + const model = models.getModel('SomeObject', 'entity'); + expect(getSelectEntityRelationsQuery(model, getActionableRelations(model, 'update'))).toMatchSnapshot(); }); }); });