diff --git a/api/src/controllers/projects.contentMapper.controller.ts b/api/src/controllers/projects.contentMapper.controller.ts index fd1000ae..03881ff8 100644 --- a/api/src/controllers/projects.contentMapper.controller.ts +++ b/api/src/controllers/projects.contentMapper.controller.ts @@ -125,6 +125,18 @@ const getSingleContentTypes = async ( res.status(201).json(resp); }; +/** + * Retrieves single global field. + * + * @param req - The request object. + * @param res - The response object. + * @returns A Promise that resolves to void. + */ +const getSingleGlobalField = async(req: Request, res: Response): Promise => { + const resp = await contentMapperService.getSingleGlobalField(req); + res.status(201).json(resp); +} + /** * update content mapping details a project. * @@ -148,5 +160,6 @@ export const contentMapperController = { getSingleContentTypes, removeContentMapper, updateContentMapper, - getExistingGlobalFields + getExistingGlobalFields, + getSingleGlobalField }; diff --git a/api/src/routes/contentMapper.routes.ts b/api/src/routes/contentMapper.routes.ts index 6b8fed79..30853461 100644 --- a/api/src/routes/contentMapper.routes.ts +++ b/api/src/routes/contentMapper.routes.ts @@ -45,7 +45,7 @@ router.get( * @route GET /:projectId */ router.get( - "/globalFields/:projectId", + "/:projectId/globalFields", asyncRouter(contentMapperController.getExistingGlobalFields) ); @@ -85,7 +85,18 @@ router.get( asyncRouter(contentMapperController.removeContentMapper) ); -//update content mapper details +/** + * Update content mapper + * @route GET /:orgId/:projectId + */ router.patch("/:orgId/:projectId/mapper_keys", asyncRouter(contentMapperController.updateContentMapper)); +/** + * Get Single Global Field data + * @route GET /:projectId/:globalFieldUid + */ +router.get("/:projectId/globalFields/:globalFieldUid", + asyncRouter(contentMapperController.getSingleGlobalField) +); + export default router; diff --git a/api/src/services/contentMapper.service.ts b/api/src/services/contentMapper.service.ts index 2146509c..e13275e6 100644 --- a/api/src/services/contentMapper.service.ts +++ b/api/src/services/contentMapper.service.ts @@ -863,9 +863,56 @@ const getSingleContentTypes = async (req: Request) => { return { title: res?.data?.content_type?.title, uid: res?.data?.content_type?.uid, - schema: res?.data?.content_type?.schema, + schema: res?.data?.content_type?.schema }; }; + +/** + * Retrieves a single global field from the specified project. + * @param req - The request object containing the project ID, content type UID, and token payload. + * @returns An object containing the title, UID, and schema of the content type, or an error object if an error occurs. + */ +const getSingleGlobalField = async (req: Request) => { + const projectId = req?.params?.projectId; + const globalFieldUID = req?.params?.globalFieldUid; + const { token_payload } = req.body; + + const authtoken = await getAuthtoken( + token_payload?.region, + token_payload?.user_id + ); + await ProjectModelLowdb.read(); + const project = ProjectModelLowdb.chain + .get("projects") + .find({ id: projectId }) + .value(); + const stackId = project?.destination_stack_id; + + const [err, res] = await safePromise( + https({ + method: "GET", + url: `${config.CS_API[ + token_payload?.region as keyof typeof config.CS_API + ]!}/global_fields/${globalFieldUID}`, + headers: { + api_key: stackId, + authtoken: authtoken, + }, + }) + ); + + if (err) + return { + data: err.response.data, + status: err.response.status, + }; + + return { + title: res?.data?.global_field?.title, + uid: res?.data?.global_field?.uid, + schema: res?.data?.global_field?.schema + }; +} /** * Removes the content mapping for a project. * @param req - The request object containing the project ID. @@ -1037,5 +1084,6 @@ export const contentMapperService = { removeMapping, getSingleContentTypes, updateContentMapper, - getExistingGlobalFields + getExistingGlobalFields, + getSingleGlobalField }; diff --git a/ui/src/components/ContentMapper/index.tsx b/ui/src/components/ContentMapper/index.tsx index 035a469a..69006e60 100644 --- a/ui/src/components/ContentMapper/index.tsx +++ b/ui/src/components/ContentMapper/index.tsx @@ -25,7 +25,8 @@ import { updateContentType, resetToInitialMapping, fetchExistingContentType, - updateContentMapper + updateContentMapper, + fetchGlobalField } from '../../services/api/migration.service'; // Redux @@ -223,8 +224,6 @@ const ContentMapper = forwardRef((props, ref: React.ForwardedRef([]); const [loading, setLoading] = useState(false); const [isLoading, setIsLoading] = useState(newMigrationData?.isprojectMapped); @@ -237,12 +236,9 @@ const ContentMapper = forwardRef((props, ref: React.ForwardedRef(''); - const [existingContentTypes, setExistingContentTypes] = useState([]); - const [existingGlobalFields, setExistingGlobalFields] = useState([]) const [isContentType, setIsContentType] = useState(contentTypes?.[0]?.type === "content_type"); const [contentModels, setContentModels] = useState([]); - const [selectedContentType, setSelectedContentType] = useState(); const [existingField, setExistingField] = useState({}); const [selectedOptions, setSelectedOptions] = useState([]); @@ -316,8 +312,6 @@ const ContentMapper = forwardRef((props, ref: React.ForwardedRef { - const mappedContentType = contentModels && contentModels?.find((item)=>item?.title === contentTypeMapped?.[otherCmsTitle]); + const mappedContentType = contentModels && contentModels?.find((item)=> item?.title === contentTypeMapped?.[otherCmsTitle]); if (contentTypeMapped && otherCmsTitle ) { @@ -342,16 +336,12 @@ const ContentMapper = forwardRef((props, ref: React.ForwardedRef{ @@ -394,7 +384,6 @@ const ContentMapper = forwardRef((props, ref: React.ForwardedRef { if (row?.contentstackField === `${schema?.display_name} > ${childSchema?.display_name} > ${nestedSchema?.display_name}`) { if(!isFieldDeleted) { @@ -459,12 +448,14 @@ const ContentMapper = forwardRef((props, ref: React.ForwardedRef { - if(isContentType) { - setContentModels(existingContentTypes); + if(isContentType) { + setContentModels(newMigrationData?.content_mapping?.existingCT); } else { - setContentModels(existingGlobalFields); + setContentModels(newMigrationData?.content_mapping?.existingGlobal); } - }, [existingContentTypes, existingGlobalFields, isContentType]) + + + }, [isContentType, newMigrationData?.content_mapping?.existingCT, newMigrationData?.content_mapping?.existingGlobal]); // To close the filter panel on outside click useEffect(() => { @@ -658,7 +649,7 @@ const ContentMapper = forwardRef((props, ref: React.ForwardedRef { const { data, status } = await getExistingContentTypes(projectId); if (status === 201) { - setExistingContentTypes(data?.contentTypes); + // setExistingContentTypes(data?.contentTypes); const mappedContentType = data?.contentTypes && data?.contentTypes?.find((item:ContentTypeList)=>item?.title === contentTypeMapped?.[otherCmsTitle]); if (mappedContentType?.uid) { @@ -677,7 +668,7 @@ const ContentMapper = forwardRef((props, ref: React.ForwardedRef { - const { data } = await fetchExistingContentType(projectId,'') ; - if(data?.contentTypes?.length <= 0){ - Notification({ - notificationContent: { text: "No content found in the stack" }, - notificationProps: { - position: 'bottom-center', - hideProgressBar: false - }, - type: 'error' - }); - } - const contentTypesArr: ContentTypeList[] = contentModels; - const index = contentModels.findIndex(ct => ct?.uid === data?.uid); + if (isContentType) { + const { data , status} = await fetchExistingContentType(projectId, otherContentType?.id ?? ''); + + if (status == 201 && data?.contentTypes?.length > 0) { + Notification({ + notificationContent: { text: 'Content type data fetched successfully' }, + notificationProps: { + position: 'bottom-center', + hideProgressBar: false + }, + type: 'success' + }); + } else if(status == 201 && data?.contentTypes?.length <= 0) { + Notification({ + notificationContent: { text: "No content found in the stack" }, + notificationProps: { + position: 'bottom-center', + hideProgressBar: false + }, + type: 'error' + }); + } + + const contentTypesArr: ContentTypeList[] = contentModels; + const index = contentModels.findIndex(ct => ct?.uid === data?.uid); - if(index != -1) { - contentTypesArr[index] = data; - } + if(index != -1) { + contentTypesArr[index] = data; + } + + setContentModels(data?.contentTypes); + setContentTypeSchema(data?.schema); + + + // const content_type = data?.contentTypes?.find((item: ContentTypeList)=>item?.title === otherContentType?.label); + // const contentTypeKey = Object.keys(contentTypeMapped).find(key => contentTypeMapped[key] === otherContentType?.label); - setContentModels(data?.contentTypes); + // if(! content_type && contentTypeKey) { + // const updatedState = { ...contentTypeMapped }; + // delete updatedState[contentTypeKey]; + + // setContentTypeMapped((prevState: ContentTypeMap) => { + // const newState = { ...prevState }; + + // delete newState[contentTypeKey] + + // return newState; + // }); + // await updateContentMapper(selectedOrganisation?.value, projectId, {... updatedState} ); + // setOtherContentType({ + // label: `Select ${isContentType ? 'Content Type' : 'Global Field'} from Existing Stack`, + // value: `Select ${isContentType ? 'Content Type' : 'Global Field'} from Existing Stack` + + // }); + // } + } else { + const { data, status } = await fetchGlobalField(projectId, otherContentType?.id ?? ''); - const content_type = data?.contentTypes?.find((item: ContentTypeList)=>item?.title === otherContentType?.label); - const contentTypeKey = Object.keys(contentTypeMapped).find(key => contentTypeMapped[key] === otherContentType?.label); + if (status == 201 && data?.globalFields?.length > 0) { + Notification({ + notificationContent: { text: 'Global field data fetched successfully' }, + notificationProps: { + position: 'bottom-center', + hideProgressBar: false + }, + type: 'success' + }); + } else if(status == 201 && data?.globalFields?.length <= 0) { + Notification({ + notificationContent: { text: "No global field in the stack" }, + notificationProps: { + position: 'bottom-center', + hideProgressBar: false + }, + type: 'error' + }); + } - - if(! content_type && contentTypeKey){ + const index = contentModels.findIndex(ct => ct?.uid === data?.uid); + + const contentTypesArr: ContentTypeList[] = contentModels; + + if(index != -1) { + contentTypesArr[index] = data; + } + setContentModels(data?.globalFields); + setContentTypeSchema(data?.schema); + } + + const contentField = contentModels?.find((item: ContentTypeList)=>item?.title === otherContentType?.label); + const contentFieldKey = Object.keys(contentTypeMapped).find(key => contentTypeMapped[key] === otherContentType?.label); + + if(! contentField && contentFieldKey) { const updatedState = { ...contentTypeMapped }; - delete updatedState[contentTypeKey]; + delete updatedState[contentFieldKey]; setContentTypeMapped((prevState: ContentTypeMap) => { const newState = { ...prevState }; - delete newState[contentTypeKey] + delete newState[contentFieldKey] return newState; }); @@ -1663,42 +1722,6 @@ const ContentMapper = forwardRef((props, ref: React.ForwardedRef ct?.uid === data?.uid); - - const contentTypesArr: ContentTypeList[] = contentModels; - - if(index != -1) { - contentTypesArr[index] = data; - } - - //setContentTypesList(contentTypesArr); - setContentTypeSchema(data?.schema); - if (status == 201) { - Notification({ - notificationContent: { text: 'Content type data fetched successfully' }, - notificationProps: { - position: 'bottom-center', - hideProgressBar: false - }, - type: 'success' - }); - } - - - } } const columns = [ diff --git a/ui/src/components/Modal/index.tsx b/ui/src/components/Modal/index.tsx index 3e76fbeb..dc40ae70 100644 --- a/ui/src/components/Modal/index.tsx +++ b/ui/src/components/Modal/index.tsx @@ -55,14 +55,26 @@ const Modal = (props: ProjectModalProps) => { }; const nameValidation = (value: string) => { - if (value && value?.length > 200) { + if (!value) { setInputValue(false); - return 'Project Name should not be more than 200 chars'; + return 'Project name is required.'; + } else if (!/^[^\s].*$/.test(value)) { + setInputValue(false); + return 'Please enter a valid project name.'; } else { setInputValue(true); } }; + // Validation function for maxLength (immediate validation) + const validateMaxLength = (value: string) => { + if (value && value.length > 200) { + setInputValue(false); + return 'Project Name should not be more than 200 chars'; + } + return undefined; + }; + const descValidation = (value: string) => { if (value?.length >= 255) { setInputValue(false); @@ -76,7 +88,7 @@ const Modal = (props: ProjectModalProps) => { <> { + closeModal={()=> { closeModal(); isOpen(false)}} closeIconTestId="cs-default-header-close" @@ -86,16 +98,7 @@ const Modal = (props: ProjectModalProps) => { { - const errors: any = {}; - if (!values.name || values.name === "") { - errors.name = 'Project name required'; - } else if (!/^[^\s].*$/.test(values.name)) { - errors.name = 'Please enter a valid project name.'; - } - return errors - }} + render={({ handleSubmit }): JSX.Element => { return (
{ input.onChange(event); }} version="v2" - // autoFocus={true} placeholder={namePlaceholder} data-testid="title-input" name="name" maxLength="200" error={(meta?.error || meta?.submitError) && meta?.touched} /> - {meta.touched && meta.error && ( + {/* Show maxLength error immediately */} + {validateMaxLength(input.value) && ( + {validateMaxLength(input.value)} + + )} + {/* Show required error only after the field has been touched */} + {meta.error && meta.touched && !validateMaxLength(input.value) && ( + diff --git a/ui/src/context/app/app.interface.ts b/ui/src/context/app/app.interface.ts index 7f82fc8e..5099459c 100644 --- a/ui/src/context/app/app.interface.ts +++ b/ui/src/context/app/app.interface.ts @@ -168,6 +168,8 @@ export interface IDestinationStack { stackArray: IDropDown[]; } export interface IContentMapper { + existingGlobal: ContentTypeList[] | (() => ContentTypeList[]); + existingCT: ContentTypeList[] | (() => ContentTypeList[]); content_type_mapping: ContentTypeMap; isDropDownChanged?: boolean; otherCmsTitle?: string; @@ -311,6 +313,8 @@ export const DEFAULT_CONTENT_MAPPER: IContentMapper = { isDropDownChanged: false, otherCmsTitle: '', contentTypeList: [], + existingCT: [], + existingGlobal: [] }; export const DEFAULT_TEST_MIGRATION: ITestMigration = { diff --git a/ui/src/pages/Migration/index.tsx b/ui/src/pages/Migration/index.tsx index fb2cfd35..ae9b3be3 100644 --- a/ui/src/pages/Migration/index.tsx +++ b/ui/src/pages/Migration/index.tsx @@ -8,7 +8,7 @@ import { RootState } from '../../store'; import { updateMigrationData, updateNewMigrationData } from '../../store/slice/migrationDataSlice'; // Services -import { getMigrationData, updateCurrentStepData, updateLegacyCMSData, updateDestinationStack, createTestStack, updateAffixData, fileformatConfirmation, updateFileFormatData, affixConfirmation, updateStackDetails, getOrgDetails } from '../../services/api/migration.service'; +import { getMigrationData, updateCurrentStepData, updateLegacyCMSData, updateDestinationStack, createTestStack, updateAffixData, fileformatConfirmation, updateFileFormatData, affixConfirmation, updateStackDetails, getOrgDetails, getExistingContentTypes, getExistingGlobalFields } from '../../services/api/migration.service'; import { getAllStacksInOrg } from '../../services/api/stacks.service'; import { getCMSDataFromFile } from '../../cmsData/cmsSelector'; @@ -70,7 +70,7 @@ const Migration = () => { fetchData(); }, [params?.stepId, params?.projectId, selectedOrganisation?.value]); - useEffect(()=>{ + useEffect(()=> { dispatch(updateNewMigrationData({ ...newMigrationData, isprojectMapped : isProjectMapper @@ -79,6 +79,23 @@ const Migration = () => { },[isProjectMapper]); + // Function to get exisiting content types list + const fetchExistingContentTypes = async () => { + const { data, status } = await getExistingContentTypes(projectId); + if (status === 201) { + return data?.contentTypes; + } + }; + + // Function to get exisiting global fields list + const fetchExistingGlobalFields = async () => { + const { data, status } = await getExistingGlobalFields(projectId); + + if (status === 201) { + return data?.globalFields; + } + } + const fetchData = async () => { setIsLoading(true); @@ -105,7 +122,7 @@ const Migration = () => { migration_steps_heading: data?.migration_steps_heading, settings: data?.settings })); - + await fetchProjectData(); const stepIndex = data?.all_steps?.findIndex((step: IFlowStep) => `${step?.name}` === params?.stepId); setCurrentStepIndex(stepIndex !== -1 ? stepIndex : 0); @@ -159,6 +176,10 @@ const Migration = () => { locales:[], isNewStack: projectData?.stackDetails?.isNewStack }; + + const existingContentTypes = await fetchExistingContentTypes(); + const existingGlobalFields = await fetchExistingGlobalFields(); + const projectMapper = { ...newMigrationData, @@ -191,7 +212,9 @@ const Migration = () => { }, content_mapping: { isDropDownChanged: false, - content_type_mapping: projectData?.mapperKeys + content_type_mapping: projectData?.mapperKeys, + existingCT: existingContentTypes, + existingGlobal: existingGlobalFields }, stackDetails: projectData?.stackDetails, // mapper_keys: projectData?.mapper_keys, diff --git a/ui/src/services/api/migration.service.ts b/ui/src/services/api/migration.service.ts index faf51090..042916ec 100644 --- a/ui/src/services/api/migration.service.ts +++ b/ui/src/services/api/migration.service.ts @@ -166,7 +166,7 @@ export const getExistingContentTypes = async (projectId: string) => { export const getExistingGlobalFields = async (projectId: string) => { try { - return await getCall(`${API_VERSION}/mapper/globalFields/${projectId}`, options); + return await getCall(`${API_VERSION}/mapper/${projectId}/globalFields`, options); } catch (error) { if (error instanceof Error) { throw new Error(`${error.message}`); @@ -242,6 +242,18 @@ export const fetchExistingContentType = async (projectId: string, contentTypeUid } } +export const fetchGlobalField = async (projectId: string, globalFieldUid: string) => { + try { + return await getCall(`${API_VERSION}/mapper/${projectId}/globalFields/${globalFieldUid}`, options); + } catch (error) { + if (error instanceof Error) { + throw new Error(`${error.message}`); + } else { + throw new Error('Unknown error'); + } + } +} + export const removeContentMapper = async(orgId: string, projectId: string) => { try { return await getCall(`${API_VERSION}/mapper/${orgId}/${projectId}/content-mapper`, options);