From f8a68bc03b751aa0a66c4dcfaf83553c9b6adf2b Mon Sep 17 00:00:00 2001 From: Alexandre Bodin Date: Tue, 24 Sep 2024 10:32:01 +0200 Subject: [PATCH] fix: unidirectional sync for non dp source --- .../EditView/components/DocumentActions.tsx | 4 +- .../components/FormInputs/Relations.tsx | 12 ++-- .../server/src/controllers/relations.ts | 5 +- .../services/document-service/repository.ts | 16 ++++- .../utils/unidirectional-relations.ts | 68 ++++++++++++++----- 5 files changed, 76 insertions(+), 29 deletions(-) diff --git a/packages/core/content-manager/admin/src/pages/EditView/components/DocumentActions.tsx b/packages/core/content-manager/admin/src/pages/EditView/components/DocumentActions.tsx index b8ca59a123..ca639613a7 100644 --- a/packages/core/content-manager/admin/src/pages/EditView/components/DocumentActions.tsx +++ b/packages/core/content-manager/admin/src/pages/EditView/components/DocumentActions.tsx @@ -501,8 +501,8 @@ const transformData = (data: Record): any => { } if (typeof data === 'object' && data !== null) { - if ('api_data' in data) { - return data.api_data; + if ('apiData' in data) { + return data.apiData; } return mapValues(transformData)(data); diff --git a/packages/core/content-manager/admin/src/pages/EditView/components/FormInputs/Relations.tsx b/packages/core/content-manager/admin/src/pages/EditView/components/FormInputs/Relations.tsx index c9a0f2baea..3b7adfb901 100644 --- a/packages/core/content-manager/admin/src/pages/EditView/components/FormInputs/Relations.tsx +++ b/packages/core/content-manager/admin/src/pages/EditView/components/FormInputs/Relations.tsx @@ -86,7 +86,7 @@ function useHandleDisconnect(fieldName: string, consumerName: string) { addFieldRow(`${fieldName}.disconnect`, { id: relation.id, - api_data: { + apiData: { documentId: relation.documentId, }, }); @@ -150,10 +150,10 @@ const RelationsField = React.forwardRef( const isMorph = props.attribute.relation.toLowerCase().includes('morph'); const isDisabled = isMorph || disabled; - const { id: componentId, uid: componentUID } = useComponent( - 'RelationsField', - ({ uid, id }) => ({ id, uid }) - ); + const { componentId, componentUID } = useComponent('RelationsField', ({ uid, id }) => ({ + componentId: id, + componentUID: uid, + })); /** * We'll always have a documentId in a created entry, so we look for a componentId first. @@ -267,7 +267,7 @@ const RelationsField = React.forwardRef( const item = { id: relation.id, - api_data: { + apiData: { documentId: relation.documentId, locale: relation.locale, }, diff --git a/packages/core/content-manager/server/src/controllers/relations.ts b/packages/core/content-manager/server/src/controllers/relations.ts index d8d48a0c38..71a5b49552 100644 --- a/packages/core/content-manager/server/src/controllers/relations.ts +++ b/packages/core/content-manager/server/src/controllers/relations.ts @@ -458,7 +458,10 @@ export default { ordering: 'desc', }); - const relationsUnion = uniqBy('documentId', concat(sanitizedRes.results, res.results)); + const relationsUnion = uniqBy( + (res: any) => `${res.documentId}-${res.locale}`, + concat(sanitizedRes.results, res.results) + ); ctx.body = { pagination: res.pagination || { diff --git a/packages/core/core/src/services/document-service/repository.ts b/packages/core/core/src/services/document-service/repository.ts index 374e1dc0cd..81d5f4e8da 100644 --- a/packages/core/core/src/services/document-service/repository.ts +++ b/packages/core/core/src/services/document-service/repository.ts @@ -291,7 +291,10 @@ export const createContentTypeRepository: RepositoryFactoryMethod = (uid) => { ]); // Load any unidirectional relation targetting the old published entries - const relationsToSync = await unidirectionalRelations.load(uid, oldPublishedVersions); + const relationsToSync = await unidirectionalRelations.load(uid, { + draftVersions: draftsToPublish, + publishedVersions: oldPublishedVersions, + }); // Delete old published versions await async.map(oldPublishedVersions, (entry: any) => entries.delete(entry.id)); @@ -302,7 +305,11 @@ export const createContentTypeRepository: RepositoryFactoryMethod = (uid) => { ); // Sync unidirectional relations with the new published entries - await unidirectionalRelations.sync(oldPublishedVersions, publishedEntries, relationsToSync); + await unidirectionalRelations.sync( + [...oldPublishedVersions, ...draftsToPublish], + publishedEntries, + relationsToSync + ); publishedEntries.forEach(emitEvent('entry.publish')); @@ -358,7 +365,10 @@ export const createContentTypeRepository: RepositoryFactoryMethod = (uid) => { ]); // Load any unidirectional relation targeting the old drafts - const relationsToSync = await unidirectionalRelations.load(uid, oldDrafts); + const relationsToSync = await unidirectionalRelations.load(uid, { + draftVersions: [], + publishedVersions: oldDrafts, + }); // Delete old drafts await async.map(oldDrafts, (entry: any) => entries.delete(entry.id)); diff --git a/packages/core/core/src/services/document-service/utils/unidirectional-relations.ts b/packages/core/core/src/services/document-service/utils/unidirectional-relations.ts index cec816d624..bf426375ab 100644 --- a/packages/core/core/src/services/document-service/utils/unidirectional-relations.ts +++ b/packages/core/core/src/services/document-service/utils/unidirectional-relations.ts @@ -1,18 +1,19 @@ /* eslint-disable no-continue */ -import { keyBy } from 'lodash/fp'; +import { keyBy, omit } from 'lodash/fp'; import { UID, Schema } from '@strapi/types'; +interface LoadContext { + publishedVersions: { id: string; locale: string }[]; + draftVersions: { id: string; locale: string }[]; +} + /** * Loads lingering relations that need to be updated when overriding a published or draft entry. * This is necessary because the relations are uni-directional and the target entry is not aware of the source entry. * This is not the case for bi-directional relations, where the target entry is also linked to the source entry. - * - * @param uid The content type uid - * @param oldEntries The old entries that are being overridden - * @returns An array of relations that need to be updated with the join table reference. */ -const load = async (uid: UID.ContentType, oldEntries: { id: string; locale: string }[]) => { +const load = async (uid: UID.ContentType, { publishedVersions, draftVersions }: LoadContext) => { const updates = [] as any; // Iterate all components and content types to find relations that need to be updated @@ -27,29 +28,61 @@ const load = async (uid: UID.ContentType, oldEntries: { id: string; locale: stri /** * Only consider unidirectional relations */ - if (attribute.type !== 'relation') continue; - if (attribute.target !== uid) continue; - if (attribute.inversedBy || attribute.mappedBy) continue; - const joinTable = attribute.joinTable; + if ( + attribute.type !== 'relation' || + attribute.target !== uid || + attribute.inversedBy || + attribute.mappedBy + ) { + continue; + } + // TODO: joinColumn relations - if (!joinTable) continue; + const joinTable = attribute.joinTable; + if (!joinTable) { + continue; + } const { name } = joinTable.inverseJoinColumn; /** * Load all relations that need to be updated */ - const oldEntriesIds = oldEntries.map((entry) => entry.id); - const relations = await strapi.db + + const oldPublishedByLocale = keyBy('locale', publishedVersions); + + // NOTE: when the model has draft and publish, we can assume relation are only draft to draft & published to published + const oldEntriesIds = publishedVersions.map((entry) => entry.id); + + const oldPublishedRelations = await strapi.db .getConnection() .select('*') .from(joinTable.name) .whereIn(name, oldEntriesIds) .transacting(trx); - if (relations.length === 0) continue; + if (oldPublishedRelations.length > 0) { + updates.push({ joinTable, relations: oldPublishedRelations }); + } - updates.push({ joinTable, relations }); + if (!model.options?.draftAndPublish) { + const oldEntriesIds = draftVersions + .filter((entry) => { + return !oldPublishedByLocale[entry.locale]; + }) + .map((entry) => entry.id); + + const draftRelations = await strapi.db + .getConnection() + .select('*') + .from(joinTable.name) + .whereIn(name, oldEntriesIds) + .transacting(trx); + + if (draftRelations.length > 0) { + updates.push({ joinTable, relations: draftRelations.map(omit('id')) }); + } + } } } }); @@ -67,7 +100,7 @@ const load = async (uid: UID.ContentType, oldEntries: { id: string; locale: stri const sync = async ( oldEntries: { id: string; locale: string }[], newEntries: { id: string; locale: string }[], - oldRelations: { joinTable: any; relations: any[] }[] + oldRelations: { joinTable: any; relations: any[]; replace?: boolean }[] ) => { /** * Create a map of old entry ids to new entry ids @@ -89,8 +122,9 @@ const sync = async ( // Iterate old relations that are deleted and insert the new ones for (const { joinTable, relations } of oldRelations) { // Update old ids with the new ones + const column = joinTable.inverseJoinColumn.name; + const newRelations = relations.map((relation) => { - const column = joinTable.inverseJoinColumn.name; const newId = oldEntriesMap[relation[column]]; return { ...relation, [column]: newId }; });