diff --git a/api/src/services/aem.service.ts b/api/src/services/aem.service.ts index 352fbcb0..674e9ef4 100644 --- a/api/src/services/aem.service.ts +++ b/api/src/services/aem.service.ts @@ -1355,15 +1355,20 @@ const createEntry = async ({ for await (const [locale, entries] of entriesLocale) { for (const entry of entries) { const flatData = deepFlattenObject(entry); + const km = keyMapper as Record | undefined; for (const [key, value] of Object.entries(flatData)) { if (key.endsWith('._content_type_uid') && typeof value === 'string') { const uidField = key?.replace('._content_type_uid', ''); - const refs: string[] = entryMapping?.[value]; + const mappedCtUid = km?.[value] && km[value] !== '' ? km[value] : value; + if (mappedCtUid !== value) { + _.set(entry, key, mappedCtUid); + } + const refs: string[] = entryMapping?.[mappedCtUid]; if (refs?.length) { _.set(entry, `${uidField}.uid`, refs?.[0]); } else { - console.info(`No entry found for content type: ${value}`); + console.info(`No entry found for content type: ${mappedCtUid}`); } } } diff --git a/api/src/utils/content-type-creator.utils.ts b/api/src/utils/content-type-creator.utils.ts index 65bf9fc1..f78072fe 100644 --- a/api/src/utils/content-type-creator.utils.ts +++ b/api/src/utils/content-type-creator.utils.ts @@ -101,8 +101,27 @@ const uidCorrector = ({ uid } : {uid : string}) => { return newUid; }; +/** + * Remap an array of reference UIDs using a mapping table. + * + * @param uids - The original reference UIDs. + * @param keyMapper - A map from UID to new UID. Callers should prefer using + * the *corrected* UID (i.e. the result of `uidCorrector({ uid })`) as the key. + * For backward compatibility, this function also supports maps keyed by the + * original UID, and will try both forms when looking up each entry. + * + * NOTE: Relying on mixed key styles (some original, some corrected) can hide + * inconsistent UID formatting. When both key styles are present for the same + * logical UID and map to different targets, a warning is logged so that such + * issues do not go unnoticed. + * @returns The remapped UIDs. + */ +function remapReferenceUids(uids: string[], keyMapper?: Record): string[] { + if (!keyMapper || !Object.keys(keyMapper).length) return uids; + return uids.map(uid => keyMapper[uid] ?? keyMapper[uidCorrector({ uid })] ?? uid); +} -function buildFieldSchema(item: any, marketPlacePath: string, parentUid = ''): any { +function buildFieldSchema(item: any, marketPlacePath: string, parentUid = '', keyMapper?: Record): any { if (item?.isDeleted === true) return null; const getCleanUid = (uid: string): string => { @@ -155,7 +174,7 @@ function buildFieldSchema(item: any, marketPlacePath: string, parentUid = ''): a const blockElements = blockItem?.schema || []; for (const element of blockElements) { if (element?.isDeleted === false) { - const fieldSchema = buildFieldSchema(element, marketPlacePath, ''); + const fieldSchema = buildFieldSchema(element, marketPlacePath, '', keyMapper); if (fieldSchema) blockSchema.push(fieldSchema); } } @@ -186,7 +205,7 @@ function buildFieldSchema(item: any, marketPlacePath: string, parentUid = ''): a for (const element of elements) { if (element?.isDeleted === false) { - const fieldSchema = buildFieldSchema(element, marketPlacePath, ''); + const fieldSchema = buildFieldSchema(element, marketPlacePath, '', keyMapper); if (fieldSchema) groupSchema.push(fieldSchema); } } @@ -210,7 +229,8 @@ function buildFieldSchema(item: any, marketPlacePath: string, parentUid = ''): a title: item?.display_name || rawUid, // Keep original for display uid: itemUid // Snake case uid }, - marketPlacePath + marketPlacePath, + keyMapper }); } @@ -470,10 +490,10 @@ export const convertToSchemaFormate = ({ field, advanced = false, marketPlacePat "error_messages": { "format": field?.advanced?.validationErrorMessage ?? '', }, - "reference_to": field?.advanced?.embedObjects?.length ? [ + "reference_to": field?.advanced?.embedObjects?.length ? remapReferenceUids([ "sys_assets", ...field?.advanced?.embedObjects?.map?.((item: any) => uidCorrector({ uid: item })) ?? [], - ] : [ + ], keyMapper) : [ "sys_assets" ], "multiple": field?.advanced?.multiple ?? false, @@ -736,7 +756,7 @@ export const convertToSchemaFormate = ({ field, advanced = false, marketPlacePat return { "data_type": "global_field", "display_name": field?.title, - "reference_to": field?.refrenceTo ?? [], + "reference_to": remapReferenceUids(field?.refrenceTo ?? [], keyMapper), "uid": cleanedUid, "mandatory": field?.advanced?.mandatory ?? false, "multiple": field?.advanced?.multiple ?? false, @@ -748,7 +768,7 @@ export const convertToSchemaFormate = ({ field, advanced = false, marketPlacePat return { data_type: "reference", display_name: field?.title, - reference_to: field?.refrenceTo ?? [], + reference_to: remapReferenceUids(field?.refrenceTo ?? [], keyMapper), field_metadata: { ref_multiple: true, ref_multiple_content_types: true @@ -816,7 +836,7 @@ export const convertToSchemaFormate = ({ field, advanced = false, marketPlacePat "mandatory": field?.advanced?.mandatory ?? false, "unique": field?.advanced?.unique ?? false, "non_localizable": field.advanced?.nonLocalizable ?? false, - "reference_to": field?.advanced?.embedObjects?.length ? field?.advanced?.embedObjects?.map?.((item: any) => uidCorrector({ uid: item })) : [] + "reference_to": field?.advanced?.embedObjects?.length ? remapReferenceUids(field?.advanced?.embedObjects?.map?.((item: any) => uidCorrector({ uid: item })), keyMapper) : [] } if ((field?.advanced?.embedObjects?.length === undefined) || (field?.advanced?.embedObjects?.length === 0) || @@ -1177,7 +1197,7 @@ export const contenTypeMaker = async ({ contentType, destinationStackId, project for (const item of ctData) { if (item?.isDeleted === true) continue; - const fieldSchema = buildFieldSchema(item, marketPlacePath, ''); + const fieldSchema = buildFieldSchema(item, marketPlacePath, '', keyMapper); if (fieldSchema) { ct?.schema.push(fieldSchema); } diff --git a/ui/src/components/ContentMapper/index.tsx b/ui/src/components/ContentMapper/index.tsx index 63305a1e..27f0c5e7 100644 --- a/ui/src/components/ContentMapper/index.tsx +++ b/ui/src/components/ContentMapper/index.tsx @@ -1450,31 +1450,48 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: ) if (groupArray?.[0]?.child && previousSelectedValue !== selectedValue?.label && groupArray?.[0]?.uid === rowIndex) { - for (const item of groupArray?.[0]?.child ?? []) { - deletedExstingField[item?.backupFieldUid] = { - label: item?.uid, - value: existingField[item?.backupFieldUid] - - } - setIsFieldDeleted(true); - const index = selectedOptions?.indexOf(existingField[item?.backupFieldUid]?.value?.label); - - if (index > -1) { - selectedOptions?.splice(index, 1); - } - delete existingField[item?.backupFieldUid] - - } + const collectAllDescendants = ( + children: FieldMapType[], + visited: Set = new Set() + ): FieldMapType[] => { + return children.flatMap((child) => { + if (!child || visited.has(child)) { + return []; + } + visited.add(child); + return [ + child, + ...collectAllDescendants(child?.child ?? [], visited) + ]; + }); + }; + + const allDescendants = collectAllDescendants(groupArray[0].child); + const labelsToRemove = new Set( + allDescendants + .map((item) => existingField[item?.backupFieldUid]?.label) + .filter(Boolean) as string[] + ); + + setExistingField((prev) => { + const next = { ...prev }; + allDescendants.forEach((item) => delete next[item?.backupFieldUid]); + next[backupFieldUid] = { label: selectedValue?.label, value: selectedValue?.value }; + return next; + }); + + setIsFieldDeleted(true); + + setSelectedOptions((prev) => prev.filter((opt) => !labelsToRemove.has(opt))); } else { setIsFieldDeleted(false); + setExistingField((prevOptions: ExistingFieldType) => ({ + ...prevOptions, + [backupFieldUid]: { label: selectedValue?.label, value: selectedValue?.value } + })); } - setExistingField((prevOptions: ExistingFieldType) => ({ - ...prevOptions, - [backupFieldUid]: { label: selectedValue?.label, value: selectedValue?.value } - })); - //add selected option to array if it is not mapped to any other field setSelectedOptions((prevSelected) => { const newSelectedOptions = prevSelected?.filter( @@ -2012,18 +2029,20 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: const OptionsForRow: OptionsType[] = []; // If OtherContentType label and contentModels are present, set the contentTypeSchema + let resolvedSchema = contentTypeSchema; if (otherContentType?.label && contentModels) { const ContentType: ContentTypeList | undefined = contentModels?.find( ({ title }) => title === otherContentType?.label ); - setContentTypeSchema(ContentType?.schema); + resolvedSchema = ContentType?.schema; + setContentTypeSchema(resolvedSchema); } - if (contentTypeSchema && validateArray(contentTypeSchema)) { + if (resolvedSchema && validateArray(resolvedSchema)) { const fieldTypeToMatch = Fields[data?.backupFieldType as keyof Mapping]?.type; // Check for UID match first - for (const value of contentTypeSchema) { + for (const value of resolvedSchema) { if (data?.uid === value?.uid && data?.backupFieldType === value?.data_type && fieldTypeToMatch) { OptionsForRow.push({ label: value?.display_name, value, isDisabled: false }); break; @@ -2032,7 +2051,7 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: // If no exact match, process schema including modular blocks if (OptionsForRow?.length === 0) { - for (const value of contentTypeSchema) { + for (const value of resolvedSchema) { const groupArray = nestedList.filter(item => item?.child?.some(e => e?.id === data?.id) ); @@ -2053,7 +2072,7 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: processSchema(value, data, array, groupArray, OptionsForRow, fieldsOfContentstack); } // Process leaf fields - else if (!array?.some(item => item?.id === data?.id) && checkConditions(fieldTypeToMatch, value, data) && !parentBlock) { + else if (!array?.some(item => item?.id === data?.id) && checkConditions(fieldTypeToMatch, value, data) && !parentBlock?.length) { OptionsForRow.push(getMatchingOption(value, true, value?.display_name || '', value?.uid ?? '')); } } @@ -2165,7 +2184,7 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: isDisabled: false }; - const adjustedOptions: OptionsType[] | OptionsType = (OptionsForRow.length === 0 && !contentTypeSchema) ? option : + const adjustedOptions: OptionsType[] | OptionsType = (OptionsForRow.length === 0 && !resolvedSchema) ? option : (OptionsForRow?.length > 0 && OptionsForRow?.every((item) => item?.isDisabled) && OptionValue?.label === Fields[data?.contentstackFieldType]?.label) ? [] : OptionsForRow.map((option: OptionsType) => ({ ...option, @@ -2213,7 +2232,7 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: >