diff --git a/packages/dataprovider/src/buildVariables/buildData.ts b/packages/dataprovider/src/buildVariables/buildData.ts index f60765c..7d34cf5 100644 --- a/packages/dataprovider/src/buildVariables/buildData.ts +++ b/packages/dataprovider/src/buildVariables/buildData.ts @@ -1,6 +1,7 @@ import { debug } from "console"; import { IntrospectionInputObjectType, + IntrospectionInputType, IntrospectionInputTypeRef, IntrospectionInputValue, IntrospectionListTypeRef, @@ -8,13 +9,14 @@ import { IntrospectionNonNullTypeRef, IntrospectionTypeRef, } from "graphql"; +import { isString } from "lodash"; import isEqual from "lodash/isEqual"; import isNil from "lodash/isNil"; import isObject from "lodash/isObject"; import { IntrospectionResult } from "../constants/interfaces"; import exhaust from "../utils/exhaust"; import getFinalType from "../utils/getFinalType"; -import { sanitizeData } from "../utils/sanitizeData"; +import { getSanitizedFieldData } from "./sanitizeData"; enum ModifiersParams { connect = "connect", @@ -381,18 +383,22 @@ export const buildData = ( params: UpdateParams | CreateParams, introspectionResults: IntrospectionResult, ) => { + console.log("buildData", inputType, params); if (!inputType) { return {}; } - const data = sanitizeData(params.data); - const previousData = - "previousData" in params ? sanitizeData(params.previousData) : null; + const data = params.data; + const previousData = "previousData" in params ? params.previousData : null; return inputType.inputFields.reduce((acc, field) => { const key = field.name; const fieldType = field.type.kind === "NON_NULL" ? field.type.ofType : field.type; - const fieldData = data[key]; - const previousFieldData = previousData?.[key] ?? null; + // we have to handle the convenience convention that adds _id(s) to the data + // the sanitize function merges that with other data + const fieldData = getSanitizedFieldData(data, field); + const previousFieldData = previousData + ? getSanitizedFieldData(previousData, field) + : null; // TODO in case the content of the array has changed but not the array itself? if ( isEqual(fieldData, previousFieldData) || diff --git a/packages/dataprovider/src/buildVariables/buildVariables.test.ts b/packages/dataprovider/src/buildVariables/buildVariables.test.ts index 954e3c3..be00487 100644 --- a/packages/dataprovider/src/buildVariables/buildVariables.test.ts +++ b/packages/dataprovider/src/buildVariables/buildVariables.test.ts @@ -23,10 +23,10 @@ describe("buildVariables", () => { testIntrospection = await getTestIntrospectionNexus(); testUserResource = testIntrospection.resources.find( (r) => r.type.kind === "OBJECT" && r.type.name === "User", - ); + )!; testBlogPostCommentResource = testIntrospection.resources.find( (r) => r.type.kind === "OBJECT" && r.type.name === "BlogPostComment", - ); + )!; }); describe("GET_LIST", () => { @@ -654,7 +654,7 @@ describe("buildVariables", () => { // one - it("update an entity and change the relation", () => { + it("update an entity and change the relation when objects with new id is passed", () => { const params = { data: { id: "einstein", @@ -681,7 +681,34 @@ describe("buildVariables", () => { }); }); - it("update an entity and update also it's related entity", () => { + it("update an entity and change the relation when _id suffixed field is passed", () => { + const params = { + data: { + id: "einstein", + userSocialMedia_id: "newId", + }, + previousData: { + userSocialMedia: "oldId", + }, + }; + + expect( + buildVariables(testIntrospection, options)( + testUserResource, + UPDATE, + params, + ), + ).toEqual({ + where: { id: "einstein" }, + data: { + userSocialMedia: { + connect: { id: "newId" }, + }, + }, + }); + }); + + it("update an entity and update also it's related entity when id is the same", () => { const params = { data: { id: "einstein", @@ -965,6 +992,34 @@ describe("buildVariables", () => { }); }); + it("update an entity and connects and disconnects entities when _ids array is passed", () => { + const params = { + data: { + id: "einstein", + roles_ids: ["professor", "husband", "human"], + }, + previousData: { + roles_ids: ["human", "student"], + }, + }; + + expect( + buildVariables(testIntrospection, options)( + testUserResource, + UPDATE, + params, + ), + ).toEqual({ + where: { id: "einstein" }, + data: { + roles: { + connect: [{ id: "professor" }, { id: "husband" }], + disconnect: [{ id: "student" }], + }, + }, + }); + }); + it("update an entity and it's related entities accordingly even with mixed primitive and object ids", () => { const params = { data: { @@ -1194,7 +1249,7 @@ describe("buildVariables", () => { (r) => r.type.kind === "OBJECT" && r.type.name === "SomePublicRecordWithIntId", - ); + )!; expect( buildVariables(testIntrospection, options)(resource, GET_ONE, params), ).toEqual({ @@ -1209,7 +1264,7 @@ describe("buildVariables", () => { (r) => r.type.kind === "OBJECT" && r.type.name === "SomePublicRecordWithIntId", - ); + )!; const result = buildVariables(testIntrospection, options)( resource, GET_ONE, diff --git a/packages/dataprovider/src/buildVariables/index.ts b/packages/dataprovider/src/buildVariables/index.ts index a226c05..d595071 100644 --- a/packages/dataprovider/src/buildVariables/index.ts +++ b/packages/dataprovider/src/buildVariables/index.ts @@ -92,6 +92,7 @@ export interface UpdateParams { const buildUpdateVariables = (introspectionResults: IntrospectionResult, options: OurOptions) => (resource: Resource, params: UpdateParams, parentResource?: Resource) => { + console.log("buildUpdateVariables", params, resource); const inputType = introspectionResults.types.find( (t) => t.name === `${resource.type.name}UpdateInput`, ) as IntrospectionInputObjectType; @@ -133,6 +134,7 @@ const buildCreateVariables = export const buildVariables = (introspectionResults: IntrospectionResult, options: OurOptions) => (resource: Resource, aorFetchType: FetchType, params: any) => { + console.log("buildVariables", aorFetchType, params, resource); switch (aorFetchType) { case GET_LIST: { return buildGetListVariables(introspectionResults, options)( diff --git a/packages/dataprovider/src/buildVariables/sanitizeData.ts b/packages/dataprovider/src/buildVariables/sanitizeData.ts new file mode 100644 index 0000000..e389df6 --- /dev/null +++ b/packages/dataprovider/src/buildVariables/sanitizeData.ts @@ -0,0 +1,46 @@ +import { IntrospectionInputValue } from "graphql"; +import { isObject } from "lodash"; + +/** + * Due to some implementation details in react-admin, we have to add copies with suffixed keys of certain field data. + * + * the data contains then both the normal version and the _id version (which is just a string or an array of strings). + * + * We cannot override the record, as users might use either the object version or the string version + */ + +export const getSanitizedFieldData = ( + data: Record, + field: IntrospectionInputValue, +) => { + const key = field.name; + const keyWithArraySuffix = `${key}_ids`; + + if (data[keyWithArraySuffix]) { + // merge + if (data[key] && Array.isArray(data[key])) { + return data[key].map((entry, index) => { + if (isObject(entry)) { + return { + id: data[keyWithArraySuffix][index], + ...entry, + }; + } + return data[keyWithArraySuffix][index]; + }); + } + return data[keyWithArraySuffix]; + } + + const keyWithIdsSuffix = `${key}_id`; + if (data[keyWithIdsSuffix]) { + if (data[key] && isObject(data[key])) { + return { + id: data[keyWithIdsSuffix], + ...data[key], + }; + } + return data[keyWithIdsSuffix]; + } + return data[key]; +}; diff --git a/packages/dataprovider/src/buildWhere.ts b/packages/dataprovider/src/buildWhere.ts index 0f86668..f0ad13c 100644 --- a/packages/dataprovider/src/buildWhere.ts +++ b/packages/dataprovider/src/buildWhere.ts @@ -12,7 +12,7 @@ import { Resource, } from "./constants/interfaces"; import { OurOptions } from "./types"; -import { sanitizeKey } from "./utils/sanitizeData"; +import { sanitizeKey } from "./utils/sanitizeKey"; const getStringFilter = ( key: string, diff --git a/packages/dataprovider/src/utils/sanitizeData.ts b/packages/dataprovider/src/utils/sanitizeData.ts deleted file mode 100644 index a02675c..0000000 --- a/packages/dataprovider/src/utils/sanitizeData.ts +++ /dev/null @@ -1,25 +0,0 @@ -export const sanitizeKey = (key: string) => { - if (key.endsWith("_ids")) { - return key.substring(0, key.lastIndexOf("_ids")); - } - if (key.endsWith("_id")) { - return key.substring(0, key.lastIndexOf("_id")); - } - return key; -}; -/** - * Due to some implementation details in react-admin, we have to add copies with suffixed keys of certain field data. - * This function sanitizes these keys: - * - suffix _id: a string reference - * - suffix _ids: an array of ids referencing - * @param data data - * @returns data where the suffixes got removed and the original data is overwritten with the suffixed version - */ -export const sanitizeData = (data: { [key: string]: any }) => { - return Object.fromEntries( - Object.entries(data).reduce((acc, [keyRaw, value]) => { - const key = sanitizeKey(keyRaw); - return [...acc, [key, value]]; - }, []), - ); -}; diff --git a/packages/dataprovider/src/utils/sanitizeKey.ts b/packages/dataprovider/src/utils/sanitizeKey.ts new file mode 100644 index 0000000..65c585d --- /dev/null +++ b/packages/dataprovider/src/utils/sanitizeKey.ts @@ -0,0 +1,9 @@ +export const sanitizeKey = (key: string) => { + if (key.endsWith("_ids")) { + return key.substring(0, key.lastIndexOf("_ids")); + } + if (key.endsWith("_id")) { + return key.substring(0, key.lastIndexOf("_id")); + } + return key; +};