diff --git a/api/src/constants/index.ts b/api/src/constants/index.ts index 0c835c803..5b85fcb48 100644 --- a/api/src/constants/index.ts +++ b/api/src/constants/index.ts @@ -78,6 +78,8 @@ export const HTTP_TEXTS = { "You cannot proceed if the project is not in draft or if any Legacy CMS or Destination Stack or Content Mapping details are missing.", CANNOT_PROCEED_TEST_MIGRATION: "You cannot proceed if the project is not in draft or if any Legacy CMS or Destination Stack or Content Mapping or Test Migration details are missing.", + CANNOT_PROCEED_MIGRATION: + "You cannot proceed if the project is not in draft or if any Legacy CMS or Destination Stack or Content Mapping or Test Migration details are missing or Migration is not completed.", CANNOT_UPDATE_CONTENT_MAPPING: "Updating the content mapping is restricted. Please verify the status and review preceding actions.", CANNOT_RESET_CONTENT_MAPPING: diff --git a/api/src/services/migration.service.ts b/api/src/services/migration.service.ts index 8491f2e9e..1ef07838a 100644 --- a/api/src/services/migration.service.ts +++ b/api/src/services/migration.service.ts @@ -92,7 +92,7 @@ const createTestStack = async (req: Request): Promise => { ProjectModelLowdb.update((data: any) => { data.projects[index].current_step = STEPPER_STEPS['TESTING']; data.projects[index].current_test_stack_id = res?.data?.stack?.api_key; - data.projects[index].test_stacks.push({ stackUid: res?.data?.stack?.api_key, isMigrated: false }); + data.projects[index].test_stacks.push({ stackUid: res?.data?.stack?.api_key, stackName: res?.data?.stack?.name, isMigrated: false }); }); } return { @@ -169,7 +169,7 @@ const deleteTestStack = async (req: Request): Promise => { .get("projects") .findIndex({ id: projectId }) .value(); - console.info(index); + if (index > -1) { ProjectModelLowdb.update((data: any) => { data.projects[index].current_test_stack_id = ""; diff --git a/api/src/services/projects.service.ts b/api/src/services/projects.service.ts index 3fc396730..67846713e 100644 --- a/api/src/services/projects.service.ts +++ b/api/src/services/projects.service.ts @@ -807,6 +807,35 @@ const updateCurrentStep = async (req: Request) => { ); } + ProjectModelLowdb.update((data: any) => { + data.projects[projectIndex].current_step = + STEPPER_STEPS.MIGRATION; + data.projects[projectIndex].status = NEW_PROJECT_STATUS[5]; + data.projects[projectIndex].updated_at = new Date().toISOString(); + }); + break; + } + case STEPPER_STEPS.MIGRATION: { + if ( + project.status === NEW_PROJECT_STATUS[0] || + !isStepCompleted || + !project?.destination_stack_id || + project?.content_mapper?.length === 0 || + !project?.current_test_stack_id || + !project?.isMigrationStarted + ) { + logger.error( + getLogMessage( + srcFunc, + HTTP_TEXTS.CANNOT_PROCEED_MIGRATION, + token_payload + ) + ); + throw new BadRequestError( + HTTP_TEXTS.CANNOT_PROCEED_MIGRATION + ); + } + ProjectModelLowdb.update((data: any) => { data.projects[projectIndex].current_step = STEPPER_STEPS.MIGRATION; @@ -1050,8 +1079,6 @@ const updateStackDetails = async (req: Request) => { * @throws ExceptionFunction if an error occurs during the update. */ const updateContentMapper = async (req: Request) => { - console.info("updateContentMapper", req.params, req.body); - const { orgId, projectId } = req.params; const { token_payload, content_mapper } = req.body; const srcFunc = "updateContentMapper"; diff --git a/ui/src/components/Common/MigrationCompletionModal/index.tsx b/ui/src/components/Common/MigrationCompletionModal/index.tsx new file mode 100644 index 000000000..37bc1e293 --- /dev/null +++ b/ui/src/components/Common/MigrationCompletionModal/index.tsx @@ -0,0 +1,39 @@ +// Libraries +import { + ModalBody, + ModalFooter, + Button, + Link +} from '@contentstack/venus-components'; + +interface Props { + closeModal: () => void; + data?: StackDetail; + isopen?: (flag: boolean) => void; +} +interface StackDetail { + value?: string; + label?: string; +} + +const MigrationCompletionModal = (props: Props) => { + const stackLink = `https://app.contentstack.com/#!/stack/${props?.data?.value}/dashboard`; + + return( + <> + + Migration Execution process is completed. You can view in the selected stack + + {props?.data?.label} + + + + + + + ) +} + +export default MigrationCompletionModal; \ No newline at end of file diff --git a/ui/src/components/ContentMapper/index.tsx b/ui/src/components/ContentMapper/index.tsx index f5a7d0a20..e076e4e95 100644 --- a/ui/src/components/ContentMapper/index.tsx +++ b/ui/src/components/ContentMapper/index.tsx @@ -261,7 +261,7 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R /** ALL HOOKS Here */ - const { projectId = '' } = useParams(); + const { projectId = '', stepId = '' } = useParams(); const navigate = useNavigate(); const filterRef = useRef(null); @@ -894,11 +894,12 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R data?.otherCmsField === 'title' || data?.otherCmsField === 'url' || data?.otherCmsField === 'reference'|| - data?.contentstackFieldType === "global_field" + data?.contentstackFieldType === "global_field" || + newMigrationData?.project_current_step?.toString() !== stepId } /> - {!( + {!( data?.otherCmsType === 'Group' || data?.otherCmsField === 'title' || data?.otherCmsField === 'url' || @@ -920,9 +921,10 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R onClick={() => handleAdvancedSetting(fieldLabel, data?.advanced || {}, data?.uid, data) } + disabled={newMigrationData?.project_current_step?.toString() !== stepId} /> - )} + )} ); }; @@ -953,7 +955,6 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R else { setIsFieldDeleted(false); } - setExistingField((prevOptions: ExistingFieldType) => ({ ...prevOptions, @@ -1375,36 +1376,34 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R maxWidth="290px" isClearable={selectedOptions?.includes(existingField?.[data?.uid]?.label ?? '')} options={adjustedOptions} - isDisabled={OptionValue?.isDisabled} + isDisabled={OptionValue?.isDisabled || newMigrationData?.project_current_step?.toString() !== stepId} /> {!OptionValue?.isDisabled && (
- - - - + +
- )} ); @@ -1996,6 +1995,7 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R placeholder={otherContentType?.label} isSearchable version="v2" + isDisabled={newMigrationData?.project_current_step?.toString() !== stepId} /> )} @@ -2012,9 +2012,10 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R />
diff --git a/ui/src/components/LogScreen/MigrationLogViewer.tsx b/ui/src/components/LogScreen/MigrationLogViewer.tsx new file mode 100644 index 000000000..cdd6e02db --- /dev/null +++ b/ui/src/components/LogScreen/MigrationLogViewer.tsx @@ -0,0 +1,220 @@ +// Libraries +import React, { useEffect, useState, useRef } from 'react'; +import { Icon, Notification, cbModal, Link } from '@contentstack/venus-components'; +import io from 'socket.io-client'; +import { useSelector, useDispatch } from 'react-redux'; + +// Redux files +import { RootState } from '../../store'; +import { updateNewMigrationData } from '../../store/slice/migrationDataSlice'; + +// Interface +import { INewMigration } from '../../context/app/app.interface'; +import { ModalObj } from '../Modal/modal.interface'; + +// Components +import MigrationCompletionModal from '../Common/MigrationCompletionModal'; +import useBlockNavigation from '../../hooks/userNavigation'; + +// CSS +import './index.scss'; + +import { MAGNIFY,DEMAGNIFY } from '../../common/assets'; + +const logStyles: { [key: string]: React.CSSProperties } = { + info: { backgroundColor: '#f1f1f1' }, + warn: { backgroundColor: '#ffeeba', color: '#856404' }, + error: { backgroundColor: '#f8d7da', color: '#721c24' }, + success: { backgroundColor: '#d4edda', color: '#155724' }, +}; + +type LogsType = { + serverPath: string; +} + +/** + * MigrationLogViewer component displays logs received from the server. + * @param {string} serverPath - The path of the server to connect to. + */ +const MigrationLogViewer = ({ serverPath }: LogsType) => { + const [logs, setLogs] = useState([JSON.stringify({ message: "Migration logs will appear here once the process begins.", level: ''})]); + const [isModalOpen, setIsModalOpen] = useState(false); + + const newMigrationData = useSelector((state: RootState) => state?.migration?.newMigrationData); + + const dispatch = useDispatch(); + + useEffect(() => { + const socket = io(serverPath || ''); // Connect to the server + + /** + * Event listener for 'logUpdate' event. + * @param {string} newLogs - The new logs received from the server. + */ + socket.on('logUpdate', (newLogs: string) => { + const logArray = newLogs.split('\n'); + setLogs(logArray); + }); + + return () => { + socket.disconnect(); // Cleanup on component unmount + }; + }, []); + + useBlockNavigation(isModalOpen); + + /** + * Scrolls to the top of the logs container. + */ + const handleScrollToTop = () => { + const logsContainer = document.querySelector('.logs-container'); + if (logsContainer) { + logsContainer.scrollTo({ + top: 0, + behavior: 'smooth', + }); + } + } + + /** + * Scrolls to the bottom of the logs container. + */ + const handleScrollToBottom = () => { + const logsContainer = document.querySelector('.logs-container'); + if (logsContainer) { + logsContainer.scrollTo({ + top: logsContainer.scrollHeight, + behavior: 'smooth', + }); + } + } + + const [zoomLevel, setZoomLevel] = useState(1); + + /** + * Zooms in the logs container. + */ + const handleZoomIn = () => { + // const logsContainer = document.querySelector('.logs-magnify') as HTMLElement; + // if (logsContainer) { + setZoomLevel(prevZoomLevel => prevZoomLevel + 0.1); + // logsContainer.style.transform = `scale(${zoomLevel})`; + // } + }; + + /** + * Zooms out the logs container. + */ + const handleZoomOut = () => { + // const logsContainer = document.querySelector('.logs-magnify') as HTMLElement; + // if (logsContainer) { + // setZoomLevel(prevZoomLevel => prevZoomLevel - 0.1); + // logsContainer.style.transform = `scale(${zoomLevel})`; + // } + setZoomLevel((prevZoomLevel) => { + const newZoomLevel = Math.max(prevZoomLevel - 0.1, 0.6); // added minimum level for zoom out + return newZoomLevel; + }); + }; + + const logsContainerRef = useRef(null); + + useEffect(() => { + if (logsContainerRef.current) { + logsContainerRef.current.scrollTop = logsContainerRef.current.scrollHeight; + } + + logs?.forEach((log) => { + try { + const logObject = JSON.parse(log); + const message = logObject.message; + + if (message === "Migration Process Completed") { + + setIsModalOpen(true); + + const newMigrationDataObj: INewMigration = { + ...newMigrationData, + migration_execution: { ...newMigrationData?.migration_execution, migrationStarted: true } + }; + + dispatch(updateNewMigrationData((newMigrationDataObj))); + + return cbModal({ + component: (props: ModalObj) => ( + + ), + modalProps: { + size: 'xsmall', + shouldCloseOnOverlayClick: false + } + }); + } + } catch (error) { + console.error('Invalid JSON string', error); + } + }); + }, [logs]); + + return ( +
+
+ {newMigrationData?.migration_execution?.migrationStarted + ?
+
+ Migration Execution process is completed. You can view in the selected stack + + {newMigrationData?.stackDetails?.label} + +
+
+ :
+ {logs?.map((log, index) => { + 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); + } + })} +
+ } +
+ {(!newMigrationData?.migration_execution?.migrationStarted) && ( +
+ + + {MAGNIFY} + {DEMAGNIFY} + +
+ )} +
+ ); +}; + +export default MigrationLogViewer; \ No newline at end of file diff --git a/ui/src/components/LogScreen/index.scss b/ui/src/components/LogScreen/index.scss index b725c12d8..a61e388f6 100644 --- a/ui/src/components/LogScreen/index.scss +++ b/ui/src/components/LogScreen/index.scss @@ -39,7 +39,7 @@ */ .log-entry { align-items: center; - background-color: $color-base-white-10; + // background-color: $color-base-white-10; color: $color-base-black-base; display: flex; font-family: "IBM Plex Mono", monospace; diff --git a/ui/src/components/LogScreen/index.tsx b/ui/src/components/LogScreen/index.tsx index 809065e4a..1c2bff092 100644 --- a/ui/src/components/LogScreen/index.tsx +++ b/ui/src/components/LogScreen/index.tsx @@ -29,10 +29,10 @@ type LogsType = { } /** - * LogViewer component displays logs received from the server. + * TestMigrationLogViewer component displays logs received from the server. * @param {string} serverPath - The path of the server to connect to. */ -const LogViewer = ({ serverPath, sendDataToParent }: LogsType) => { +const TestMigrationLogViewer = ({ serverPath, sendDataToParent }: LogsType) => { const [logs, setLogs] = useState([JSON.stringify({ message: "Migration logs will appear here once the process begins.", level: ''})]); const newMigrationData = useSelector((state: RootState) => state?.migration?.newMigrationData); @@ -82,21 +82,6 @@ const LogViewer = ({ serverPath, sendDataToParent }: LogsType) => { } } - /** - * Copies the logs to the clipboard. - */ - const handleCopyLogs = () => { - const logsContainer = document.querySelector('.logs-container'); - if (logsContainer) { - const range = document.createRange(); - range.selectNode(logsContainer); - window.getSelection()?.removeAllRanges(); - window.getSelection()?.addRange(range); - navigator.clipboard.writeText(logsContainer.textContent || ''); - window.getSelection()?.removeAllRanges(); - } - } - const [zoomLevel, setZoomLevel] = useState(1); /** @@ -127,6 +112,8 @@ const LogViewer = ({ serverPath, sendDataToParent }: LogsType) => { const logsContainerRef = useRef(null); + const migratedTestStack = newMigrationData?.testStacks?.find((test) => test?.stackUid === newMigrationData?.test_migration?.stack_api_key) + useEffect(() => { if (logsContainerRef.current) { logsContainerRef.current.scrollTop = logsContainerRef.current.scrollHeight; @@ -137,7 +124,7 @@ const LogViewer = ({ serverPath, sendDataToParent }: LogsType) => { const logObject = JSON.parse(log); const message = logObject.message; - if (message === "Test Migration Process Completed" || message === "Migration Execution Process Completed") { + if (message === "Test Migration Process Completed") { Notification({ notificationContent: { text: message }, notificationProps: { @@ -150,7 +137,7 @@ const LogViewer = ({ serverPath, sendDataToParent }: LogsType) => { const newMigrationObj: INewMigration = { ...newMigrationData, - test_migration: { ...newMigrationData?.test_migration, isMigrationComplete: true, isMigrationStarted: false } + testStacks: [...newMigrationData?.testStacks ?? [], {stackUid: newMigrationData?.test_migration?.stack_api_key, stackName: newMigrationData?.test_migration?.stack_name, isMigrated: true}] }; dispatch(updateNewMigrationData((newMigrationObj))); @@ -164,48 +151,56 @@ const LogViewer = ({ serverPath, sendDataToParent }: LogsType) => { return (
-
- {logs?.map((log, index) => { - 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); - } - })} -
+ {migratedTestStack?.isMigrated + ?
+
Test Migration is completed for stack {migratedTestStack?.stackName}
+
+ :
+ {logs?.map((log, index) => { + 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); + } + })} +
+ }
- {(newMigrationData?.test_migration?.isMigrationStarted || newMigrationData?.migration_execution?.migrationStarted) && ( + + {!migratedTestStack?.isMigrated && !logs?.some((log) => log === "Migration logs will appear here once the process begins.") && (
{MAGNIFY} {DEMAGNIFY} -
)}
); }; -export default LogViewer; \ No newline at end of file +export default TestMigrationLogViewer; \ No newline at end of file diff --git a/ui/src/components/MigrationExecution/index.tsx b/ui/src/components/MigrationExecution/index.tsx index 2302830d8..8603c3386 100644 --- a/ui/src/components/MigrationExecution/index.tsx +++ b/ui/src/components/MigrationExecution/index.tsx @@ -17,7 +17,7 @@ import { validateArray } from '../../utilities/functions'; import { DEFAULT_MIGRATION_EXECUTION } from '../../context/app/app.interface'; // Component -import LogViewer from '../LogScreen'; +import MigrationLogViewer from '../LogScreen/MigrationLogViewer'; //stylesheet import './index.scss'; @@ -59,16 +59,16 @@ const MigrationExecution = () => { const getPlaceHolder = (title: string) => { switch (title) { - case 'Uploaded CMS': + case 'Legacy CMS': return newMigrationData?.legacy_cms?.selectedCms?.title; case 'Organization': return newMigrationData?.destination_stack?.selectedOrg?.label; - case 'Selected stack': + case 'Selected Stack': return newMigrationData?.destination_stack?.selectedStack?.label; - case 'Selected locale': + case 'Selected Locale': return newMigrationData?.destination_stack?.selectedStack?.master_locale; } }; @@ -113,7 +113,7 @@ const MigrationExecution = () => {
Execution Logs
- +
diff --git a/ui/src/components/MigrationFlowHeader/index.tsx b/ui/src/components/MigrationFlowHeader/index.tsx index 137161423..ab735811f 100644 --- a/ui/src/components/MigrationFlowHeader/index.tsx +++ b/ui/src/components/MigrationFlowHeader/index.tsx @@ -71,8 +71,8 @@ const MigrationFlowHeader = ({projectData, handleOnClick, isLoading, finalExecut version="v2" aria-label='Save and Continue' isLoading={isLoading || newMigrationData?.isprojectMapped} - disabled={(params?.stepId === '4' && !newMigrationData?.test_migration?.isMigrationComplete) || - (params?.stepId && params?.stepId <= '2' && newMigrationData?.project_current_step?.toString() !== params?.stepId) || finalExecutionStarted + disabled={(params?.stepId === '4' && !(newMigrationData?.testStacks?.find((stack) => stack?.stackUid === newMigrationData?.test_migration?.stack_api_key)?.isMigrated)) || + (params?.stepId && params?.stepId <= '2' && newMigrationData?.project_current_step?.toString() !== params?.stepId) || (finalExecutionStarted || newMigrationData?.migration_execution?.migrationStarted) } > {stepValue} diff --git a/ui/src/components/TestMigration/index.tsx b/ui/src/components/TestMigration/index.tsx index 7e7955ad2..127719b74 100644 --- a/ui/src/components/TestMigration/index.tsx +++ b/ui/src/components/TestMigration/index.tsx @@ -1,3 +1,4 @@ +// Libraries import { useEffect, useState } from 'react'; import { useParams } from 'react-router'; import { Field, FieldLabel, TextInput, Link, Icon, Tooltip, Button, Notification, CircularLoader } from '@contentstack/venus-components'; @@ -22,7 +23,7 @@ import { INewMigration } from '../../context/app/app.interface'; // Component -import LogViewer from '../LogScreen'; +import TestMigrationLogViewer from '../LogScreen'; // CSS import './index.scss'; @@ -58,26 +59,30 @@ const TestMigration = () => { }); }, []); - // to disable buttons as per isMigrated state + /** + * to disable Create Test Stack and Start Test Migration buttons as per isMigrated state + */ useEffect(() => { - if (newMigrationData?.testStacks?.find((stack) => stack?.stackUid === newMigrationData?.test_migration?.stack_api_key)?.isMigrated === false) { - setDisableCreateStack(true); + if (!newMigrationData?.testStacks?.find((stack) => stack?.stackUid === newMigrationData?.test_migration?.stack_api_key)?.isMigrated) { + setDisableCreateStack(false); } if (newMigrationData?.testStacks?.find((stack) => stack?.stackUid === newMigrationData?.test_migration?.stack_api_key)?.isMigrated === true) { setdisableTestMigration(true); } - }, [newMigrationData]); + }, [newMigrationData?.testStacks]); - // Method to create test stack + /** + * Handles create test stack function + */ const handleCreateTestStack = async () => { setIsStackLoading(true); //get org plan details try { const orgDetails = await getOrgDetails(selectedOrganisation?.value); - const stacks_details_key = Object.keys(orgDetails?.data?.organization?.plan?.features)?.find(key => orgDetails?.data?.organization?.plan?.features[key].uid === 'stacks') || ''; + const stacks_details_key = Object.keys(orgDetails?.data?.organization?.plan?.features)?.find(key => orgDetails?.data?.organization?.plan?.features[key].uid === 'stacks') ?? ''; const max_stack_limit = orgDetails?.data?.organization?.plan?.features[stacks_details_key]?.max_limit; @@ -128,7 +133,7 @@ const TestMigration = () => { const newMigrationDataObj: INewMigration = { ...newMigrationData, - test_migration: { ...newMigrationData?.test_migration, stack_link: res?.data?.data?.url, stack_api_key: res?.data?.data?.data?.stack?.api_key } + test_migration: { ...newMigrationData?.test_migration, stack_link: res?.data?.data?.url, stack_api_key: res?.data?.data?.data?.stack?.api_key, stack_name: res?.data?.data?.data?.stack?.name } }; dispatch(updateNewMigrationData((newMigrationDataObj))); } @@ -137,7 +142,9 @@ const TestMigration = () => { } } - // Method to start test migration + /** + * Start the test migration + */ const handleTestMigration = async () => { try { const testRes = await createTestMigration( @@ -146,6 +153,7 @@ const TestMigration = () => { ); if (testRes?.status === 200) { + setdisableTestMigration(true); handleMigrationState(true); Notification({ notificationContent: { text: 'Test Migration started' }, @@ -155,13 +163,6 @@ const TestMigration = () => { }, type: 'message' }); - - const newMigrationDataObj: INewMigration = { - ...newMigrationData, - testStacks: [...newMigrationData?.testStacks ?? [], {stackUid: newMigrationData?.test_migration?.stack_api_key, isMigrated: true} ], - test_migration: { ...newMigrationData?.test_migration, isMigrationStarted: true, isMigrationComplete: false } - }; - dispatch(updateNewMigrationData((newMigrationDataObj))); } } catch (error) { console.log(error); @@ -171,7 +172,9 @@ const TestMigration = () => { // Function to update the parent state const handleMigrationState = (newState: boolean) => { setDisableCreateStack(newState); - setdisableTestMigration(!newState); + if (newMigrationData?.testStacks?.find((stack) => stack?.stackUid === newMigrationData?.test_migration?.stack_api_key)?.isMigrated === true) { + setdisableTestMigration(!newState); + } } ; return ( @@ -247,7 +250,7 @@ const TestMigration = () => {
Execution Logs
- +
diff --git a/ui/src/context/app/app.interface.ts b/ui/src/context/app/app.interface.ts index e66aaaded..d741e8306 100644 --- a/ui/src/context/app/app.interface.ts +++ b/ui/src/context/app/app.interface.ts @@ -195,6 +195,7 @@ export interface INewMigration { export interface TestStacks { stackUid?: string; + stackName?: string; isMigrated?: boolean; } @@ -223,6 +224,7 @@ export interface IDropDown { export interface ITestMigration { stack_link: string; stack_api_key: string; + stack_name: string; isMigrationStarted: boolean; isMigrationComplete: boolean; } @@ -341,6 +343,7 @@ export const DEFAULT_CONTENT_MAPPER: IContentMapper = { export const DEFAULT_TEST_MIGRATION: ITestMigration = { stack_link: '', stack_api_key: '', + stack_name: '', isMigrationStarted: false, isMigrationComplete: false }; diff --git a/ui/src/pages/Migration/index.tsx b/ui/src/pages/Migration/index.tsx index 2effd94e2..c8e407a11 100644 --- a/ui/src/pages/Migration/index.tsx +++ b/ui/src/pages/Migration/index.tsx @@ -15,6 +15,7 @@ import { getCMSDataFromFile } from '../../cmsData/cmsSelector'; // Utilities import { CS_ENTRIES, CS_URL } from '../../utilities/constants'; import { isEmptyString, validateArray } from '../../utilities/functions'; +import useBlockNavigation from '../../hooks/userNavigation'; // Interface import { defaultMigrationResponse, MigrationResponse } from '../../services/api/service.interface'; @@ -61,12 +62,19 @@ const Migration = () => { const [isCompleted, setIsCompleted] = useState(false); const [isProjectMapper, setIsProjectMapper] = useState(false); + const [disableMigration, setDisableMigration] = useState(false); + const [isModalOpen, setIsModalOpen] = useState(false); + + const saveRef = useRef(null); useEffect(() => { fetchData(); }, [params?.stepId, params?.projectId, selectedOrganisation?.value]); + /** + * Dispatches the isprojectMapped key to redux + */ useEffect(()=> { dispatch(updateNewMigrationData({ ...newMigrationData, @@ -76,7 +84,20 @@ const Migration = () => { },[isProjectMapper]); - // Function to get exisiting content types list + /** + * Updates the Migration excution step as completed if migration completes. + */ + useEffect(() => { + if (newMigrationData?.migration_execution?.migrationStarted) { + updateCurrentStepData(selectedOrganisation.value, projectId); + } + }, [newMigrationData?.migration_execution?.migrationStarted]) + + useBlockNavigation(isModalOpen); + + /** + * Function to get exisiting content types list + */ const fetchExistingContentTypes = async () => { try { const { data, status } = await getExistingContentTypes(projectId); @@ -89,7 +110,9 @@ const Migration = () => { } }; - // Function to get exisiting global fields list + /** + * Function to get exisiting global fields list + */ const fetchExistingGlobalFields = async () => { try { const { data, status } = await getExistingGlobalFields(projectId); @@ -104,6 +127,9 @@ const Migration = () => { } } + /** + * Fetch the CMS data + */ const fetchData = async () => { setIsLoading(true); @@ -136,7 +162,9 @@ const Migration = () => { setCurrentStepIndex(stepIndex !== -1 ? stepIndex : 0); }; - //Fetch project data + /** + * Fetch the project data + */ const fetchProjectData = async () => { if (isEmptyString(selectedOrganisation?.value) || isEmptyString(params?.projectId)) return; @@ -160,7 +188,7 @@ const Migration = () => { ? selectedCmsData.allowed_file_formats?.find( (cms: ICardType) => cms?.fileformat_id === projectData?.legacy_cms?.file_format ) - : newMigrationData?.legacy_cms?.selectedFileFormat || defaultCardType; + : newMigrationData?.legacy_cms?.selectedFileFormat ?? defaultCardType; const selectedOrganisationData = validateArray(organisationsList) @@ -207,7 +235,7 @@ const Migration = () => { }, isLocalPath: projectData?.legacy_cms?.is_localPath }, - isValidated: newMigrationData?.legacy_cms?.uploadedFile?.isValidated ||projectData?.legacy_cms?.is_fileValid, + isValidated: newMigrationData?.legacy_cms?.uploadedFile?.isValidated || projectData?.legacy_cms?.is_fileValid, reValidate: newMigrationData?.legacy_cms?.uploadedFile?.reValidate }, isFileFormatCheckboxChecked: true, @@ -244,6 +272,9 @@ const Migration = () => { setIsProjectMapper(false); }; + /** + * Create Stepper and call the steps components + */ const createStepper = (projectData: MigrationResponse,handleStepChange: (currentStep: number) => void) => { const steps = [ { @@ -286,6 +317,9 @@ const Migration = () => { return steps; } + /** + * Fetch the project data + */ const handleClick = () => { // Call handleStepChange function const x : string | undefined= params.stepId @@ -293,18 +327,25 @@ const Migration = () => { stepperRef?.current?.handleStepChange(currentStep-1); }; + /** + * Changes the step + */ const handleStepChange = (currentStep: number) => { if (stepperRef?.current) { stepperRef.current.handleStepChange(currentStep-1); } }; - //Handle on all steps are completed + /** + * Set the flag is step is completed + */ const handleOnAllStepsComplete = (flag = false) => { setIsCompleted(flag); }; - // handle on proceed to destination stack + /** + * Calls when click Continue button on Legacy CMS step and handles to proceed to destination stack + */ const handleOnClickLegacyCms = async (event: MouseEvent ) => { setIsLoading(true); @@ -372,7 +413,9 @@ const Migration = () => { }; - // handle on proceed to content mapping + /** + * Calls when click Save and Continue button on Destination Stack step and handles to proceed to content mapping + */ const handleOnClickDestinationStack = async (event: MouseEvent) => { setIsLoading(true); @@ -406,16 +449,18 @@ const Migration = () => { } }; - // handle on proceed to Test Migration + /** + * Calls when click Continue button on Content Mapper step and handles to proceed to Test Migration + */ const handleOnClickContentMapper = async (event: MouseEvent) => { - // setIsModalOpen(true); - if(newMigrationData?.content_mapping?.isDropDownChanged) { + setIsModalOpen(true); + return cbModal({ component: (props: ModalObj) => ( { @@ -443,10 +488,11 @@ const Migration = () => { await updateCurrentStepData(selectedOrganisation.value, projectId); handleStepChange(3); } - } - // handle on proceed to Final Migration + /** + * Calls when click Continue button on Test Migration step and handles to proceed to Migration Execution + */ const handleOnClickTestMigration = async () => { setIsLoading(false); @@ -457,7 +503,9 @@ const Migration = () => { handleStepChange(4); } - // handle to start Final Migration process + /** + * Calls when click Start Migration button on Migration Execution step and handles to start Final Migration process + */ const handleOnClickMigrationExecution = async () => { setIsLoading(true); @@ -466,6 +514,7 @@ const Migration = () => { if (migrationRes?.status === 200) { setIsLoading(false); + setDisableMigration(true); Notification({ notificationContent: { text: 'Migration Execution process started' }, notificationProps: { @@ -474,13 +523,6 @@ const Migration = () => { }, type: 'message' }); - - const newMigrationDataObj: INewMigration = { - ...newMigrationData, - migration_execution: { ...newMigrationData?.migration_execution, migrationStarted: true } - }; - - dispatch(updateNewMigrationData((newMigrationDataObj))); } } catch (error) { // return error; @@ -488,6 +530,9 @@ const Migration = () => { } } + /** + * Once Save Changes Modal is shown, Change the dropdown state to false and store in rdux + */ const changeDropdownState = () =>{ const newMigrationDataObj: INewMigration = { ...newMigrationData, @@ -508,7 +553,7 @@ const Migration = () => { return (
{projectData && - + }
diff --git a/ui/src/pages/Projects/index.scss b/ui/src/pages/Projects/index.scss index b6273e4d8..9e8e6d713 100644 --- a/ui/src/pages/Projects/index.scss +++ b/ui/src/pages/Projects/index.scss @@ -27,7 +27,11 @@ border-top: 0 none; height: auto; border-bottom: 1px solid $color-brand-secondary-lightest; + border-top: 1px solid $color-brand-secondary-lightest; + left: 56px; padding:20px 10px 20px 13px; + position: fixed; + width: calc(100% - 56px) !important; } } } diff --git a/ui/src/scss/App.scss b/ui/src/scss/App.scss index 398395498..f205cbf57 100644 --- a/ui/src/scss/App.scss +++ b/ui/src/scss/App.scss @@ -417,7 +417,8 @@ h2 { } .layout-container { .PageLayout__content { - border-top: 1px solid $color-brand-secondary-lightest; + height: calc(100vh - 137px) !important; + margin-top: 137px; } } .loader-container {