From 0ddf3901dc718796d9995f49360eb9468bfb8f23 Mon Sep 17 00:00:00 2001 From: Ivo Patty Date: Wed, 1 May 2019 21:01:38 +0200 Subject: [PATCH] Fixes #2 for SCALAR Type --- src/buildVariables.test.ts | 44 ++++++++++++++++-- src/buildVariables.ts | 15 ++++-- src/utils/computeAddRemoveUpdate.ts | 72 ++++++++++++++++++++++++++++- 3 files changed, 122 insertions(+), 9 deletions(-) diff --git a/src/buildVariables.test.ts b/src/buildVariables.test.ts index 4ed5996..f60135f 100644 --- a/src/buildVariables.test.ts +++ b/src/buildVariables.test.ts @@ -468,7 +468,8 @@ describe('buildVariables', () => { author: { connect: { id: 'author1' } }, tags: { connect: [{ id: 'tags2' }], - disconnect: [] + disconnect: [], + update: [] }, title: 'Foo' } @@ -502,7 +503,8 @@ describe('buildVariables', () => { author: { connect: { id: 'author1' } }, tags: { connect: [{ id: 'tags2' }], - disconnect: [] + disconnect: [], + update: [] }, title: 'Foo' } @@ -536,13 +538,49 @@ describe('buildVariables', () => { author: { connect: { id: 'author1' } }, tags: { connect: [], - disconnect: [{id: 'tags3'}] + disconnect: [{id: 'tags3'}], + update: [] }, title: 'Foo' } }); }); + it('can update nested scalars', () => { + const params = { + data: { + id: 'postId', + tags: [{ id: 'tags1', name: 'test' }, { id: 'tags2' }], + tagsIds: ['tags1', 'tags2'], + author: { id: 'author1' }, + title: 'Foo' + }, + previousData: { + tags: [{ id: 'tags1', name: 'works' }, { id: 'tags2' }], + tagsIds: ['tags1', 'tags2'] + } + }; + + expect( + buildVariables(introspectionResult as unknown as IntrospectionResult)( + { type: { name: 'Post' } } as Resource, + UPDATE, + params + ) + ).toEqual({ + where: { id: 'postId' }, + data: { + author: { connect: { id: 'author1' } }, + tags: { + connect: [], + disconnect: [], + update: [{where: {id: 'tags1'}, data: {name: 'test'}}] + }, + title: 'Foo' + } + }); + + }) }); describe('GET_MANY', () => { diff --git a/src/buildVariables.ts b/src/buildVariables.ts index c7f6efa..a869538 100644 --- a/src/buildVariables.ts +++ b/src/buildVariables.ts @@ -3,8 +3,8 @@ import isObject from 'lodash/isObject'; import { CREATE, DELETE, GET_LIST, GET_MANY, GET_MANY_REFERENCE, GET_ONE, UPDATE } from 'react-admin'; import { IntrospectionResult, Resource } from './constants/interfaces'; -import { PRISMA_CONNECT, PRISMA_CREATE, PRISMA_DISCONNECT } from './constants/mutations'; -import { computeFieldsToAddRemoveUpdate } from './utils/computeAddRemoveUpdate'; +import {PRISMA_CONNECT, PRISMA_CREATE, PRISMA_DISCONNECT, PRISMA_UPDATE} from './constants/mutations'; +import {computedNestedFieldsToUpdate, computeFieldsToAddRemoveUpdate} from './utils/computeAddRemoveUpdate'; import getFinalType from './utils/getFinalType'; @@ -272,15 +272,20 @@ const buildUpdateVariables = (introspectionResults: IntrospectionResult) => ( if (Array.isArray(params.data[key])) { //TODO: Make connect, disconnect and update overridable - //TODO: Make updates working const { fieldsToAdd, - fieldsToRemove, /* fieldsToUpdate */ + fieldsToRemove, fieldsToUpdate } = computeFieldsToAddRemoveUpdate( params.previousData[`${key}Ids`], params.data[`${key}Ids`], ); + const updatedFields: any[] = computedNestedFieldsToUpdate( + fieldsToUpdate, + params.previousData[key], + params.data[key] + ); + return { ...acc, data: { @@ -288,7 +293,7 @@ const buildUpdateVariables = (introspectionResults: IntrospectionResult) => ( [key]: { [PRISMA_CONNECT]: fieldsToAdd, [PRISMA_DISCONNECT]: fieldsToRemove, - //[PRISMA_UPDATE]: fieldsToUpdate + [PRISMA_UPDATE]: updatedFields }, }, }; diff --git a/src/utils/computeAddRemoveUpdate.ts b/src/utils/computeAddRemoveUpdate.ts index bc5b0b0..e0c94fb 100644 --- a/src/utils/computeAddRemoveUpdate.ts +++ b/src/utils/computeAddRemoveUpdate.ts @@ -1,6 +1,9 @@ import difference from 'lodash/difference'; - +import _ from 'lodash' type ID = string; +interface IDObject { + id: string +} const formatId = (id: ID) => ({ id }); @@ -21,3 +24,70 @@ export const computeFieldsToAddRemoveUpdate = (oldIds: ID[], newIds: ID[]) => ({ fieldsToRemove: computeFieldsToRemove(oldIds, newIds), fieldsToUpdate: computeFieldsToUpdate(oldIds, newIds) }); + +/** + * When updating a relation also update the nested fields on the relation. + * + * Calculates the difference between the previousData passed from ReactAdmin + * and the fields changed by the user and only sends the changed values + * back to the GraphQL API. + * + * @param idList A List of IDs of values that may have been changed + * @param previousData The previous data retrieved before editing + * @param newData The data containing changes from the user + */ +export const computedNestedFieldsToUpdate = + (idList: IDObject[], previousData: any, newData: any): any => { + const ids = idList.map(obj => obj.id); + return ids.map((id) => ({ + data: objectDifferentiation( + objectFinder(previousData, id), + objectFinder(newData, id)), + where: {id}})) + .filter(hasData) +}; + +/** + * Calculate the difference between two objects. + * + * Based on the left object determine what fields have changed on the + * right side object. We do not check for the ID value as these are + * always the same so they're handled by the logic here. + * + * @param left Object for comparison to + * @param right The object to get the difference from + */ +const objectDifferentiation = (left: any, right: any): object => { + // return if the objects are identical + if(left === right) return {}; + const output = _.mapValues(left, (data, name) => { + if(right[name] === data) return undefined; + // TODO: Handle possibility for deep nesting + if(data.constructor === Object) return undefined; + if(data.constructor === Array) return undefined; + else return right[name] + }); + Object.keys(output).forEach(key => output[key] === undefined && delete output[key]) + return output +}; + +/** + * Find an object in an array of object by its ID value. + * + * Does assume the values inserted here posses an ID object in their body. + * + * @param array Array of objects to find the specified value in + * @param idValue The ID to search for + */ +function objectFinder(array: object[], idValue: string) { + return array.find((obj: any) => obj['id'] && obj['id'] === idValue) +} + +/** + * Checks for an empty object. + * + * @param object The object to check against. + */ +function hasData(object: {data: object, where: {id: string}}){ + return Object.keys(object.data).length !== 0 +} \ No newline at end of file