diff --git a/api/src/controllers/projects.controller.ts b/api/src/controllers/projects.controller.ts index d05e0867d..b97d82c88 100644 --- a/api/src/controllers/projects.controller.ts +++ b/api/src/controllers/projects.controller.ts @@ -171,6 +171,11 @@ const updateMigrationExecution = async (req: Request, res: Response): Promise => { + const project = await projectService.getMigratedStacks(req); + res.status(project.status).json(project); +} + export const projectController = { getAllProjects, getProject, @@ -187,4 +192,5 @@ export const projectController = { revertProject, updateStackDetails, updateMigrationExecution, + getMigratedStacks, }; diff --git a/api/src/routes/projects.routes.ts b/api/src/routes/projects.routes.ts index 00a0fed81..72f65457d 100644 --- a/api/src/routes/projects.routes.ts +++ b/api/src/routes/projects.routes.ts @@ -80,4 +80,6 @@ router.patch("/:projectId/stack-details", asyncRouter(projectController.updateSt //update migration execution key router.put("/:projectId/migration-excution",asyncRouter(projectController.updateMigrationExecution)); +router.get("/:projectId/get-migrated-stacks", asyncRouter(projectController.getMigratedStacks)) + export default router; diff --git a/api/src/services/contentMapper.service.ts b/api/src/services/contentMapper.service.ts index 8febbd8ec..7534a1930 100644 --- a/api/src/services/contentMapper.service.ts +++ b/api/src/services/contentMapper.service.ts @@ -292,19 +292,12 @@ const getExistingContentTypes = async (req: Request) => { headers, }) ); - - if (err) { - throw new Error( - `Error fetching selected content type: ${ - err.response?.data || err.message - }` - ); - } + selectedContentType = { - title: res.data.content_type?.title, - uid: res.data.content_type?.uid, - schema: res.data.content_type?.schema, + title: res?.data?.content_type?.title, + uid: res?.data?.content_type?.uid, + schema: res?.data?.content_type?.schema, }; } return { @@ -314,7 +307,7 @@ const getExistingContentTypes = async (req: Request) => { } catch (error: any) { return { data: error.message, - status: 500, + status: error.status || 500, }; } }; diff --git a/api/src/services/projects.service.ts b/api/src/services/projects.service.ts index f684c90d7..945408a50 100644 --- a/api/src/services/projects.service.ts +++ b/api/src/services/projects.service.ts @@ -1212,6 +1212,51 @@ const updateMigrationExecution = async (req: Request) => { }; +/** + * get the destination_stack_id of completed projects. + * + * @param req - The request object containing the parameters and body. + * @returns An object with the status and data of the update operation. + * @throws ExceptionFunction if an error occurs during the process. + */ +const getMigratedStacks = async(req: Request) => { + + const { token_payload } = req.body; + const srcFunc = "getMigratedStacks"; + + try { + await ProjectModelLowdb.read(); + const projects = ProjectModelLowdb.data?.projects || []; + + // Map through projects to extract `destinationstack` key + const destinationStacks = projects.filter((project: any) => project?.status === 5 && project.current_step === 5) + .map((project: any) => project.destination_stack_id); + + return { + status: HTTP_CODES.OK, + destinationStacks + }; + + } catch (error:any) { + // Log error message + logger.error( + getLogMessage( + srcFunc, + "Error occurred while while getting all destinationstack id's of projects", + token_payload, + error + ) + ); + + // Throw a custom exception with the error details + throw new ExceptionFunction( + error?.message || HTTP_TEXTS.INTERNAL_ERROR, + error?.statusCode || error?.status || HTTP_CODES.SERVER_ERROR + ); + + } + +} export const projectService = { getAllProjects, @@ -1229,5 +1274,6 @@ export const projectService = { revertProject, updateStackDetails, updateContentMapper, - updateMigrationExecution + updateMigrationExecution, + getMigratedStacks }; diff --git a/api/src/utils/content-type-creator.utils.ts b/api/src/utils/content-type-creator.utils.ts index d02d1ba63..2c3fa357e 100644 --- a/api/src/utils/content-type-creator.utils.ts +++ b/api/src/utils/content-type-creator.utils.ts @@ -192,7 +192,8 @@ const convertToSchemaFormate = ({ field, advanced = true }: any) => { "unique": field?.advanced?.unique ?? false, "non_localizable": field.advanced?.nonLocalizable ?? false }; - data.field_metadata.default_value = field?.advanced?.default_value ?? null; + const default_value = field?.advanced?.options?.length ? (field?.advanced?.options?.find((item: any) => (item?.key === field?.advanced?.default_value) || (item?.key === field?.advanced?.default_value))) : { value: field?.advanced?.default_value }; + data.field_metadata.default_value = default_value?.value ?? null; return data; } case 'radio': { diff --git a/api/src/utils/test-folder-creator.utils.ts b/api/src/utils/test-folder-creator.utils.ts index 8e83f670e..79e993cb9 100644 --- a/api/src/utils/test-folder-creator.utils.ts +++ b/api/src/utils/test-folder-creator.utils.ts @@ -10,7 +10,9 @@ const { ASSETS_SCHEMA_FILE, CONTENT_TYPES_DIR_NAME, CONTENT_TYPES_SCHEMA_FILE, - ENTRIES_MASTER_FILE + ENTRIES_MASTER_FILE, + GLOBAL_FIELDS_DIR_NAME, + GLOBAL_FIELDS_FILE_NAME } = MIGRATION_DATA_CONFIG; @@ -201,10 +203,41 @@ const sortAssets = async (baseDir: string) => { await fs.promises.writeFile(path.join(assetsPath, ASSETS_SCHEMA_FILE), JSON?.stringify?.(assetsMeta)); } +const writeGlobalField = async (schema: any, globalSave: string, filePath: string) => { + try { + await fs.promises.access(globalSave); + } catch (err) { + try { + await fs.promises.mkdir(globalSave, { recursive: true }); + } catch (mkdirErr) { + console.error("🚀 ~ fs.mkdir ~ err:", mkdirErr); + return; + } + } + try { + await fs.promises.writeFile(filePath, JSON.stringify(schema, null, 2)); + } catch (writeErr) { + console.error("🚀 ~ fs.writeFile ~ err:", writeErr); + } +}; + +const sortGlobalField = async (baseDir: string, finalData: any) => { + const globalSave = path.join(process.cwd(), baseDir, GLOBAL_FIELDS_DIR_NAME); + const globalPath = path.join(globalSave, GLOBAL_FIELDS_FILE_NAME); + const globalData = await JSON.parse(await fs.promises.readFile(globalPath, 'utf8')); + const globalResult = []; + for await (const ct of globalData) { + await lookForReference(ct, finalData); + globalResult?.push(ct); + } + await writeGlobalField(globalResult, globalPath, globalPath); +} + const sortContentType = async (baseDir: string, finalData: any) => { const contentTypePath: string = path.join(process.cwd(), baseDir, CONTENT_TYPES_DIR_NAME); const contentSave = path.join(baseDir, CONTENT_TYPES_DIR_NAME); const ctData = await JSON.parse(await fs.promises.readFile(path.join(contentTypePath, CONTENT_TYPES_SCHEMA_FILE), 'utf8')); + await sortGlobalField(baseDir, finalData); const contentTypes: any = []; for await (const ct of finalData) { const findCtData = ctData?.find((ele: any) => ele?.uid === ct?.contentType); @@ -218,6 +251,7 @@ const sortContentType = async (baseDir: string, finalData: any) => { } + export const testFolderCreator = async ({ destinationStackId }: any) => { const sanitizedStackId = path.basename(destinationStackId); const baseDir = path.join(MIGRATION_DATA_CONFIG.DATA, sanitizedStackId); diff --git a/ui/src/components/AdvancePropertise/index.tsx b/ui/src/components/AdvancePropertise/index.tsx index c5414c901..4bd709a99 100644 --- a/ui/src/components/AdvancePropertise/index.tsx +++ b/ui/src/components/AdvancePropertise/index.tsx @@ -17,6 +17,9 @@ import { // Service import { getContentTypes } from '../../services/api/migration.service'; +// Utilities +import { validateArray } from '../../utilities/functions'; + // Interfaces import { optionsType, SchemaProps } from './advanceProperties.interface'; import { ContentType } from '../ContentMapper/contentMapper.interface'; @@ -330,9 +333,11 @@ const AdvancePropertise = (props: SchemaProps) => { }, [ctValue]); // Option for content types - const option = Array.isArray(contentTypes) - ? contentTypes.map((option) => ({ label: option?.contentstackTitle, value: option?.contentstackUid })) - : [{ label: contentTypes, value: contentTypes }]; + const contentTypesList = contentTypes?.filter((ct: ContentType) => ct?.type === "content_type"); + + const option = validateArray(contentTypesList) + ? contentTypesList?.map((option: ContentType) => ({ label: option?.contentstackTitle, value: option?.contentstackUid })) + : [{ label: contentTypesList, value: contentTypesList }]; return ( <> diff --git a/ui/src/components/Common/Card/card.interface.ts b/ui/src/components/Common/Card/card.interface.ts index db2068ec0..a756ca542 100644 --- a/ui/src/components/Common/Card/card.interface.ts +++ b/ui/src/components/Common/Card/card.interface.ts @@ -26,6 +26,8 @@ export interface ICardType { * The file format ID of the card. */ fileformat_id?: string; + + [key: string]: any; } /** diff --git a/ui/src/components/Common/Card/card.scss b/ui/src/components/Common/Card/card.scss index e3dcbd605..a45b4c701 100644 --- a/ui/src/components/Common/Card/card.scss +++ b/ui/src/components/Common/Card/card.scss @@ -76,10 +76,17 @@ * @cssproperty box-shadow - The box shadow effect to apply. * @cssvalue 0 3px $px-5 $px-2 rgb(215 215 215) - The specific box shadow values. */ -.connector_list:hover { + .connector_list:hover { box-shadow: 0 3px $px-5 $px-2 rgb(215 215 215); } +.connector_list.disabled-card { + &:hover { + box-shadow: none; + cursor: not-allowed; + } +} + /** * Styles for the service icon in the card component. * @@ -148,4 +155,8 @@ p { color: $color-stepper-title; } +} +.Card__disabled{ + cursor: not-allowed; + opacity: .5; } \ No newline at end of file diff --git a/ui/src/components/Common/Card/card.tsx b/ui/src/components/Common/Card/card.tsx index 0be5b932b..c39b7653d 100644 --- a/ui/src/components/Common/Card/card.tsx +++ b/ui/src/components/Common/Card/card.tsx @@ -1,19 +1,27 @@ +// Libraries import { Icon, Paragraph, Radio, Tooltip } from '@contentstack/venus-components'; import { MouseEvent, useState } from 'react'; -import WordWrapper from '../WordWrapper/WordWrapper'; -import { addDomainInPath } from '../../../utilities/functions'; +import { useSelector } from 'react-redux'; +// Redux Store +import { RootState } from '../../../store'; + +// Interface +import { ICardType } from './card.interface'; + +// CSS import './card.scss'; /** * Props for the Card component. */ type CardProps = { - data: any; + data: ICardType; idField?: string; onCardClick?: (T: any) => unknown; - selectedCard: any; + selectedCard: ICardType; cardType?: string; + disabled: boolean; }; /** @@ -25,17 +33,17 @@ type CardProps = { * @param cardType - The type of the card. * @param idField - The field name for the card's ID. Defaults to 'id'. */ -const Card = ({ data, selectedCard, onCardClick, cardType, idField = 'id' }: CardProps) => { - const imgStyle = { - width: cardType === 'legacyCMS' ? '60px' : '46px', - height: cardType === 'legacyCMS' ? '60px' : '46px' - }; +const Card = ({ data, selectedCard, onCardClick, cardType, idField = 'id',disabled }: CardProps) => { const [isHovered, setIsHovered] = useState(false); + const newMigrationData = useSelector((state:RootState)=>state?.migration?.newMigrationData); + const handleMouseEnter = () => { - if (selectedCard[idField] === data?.[idField]) { - setIsHovered(true); - } + if (!newMigrationData?.legacy_cms?.uploadedFile?.isValidated) { + if (selectedCard[idField] === data?.[idField]) { + setIsHovered(true); + } + } }; const handleMouseLeave = () => { @@ -43,17 +51,19 @@ const Card = ({ data, selectedCard, onCardClick, cardType, idField = 'id' }: Car }; const handleClick = (event: MouseEvent) => { - event.preventDefault(); // Prevent the default action - onCardClick?.(data); + if (!newMigrationData?.legacy_cms?.uploadedFile?.isValidated) { + event.preventDefault(); // Prevent the default action + onCardClick?.(data); + } }; return (
{data.description && (
diff --git a/ui/src/components/ContentMapper/index.tsx b/ui/src/components/ContentMapper/index.tsx index c9b36ecf0..15c1fceb5 100644 --- a/ui/src/components/ContentMapper/index.tsx +++ b/ui/src/components/ContentMapper/index.tsx @@ -266,12 +266,10 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R //Check for null if (!data) { dispatch(updateMigrationData({ contentMappingData: DEFAULT_CONTENT_MAPPING_DATA })); - setIsLoading(false); return; } dispatch(updateMigrationData({ contentMappingData: data})); - setIsLoading(false); }) .catch((err) => { console.error(err); @@ -455,6 +453,17 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R [key]: { label: `${item?.display_name} > ${schemaItem?.display_name}`, value: schemaItem }, })); } + else if(! item?.schema?.some( + (schema) => schema?.uid === existingField[key]?.value?.uid) ){ + + setExistingField((prevOptions: ExistingFieldType) => { + const { [key]: _, ...rest } = prevOptions; // Destructure to exclude the key to remove + return { + ...rest + }; + }); + + } }); } } @@ -541,6 +550,7 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R // Method to fetch content types const fetchContentTypes = async (searchText: string) => { try { + setIsLoading(true); const { data } = await getContentTypes(projectId || '', 0, 5000, searchContentType || ''); //org id will always present setContentTypes(data?.contentTypes); @@ -588,21 +598,19 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R setItemStatusMap(itemStatusMap); - setLoading(true); for (let index = 0; index <= 30; index++) { itemStatusMap[index] = 'loaded'; } setItemStatusMap({ ...itemStatusMap }); - setLoading(false); const newTableData = data?.fieldMapping?.filter((field: FieldMapType) => field !== null) setTableData(newTableData || []); setTotalCounts(data?.count); setInitialRowSelectedData(newTableData.filter((item: FieldMapType) => !item.isDeleted)) - + setIsLoading(false); generateSourceGroupSchema(data?.fieldMapping); } catch (error) { console.error('fetchData -> error', error); @@ -636,10 +644,10 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R } setItemStatusMap({ ...updateditemStatusMapCopy }); - setLoading(false); // eslint-disable-next-line no-unsafe-optional-chaining setTableData([...tableData, ...data?.fieldMapping ?? tableData]); setTotalCounts(data?.count); + setIsLoading(false) } catch (error) { console.log('loadMoreItems -> error', error); } @@ -1006,6 +1014,7 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R const groupArray = nestedList.filter(item => item?.child?.some(e => e?.id) ) + if(groupArray[0].child && previousSelectedValue !== selectedValue?.label && groupArray[0]?.uid === rowIndex){ for(const item of groupArray[0].child){ deletedExstingField[item?.uid] = { @@ -1316,7 +1325,7 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R } } - const selectedOption = OptionsForRow.length; + const selectedOption = OptionsForRow?.filter((option) => !option?.isDisabled)?.length // Handle case where there is exactly one match and it is auto-mapped if(OptionsForRow.length === 1 && @@ -1415,7 +1424,7 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R isDisabled: false }; - const adjustedOptions = (OptionsForRow.length === 0 && !contentTypeSchema) ? option : + const adjustedOptions: OptionsType[] | OptionsType = (OptionsForRow.length === 0 && !contentTypeSchema) ? option : (OptionsForRow.length > 0 && OptionsForRow.every((item)=>item.isDisabled) && OptionValue.label === Fields[data?.contentstackFieldType]?.label) ? [] : OptionsForRow.map((option: OptionsType) => ({ ...option, @@ -1685,6 +1694,97 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R } }; + const handleCTDeleted = async(isContentType:boolean) => { + const updatedContentTypeMapping = Object.fromEntries( + Object.entries(newMigrationData?.content_mapping?.content_type_mapping || {}).filter( + ([key]) => !selectedContentType?.contentstackUid.includes(key) + ) + ); + + const orgId = selectedOrganisation?.value; + const projectID = projectId; + setIsDropDownChanged(false); + + const updatedRows: FieldMapType[] = tableData.map((row) => { + return { ...row, contentstackFieldType: row?.backupFieldType }; + }); + setTableData(updatedRows); + setSelectedEntries(updatedRows); + + const dataCs = { + contentTypeData: { + status: selectedContentType?.status, + id: selectedContentType?.id, + projectId:projectId, + otherCmsTitle: otherCmsTitle, + otherCmsUid: selectedContentType?.otherCmsUid, + isUpdated: true, + updateAt: new Date(), + contentstackTitle: selectedContentType?.contentstackTitle, + contentstackUid: selectedContentType?.contentstackUid, + fieldMapping: updatedRows + } + }; + let newstate = {} ; + setContentTypeMapped((prevState: ContentTypeMap) => { + const newState = { ...prevState }; + + delete newState[selectedContentType?.contentstackUid ?? '']; + newstate = newState; + + return newstate; + }); + + if (orgId && selectedContentType) { + try { + const { data, status } = await resetToInitialMapping( + orgId, + projectID, + selectedContentType?.id ?? '', + dataCs + ); + + setExistingField({}); + setContentTypeSchema([]); + setOtherContentType({ + label: `Select ${isContentType ? 'Content Type' : 'Global Field'} from Existing Stack`, + value: `Select ${isContentType ? 'Content Type' : 'Global Field'} from Existing Stack` + }); + + if (status === 200) { + const resetCT = filteredContentTypes?.map(ct => + ct?.id === selectedContentType?.id ? { ...ct, status: data?.data?.status } : ct + ); + setFilteredContentTypes(resetCT); + setContentTypes(resetCT); + + try { + await updateContentMapper(orgId, projectID, {...newstate} ); + } catch (err) { + console.log(err); + return err; + } + + } + } catch (error) { + console.log(error); + return error; + } + } + + const newMigrationDataObj : INewMigration = { + ...newMigrationData, + content_mapping:{ + ...newMigrationData?.content_mapping, + content_type_mapping : updatedContentTypeMapping + + } + + } + dispatch(updateNewMigrationData(newMigrationDataObj)); + + } + /** * Retrieves existing content types for a given project. * @returns An array containing the retrieved content types or global fields based on condition if itContentType true and if existing content type or global field id is passed then returns an object containing title, uid and schema of that particular content type or global field. @@ -1693,7 +1793,7 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R if (isContentType) { try { const { data , status} = await getExistingContentTypes(projectId, otherContentType?.id ?? ''); - if (status == 201 && data?.contentTypes?.length > 0) { + if (status == 201 && data?.contentTypes?.length > 0 && data?.selectedContentType) { (otherContentType?.id === data?.selectedContentType?.uid) && setsCsCTypeUpdated(false); (otherContentType?.id && otherContentType?.label !== data?.selectedContentType?.title) @@ -1724,6 +1824,8 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R setContentTypeSchema(data?.selectedContentType?.schema); } } else { + + await handleCTDeleted(isContentType); Notification({ notificationContent: { text: "No content found in the stack" }, notificationProps: { @@ -1741,7 +1843,7 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R try { const { data, status } = await getExistingGlobalFields(projectId, otherContentType?.id ?? ''); - if (status == 201 && data?.globalFields?.length > 0) { + if (status == 201 && data?.globalFields?.length > 0 && data?.selectedGlobalField) { (otherContentType?.id === data?.selectedGlobalField?.uid) && setsCsCTypeUpdated(false); (otherContentType?.id && otherContentType?.label !== data?.selectedGlobalField?.title) @@ -1771,6 +1873,9 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R type: 'success' }); } else { + + await handleCTDeleted(isContentType); + Notification({ notificationContent: { text: "No Global Fields found in the stack" }, notificationProps: { @@ -1788,7 +1893,7 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R 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[contentFieldKey]; @@ -1854,7 +1959,7 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R isDisabled: (contentTypeMapped && Object.values(contentTypeMapped).includes(item?.uid)) }; }); - + const adjustedOption = options?.map((option) => ({ @@ -2160,7 +2265,7 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R testId="no-results-found-page" />} -
+
); }); diff --git a/ui/src/components/DestinationStack/Actions/LoadStacks.tsx b/ui/src/components/DestinationStack/Actions/LoadStacks.tsx index eb618dca6..fd9ef8105 100644 --- a/ui/src/components/DestinationStack/Actions/LoadStacks.tsx +++ b/ui/src/components/DestinationStack/Actions/LoadStacks.tsx @@ -38,6 +38,7 @@ const LoadStacks = (props: LoadFileFormatProps) => { const newMigrationData = useSelector((state:RootState)=>state?.migration?.newMigrationData); const selectedOrganisation = useSelector((state:RootState)=>state?.authentication?.selectedOrganisation); const dispatch = useDispatch(); + /**** ALL UseStates HERE ****/ const [selectedStack, setSelectedStack] = useState( null @@ -61,7 +62,8 @@ const LoadStacks = (props: LoadFileFormatProps) => { default: false, master_locale: '', locales: [], - created_at: '' + created_at: '', + disabled: false, } ]; const [allStack, setAllStack] = useState(newMigrationData?.destination_stack?.stackArray); @@ -106,7 +108,8 @@ const LoadStacks = (props: LoadFileFormatProps) => { locales: resp?.data?.stack?.locales, created_at: resp?.data?.stack?.created_at, uid: resp?.data?.stack?.api_key, - isNewStack: true + isNewStack: true, + isDisabled: false, }; setSelectedStack(newCreatedStack); @@ -183,7 +186,7 @@ const LoadStacks = (props: LoadFileFormatProps) => { if (allStack?.length <= 0) { setAllStack(loadingOption); const stackData = await getAllStacksInOrg(selectedOrganisation?.value, ''); // org id will always be there - + const stackArray = validateArray(stackData?.data?.stacks) ? stackData?.data?.stacks?.map((stack: StackResponse) => ({ label: stack?.name, @@ -192,7 +195,8 @@ const LoadStacks = (props: LoadFileFormatProps) => { master_locale: stack?.master_locale, locales: stack?.locales, created_at: stack?.created_at, - isNewStack: newStackCreated + isNewStack: newStackCreated, + isDisabled: newMigrationDataRef?.current?.destination_stack?.migratedStacks?.includes(stack?.api_key), })) : []; diff --git a/ui/src/components/LegacyCms/Actions/LoadSelectCms.tsx b/ui/src/components/LegacyCms/Actions/LoadSelectCms.tsx index e571f02d2..0d676c5bb 100644 --- a/ui/src/components/LegacyCms/Actions/LoadSelectCms.tsx +++ b/ui/src/components/LegacyCms/Actions/LoadSelectCms.tsx @@ -106,7 +106,7 @@ const LoadSelectCms = (props: LoadSelectCmsProps) => { } }; - dispatch(updateNewMigrationData(newMigrationDataObj)); + //dispatch(updateNewMigrationData(newMigrationDataObj)); setCmsData(filteredCmsData) @@ -205,6 +205,7 @@ const LoadSelectCms = (props: LoadSelectCmsProps) => { onCardClick={data?.cms_id !== selectedCard?.cms_id ? handleCardClick : undefined} selectedCard={selectedCard} idField="cms_id" + disabled={newMigrationData?.project_current_step > 1} /> ))} )) diff --git a/ui/src/components/LogScreen/MigrationLogViewer.tsx b/ui/src/components/LogScreen/MigrationLogViewer.tsx index 75c082b54..89fabf83c 100644 --- a/ui/src/components/LogScreen/MigrationLogViewer.tsx +++ b/ui/src/components/LogScreen/MigrationLogViewer.tsx @@ -218,6 +218,11 @@ const MigrationLogViewer = ({ serverPath }: LogsType) => { const message = logObject.message; return ( + newMigrationData?.destination_stack?.migratedStacks?.includes(newMigrationData?.destination_stack?.selectedStack?.value) ? +
+
Migration has already done in selected stack. Please create a new project.
+
+ : message === "Migration logs will appear here once the process begins." ?
{message}
diff --git a/ui/src/components/LogScreen/index.tsx b/ui/src/components/LogScreen/index.tsx index b417ea779..1370695a8 100644 --- a/ui/src/components/LogScreen/index.tsx +++ b/ui/src/components/LogScreen/index.tsx @@ -15,7 +15,7 @@ import { INewMigration } from '../../context/app/app.interface'; import './index.scss'; import { MAGNIFY,DEMAGNIFY } from '../../common/assets'; -import { saveStateToLocalStorage } from '../TestMigration'; +import { saveStateToLocalStorage } from '../../utilities/functions'; // Define log styles for different levels const logStyles: { [key: string]: React.CSSProperties } = { @@ -135,10 +135,10 @@ const TestMigrationLogViewer = ({ serverPath, sendDataToParent,projectId }: Logs if (message === "Test Migration Process Completed") { // Save test migration state to local storage - saveStateToLocalStorage({ + saveStateToLocalStorage(`testmigration_${projectId}`, { isTestMigrationCompleted : true, isTestMigrationStarted : false, - }, projectId); + }); Notification({ notificationContent: { text: message }, @@ -184,25 +184,25 @@ const TestMigrationLogViewer = ({ serverPath, sendDataToParent,projectId }: Logs transition: "transform 0.1s ease" }}> {logs?.map((log, index) => { - const key = `${index}-${new Date().getMilliseconds()}` + // const key = `${index}-${new Date().getMilliseconds()}` try { const logObject = JSON.parse(log); const level = logObject.level; const timestamp = logObject.timestamp; const message = logObject.message; return ( - <> +
{message === "Migration logs will appear here once the process begins." - ?
+ ?
{message}
- :
+ :
{index}
{ timestamp ? new Date(timestamp)?.toTimeString()?.split(' ')[0] : new Date()?.toTimeString()?.split(' ')[0]}
{message}
} - +
); } catch (error) { console.error('Invalid JSON string', error); diff --git a/ui/src/components/MigrationFlowHeader/index.tsx b/ui/src/components/MigrationFlowHeader/index.tsx index 4c224f157..32bb5ebb0 100644 --- a/ui/src/components/MigrationFlowHeader/index.tsx +++ b/ui/src/components/MigrationFlowHeader/index.tsx @@ -80,6 +80,8 @@ const MigrationFlowHeader = ({projectData, handleOnClick, isLoading, finalExecut finalExecutionStarted || newMigrationData?.migration_execution?.migrationStarted || newMigrationData?.migration_execution?.migrationCompleted; + const destinationStackMigrated = params?.stepId === '5' && newMigrationData?.destination_stack?.migratedStacks?.includes(newMigrationData?.destination_stack?.selectedStack?.value); + return (
@@ -96,7 +98,7 @@ const MigrationFlowHeader = ({projectData, handleOnClick, isLoading, finalExecut version="v2" aria-label='Save and Continue' isLoading={isLoading || newMigrationData?.isprojectMapped} - disabled={isStep4AndNotMigrated || isStepInvalid || isExecutionStarted} + disabled={isStep4AndNotMigrated || isStepInvalid || isExecutionStarted || destinationStackMigrated} > {stepValue} diff --git a/ui/src/components/Modal/index.tsx b/ui/src/components/Modal/index.tsx index dc40ae702..f0db50817 100644 --- a/ui/src/components/Modal/index.tsx +++ b/ui/src/components/Modal/index.tsx @@ -60,7 +60,7 @@ const Modal = (props: ProjectModalProps) => { return 'Project name is required.'; } else if (!/^[^\s].*$/.test(value)) { setInputValue(false); - return 'Please enter a valid project name.'; + //return 'Please enter a valid project name.'; } else { setInputValue(true); } diff --git a/ui/src/components/ProjectsHeader/index.tsx b/ui/src/components/ProjectsHeader/index.tsx index efb61e1fe..abd87fdc5 100644 --- a/ui/src/components/ProjectsHeader/index.tsx +++ b/ui/src/components/ProjectsHeader/index.tsx @@ -1,8 +1,10 @@ // Libraries +import { useEffect, useState } from 'react'; import { Button, Icon, PageHeader, Search } from '@contentstack/venus-components'; // Interface import { ProjectsHeaderType } from './projectsHeader.interface'; +import { ProjectsObj } from '../../pages/Projects/projects.interface'; const ProjectsHeader = ({ headingText, @@ -13,6 +15,15 @@ const ProjectsHeader = ({ allProject, handleModal }: ProjectsHeaderType) => { + + const [disableCreateProject, setDisableCreateProject] = useState(false); + + useEffect(() => { + allProject?.forEach((project: ProjectsObj) => { + setDisableCreateProject(project?.isMigrationStarted && !project?.isMigrationCompleted); + }); + },[allProject]); + let interval: ReturnType; function setFocus() { clearTimeout(interval); @@ -22,64 +33,27 @@ const ProjectsHeader = ({ } const SearchProject = ( - <> -
- { - setSearchText(search)} - } - width="large" - onClear={true} - onClick={setFocus} - value={searchText} - debounceSearch={true} - id="search-project-input" - version="v2" - //disabled={allProject && allProject?.length <= 0 } - /> -
- {/* {allProject && allProject?.length > 0 ? ( -
- - search.replace(/\s/g, '').length - ? setSearchText(search?.trim()) - : setSearchText(search) - } - width="large" - onClear={true} - onClick={setFocus} - value={searchText} - debounceSearch={true} - id="search-project-input" - /> -
- ) : ( - searchText?.length > 0 && ( -
- setSearchText(search)} - onClear={true} - onClick={setFocus} - value={searchText} - debounceSearch={true} - id="search-project-input" - /> -
- ) - )} */} - +
+ { + search.trim()?.length > 0 ? setSearchText(search?.trim()) : setSearchText(search)} + } + width="large" + onClear={true} + onClick={setFocus} + value={searchText} + debounceSearch={true} + id="search-project-input" + version="v2" + /> +
); - const pageActions: any = [ { - label: cta && cta?.title && ( + label: cta?.title && (