diff --git a/.talismanrc b/.talismanrc index 0252a7bb7..3b24d02c6 100644 --- a/.talismanrc +++ b/.talismanrc @@ -10,4 +10,6 @@ ignoreconfig: ignore_detectors: - Base64Detector -version: "1.0" + - filename: ui/src/components/ContentMapper/index.tsx + checksum: 6743142fce18f945d55fc61d7d774118db400b72ccc7a0b5e089ccc8aed20340 +version: "1.0.1-beta" diff --git a/api/src/services/migration.service.ts b/api/src/services/migration.service.ts index f1aaf1d02..919dda63c 100644 --- a/api/src/services/migration.service.ts +++ b/api/src/services/migration.service.ts @@ -666,7 +666,7 @@ const startMigration = async (req: Request): Promise => { .findIndex({ id: projectId }) .value(); if (index > -1) { - ProjectModelLowdb.update((data: any) => { + await ProjectModelLowdb.update((data: any) => { data.projects[index].isMigrationStarted = true; }); } @@ -682,6 +682,18 @@ const startMigration = async (req: Request): Promise => { projectId, `${project?.destination_stack_id}.log` ); + const message = getLogMessage( + 'start Migration', + 'Starting Migration...', + {} + ); + await customLogger( + projectId, + project?.destination_stack_id, + 'info', + message + ); + await setLogFilePath(loggerPath); const copyLogsToStack = async ( diff --git a/api/src/services/wordpress.service.ts b/api/src/services/wordpress.service.ts index 72b517c6e..9c9a4ea2a 100644 --- a/api/src/services/wordpress.service.ts +++ b/api/src/services/wordpress.service.ts @@ -2330,6 +2330,17 @@ async function extractPosts( packagePath: string, destinationStackId: string, pr // Process the current chunk const chunkPostData = await processChunkData(chunkData, filename, isLastChunk, contenttype); postdataCombined = { ...postdataCombined, ...chunkPostData }; + + const seenTitles = new Map(); + Object?.entries?.(postdataCombined)?.forEach?.(([uid, item]:any) => { + const originalTitle = item?.title; + + if (seenTitles?.has(originalTitle)) { + item.title = `${originalTitle} - ${item?.uid}`; + } + seenTitles?.set?.(item?.title, true); + }); + const message = getLogMessage( srcFunc, `${filename.split(".").slice(0, -1).join(".")} has been successfully transformed.`, diff --git a/api/src/utils/content-type-creator.utils.ts b/api/src/utils/content-type-creator.utils.ts index e00b14c14..2233faf68 100644 --- a/api/src/utils/content-type-creator.utils.ts +++ b/api/src/utils/content-type-creator.utils.ts @@ -74,9 +74,19 @@ const arrangGroups = ({ schema, newStack }: any) => { schema?.forEach((item: any) => { if (item?.contentstackFieldType === 'group') { const groupSchema: any = { ...item, schema: [] } + if (item?.contentstackFieldUid?.includes('.')) { + const parts = item?.contentstackFieldUid?.split('.'); + groupSchema.contentstackFieldUid = parts?.[parts?.length - 1]; + } schema?.forEach((et: any) => { if (et?.contentstackFieldUid?.includes(`${item?.contentstackFieldUid}.`) || (newStack === false && et?.uid?.includes(`${item?.uid}.`))) { + const target = groupSchema?.contentstackFieldUid; + const index = et?.contentstackFieldUid?.indexOf?.(target); + + if (index > 0) { + et.contentstackFieldUid = et?.contentstackFieldUid?.substring(index); + } groupSchema?.schema?.push(et); } }) @@ -112,7 +122,7 @@ const saveAppMapper = async ({ marketPlacePath, data, fileName }: any) => { } } -const convertToSchemaFormate = ({ field, advanced = true, marketPlacePath }: any) => { +const convertToSchemaFormate = ({ field, advanced = true, marketPlacePath, keyMapper }: any) => { switch (field?.contentstackFieldType) { case 'single_line_text': { return { @@ -435,7 +445,7 @@ const convertToSchemaFormate = ({ field, advanced = true, marketPlacePath }: any return { data_type: "reference", display_name: field?.title, - reference_to: field?.refrenceTo ?? [], + reference_to: field?.refrenceTo?.map((item:string) => keyMapper[item] || item) ?? [], field_metadata: { ref_multiple: true, ref_multiple_content_types: true @@ -654,45 +664,98 @@ const existingCtMapper = async ({ keyMapper, contentTypeUid, projectId, region, } } -const mergeArrays = async (a: any[], b: any[]) => { - for await (const fieldGp of b) { - const exists = a.some(fld => - fld?.uid === fieldGp?.uid && - fld?.data_type === fieldGp?.data_type +const mergeArrays = (a: any[], b: any[]): any[] => { + const result = [...a]; + + for (const field of b) { + const exists = result?.some(f => + f?.uid === field?.uid && + f?.data_type === field?.data_type ); if (!exists) { - a.push(fieldGp); + result?.push(field); } } - return a; + + return result; +}; + +const mergeFields = async (schema1: any[], schema2: any[]): Promise => { + const result: any[] = []; + + for (const field2 of schema2) { + if (field2?.data_type === 'group') { + const machingGroup = findGroupByUid(schema1, field2?.uid); + if(machingGroup){ + const schema = await mergeArrays(machingGroup?.schema ?? [],field2?.schema ?? [] ); + result?.push({ + ...field2, + schema: schema + }); + } + else{ + result?.push({ + ...field2, + schema: await mergeFields(schema1, field2?.schema) + }) + } + } + else{ + const exists = schema1?.find( + (fld) => + fld?.uid === field2?.uid && + fld?.data_type === field2?.data_type + ); + result?.push({ + ...field2 + }); + + } + + } + + for (const field1 of schema1) { + const isMatched = schema2?.some( + (field2) => + field1?.uid === field2?.uid && + field1?.data_type === field2?.data_type + ); + + if (!isMatched) { + result?.push(field1); + } + } + + return result; } + +const findGroupByUid = ( + schema: any[], + uid: string, + excludeRef: any = null +): any | undefined => { + for (const field of schema) { + if (field?.data_type === 'group') { + if (field?.uid === uid && field !== excludeRef) return field; + const nested = findGroupByUid(field?.schema ?? [], uid, excludeRef); + if (nested) return nested; + } + } + return undefined; +}; + const mergeTwoCts = async (ct: any, mergeCts: any) => { - const ctData: any = { - ...ct, + const merged :any = { title: mergeCts?.title, uid: mergeCts?.uid, options: { "singleton": false, - } - } - for await (const field of ctData?.schema ?? []) { - if (field?.data_type === 'group') { - const currentGroup = mergeCts?.schema?.find((grp: any) => grp?.uid === field?.uid && - grp?.data_type === 'group'); - const group = []; - for await (const fieldGp of currentGroup?.schema ?? []) { - const fieldNst = field?.schema?.find((fld: any) => fld?.uid === fieldGp?.uid && - fld?.data_type === fieldGp?.data_type); - if (fieldNst === undefined) { - group?.push(fieldGp); - } - } - field.schema = [...field?.schema ?? [], ...group]; - } - } - ctData.schema = await mergeArrays(ctData?.schema, mergeCts?.schema) ?? []; - return ctData; + }, + schema: await mergeFields(ct?.schema ?? [], mergeCts?.schema ?? []) + }; + + return merged; } export const contenTypeMaker = async ({ contentType, destinationStackId, projectId, newStack, keyMapper, region, user_id }: any) => { @@ -717,7 +780,7 @@ export const contenTypeMaker = async ({ contentType, destinationStackId, project "display_name": item?.contentstackField, "field_metadata": {}, "schema": [], - "uid": item?.contentstackFieldUid, + "uid": item?.contentstackFieldUid?.replace(/\./g, '_'), "multiple": false, "mandatory": false, "unique": false @@ -728,7 +791,7 @@ export const contenTypeMaker = async ({ contentType, destinationStackId, project uid: extractValue(element?.contentstackFieldUid, item?.contentstackFieldUid, '.'), title: extractValue(element?.contentstackField, item?.contentstackField, ' >')?.trim(), } - const schema: any = convertToSchemaFormate({ field, marketPlacePath }); + const schema: any = convertToSchemaFormate({ field, marketPlacePath, keyMapper}); if (typeof schema === 'object' && Array.isArray(group?.schema) && element?.isDeleted === false) { group.schema.push(schema); } @@ -741,7 +804,8 @@ export const contenTypeMaker = async ({ contentType, destinationStackId, project title: item?.contentstackField, uid: item?.contentstackFieldUid }, - marketPlacePath + marketPlacePath, + keyMapper }); if (dt && item?.isDeleted === false) { ct?.schema?.push(dt); diff --git a/api/src/utils/field-attacher.utils.ts b/api/src/utils/field-attacher.utils.ts index 7ac4efb68..1eafa6821 100644 --- a/api/src/utils/field-attacher.utils.ts +++ b/api/src/utils/field-attacher.utils.ts @@ -22,7 +22,7 @@ export const fieldAttacher = async ({ projectId, orgId, destinationStackId, regi contentType.fieldMapping = contentType?.fieldMapping?.map((fieldUid: any) => { const field = FieldMapperModel.chain .get("field_mapper") - .find({ id: fieldUid, projectId: projectId }) + .find({ id: fieldUid, projectId: projectId , contentTypeId: contentId}) .value() return field; }) diff --git a/ui/src/components/AdvancePropertise/advanceProperties.interface.ts b/ui/src/components/AdvancePropertise/advanceProperties.interface.ts index 87d1b6830..f018cea6e 100644 --- a/ui/src/components/AdvancePropertise/advanceProperties.interface.ts +++ b/ui/src/components/AdvancePropertise/advanceProperties.interface.ts @@ -25,7 +25,7 @@ export interface SchemaProps { * @param value - The advanced settings. * @param checkBoxChanged - Indicates whether the checkbox has changed. */ - updateFieldSettings: (rowId: string, value: Advanced, checkBoxChanged: boolean) => void; + updateFieldSettings: (rowId: string, value: Advanced, checkBoxChanged: boolean, rowContentstackFieldUid: string) => void; /** * Indicates whether the field is localized. diff --git a/ui/src/components/AdvancePropertise/index.tsx b/ui/src/components/AdvancePropertise/index.tsx index fe3073873..7834b3dd2 100644 --- a/ui/src/components/AdvancePropertise/index.tsx +++ b/ui/src/components/AdvancePropertise/index.tsx @@ -147,7 +147,9 @@ const AdvancePropertise = (props: SchemaProps) => { title: currentToggleStates?.title, url: currentToggleStates?.url }, - checkBoxChanged + checkBoxChanged, + props?.data?.contentstackFieldUid + ); }; @@ -189,7 +191,8 @@ const AdvancePropertise = (props: SchemaProps) => { title: currentToggleStates?.title, url: currentToggleStates?.url }, - checkBoxChanged + checkBoxChanged, + props?.data?.contentstackFieldUid ); }; @@ -215,7 +218,8 @@ const AdvancePropertise = (props: SchemaProps) => { embedObject: currentToggleStates?.embedObject, embedObjects: embedObjectsLabels }, - true + true, + props?.data?.contentstackFieldUid ); }; @@ -251,7 +255,8 @@ const AdvancePropertise = (props: SchemaProps) => { embedObjects: embedObjectsLabels, options: options }, - true + true, + props?.data?.contentstackFieldUid ); }; const handleRemoveDefalutValue = (index: number) => { @@ -280,7 +285,8 @@ const AdvancePropertise = (props: SchemaProps) => { embedObjects: embedObjectsLabels, options: options }, - true + true, + props?.data?.contentstackFieldUid ); }; @@ -583,7 +589,8 @@ const AdvancePropertise = (props: SchemaProps) => { validationRegex: toggleStates?.validationRegex ?? '', embedObjects: embedObject }, - true + true, + props?.data?.contentstackFieldUid ); }} options={option} diff --git a/ui/src/components/ContentMapper/contentMapper.interface.ts b/ui/src/components/ContentMapper/contentMapper.interface.ts index 866860956..10d096d1c 100644 --- a/ui/src/components/ContentMapper/contentMapper.interface.ts +++ b/ui/src/components/ContentMapper/contentMapper.interface.ts @@ -206,4 +206,5 @@ export interface ModifiedField { backupFieldType: string; parentId: string; uid: string; + _canSelect?: boolean; } diff --git a/ui/src/components/ContentMapper/index.scss b/ui/src/components/ContentMapper/index.scss index fa2f9f237..11d01fdc3 100644 --- a/ui/src/components/ContentMapper/index.scss +++ b/ui/src/components/ContentMapper/index.scss @@ -311,3 +311,7 @@ div .table-row { font-size: $size-font-large; font-weight: $font-weight-semi-bold; } +.filterButton-color{ + color: $color-brand-primary-base; + font-weight: $font-weight-bold; +} \ No newline at end of file diff --git a/ui/src/components/ContentMapper/index.tsx b/ui/src/components/ContentMapper/index.tsx index 0ffa4c062..575247e04 100644 --- a/ui/src/components/ContentMapper/index.tsx +++ b/ui/src/components/ContentMapper/index.tsx @@ -35,7 +35,7 @@ import { updateMigrationData, updateNewMigrationData } from '../../store/slice/m // Utilities import { CS_ENTRIES, CONTENT_MAPPING_STATUS, STATUS_ICON_Mapping } from '../../utilities/constants'; -import { validateArray } from '../../utilities/functions'; +import { isEmptyString, validateArray } from '../../utilities/functions'; import useBlockNavigation from '../../hooks/userNavigation'; // Interface @@ -291,6 +291,7 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R const [isContentDeleted, setIsContentDeleted] = useState(false); const [isCsCTypeUpdated, setsCsCTypeUpdated] = useState(false); const [isLoadingSaveButton, setisLoadingSaveButton] = useState(false); + const [activeFilter, setActiveFilter] = useState(''); /** ALL HOOKS Here */ @@ -367,7 +368,7 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R if (!updatedSelectedOptions?.includes?.(schema?.display_name)) { updatedSelectedOptions.push(schema?.display_name); } - updatedExstingField[row?.uid] = { + updatedExstingField[row?.backupFieldUid] = { label: schema?.display_name, value: schema }; @@ -381,7 +382,7 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R if (!updatedSelectedOptions?.includes?.(`${schema?.display_name} > ${childSchema?.display_name}`)) { updatedSelectedOptions.push(`${schema?.display_name} > ${childSchema?.display_name}`); } - updatedExstingField[row?.uid] = { + updatedExstingField[row?.backupFieldUid] = { label: `${schema?.display_name} > ${childSchema?.display_name}`, value: childSchema } @@ -396,7 +397,7 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R if (!updatedSelectedOptions?.includes?.(`${schema?.display_name} > ${childSchema?.display_name} > ${nestedSchema?.display_name}`)) { updatedSelectedOptions.push(`${schema?.display_name} > ${childSchema?.display_name} > ${nestedSchema?.display_name}`); } - updatedExstingField[row?.uid] = { + updatedExstingField[row?.backupFieldUid] = { label: `${schema?.display_name} > ${childSchema?.display_name} > ${nestedSchema?.display_name}`, value: nestedSchema } @@ -411,7 +412,7 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R if (!updatedSelectedOptions?.includes?.(`${schema?.display_name} > ${childSchema?.display_name} > ${nestedSchema?.display_name} > ${nestedChild?.display_name}`)) { updatedSelectedOptions.push(`${schema?.display_name} > ${childSchema?.display_name} > ${nestedSchema?.display_name} > ${nestedChild?.display_name}`); } - updatedExstingField[row?.uid] = { + updatedExstingField[row?.backupFieldUid] = { label: `${schema?.display_name} > ${childSchema?.display_name} > ${nestedSchema?.display_name} > ${nestedChild?.display_name}`, value: nestedChild } @@ -765,11 +766,11 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R }); } - const updateFieldSettings = (rowId: string, updatedSettings: Advanced, checkBoxChanged: boolean) => { + const updateFieldSettings = (rowId: string, updatedSettings: Advanced, checkBoxChanged: boolean, rowContentstackFieldUid: string) => { setIsDropDownChanged(checkBoxChanged); const newTableData = tableData?.map?.((row) => { - if (row?.uid === rowId) { + if (row?.uid === rowId && row?.contentstackFieldUid === rowContentstackFieldUid) { return { ...row, advanced: { ...row?.advanced, ...updatedSettings } }; } @@ -827,17 +828,18 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R }, [tableData]); const getParentId = (uid: string) => { - return tableData?.find(i => i?.uid?.toLowerCase() === uid?.toLowerCase())?.id ?? '' + return tableData?.find((i) => i?.uid?.toLowerCase() === uid?.toLowerCase() && i?.backupFieldType?.toLowerCase() === 'group')?.id ?? '' } const modifiedObj = (obj: FieldMapType) => { - const {backupFieldType, uid, id} = obj ?? {} + const {backupFieldType, uid, id, _canSelect} = obj ?? {} const excludeArr = ["group"] return { id, backupFieldType, uid, - parentId : excludeArr?.includes?.(backupFieldType?.toLowerCase()) ? '' : getParentId(uid?.split('.')[0]?.toLowerCase()) + parentId : excludeArr?.includes?.(backupFieldType?.toLowerCase()) ? '' : getParentId(uid?.split('.')[0]?.toLowerCase()), + _canSelect, } } @@ -939,7 +941,7 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R }) } } - } else if(latestRow?.parentId && !["title", "url"]?.includes?.(latestRow?.uid?.toLowerCase())){ + } else if(latestRow?.parentId && latestRow?._canSelect === true){ // Extract the group UID if item is child of any group const uidBeforeDot = latestRow?.uid?.split?.('.')?.[0]?.toLowerCase(); const groupItem = tableData?.find((entry) => entry?.uid?.toLowerCase() === uidBeforeDot); @@ -980,15 +982,16 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R }); setRowIds(selectedObj); + setTableData(updatedTableData); setSelectedEntries(updatedTableData); }; // Method for change select value - const handleValueChange = (value: FieldTypes, rowIndex: string) => { + const handleValueChange = (value: FieldTypes, rowIndex: string, rowContentstackFieldUid: string) => { setIsDropDownChanged(true); setFieldValue(value); const updatedRows: FieldMapType[] = selectedEntries?.map?.((row) => { - if (row?.uid === rowIndex) { + if (row?.uid === rowIndex && row?.contentstackFieldUid === rowContentstackFieldUid) { return { ...row, contentstackFieldType: value?.value }; } return row; @@ -1068,7 +1071,7 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R { if (OptionsForRow?.length === 0) { - handleValueChange(selectedOption, data?.uid) + handleValueChange(selectedOption, data?.uid, data?.backupFieldUid) } else { - handleFieldChange(selectedOption, data?.uid) + handleFieldChange(selectedOption, data?.uid, data?.contentstackFieldUid, data?.backupFieldUid) } }} placeholder="Select Field" version={'v2'} maxWidth="290px" - isClearable={selectedOptions?.includes?.(existingField?.[data?.uid]?.label ?? '')} + isClearable={selectedOptions?.includes?.(existingField?.[data?.backupFieldUid]?.label ?? '')} options={adjustedOptions} isDisabled={OptionValue?.isDisabled || newMigrationData?.project_current_step > 4} menuPlacement="auto" @@ -1599,7 +1600,7 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R > diff --git a/ui/src/components/DestinationStack/Actions/LoadLanguageMapper.tsx b/ui/src/components/DestinationStack/Actions/LoadLanguageMapper.tsx index feea23612..4537c2aa4 100644 --- a/ui/src/components/DestinationStack/Actions/LoadLanguageMapper.tsx +++ b/ui/src/components/DestinationStack/Actions/LoadLanguageMapper.tsx @@ -13,7 +13,7 @@ import TableHeader from './tableHeader' import { useDispatch, useSelector } from 'react-redux'; import { RootState } from '../../../store'; import { updateNewMigrationData } from '../../../store/slice/migrationDataSlice'; -import { IDropDown, INewMigration } from '../../../context/app/app.interface'; +import { DEFAULT_DROPDOWN, IDropDown, INewMigration } from '../../../context/app/app.interface'; export type ExistingFieldType = { [key: string]: { label: string; value: string }; @@ -30,22 +30,24 @@ export type ExistingFieldType = { */ const Mapper = ({ key, - stack, + uid, cmsLocaleOptions, handleLangugeDelete, options, sourceOptions, isDisabled, - localeChanged, + isStackChanged, + stack, }: { + key: string; + uid:string; cmsLocaleOptions: Array<{ label: string; value: string }>; handleLangugeDelete: (index: number, locale: { label: string; value: string }) => void; options: Array<{ label: string; value: string }>; sourceOptions: Array<{ label: string; value: string }>; isDisabled: boolean; - localeChanged: boolean, - stack: IDropDown, - key: string, + isStackChanged: boolean; + stack: IDropDown }) => { const [selectedMappings, setSelectedMappings] = useState<{ [key: string]: string }>({}); const [existingField, setExistingField] = useState({}); @@ -55,12 +57,12 @@ const Mapper = ({ const [csOptions, setcsOptions] = useState(options); const [sourceoptions, setsourceoptions] = useState(sourceOptions); const newMigrationData = useSelector((state: RootState) => state?.migration?.newMigrationData); - const [selectedStack,setSelectedStack] = useState(); const dispatch = useDispatch(); + const [selectedStack, setSelectedStack] = useState(); const [placeholder] = useState('Select language'); useEffect(()=>{ - setSelectedStack(stack) + setSelectedStack(stack); },[]); useEffect(() => { @@ -107,28 +109,38 @@ const Mapper = ({ const updatedExistingLocale = {...existingLocale}; let updatedSelectedMappings = { ...selectedMappings }; - const validLabels = cmsLocaleOptions?.map((item)=> item?.label); + // const validLabels = cmsLocaleOptions?.map((item)=> item?.label); - const existingMasterKey = Object?.keys(selectedMappings || {})?.find((key) => + const existingMasterID = Object?.keys?.(selectedMappings || {})?.find((key) => key?.includes('-master_locale') ); - const recentMsterLocale = cmsLocaleOptions?.find((item)=>{item?.value === 'master_locale'}) + const recentMsterLocale = cmsLocaleOptions?.find((item) => item?.value === 'master_locale')?.label; + const presentLocale = `${recentMsterLocale}-master_locale`; Object.keys(updatedExistingField || {})?.forEach((key) => { - if (!validLabels?.includes(updatedExistingField?.[key]?.label) || existingMasterKey !== `${recentMsterLocale}-master_locale`) { - delete updatedExistingField?.[key]; + if ((existingMasterID !== presentLocale) || isStackChanged) { + delete updatedExistingField[key]; } }); Object.keys(updatedExistingLocale || {})?.forEach((key) => { - - if ((!validLabels?.includes(updatedExistingLocale?.[key]?.label) && Object?.entries(updatedExistingField)?.length === 0) || existingMasterKey !== `${recentMsterLocale}-master_locale`) { - delete updatedExistingLocale?.[key]; + if ((existingMasterID !== presentLocale) || isStackChanged) { + delete updatedExistingLocale[key]; } }); + if ( (existingMasterID !== presentLocale) || isStackChanged) { + setselectedCsOption([]); + setselectedSourceOption([]); + } + + setexistingLocale(updatedExistingLocale); cmsLocaleOptions?.map((locale, index)=>{ + const existingLabel = existingMasterID; + const expectedLabel = `${locale?.label}-master_locale`; + + const isLabelMismatch = existingLabel && existingLabel?.localeCompare(expectedLabel) !== 0; if(locale?.value === 'master_locale'){ if (!updatedExistingField?.[index]) { updatedExistingField[index] = { @@ -138,20 +150,22 @@ const Mapper = ({ } - if ( existingMasterKey !== `${locale?.label}-master_locale`) { + if (isLabelMismatch || isStackChanged) { setselectedCsOption([]); setselectedSourceOption([]); + setexistingLocale({}); + setExistingField({}); + updatedSelectedMappings = { [`${locale?.label}-master_locale`]: '', }; + setSelectedMappings(updatedSelectedMappings); } } }) - + setExistingField(updatedExistingField); - setexistingLocale(updatedExistingLocale); - setSelectedMappings(updatedSelectedMappings); }, [cmsLocaleOptions]); @@ -165,6 +179,7 @@ const Mapper = ({ ) => { const selectedLocaleKey = selectedValue?.value; + let existingLabel = existingField?.[index]; if (!selectedValue?.label) { setselectedCsOption((prevSelected) => prevSelected?.filter((item) => item !== existingField?.[index]?.label) @@ -173,9 +188,10 @@ const Mapper = ({ setExistingField((prevOptions: ExistingFieldType) => { + existingLabel = prevOptions[index]; const updatedOptions = { ...prevOptions, - [index]: { label: selectedValue?.label || null, value: selectedValue?.label } + [index]: selectedValue?.label ? { label: selectedValue?.label || null, value: selectedValue?.label } : null }; // setselectedOption((prevSelected) => // prevSelected.filter((item) => @@ -199,9 +215,10 @@ const Mapper = ({ setSelectedMappings((prev) => { const updatedMappings = { ...prev }; - if (!selectedValue) { - delete updatedMappings[existingField[index]?.value]; + //const valueToKeep = updatedMappings[existingLabel?.value]; + delete updatedMappings[existingLabel?.value]; + //updatedMappings[""] = valueToKeep; } else if (type === 'csLocale' && selectedLocaleKey) { updatedMappings[selectedLocaleKey] = existingLocale[index]?.label @@ -215,8 +232,6 @@ const Mapper = ({ const handleSelectedSourceLocale = ( selectedValue: { label: string; value: string }, index: number, - type: 'csLocale' | 'sourceLocale', - label: any ) => { const csLocaleKey = existingField?.[index]?.value; @@ -230,9 +245,8 @@ const Mapper = ({ setexistingLocale((prevOptions: ExistingFieldType) => { const updatedOptions: ExistingFieldType = { ...prevOptions, - [index]: { label: selectedValue?.label || null, value: selectedValue?.label } + [index]: selectedValue?.label ? { label: selectedValue?.label, value: selectedValue?.label } : null }; - // Ensure selectedOption only contains values that exist in updatedOptions setselectedSourceOption((prevSelected) => prevSelected?.filter((item) => @@ -260,37 +274,40 @@ const Mapper = ({ [csLocaleKey]: selectedValue?.label || '' })); }; - const handleLanguageDeletaion = (index: number, locale: { label: string; value: string }) => { + const handleLanguageDeletaion = async (index: number, locale: { label: string; value: string }) => { // Remove item at index from existingField - let csLocale = ''; + const csLocale = existingField?.[index]?.label ?? ''; + const sourceLocale = existingLocale?.[index]?.label ?? ''; - setExistingField( - (prevOptions: Record | undefined) => { - if (!prevOptions) return {}; // Ensure it's an object - - const updatedOptions = { ...prevOptions }; // Create a shallow copy - csLocale = updatedOptions[index]?.label; - setselectedCsOption((prevSelected) => { - const newSelectedOptions: string[] = prevSelected?.filter( - (item) => item !== csLocale // Remove the item equal to csLocale - ); - return newSelectedOptions; - }); - - delete updatedOptions[index]; // Remove the key - - return updatedOptions; + setExistingField((prevOptions) => { + const updatedOptions: Record = {}; + const prev = prevOptions ?? {}; + + setselectedCsOption((prevSelected) => + prevSelected?.filter((item) => item !== csLocale) + ); + + for (let i = 0; i < Object?.keys(prev)?.length; i++) { + if (i < index) { + updatedOptions[i] = prev?.[i]; + } else if (i > index) { + updatedOptions[i - 1] = prev?.[i]; + } } - ); + + return updatedOptions; + }); + // Remove item at index from existingLocale setexistingLocale((prevLocales: ExistingFieldType) => { if (!prevLocales) return {}; const updatedOptions = { ...prevLocales }; // Create a shallow copy; - const locale = updatedOptions[index]?.label + + //sourceLocale = updatedOptions[index]?.label; setselectedSourceOption((prevSelected) => { const newSelectedOptions: string[] = prevSelected?.filter( - (item) => item !== locale // Remove the item equal to locale + (item) => item !== sourceLocale // Remove the item equal to locale ); return newSelectedOptions; }); @@ -300,14 +317,24 @@ const Mapper = ({ setSelectedMappings((prev) => { const updatedMappings = { ...prev }; - - delete updatedMappings[csLocale]; - + if(!csLocale){ + for (const key in updatedMappings) { + if (Object?.prototype?.hasOwnProperty?.call(updatedMappings, key)) { + const value = updatedMappings?.[key]; + if (value === sourceLocale) { + delete updatedMappings?.[key]; + } + } + } + }else{ + delete updatedMappings[csLocale]; + } return updatedMappings; }); handleLangugeDelete(index, locale); }; + return ( <> {cmsLocaleOptions?.length > 0 ? ( @@ -392,7 +419,7 @@ const Mapper = ({ : existingLocale[locale?.label] } onChange={(data: { label: string; value: string }) => - handleSelectedSourceLocale(data, index, 'sourceLocale', locale) + handleSelectedSourceLocale(data, index) } styles={{ menuPortal: (base: any) => ({ ...base, zIndex: 9999 }) @@ -446,26 +473,27 @@ const Mapper = ({ ); }; -const LanguageMapper = ({stack, uid } :{ stack : IDropDown, uid: string}) => { - +const LanguageMapper = ({stack, uid} :{ stack : IDropDown, uid : string}) => { + const newMigrationData = useSelector((state: RootState) => state?.migration?.newMigrationData); const [options, setoptions] = useState<{ label: string; value: string }[]>([]); const [cmsLocaleOptions, setcmsLocaleOptions] = useState<{ label: string; value: string }[]>([]); const [sourceLocales, setsourceLocales] = useState<{ label: string; value: string }[]>([]); const [isLoading, setisLoading] = useState(false); - const [localeChanged, setlocaleChanged] = useState(false); const [currentStack, setCurrentStack] = useState(); const [previousStack, setPreviousStack] = useState(); const [isStackChanged, setisStackChanged] = useState(false); + const [stackValue, setStackValue] = useState(stack?.value) const prevStackRef:any = useRef(null); useEffect(() => { - if (prevStackRef.current && stack?.uid !== prevStackRef.current?.uid) { + if (prevStackRef?.current && stack && stack?.uid !== prevStackRef?.current?.uid) { setisStackChanged(true); setCurrentStack(stack); setPreviousStack(prevStackRef?.current); } + prevStackRef.current = stack; }, [stack]); @@ -491,15 +519,14 @@ const LanguageMapper = ({stack, uid } :{ stack : IDropDown, uid: string}) => { currentStack?.uid !== previousStack?.uid || isStackChanged) && newMigrationData?.project_current_step <= 2) { - setcmsLocaleOptions((prevList: { label: string; value: string }[]) => { - const newLabel = stack?.master_locale; + setcmsLocaleOptions((prevList: { label: string ; value: string }[]) => { + const newLabel = stack?.master_locale ?? ''; const isPresent = prevList?.filter( (item: { label: string; value: string }) => (item?.value === 'master_locale') ); if(isPresent?.[0]?.label !== newLabel || currentStack?.uid !== previousStack?.uid || isStackChanged){ - setisStackChanged(false); - setlocaleChanged(true); + //setisStackChanged(false); return [ ...prevList?.filter(item => (item?.value !== 'master_locale' && item?.value !== '')) ?? [], { @@ -557,7 +584,6 @@ const LanguageMapper = ({stack, uid } :{ stack : IDropDown, uid: string}) => { // return await getStackLocales(newMigrationData?.destination_stack?.selectedOrg?.value); // }; const addRowComp = () => { - setlocaleChanged(false); setcmsLocaleOptions((prevList: { label: string; value: string }[]) => [ ...prevList, // Keep existing elements { @@ -574,7 +600,6 @@ const LanguageMapper = ({stack, uid } :{ stack : IDropDown, uid: string}) => { ); }); }; - return (
{isLoading ? ( @@ -588,14 +613,15 @@ const LanguageMapper = ({stack, uid } :{ stack : IDropDown, uid: string}) => { } rowComponent={ 2} + isStackChanged={isStackChanged} + stack={stack ?? DEFAULT_DROPDOWN} /> } type="Secondary" diff --git a/ui/src/components/DestinationStack/index.tsx b/ui/src/components/DestinationStack/index.tsx index dec93e4a9..6e6ace5ea 100644 --- a/ui/src/components/DestinationStack/index.tsx +++ b/ui/src/components/DestinationStack/index.tsx @@ -14,6 +14,7 @@ import { getCMSDataFromFile } from '../../cmsData/cmsSelector'; import { RootState } from '../../store'; import { updateMigrationData } from '../../store/slice/migrationDataSlice'; import { AutoVerticalStepperRef } from '../LegacyCms'; +import { isEmptyString } from '../../utilities/functions'; type DestinationStackComponentProps = { isCompleted: boolean; @@ -99,6 +100,16 @@ const DestinationStackComponent = ({ setisProjectMapped(newMigrationData?.isprojectMapped); },[newMigrationData?.isprojectMapped]); + useEffect(()=>{ + if(! isEmptyString(newMigrationData?.destination_stack?.selectedStack?.value ) + ){ + handleAllStepsComplete(true); + } + else{ + handleAllStepsComplete(false); + } + },[newMigrationData?.destination_stack?.selectedStack]) + return ( <> {isLoading || isProjectMapped ? ( diff --git a/ui/src/components/Modal/index.tsx b/ui/src/components/Modal/index.tsx index e54106516..8ddba2832 100644 --- a/ui/src/components/Modal/index.tsx +++ b/ui/src/components/Modal/index.tsx @@ -19,7 +19,6 @@ import { ProjectModalProps, FormData } from './modal.interface'; // Services import { useState } from 'react'; -import { createProject } from '../../services/api/project.service'; const Modal = (props: ProjectModalProps) => { const { @@ -34,23 +33,17 @@ const Modal = (props: ProjectModalProps) => { title }, selectedOrg, - isOpen + isOpen, + createProject } = props; const [inputValue, setInputValue] = useState(false); - const handleSubmit = async (values: FormData): Promise => { + const handleSubmit = async (values: FormData)=> { // const payload = {name: values?.name, description: values?.description || ''} - const res = await createProject(selectedOrg?.uid || '', values); - if (res?.error) { - return res?.error; - } - if (res?.status === 201) { - const projectId = res?.data?.project?.id; - window.location.href = `/projects/${projectId}/migration/steps/1`; - } - return res; + const result = await createProject(values); + return result; }; const nameValidation = (value: string) => { diff --git a/ui/src/components/Modal/modal.interface.ts b/ui/src/components/Modal/modal.interface.ts index 7248e2cb2..76c5a2203 100644 --- a/ui/src/components/Modal/modal.interface.ts +++ b/ui/src/components/Modal/modal.interface.ts @@ -4,12 +4,12 @@ import { ModalType } from '../../pages/Projects/projects.interface'; export interface ModalObj { closeModal: () => void; } - export interface ProjectModalProps { modalData: ModalType; selectedOrg: IDropDown; closeModal: () => void; isOpen: (flag: boolean) => void; + createProject: (values : FormData)=> Promise } export interface SettingsModalProps { selectedOrg: IDropDown; @@ -23,3 +23,17 @@ export interface SettingsModalProps { export interface FormData { name?: string; } + +export interface Project { + name: string; + id: string; + status: string; + created_at: string; + modified_at: string; +} + +export interface CreateProjectResponse { + status: "success"; + message: string; + project: Project; +} diff --git a/ui/src/context/app/app.interface.ts b/ui/src/context/app/app.interface.ts index 148327420..7b6cd6c81 100644 --- a/ui/src/context/app/app.interface.ts +++ b/ui/src/context/app/app.interface.ts @@ -176,7 +176,7 @@ export interface IDestinationStack { stackArray: IDropDown[]; migratedStacks: string[]; sourceLocale: string[]; - localeMapping: {}; + localeMapping: Record; csLocale: string[]; } export interface IContentMapper { diff --git a/ui/src/pages/Migration/index.tsx b/ui/src/pages/Migration/index.tsx index c8e11681f..2666f0e98 100644 --- a/ui/src/pages/Migration/index.tsx +++ b/ui/src/pages/Migration/index.tsx @@ -533,8 +533,12 @@ const Migration = () => { const hasNonEmptyMapping = newMigrationData?.destination_stack?.localeMapping && - Object.values(newMigrationData?.destination_stack?.localeMapping)?.every( - (value) => value !== '' && value !== null && value !== undefined + Object.entries(newMigrationData?.destination_stack?.localeMapping || {})?.every( + ([label, value]: [string, string]) => + Boolean(label?.trim()) && + value !== '' && + value !== null && + value !== undefined ); const master_locale: LocalesType = {}; diff --git a/ui/src/pages/Projects/index.tsx b/ui/src/pages/Projects/index.tsx index 80e5b6712..140ca6614 100644 --- a/ui/src/pages/Projects/index.tsx +++ b/ui/src/pages/Projects/index.tsx @@ -10,7 +10,7 @@ import { } from '@contentstack/venus-components'; import { jsonToHtml } from '@contentstack/json-rte-serializer'; import HTMLReactParser from 'html-react-parser'; -import { useLocation } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import { useSelector } from 'react-redux'; // Redux @@ -19,7 +19,7 @@ import useBlockNavigation from '../../hooks/userNavigation'; // Services import { getCMSDataFromFile } from '../../cmsData/cmsSelector'; -import { getAllProjects } from '../../services/api/project.service'; +import { createProject, getAllProjects } from '../../services/api/project.service'; // Utilities import { CS_ENTRIES } from '../../utilities/constants'; @@ -27,7 +27,7 @@ import { validateObject } from '../../utilities/functions'; // Interfaces import { ProjectsType, ProjectsObj } from './projects.interface'; -import { ModalObj } from '../../components/Modal/modal.interface'; +import { CreateProjectResponse, ModalObj, FormData } from '../../components/Modal/modal.interface'; import { CTA } from '../Home/home.interface'; import usePreventBackNavigation from '../../hooks/usePreventBackNavigation'; @@ -41,6 +41,9 @@ import { NO_PROJECTS, NO_PROJECTS_SEARCH } from '../../common/assets'; // styles import './index.scss'; +import { useDispatch } from 'react-redux'; +import { DEFAULT_NEW_MIGRATION } from '../../context/app/app.interface'; +import { updateNewMigrationData } from '../../store/slice/migrationDataSlice'; const Projects = () => { const [data, setData] = useState({}); @@ -69,6 +72,8 @@ const Projects = () => { const [isModalOpen, setIsModalOpen] = useState(false); usePreventBackNavigation(); + const navigate = useNavigate(); + const dispatch = useDispatch(); const fetchProjects = async () => { setLoadStatus(true); @@ -132,6 +137,20 @@ const Projects = () => { }; useBlockNavigation(isModalOpen || true); + const createProjectCall = async(values : FormData): Promise => { + const res = await createProject(selectedOrganisation?.uid || '', values); + if (res?.error) { + return res?.error; + } + if (res?.status === 201) { + const projectId = res?.data?.project?.id; + dispatch(updateNewMigrationData(DEFAULT_NEW_MIGRATION)) + navigate(`/projects/${projectId}/migration/steps/1`); + + } + return res; + + } // Function for open modal const openModal = () => { setIsModalOpen(true); @@ -144,6 +163,7 @@ const Projects = () => { } selectedOrg={selectedOrganisation} isOpen={setIsModalOpen} + createProject={createProjectCall} {...props} /> ),