From 08761fa25dff6c841d64190cca2e420afb8c41c9 Mon Sep 17 00:00:00 2001 From: Sayali Joshi Date: Wed, 4 Dec 2024 12:52:47 +0530 Subject: [PATCH 1/3] Debug issue CMG-394 --- api/src/services/migration.service.ts | 4 +++- ui/src/components/LogScreen/index.tsx | 15 ++++++++++++--- ui/src/components/TestMigration/index.tsx | 16 ++++++++++------ ui/src/context/app/app.interface.ts | 3 +++ 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/api/src/services/migration.service.ts b/api/src/services/migration.service.ts index 8790fee41..f75e30ad4 100644 --- a/api/src/services/migration.service.ts +++ b/api/src/services/migration.service.ts @@ -68,6 +68,8 @@ const createTestStack = async (req: Request): Promise => { }) ); + console.info("res test", res) + if (err) { logger.error( getLogMessage( @@ -92,7 +94,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 { diff --git a/ui/src/components/LogScreen/index.tsx b/ui/src/components/LogScreen/index.tsx index 809065e4a..e5c71eb95 100644 --- a/ui/src/components/LogScreen/index.tsx +++ b/ui/src/components/LogScreen/index.tsx @@ -150,7 +150,8 @@ const LogViewer = ({ serverPath, sendDataToParent }: LogsType) => { const newMigrationObj: INewMigration = { ...newMigrationData, - test_migration: { ...newMigrationData?.test_migration, isMigrationComplete: true, isMigrationStarted: false } + testStacks: [...newMigrationData?.testStacks ?? [], {isMigrated: true}] + // test_migration: { ...newMigrationData?.test_migration, isMigrationComplete: true, isMigrationStarted: false } }; dispatch(updateNewMigrationData((newMigrationObj))); @@ -177,12 +178,19 @@ const LogViewer = ({ serverPath, sendDataToParent }: LogsType) => { const level = logObject.level; const timestamp = logObject.timestamp; const message = logObject.message; + + const migratedTestStack = newMigrationData?.testStacks?.find((test) => test?.stackUid === newMigrationData?.test_migration?.stack_api_key) + return ( - message === "Migration logs will appear here once the process begins." + !migratedTestStack?.isMigrated && message === "Migration logs will appear here once the process begins." ?
{message}
- :
+ : migratedTestStack?.isMigrated + ?
+
{`Test Migration is completed for stack ${migratedTestStack?.stackName}`}
+
+ :
{index}
{ timestamp ? new Date(timestamp)?.toTimeString()?.split(' ')[0] : new Date()?.toTimeString()?.split(' ')[0]}
{message}
@@ -193,6 +201,7 @@ const LogViewer = ({ serverPath, sendDataToParent }: LogsType) => { } })}
+
{(newMigrationData?.test_migration?.isMigrationStarted || newMigrationData?.migration_execution?.migrationStarted) && (
diff --git a/ui/src/components/TestMigration/index.tsx b/ui/src/components/TestMigration/index.tsx index af2b1144e..bf587e7cc 100644 --- a/ui/src/components/TestMigration/index.tsx +++ b/ui/src/components/TestMigration/index.tsx @@ -60,7 +60,7 @@ const TestMigration = () => { // to disable buttons as per isMigrated state useEffect(() => { if (newMigrationData?.testStacks?.find((stack) => stack?.stackUid === newMigrationData?.test_migration?.stack_api_key)?.isMigrated === false) { - setDisableCreateStack(true); + setDisableCreateStack(false); } if (newMigrationData?.testStacks?.find((stack) => stack?.stackUid === newMigrationData?.test_migration?.stack_api_key)?.isMigrated === true) { @@ -76,7 +76,7 @@ const TestMigration = () => { //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; @@ -125,7 +125,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))); } @@ -155,8 +155,8 @@ const TestMigration = () => { 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 } + testStacks: [...newMigrationData?.testStacks ?? [], {stackUid: newMigrationData?.test_migration?.stack_api_key}], + test_migration: { ...newMigrationData?.test_migration} }; dispatch(updateNewMigrationData((newMigrationDataObj))); } @@ -168,7 +168,11 @@ 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); + } else { + setdisableTestMigration(newState); + } } ; return ( 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 }; From 4ccf79adaba29adfa39d93a3c9b679dbd021c85c Mon Sep 17 00:00:00 2001 From: Sayali Joshi Date: Wed, 4 Dec 2024 18:09:54 +0530 Subject: [PATCH 2/3] [CMG-393], [CMG-394] --- .../Common/MigrationCompletionModal/index.tsx | 38 +++ .../LogScreen/MigrationLogViewer.tsx | 236 ++++++++++++++++++ ui/src/components/LogScreen/index.tsx | 38 +-- .../components/MigrationExecution/index.tsx | 10 +- .../components/MigrationFlowHeader/index.tsx | 2 +- ui/src/components/TestMigration/index.tsx | 21 +- ui/src/pages/Migration/index.tsx | 12 +- 7 files changed, 314 insertions(+), 43 deletions(-) create mode 100644 ui/src/components/Common/MigrationCompletionModal/index.tsx create mode 100644 ui/src/components/LogScreen/MigrationLogViewer.tsx diff --git a/ui/src/components/Common/MigrationCompletionModal/index.tsx b/ui/src/components/Common/MigrationCompletionModal/index.tsx new file mode 100644 index 000000000..dde1993ef --- /dev/null +++ b/ui/src/components/Common/MigrationCompletionModal/index.tsx @@ -0,0 +1,38 @@ +// Libraries +import { + ModalBody, + ModalFooter, + Button, + Link +} from '@contentstack/venus-components'; + +interface Props { + closeModal: () => void; + data?: StackDetail; +} +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/LogScreen/MigrationLogViewer.tsx b/ui/src/components/LogScreen/MigrationLogViewer.tsx new file mode 100644 index 000000000..33c5f29e2 --- /dev/null +++ b/ui/src/components/LogScreen/MigrationLogViewer.tsx @@ -0,0 +1,236 @@ +// 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'; + +// 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 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 + }; + }, []); + + /** + * 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', + }); + } + } + + /** + * 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); + + /** + * 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") { + Notification({ + notificationContent: { text: message }, + notificationProps: { + position: 'bottom-center', + hideProgressBar: false + }, + type: 'success' + }); + + 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 ( +
+
+
+ {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 ( + newMigrationData?.migration_execution?.migrationStarted + ?
+
+ Migration Execution process is completed. You can view in the selected stack + + {newMigrationData?.stackDetails?.label} + +
+
+ : 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); + } + })} +
+ +
+ {logs?.some((log) => log === "Migration logs will appear here once the process begins.") && ( +
+ + + {MAGNIFY} + {DEMAGNIFY} + +
+ )} +
+ ); +}; + +export default MigrationLogViewer; \ No newline at end of file diff --git a/ui/src/components/LogScreen/index.tsx b/ui/src/components/LogScreen/index.tsx index e5c71eb95..474df163a 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); @@ -150,7 +150,7 @@ const LogViewer = ({ serverPath, sendDataToParent }: LogsType) => { const newMigrationObj: INewMigration = { ...newMigrationData, - testStacks: [...newMigrationData?.testStacks ?? [], {isMigrated: true}] + testStacks: [...newMigrationData?.testStacks ?? [], {stackUid: newMigrationData?.test_migration?.stack_api_key, isMigrated: true}] // test_migration: { ...newMigrationData?.test_migration, isMigrationComplete: true, isMigrationStarted: false } }; @@ -182,19 +182,22 @@ const LogViewer = ({ serverPath, sendDataToParent }: LogsType) => { const migratedTestStack = newMigrationData?.testStacks?.find((test) => test?.stackUid === newMigrationData?.test_migration?.stack_api_key) return ( - !migratedTestStack?.isMigrated && message === "Migration logs will appear here once the process begins." - ?
-
{message}
-
- : migratedTestStack?.isMigrated - ?
-
{`Test Migration is completed for stack ${migratedTestStack?.stackName}`}
-
- :
-
{index}
-
{ timestamp ? new Date(timestamp)?.toTimeString()?.split(' ')[0] : new Date()?.toTimeString()?.split(' ')[0]}
-
{message}
-
+ <> + {migratedTestStack?.isMigrated + ?
+
{`Test Migration is completed for stack ${migratedTestStack?.stackName}`}
+
+ : !migratedTestStack?.isMigrated && 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); @@ -210,11 +213,10 @@ const LogViewer = ({ serverPath, sendDataToParent }: LogsType) => { {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..b190d47d8 100644 --- a/ui/src/components/MigrationFlowHeader/index.tsx +++ b/ui/src/components/MigrationFlowHeader/index.tsx @@ -71,7 +71,7 @@ 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) || + 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 } > diff --git a/ui/src/components/TestMigration/index.tsx b/ui/src/components/TestMigration/index.tsx index 1b197ba33..6c7580f81 100644 --- a/ui/src/components/TestMigration/index.tsx +++ b/ui/src/components/TestMigration/index.tsx @@ -22,7 +22,7 @@ import { INewMigration } from '../../context/app/app.interface'; // Component -import LogViewer from '../LogScreen'; +import TestMigrationLogViewer from '../LogScreen'; // CSS import './index.scss'; @@ -146,6 +146,7 @@ const TestMigration = () => { ); if (testRes?.status === 200) { + setdisableTestMigration(true); handleMigrationState(true); Notification({ notificationContent: { text: 'Test Migration started' }, @@ -156,12 +157,12 @@ const TestMigration = () => { type: 'message' }); - const newMigrationDataObj: INewMigration = { - ...newMigrationData, - testStacks: [...newMigrationData?.testStacks ?? [], {stackUid: newMigrationData?.test_migration?.stack_api_key}], - test_migration: { ...newMigrationData?.test_migration} - }; - dispatch(updateNewMigrationData((newMigrationDataObj))); + // const newMigrationDataObj: INewMigration = { + // ...newMigrationData, + // testStacks: [...newMigrationData?.testStacks ?? [], {stackUid: newMigrationData?.test_migration?.stack_api_key, isMigrated: false}], + // // test_migration: { ...newMigrationData?.test_migration} + // }; + // dispatch(updateNewMigrationData((newMigrationDataObj))); } } catch (error) { console.log(error); @@ -173,9 +174,7 @@ const TestMigration = () => { setDisableCreateStack(newState); if (newMigrationData?.testStacks?.find((stack) => stack?.stackUid === newMigrationData?.test_migration?.stack_api_key)?.isMigrated === true) { setdisableTestMigration(!newState); - } else { - setdisableTestMigration(newState); - } + } } ; return ( @@ -251,7 +250,7 @@ const TestMigration = () => {
Execution Logs
- +
diff --git a/ui/src/pages/Migration/index.tsx b/ui/src/pages/Migration/index.tsx index 20f7f21b5..c8a42da68 100644 --- a/ui/src/pages/Migration/index.tsx +++ b/ui/src/pages/Migration/index.tsx @@ -61,6 +61,8 @@ const Migration = () => { const [isCompleted, setIsCompleted] = useState(false); const [isProjectMapper, setIsProjectMapper] = useState(false); + const [disableMigration, setDisableMigration] = useState(false); + const saveRef = useRef(null); useEffect(() => { @@ -466,6 +468,7 @@ const Migration = () => { if (migrationRes?.status === 200) { setIsLoading(false); + setDisableMigration(true); Notification({ notificationContent: { text: 'Migration Execution process started' }, notificationProps: { @@ -474,13 +477,6 @@ const Migration = () => { }, type: 'message' }); - - const newMigrationDataObj: INewMigration = { - ...newMigrationData, - migration_execution: { ...newMigrationData?.migration_execution, migrationStarted: true } - }; - - dispatch(updateNewMigrationData((newMigrationDataObj))); } } catch (error) { // return error; @@ -508,7 +504,7 @@ const Migration = () => { return (
{projectData && - + }
From e08a896255b138c1119c5b22e802b7247914ed8d Mon Sep 17 00:00:00 2001 From: Sayali Joshi Date: Thu, 5 Dec 2024 14:50:23 +0530 Subject: [PATCH 3/3] [CMG-393], [CMG-394] updated --- api/src/constants/index.ts | 2 + api/src/services/projects.service.ts | 29 +++++ .../Common/MigrationCompletionModal/index.tsx | 3 +- ui/src/components/ContentMapper/index.tsx | 2 +- .../LogScreen/MigrationLogViewer.tsx | 112 ++++++++---------- ui/src/components/LogScreen/index.scss | 2 +- ui/src/components/LogScreen/index.tsx | 102 +++++++--------- .../components/MigrationFlowHeader/index.tsx | 4 +- .../HorizontalStepper/HorizontalStepper.tsx | 2 +- ui/src/components/TestMigration/index.tsx | 24 ++-- ui/src/pages/Migration/index.tsx | 75 ++++++++++-- 11 files changed, 203 insertions(+), 154 deletions(-) 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/projects.service.ts b/api/src/services/projects.service.ts index 3fc396730..dd2e66484 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[4]; + 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; diff --git a/ui/src/components/Common/MigrationCompletionModal/index.tsx b/ui/src/components/Common/MigrationCompletionModal/index.tsx index dde1993ef..37bc1e293 100644 --- a/ui/src/components/Common/MigrationCompletionModal/index.tsx +++ b/ui/src/components/Common/MigrationCompletionModal/index.tsx @@ -9,6 +9,7 @@ import { interface Props { closeModal: () => void; data?: StackDetail; + isopen?: (flag: boolean) => void; } interface StackDetail { value?: string; @@ -27,7 +28,7 @@ const MigrationCompletionModal = (props: Props) => { - diff --git a/ui/src/components/ContentMapper/index.tsx b/ui/src/components/ContentMapper/index.tsx index 96f9f9c60..3f7109f19 100644 --- a/ui/src/components/ContentMapper/index.tsx +++ b/ui/src/components/ContentMapper/index.tsx @@ -1485,7 +1485,7 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R setContentTypes(savedCT); try { - await updateContentMapper(orgId, projectID, {...contentTypeMapped, [otherCmsTitle]: otherContentType?.label}); + await updateContentMapper(orgId, projectID, {...contentTypeMapped, [otherCmsUid]: otherContentType?.value}); } catch (err) { console.log(err); return err; diff --git a/ui/src/components/LogScreen/MigrationLogViewer.tsx b/ui/src/components/LogScreen/MigrationLogViewer.tsx index 33c5f29e2..cdd6e02db 100644 --- a/ui/src/components/LogScreen/MigrationLogViewer.tsx +++ b/ui/src/components/LogScreen/MigrationLogViewer.tsx @@ -14,6 +14,7 @@ import { ModalObj } from '../Modal/modal.interface'; // Components import MigrationCompletionModal from '../Common/MigrationCompletionModal'; +import useBlockNavigation from '../../hooks/userNavigation'; // CSS import './index.scss'; @@ -37,6 +38,7 @@ type LogsType = { */ 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); @@ -59,6 +61,8 @@ const MigrationLogViewer = ({ serverPath }: LogsType) => { }; }, []); + useBlockNavigation(isModalOpen); + /** * Scrolls to the top of the logs container. */ @@ -85,21 +89,6 @@ const MigrationLogViewer = ({ serverPath }: 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); /** @@ -141,14 +130,8 @@ const MigrationLogViewer = ({ serverPath }: LogsType) => { const message = logObject.message; if (message === "Migration Process Completed") { - Notification({ - notificationContent: { text: message }, - notificationProps: { - position: 'bottom-center', - hideProgressBar: false - }, - type: 'success' - }); + + setIsModalOpen(true); const newMigrationDataObj: INewMigration = { ...newMigrationData, @@ -161,6 +144,7 @@ const MigrationLogViewer = ({ serverPath }: LogsType) => { component: (props: ModalObj) => ( ), @@ -179,48 +163,48 @@ const MigrationLogViewer = ({ serverPath }: 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 ( - newMigrationData?.migration_execution?.migrationStarted - ?
-
- Migration Execution process is completed. You can view in the selected stack - - {newMigrationData?.stackDetails?.label} - -
-
- : 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 + ?
+
+ 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); + } + })} +
+ }
- {logs?.some((log) => log === "Migration logs will appear here once the process begins.") && ( + {(!newMigrationData?.migration_execution?.migrationStarted) && (
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 474df163a..1c2bff092 100644 --- a/ui/src/components/LogScreen/index.tsx +++ b/ui/src/components/LogScreen/index.tsx @@ -82,21 +82,6 @@ const TestMigrationLogViewer = ({ 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 TestMigrationLogViewer = ({ 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 TestMigrationLogViewer = ({ 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,8 +137,7 @@ const TestMigrationLogViewer = ({ serverPath, sendDataToParent }: LogsType) => { const newMigrationObj: INewMigration = { ...newMigrationData, - testStacks: [...newMigrationData?.testStacks ?? [], {stackUid: newMigrationData?.test_migration?.stack_api_key, isMigrated: true}] - // 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))); @@ -165,48 +151,46 @@ const TestMigrationLogViewer = ({ 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; - - const migratedTestStack = newMigrationData?.testStacks?.find((test) => test?.stackUid === newMigrationData?.test_migration?.stack_api_key) - - return ( - <> - {migratedTestStack?.isMigrated - ?
-
{`Test Migration is completed for stack ${migratedTestStack?.stackName}`}
-
- : !migratedTestStack?.isMigrated && 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.") && (
diff --git a/ui/src/components/MigrationFlowHeader/index.tsx b/ui/src/components/MigrationFlowHeader/index.tsx index b190d47d8..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?.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 + 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/Stepper/HorizontalStepper/HorizontalStepper.tsx b/ui/src/components/Stepper/HorizontalStepper/HorizontalStepper.tsx index afd2a48aa..3a3321e8f 100644 --- a/ui/src/components/Stepper/HorizontalStepper/HorizontalStepper.tsx +++ b/ui/src/components/Stepper/HorizontalStepper/HorizontalStepper.tsx @@ -102,7 +102,7 @@ const HorizontalStepper = forwardRef( !newMigrationData?.isprojectMapped && setShowStep(stepIndex); setStepsCompleted(prev => { const updatedStepsCompleted = [...prev]; - for (let i = 0; i < stepIndex; i++) { + for (let i = 0; i <= stepIndex; i++) { if (!updatedStepsCompleted?.includes(i)) { updatedStepsCompleted?.push(i); } diff --git a/ui/src/components/TestMigration/index.tsx b/ui/src/components/TestMigration/index.tsx index 6c7580f81..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'; @@ -58,19 +59,23 @@ 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) { + 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); @@ -137,7 +142,9 @@ const TestMigration = () => { } } - // Method to start test migration + /** + * Start the test migration + */ const handleTestMigration = async () => { try { const testRes = await createTestMigration( @@ -156,13 +163,6 @@ const TestMigration = () => { }, type: 'message' }); - - // const newMigrationDataObj: INewMigration = { - // ...newMigrationData, - // testStacks: [...newMigrationData?.testStacks ?? [], {stackUid: newMigrationData?.test_migration?.stack_api_key, isMigrated: false}], - // // test_migration: { ...newMigrationData?.test_migration} - // }; - // dispatch(updateNewMigrationData((newMigrationDataObj))); } } catch (error) { console.log(error); diff --git a/ui/src/pages/Migration/index.tsx b/ui/src/pages/Migration/index.tsx index c8a42da68..f7519eef8 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'; @@ -62,6 +63,8 @@ const Migration = () => { const [isProjectMapper, setIsProjectMapper] = useState(false); const [disableMigration, setDisableMigration] = useState(false); + const [isModalOpen, setIsModalOpen] = useState(false); + const saveRef = useRef(null); @@ -69,6 +72,9 @@ const Migration = () => { fetchData(); }, [params?.stepId, params?.projectId, selectedOrganisation?.value]); + /** + * Dispatches the isprojectMapped key to redux + */ useEffect(()=> { dispatch(updateNewMigrationData({ ...newMigrationData, @@ -78,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); @@ -91,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); @@ -106,6 +127,9 @@ const Migration = () => { } } + /** + * Fetch the CMS data + */ const fetchData = async () => { setIsLoading(true); @@ -138,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; @@ -246,6 +272,9 @@ const Migration = () => { setIsProjectMapper(false); }; + /** + * Create Stepper and call the steps components + */ const createStepper = (projectData: MigrationResponse,handleStepChange: (currentStep: number) => void) => { const steps = [ { @@ -288,6 +317,9 @@ const Migration = () => { return steps; } + /** + * Fetch the project data + */ const handleClick = () => { // Call handleStepChange function const x : string | undefined= params.stepId @@ -295,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); @@ -374,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); @@ -408,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) => ( { @@ -445,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); @@ -459,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); @@ -484,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,