diff --git a/api/src/config/prod.config.ts b/api/src/config/prod.config.ts index a873b12a..07f9ef2a 100644 --- a/api/src/config/prod.config.ts +++ b/api/src/config/prod.config.ts @@ -14,5 +14,5 @@ export const prodConfig = { GCP_NA: "https://gcp-na-app.contentstack.com/#!", }, LOG_FILE_PATH: - process.platform === "win32" ? ".\\combine.log" : "./combine.log", // Replace with the actual path to your log file + process.platform === "win32" ? ".\\sample.log" : "./sample.log", // Replace with the actual path to your log file }; diff --git a/api/src/constants/index.ts b/api/src/constants/index.ts index ac316320..fe978ac3 100644 --- a/api/src/constants/index.ts +++ b/api/src/constants/index.ts @@ -82,6 +82,8 @@ export const HTTP_TEXTS = { "Project Deleted Successfully", PROJECT_REVERT: "Project Reverted Successfully", + LOGS_NOT_FOUND: + "Sorry, no logs found for requested stack migration." }; export const HTTP_RESPONSE_HEADERS = { diff --git a/api/src/controllers/migration.controller.ts b/api/src/controllers/migration.controller.ts index ce5d6d18..9f46dc4a 100644 --- a/api/src/controllers/migration.controller.ts +++ b/api/src/controllers/migration.controller.ts @@ -50,9 +50,15 @@ const deleteTestStack = async (req: Request, res: Response): Promise => { res.status(200).json(resp); }; +const getLogs = async (req: Request, res: Response): Promise => { + const resp = await migrationService.getLogs(req); + res.status(200).json(resp); +}; + export const migrationController = { createTestStack, deleteTestStack, startTestMigration, - startMigration + startMigration, + getLogs, }; diff --git a/api/src/models/FieldMapper.ts b/api/src/models/FieldMapper.ts index aedea35e..4f93770a 100644 --- a/api/src/models/FieldMapper.ts +++ b/api/src/models/FieldMapper.ts @@ -1,5 +1,6 @@ import { JSONFile } from "lowdb/node"; import LowWithLodash from "../utils/lowdb-lodash.utils.js"; +import path from "path"; /** * Represents the advanced configuration options for a field mapper. @@ -44,7 +45,7 @@ const defaultData: FieldMapper = { field_mapper: [] }; * Represents the database instance for the FieldMapper model. */ const db = new LowWithLodash( - new JSONFile("database/field-mapper.json"), + new JSONFile(path.join(process.cwd(), "database", "field-mapper.json")), defaultData ); diff --git a/api/src/models/contentTypesMapper-lowdb.ts b/api/src/models/contentTypesMapper-lowdb.ts index cf7c667f..e4163818 100644 --- a/api/src/models/contentTypesMapper-lowdb.ts +++ b/api/src/models/contentTypesMapper-lowdb.ts @@ -1,4 +1,5 @@ import { JSONFile } from "lowdb/node"; +import path from 'path'; import LowWithLodash from "../utils/lowdb-lodash.utils.js"; /** @@ -80,7 +81,7 @@ const defaultData: ContentTypeMapperDocument = { ContentTypesMappers: [] }; * Represents the database instance for the content types mapper. */ const db = new LowWithLodash( - new JSONFile("database/contentTypesMapper.json"), + new JSONFile(path.join(process.cwd(), "database", 'contentTypesMapper.json')), defaultData ); diff --git a/api/src/models/project-lowdb.ts b/api/src/models/project-lowdb.ts index 693899ba..f2fb6d81 100644 --- a/api/src/models/project-lowdb.ts +++ b/api/src/models/project-lowdb.ts @@ -63,7 +63,7 @@ interface Project { test_stacks: []; current_test_stack_id: string; legacy_cms: LegacyCMS; - content_mapper: []; + content_mapper: any[]; execution_log: [ExecutionLog]; created_at: string; updated_at: string; @@ -73,6 +73,7 @@ interface Project { stackDetails: []; mapperKeys: {}; extract_path: string; + isMigrationStarted: boolean; } interface ProjectDocument { diff --git a/api/src/routes/migration.routes.ts b/api/src/routes/migration.routes.ts index 7e4c29d9..e22a87cf 100644 --- a/api/src/routes/migration.routes.ts +++ b/api/src/routes/migration.routes.ts @@ -59,5 +59,11 @@ router.post( asyncRouter(migrationController.startMigration) ); +router.get( + "/get_migration_logs/:orgId/:projectId/:stackId", + asyncRouter(migrationController.getLogs) + +) + export default router; diff --git a/api/src/server.ts b/api/src/server.ts index 5ae0274b..157dd0e9 100644 --- a/api/src/server.ts +++ b/api/src/server.ts @@ -19,14 +19,15 @@ import migrationRoutes from "./routes/migration.routes.js"; import chokidar from "chokidar"; import { Server } from "socket.io"; import fs from "fs"; +import path from "path"; // Initialize file watcher for the log file const watcher = chokidar.watch(config.LOG_FILE_PATH, { usePolling: true, // Enables polling to detect changes in all environments - interval: 100, // Poll every 100ms (you can adjust this if needed) + interval: 1, // Poll every 100ms (you can adjust this if needed) awaitWriteFinish: { // Wait for file to finish being written before triggering - stabilityThreshold: 500, // Time to wait before considering the file stable - pollInterval: 100, // Interval at which to poll for file stability + stabilityThreshold: 1, // Time to wait before considering the file stable + pollInterval: 1, // Interval at which to poll for file stability }, persistent: true, // Keeps watching the file even after initial change }); // Initialize with initial log path @@ -34,15 +35,32 @@ const watcher = chokidar.watch(config.LOG_FILE_PATH, { let io: Server; // Socket.IO server instance // Dynamically change the log file path and update the watcher -export async function setLogFilePath(path: string) { - console.info(`Setting new log file path: ${path}`); - - // Stop watching the old log file - watcher.unwatch(config.LOG_FILE_PATH); - - // Update the config and start watching the new log file - config.LOG_FILE_PATH = path; - watcher.add(path); +export async function setLogFilePath(newPath: string) { + try { + // Ensure the new log file path is absolute and valid + const absolutePath = path.resolve(newPath); + console.info(`Attempting to set new log file path: ${absolutePath}`); + // Check if the new log file exists + // Stop watching the old log file + if (config.LOG_FILE_PATH) { + console.info(`Stopping watcher for previous log file: ${config.LOG_FILE_PATH}`); + watcher.unwatch(config.LOG_FILE_PATH); + } + // Update the config to use the new log file path + config.LOG_FILE_PATH = absolutePath; + + // Start watching the new log file + console.info(`Starting watcher for new log file: ${absolutePath}`); + watcher.add(absolutePath); + + } catch (error: any) { + console.error(`Failed to set new log file path: ${error.message}`); + // Optional: fallback to default or previous log file if the new path is invalid + if (config.LOG_FILE_PATH) { + console.info(`Reverting to previous log file path: ${config.LOG_FILE_PATH}`); + watcher.add(config.LOG_FILE_PATH); // Re-watch previous log file + } + } } try { @@ -99,26 +117,20 @@ try { }); // Emit initial log file content to connected clients - - // File watcher for log file changes - watcher.on("change", (path) => { + // File watcher for log file changes + watcher.on("change", async (path) => { console.info(`File changed: ${path}`); // Read the updated file content - fs.readFile(path, "utf8", (err, data) => { - if (err) { - logger.error(`Error reading log file: ${err}`); - return; - } - try { - // Emit the updated log content to connected clients - io.emit("logUpdate", data); - } catch (error) { - logger.error(`Error emitting log data: ${error}`); - } - }); + try { + const data = await fs.promises.readFile(path, "utf8") + // Emit the updated log content to connected clients + io.emit("logUpdate", data); + } catch (error) { + logger.error(`Error emitting log data: ${error}`); + } }); - })(); + } catch (e) { logger.error("Error while starting the server!"); logger.error(e); diff --git a/api/src/services/contentMapper.service.ts b/api/src/services/contentMapper.service.ts index e13275e6..7d6ea6c4 100644 --- a/api/src/services/contentMapper.service.ts +++ b/api/src/services/contentMapper.service.ts @@ -34,6 +34,7 @@ const putTestData = async (req: Request) => { const projectId = req.params.projectId; const contentTypes = req.body.contentTypes; + await FieldMapperModel.read(); /* @@ -61,7 +62,7 @@ const putTestData = async (req: Request) => { }); await ContentTypesMapperModelLowdb.read(); - const contentIds: string[] = []; + const contentIds: any[] = []; /* this code snippet is iterating over an array called contentTypes and @@ -70,7 +71,7 @@ const putTestData = async (req: Request) => { and the generated id values are pushed into the contentIds array. */ const contentType = contentTypes.map((item: any) => { - const id = item?.id.replace(/[{}]/g, "")?.toLowerCase() || uuidv4(); + const id = item?.id?.replace(/[{}]/g, "")?.toLowerCase() || uuidv4(); item.id = id; contentIds.push(id); return { ...item, id, projectId }; @@ -88,11 +89,12 @@ const putTestData = async (req: Request) => { .get("projects") .findIndex({ id: projectId }) .value(); - if (index > -1) { - ProjectModelLowdb.update((data: any) => { - data.projects[index].content_mapper = contentIds; - data.projects[index].extract_path = req?.body?.extractPath; - }); + if (index > -1 && contentIds?.length) { + ProjectModelLowdb.data.projects[index].content_mapper = contentIds; + ProjectModelLowdb.data.projects[index].extract_path = req?.body?.extractPath; + ProjectModelLowdb.write(); + } else { + throw new BadRequestError(HTTP_TEXTS.CONTENT_TYPE_NOT_FOUND); } const pData = ProjectModelLowdb.chain @@ -900,7 +902,7 @@ const getSingleGlobalField = async (req: Request) => { }, }) ); - + if (err) return { data: err.response.data, diff --git a/api/src/services/migration.service.ts b/api/src/services/migration.service.ts index 9d58ac7f..b3def8e4 100644 --- a/api/src/services/migration.service.ts +++ b/api/src/services/migration.service.ts @@ -1,4 +1,5 @@ import { Request } from "express"; +import path from "path"; import ProjectModelLowdb from "../models/project-lowdb.js"; import { config } from "../config/index.js"; import { safePromise, getLogMessage } from "../utils/index.js"; @@ -7,11 +8,15 @@ import { LoginServiceType } from "../models/types.js" import getAuthtoken from "../utils/auth.utils.js"; import logger from "../utils/logger.js"; import { HTTP_TEXTS, HTTP_CODES, LOCALE_MAPPER, STEPPER_STEPS } from "../constants/index.js"; -import { ExceptionFunction } from "../utils/custom-errors.utils.js"; +import { BadRequestError, ExceptionFunction } from "../utils/custom-errors.utils.js"; import { fieldAttacher } from "../utils/field-attacher.utils.js"; import { siteCoreService } from "./sitecore.service.js"; import { testFolderCreator } from "../utils/test-folder-creator.utils.js"; import { utilsCli } from './runCli.service.js'; +import customLogger from "../utils/custom-logger.utils.js"; +import { setLogFilePath } from "../server.js"; +import fs from 'fs'; + @@ -208,12 +213,16 @@ const startTestMigration = async (req: Request): Promise => { const project = ProjectModelLowdb.chain.get("projects").find({ id: projectId }).value(); const packagePath = project?.extract_path; if (packagePath && project?.current_test_stack_id) { + const loggerPath = path.join(process.cwd(), 'logs', projectId, `${project?.current_test_stack_id}.log`); + const message = getLogMessage('startTestMigration', 'Starting Test Migration...', {}); + await customLogger(projectId, project?.current_test_stack_id, 'info', message); + await setLogFilePath(loggerPath); const contentTypes = await fieldAttacher({ orgId, projectId, destinationStackId: project?.current_test_stack_id }); - await siteCoreService?.createEntry({ packagePath, contentTypes, destinationStackId: project?.current_test_stack_id }); - await siteCoreService?.createLocale(req, project?.current_test_stack_id); + await siteCoreService?.createEntry({ packagePath, contentTypes, destinationStackId: project?.current_test_stack_id, projectId }); + await siteCoreService?.createLocale(req, project?.current_test_stack_id, projectId); await siteCoreService?.createVersionFile(project?.current_test_stack_id); await testFolderCreator?.({ destinationStackId: project?.current_test_stack_id }); - await utilsCli?.runCli(region, user_id, project?.current_test_stack_id, projectId, true); + await utilsCli?.runCli(region, user_id, project?.current_test_stack_id, projectId, true, loggerPath); } } @@ -228,19 +237,84 @@ const startMigration = async (req: Request): Promise => { const { region, user_id } = req?.body?.token_payload ?? {}; await ProjectModelLowdb.read(); const project = ProjectModelLowdb.chain.get("projects").find({ id: projectId }).value(); + + const index = ProjectModelLowdb.chain.get("projects").findIndex({ id: projectId }).value(); + if (index > -1) { + ProjectModelLowdb.update((data: any) => { + data.projects[index].isMigrationStarted = true; + }); + } + const packagePath = project?.extract_path; if (packagePath && project?.destination_stack_id) { + const loggerPath = path.join(process.cwd(), 'logs', projectId, `${project?.destination_stack_id}.log`); + const message = getLogMessage('startTestMigration', 'Starting Migration...', {}); + await customLogger(projectId, project?.destination_stack_id, 'info', message); + await setLogFilePath(loggerPath); const contentTypes = await fieldAttacher({ orgId, projectId, destinationStackId: project?.destination_stack_id }); - await siteCoreService?.createEntry({ packagePath, contentTypes, destinationStackId: project?.destination_stack_id }); - await siteCoreService?.createLocale(req, project?.destination_stack_id); + await siteCoreService?.createEntry({ packagePath, contentTypes, destinationStackId: project?.destination_stack_id, projectId }); + await siteCoreService?.createLocale(req, project?.destination_stack_id, projectId); await siteCoreService?.createVersionFile(project?.destination_stack_id); - await utilsCli?.runCli(region, user_id, project?.destination_stack_id, projectId); + await utilsCli?.runCli(region, user_id, project?.destination_stack_id, projectId, false, loggerPath); + } +} + +const getLogs = async (req: Request): Promise => { + const orgId = req?.params?.orgId; + const projectId = req?.params?.projectId; + const stackId = req?.params?.stackId; + const srcFunc = "getLogs"; + const { region, user_id } = req?.body?.token_payload ?? {}; + try { + const loggerPath = path.join(process.cwd(), 'logs', projectId, `${stackId}.log`); + if(fs.existsSync(loggerPath)){ + const logs = fs.readFileSync(loggerPath,'utf-8'); + const logEntries = logs + .split('\n') + .map(line => { + try { + return JSON.parse(line); + } catch (error) { + return null; + } + }) + .filter(entry => entry !== null); + return logEntries + + } + else{ + logger.error( + getLogMessage( + srcFunc, + HTTP_TEXTS.LOGS_NOT_FOUND, + + ) + ); + throw new BadRequestError(HTTP_TEXTS.LOGS_NOT_FOUND); + + } + + } catch (error:any) { + logger.error( + getLogMessage( + srcFunc, + HTTP_TEXTS.LOGS_NOT_FOUND, + error + ) + ); + throw new ExceptionFunction( + error?.message || HTTP_TEXTS.INTERNAL_ERROR, + error?.statusCode || error?.status || HTTP_CODES.SERVER_ERROR + ); + } + } export const migrationService = { createTestStack, deleteTestStack, startTestMigration, - startMigration + startMigration, + getLogs, }; diff --git a/api/src/services/projects.service.ts b/api/src/services/projects.service.ts index db644bf0..3fc39673 100644 --- a/api/src/services/projects.service.ts +++ b/api/src/services/projects.service.ts @@ -125,7 +125,8 @@ const createProject = async (req: Request) => { created_at: '', isNewStack: false }, - mapperKeys: {} + mapperKeys: {}, + isMigrationStarted: false }; try { diff --git a/api/src/services/runCli.service.ts b/api/src/services/runCli.service.ts index 94c9ee6b..61cd8222 100644 --- a/api/src/services/runCli.service.ts +++ b/api/src/services/runCli.service.ts @@ -2,11 +2,12 @@ import path from "path"; import fs from 'fs'; import shell from 'shelljs' import { v4 } from "uuid"; -import { setLogFilePath } from "../server.js"; import { copyDirectory, createDirectoryAndFile } from '../utils/index.js' import { CS_REGIONS } from "../constants/index.js"; import ProjectModelLowdb from "../models/project-lowdb.js"; import AuthenticationModel from "../models/authentication.js"; +import watchLogs from "../utils/watch.utils.js"; +import { setLogFilePath } from "../server.js"; const addCustomMessageInCliLogs = async (loggerPath: string, level: string = 'info', message: string) => { try { @@ -22,9 +23,9 @@ const addCustomMessageInCliLogs = async (loggerPath: string, level: string = 'in } } - -export const runCli = async (rg: string, user_id: string, stack_uid: any, projectId: string, isTest = false) => { +export const runCli = async (rg: string, user_id: string, stack_uid: any, projectId: string, isTest = false, transformePath: string) => { try { + const message: string = isTest ? 'Test Migration Process Completed' : 'Migration Process Completed' const regionPresent = CS_REGIONS?.find((item: string) => item === rg) ?? 'NA'; await AuthenticationModel.read(); const userData = AuthenticationModel.chain @@ -36,10 +37,10 @@ export const runCli = async (rg: string, user_id: string, stack_uid: any, projec const backupPath = path.join(process.cwd(), 'migration-data', `${stack_uid}_${v4().slice(0, 4)}`); await copyDirectory(sourcePath, backupPath); const loggerPath = path.join(backupPath, 'logs', 'import', 'success.log'); - createDirectoryAndFile(loggerPath); + await createDirectoryAndFile(loggerPath, transformePath); await setLogFilePath(loggerPath); + await watchLogs(loggerPath, transformePath); shell.cd(path.join(process.cwd(), '..', 'cli', 'packages', 'contentstack')); - shell.exec('pwd'); shell.exec(`node bin/run config:set:region ${regionPresent}`); shell.exec(`node bin/run login -a ${userData?.authtoken} -e ${userData?.email}`); const importData = shell.exec(`node bin/run cm:stacks:import -k ${stack_uid} -d ${sourcePath} --backup-dir=${backupPath} --yes`, { async: true }); @@ -56,7 +57,7 @@ export const runCli = async (rg: string, user_id: string, stack_uid: any, projec }) ProjectModelLowdb.write(); } - await addCustomMessageInCliLogs(loggerPath, 'info', 'Test Migration Process Completed'); + await addCustomMessageInCliLogs(loggerPath, 'info', message); } }); diff --git a/api/src/services/sitecore.service.ts b/api/src/services/sitecore.service.ts index 4fe9fa45..f4de0dea 100644 --- a/api/src/services/sitecore.service.ts +++ b/api/src/services/sitecore.service.ts @@ -6,6 +6,9 @@ import _ from 'lodash'; import { LOCALE_MAPPER } from '../constants/index.js'; import { entriesFieldCreator, unflatten } from '../utils/entries-field-creator.utils.js'; import { orgService } from './org.service.js'; +import { getLogMessage } from '../utils/index.js'; +import customLogger from '../utils/custom-logger.utils.js'; + const append = "a"; @@ -77,7 +80,8 @@ const uidCorrector = ({ uid }: any) => { return _.replace(uid, new RegExp("[ -]", "g"), '_')?.toLowerCase() } -const cretaeAssets = async ({ packagePath, baseDir }: any) => { +const cretaeAssets = async ({ packagePath, baseDir, destinationStackId, projectId }: any) => { + const srcFunc = 'cretaeAssets'; const assetsSave = path.join(baseDir, 'assets'); const allAssetJSON: any = {}; const folderName: any = path.join(packagePath, 'items', 'master', 'sitecore', 'media library'); @@ -121,6 +125,13 @@ const cretaeAssets = async ({ packagePath, baseDir }: any) => { , assets) } catch (err) { console.error("🚀 ~ file: assets.js:52 ~ xml_folder?.forEach ~ err:", err) + const message = getLogMessage( + srcFunc, + `Not able to read the asset"${jsonAsset?.item?.$?.name}(${mestaData?.uid})".`, + {}, + err + ) + await customLogger(projectId, destinationStackId, 'error', message); } allAssetJSON[mestaData?.uid] = { urlPath: `/assets/${mestaData?.uid}`, @@ -135,9 +146,20 @@ const cretaeAssets = async ({ packagePath, baseDir }: any) => { publish_details: [], assetPath } + const message = getLogMessage( + srcFunc, + `Asset "${jsonAsset?.item?.$?.name}" has been successfully transformed.`, + {} + ) + await customLogger(projectId, destinationStackId, 'info', message); allAssetJSON[mestaData?.uid].parent_uid = '2146b0cee522cc3a38d' } else { - console.info('blob is not there for this asstes', mestaData?.uid, '.') + const message = getLogMessage( + srcFunc, + `Asset "${jsonAsset?.item?.$?.name}" blob is not there for this asstes.`, + {} + ) + await customLogger(projectId, destinationStackId, 'error', message); } } } @@ -162,11 +184,12 @@ const cretaeAssets = async ({ packagePath, baseDir }: any) => { return allAssetJSON; } -const createEntry = async ({ packagePath, contentTypes, master_locale = 'en-us', destinationStackId }: { packagePath: any; contentTypes: any; master_locale?: string, destinationStackId: string }) => { +const createEntry = async ({ packagePath, contentTypes, master_locale = 'en-us', destinationStackId, projectId }: { packagePath: any; contentTypes: any; master_locale?: string, destinationStackId: string, projectId: string }) => { try { + const srcFunc = 'createEntry'; const baseDir = path.join('sitecoreMigrationData', destinationStackId); const entrySave = path.join(baseDir, 'entries'); - const allAssetJSON: any = await cretaeAssets({ packagePath, baseDir }); + const allAssetJSON: any = await cretaeAssets({ packagePath, baseDir, destinationStackId, projectId }); const folderName: any = path.join(packagePath, 'items', 'master', 'sitecore', 'content'); const entriesData: any = []; if (fs.existsSync(folderName)) { @@ -192,6 +215,12 @@ const createEntry = async ({ packagePath, contentTypes, master_locale = 'en-us', } } for await (const ctType of contentTypes) { + const message = getLogMessage( + srcFunc, + `Transforming entries of Content Type ${ctType?.contentstackUid} has begun.`, + {} + ) + await customLogger(projectId, destinationStackId, 'info', message); const entryPresent: any = entriesData?.find((item: any) => uidCorrector({ uid: item?.template }) === ctType?.contentstackUid) if (entryPresent) { const locales: any = Object?.keys(entryPresent?.locale); @@ -221,6 +250,12 @@ const createEntry = async ({ packagePath, contentTypes, master_locale = 'en-us', } if (Object.keys?.(entryObj)?.length > 1) { entryLocale[uid] = unflatten(entryObj) ?? {}; + const message = getLogMessage( + srcFunc, + `Entry title "${entryObj?.title}"(${ctType?.contentstackUid}) in the ${newLocale} locale has been successfully transformed.`, + {} + ) + await customLogger(projectId, destinationStackId, 'info', message) } }); } @@ -234,6 +269,12 @@ const createEntry = async ({ packagePath, contentTypes, master_locale = 'en-us', await writeFiles(entryPath, fileMeta, entryLocale, newLocale) } } else { + const message = getLogMessage( + srcFunc, + `No entries found for the content type ${ctType?.contentstackUid}.`, + {} + ) + await customLogger(projectId, destinationStackId, 'error', message) console.info('Entries missing for', ctType?.contentstackUid) } } @@ -243,46 +284,69 @@ const createEntry = async ({ packagePath, contentTypes, master_locale = 'en-us', } } -const createLocale = async (req: any, destinationStackId: string) => { - const baseDir = path.join('sitecoreMigrationData', destinationStackId); - const localeSave = path.join(baseDir, 'locale'); - const allLocalesResp = await orgService.getLocales(req) - const masterLocale = Object?.keys?.(LOCALE_MAPPER?.masterLocale)?.[0]; - const msLocale: any = {}; - const uid = uuidv4(); - msLocale[uid] = { - "code": masterLocale, - "fallback_locale": null, - "uid": uid, - "name": allLocalesResp?.data?.locales?.[masterLocale] ?? '' - } - const allLocales: any = {}; - for (const [key, value] of Object.entries(LOCALE_MAPPER)) { - const localeUid = uuidv4(); - if (key !== 'masterLocale' && typeof value === 'string') { - allLocales[localeUid] = { - "code": value, - "fallback_locale": masterLocale, - "uid": localeUid, - "name": allLocalesResp?.data?.locales?.[value] ?? '' - } +const createLocale = async (req: any, destinationStackId: string, projectId: string) => { + const srcFunc = 'createLocale'; + try { + const baseDir = path.join('sitecoreMigrationData', destinationStackId); + const localeSave = path.join(baseDir, 'locale'); + const allLocalesResp = await orgService.getLocales(req) + const masterLocale = Object?.keys?.(LOCALE_MAPPER?.masterLocale)?.[0]; + const msLocale: any = {}; + const uid = uuidv4(); + msLocale[uid] = { + "code": masterLocale, + "fallback_locale": null, + "uid": uid, + "name": allLocalesResp?.data?.locales?.[masterLocale] ?? '' } - } - const masterPath = path.join(localeSave, 'master-locale.json'); - const allLocalePath = path.join(localeSave, 'locales.json'); - fs.access(localeSave, async (err) => { - if (err) { - fs.mkdir(localeSave, { recursive: true }, async (err) => { - if (!err) { - await writeOneFile(masterPath, msLocale); - await writeOneFile(allLocalePath, allLocales); + const message = getLogMessage( + srcFunc, + `Master locale ${masterLocale} has been successfully transformed.`, + {} + ) + await customLogger(projectId, destinationStackId, 'info', message); + const allLocales: any = {}; + for (const [key, value] of Object.entries(LOCALE_MAPPER)) { + const localeUid = uuidv4(); + if (key !== 'masterLocale' && typeof value === 'string') { + allLocales[localeUid] = { + "code": value, + "fallback_locale": masterLocale, + "uid": localeUid, + "name": allLocalesResp?.data?.locales?.[value] ?? '' } - }) - } else { - await writeOneFile(masterPath, msLocale); - await writeOneFile(allLocalePath, allLocales); + const message = getLogMessage( + srcFunc, + `locale ${value} has been successfully transformed.`, + {} + ) + await customLogger(projectId, destinationStackId, 'info', message); + } } - }) + const masterPath = path.join(localeSave, 'master-locale.json'); + const allLocalePath = path.join(localeSave, 'locales.json'); + fs.access(localeSave, async (err) => { + if (err) { + fs.mkdir(localeSave, { recursive: true }, async (err) => { + if (!err) { + await writeOneFile(masterPath, msLocale); + await writeOneFile(allLocalePath, allLocales); + } + }) + } else { + await writeOneFile(masterPath, msLocale); + await writeOneFile(allLocalePath, allLocales); + } + }) + } catch (err) { + const message = getLogMessage( + srcFunc, + `error while Createing the locales.`, + {}, + err + ) + await customLogger(projectId, destinationStackId, 'error', message); + } } const createVersionFile = async (destinationStackId: string) => { diff --git a/api/src/utils/content-type-creator.utils.ts b/api/src/utils/content-type-creator.utils.ts index 1e0ffce4..12c9a709 100644 --- a/api/src/utils/content-type-creator.utils.ts +++ b/api/src/utils/content-type-creator.utils.ts @@ -1,5 +1,9 @@ import fs from 'fs'; import path from 'path'; +import logger from './logger.js'; +import { getLogMessage } from './index.js'; +import customLogger from './custom-logger.utils.js'; + interface Group { data_type: string; display_name?: string; // Assuming item?.contentstackField might be undefined @@ -47,7 +51,7 @@ const arrangGroups = ({ schema }: any) => { } const convertToSchemaFormate = ({ field, advanced = true }: any) => { - // console.info("🚀 ~ convertToSchemaFormate ~ field:", field) + switch (field?.ContentstackFieldType) { case 'single_line_text': { return { @@ -405,7 +409,8 @@ const writeGlobalField = async (schema: any, globalSave: string) => { } }; -export const contenTypeMaker = async ({ contentType, destinationStackId }: any) => { +export const contenTypeMaker = async ({ contentType, destinationStackId, projectId }: any) => { + const srcFunc = 'contenTypeMaker'; const ct: ContentType = { title: contentType?.contentstackTitle, uid: contentType?.contentstackUid, @@ -451,9 +456,13 @@ export const contenTypeMaker = async ({ contentType, destinationStackId }: any) if (ct?.uid) { if (contentType?.type === 'global_field') { const globalSave = path.join('sitecoreMigrationData', destinationStackId, 'global_fields'); + const message = getLogMessage(srcFunc, `Global Field ${ct?.uid} has been successfully Transformed.`, {}); + await customLogger(projectId, destinationStackId, 'info', message); await writeGlobalField(ct, globalSave); } else { const contentSave = path.join('sitecoreMigrationData', destinationStackId, 'content_types'); + const message = getLogMessage(srcFunc, `ContentType ${ct?.uid} has been successfully Transformed.`, {}); + await customLogger(projectId, destinationStackId, 'info', message); await saveContent(ct, contentSave); } } else { diff --git a/api/src/utils/custom-logger.utils.ts b/api/src/utils/custom-logger.utils.ts new file mode 100644 index 00000000..01a365e4 --- /dev/null +++ b/api/src/utils/custom-logger.utils.ts @@ -0,0 +1,82 @@ +import fs from 'fs'; +import path from "path"; +import { createLogger, format, transports } from "winston"; +import logger from './logger.js'; + + +const fileExists = async (path: string): Promise => { + try { + await fs.promises.access(path); + return true; // Path exists + } catch (error) { + return false; // Path does not exist + } +} + +//Logger for custom logs +/** + * The logger instance used for logging messages. + */ +const customLogger = async (projectId: string, apiKey: string, level: string, message: string) => { + try { + const logDir = path.join(process.cwd(), 'logs', projectId); + const logFilePath = path.join(logDir, `${apiKey}.log`); + // Ensure log directory exists, using async/await with fs.promises + if (!fs.existsSync(logDir)) { + await fs.promises.mkdir(logDir, { recursive: true }); + } + + if (!fs.existsSync(logFilePath)) { + // If the file does not exist, create it and write an initial log entry + fs.promises.writeFile(logFilePath, 'Log file created\n', { flag: 'a' }); // 'a' flag for appending + console.info(`Log file created and initial entry written: ${logFilePath}`); + } + // Create a logger instance with a file transport + const log = createLogger({ + level: 'info', + format: format.combine(format.timestamp(), format.json()), + transports: [ + // Write logs to a file named after the apiKey + new transports.File({ filename: logFilePath }), + ], + }); + + // Handle the logging levels dynamically + switch (level) { + case 'error': { + log.error(message); // Log to file + logger.error(message); // Log to console/logger + break; + } + case 'warn': { + log.warn(message); + logger.warn(message); + break; + } + case 'info': { + log.info(message); + logger.info(message); + break; + } + case 'debug': { + log.debug(message); + logger.debug(message); + break; + } + default: { + log.info(message); // Default to info level + logger.info(message); + } + } + + if (await fileExists(logFilePath)) { + return; // Exit function if log file exists after logging + } else { + console.error(`Log file was not created.`); + } + } catch (error: any) { + console.error(`Failed to log message: ${error.message}`); + } +}; + +export default customLogger; \ No newline at end of file diff --git a/api/src/utils/entries-field-creator.utils.ts b/api/src/utils/entries-field-creator.utils.ts index 7f0cb287..68098ebb 100644 --- a/api/src/utils/entries-field-creator.utils.ts +++ b/api/src/utils/entries-field-creator.utils.ts @@ -324,7 +324,7 @@ export const entriesFieldCreator = async ({ field, content, idCorrector, allAsse } default: { - console.info(field?.ContentstackFieldType, 'umesh'); + console.info(field?.ContentstackFieldType, 'field missing'); return content; } } diff --git a/api/src/utils/field-attacher.utils.ts b/api/src/utils/field-attacher.utils.ts index 40459d52..3977c625 100644 --- a/api/src/utils/field-attacher.utils.ts +++ b/api/src/utils/field-attacher.utils.ts @@ -16,18 +16,18 @@ export const fieldAttacher = async ({ projectId, orgId, destinationStackId }: an for await (const contentId of projectData?.content_mapper ?? []) { const contentType: any = ContentTypesMapperModelLowdb.chain .get("ContentTypesMappers") - .find({ id: contentId }) + .find({ id: contentId, projectId: projectId }) .value(); if (contentType?.fieldMapping?.length) { contentType.fieldMapping = contentType?.fieldMapping?.map((fieldUid: any) => { const field = FieldMapperModel.chain .get("field_mapper") - .find({ id: fieldUid }) + .find({ id: fieldUid, projectId: projectId }) .value() return field; }) } - await contenTypeMaker({ contentType, destinationStackId }) + await contenTypeMaker({ contentType, destinationStackId, projectId }) contentTypes?.push?.(contentType); } } diff --git a/api/src/utils/index.ts b/api/src/utils/index.ts index 71940cb5..17fb91de 100644 --- a/api/src/utils/index.ts +++ b/api/src/utils/index.ts @@ -77,16 +77,21 @@ export async function copyDirectory(srcDir: string, destDir: string): Promise { + try { + const transformeLogs = await fs.promises.readFile(sourceFile, 'utf8'); + const logMessage = transformeLogs + '\n'; // Append a newline after the log content + await fs.promises.appendFile(destinationFile, logMessage); + console.info(`Merged logs from ${sourceFile} to ${destinationFile}`); + } catch (error: any) { + console.error(`Error merging logs: ${error.message}`); + } +}; + +// Function to watch the log file +const watchLogs = async (sourceFile: string, destinationFile: string) => { + // Initialize watcher + const watcher = chokidar.watch(sourceFile, { + persistent: true, + ignoreInitial: true, // Do not trigger on the existing state + awaitWriteFinish: { + stabilityThreshold: 100, // Wait 1 millisecond before triggering (for fast writes) + pollInterval: 100 // Check for changes every 1 ms + }, + }); + // Event listener for file changes + watcher.on('change', async (path: string) => { + console.info(`File ${path} has been changed`); + // Update the destination file when the source file changes + await mergeLogs(destinationFile, sourceFile); // Await the mergeLogs function + }); + // Handle errors + watcher.on('error', (error) => { + console.error(`Watcher error: ${error}`); + }); +}; + +export default watchLogs; \ No newline at end of file diff --git a/cli/packages/contentstack/.gitignore b/cli/packages/contentstack/.gitignore index 2bf7d18b..9306742c 100644 --- a/cli/packages/contentstack/.gitignore +++ b/cli/packages/contentstack/.gitignore @@ -11,4 +11,6 @@ coverage tsconfig.tsbuildinfo /merge_scripts /merge-summary.json -!bin \ No newline at end of file +!bin +/sitecoreMigrationData +/migration-data \ No newline at end of file diff --git a/ui/src/cmsData/test_migration.json b/ui/src/cmsData/test_migration.json index faeaea63..2ade9b92 100644 --- a/ui/src/cmsData/test_migration.json +++ b/ui/src/cmsData/test_migration.json @@ -1,12 +1,19 @@ { "title": "Test Migration", - "subtitle": "Link to the Demo Stack", "locale": "en-us", - "cta": { + "create_stack_cta": { "open_in_new_tab": false, "theme": "primary", - "title": "Start Migration", - "url": "/projects/${projectId}/migration/steps/5", + "title": "Create Test Stack", + "url": "", "with_icon": false - } + }, + "start_migration_cta": { + "open_in_new_tab": false, + "theme": "primary", + "title": "Start Test Migration", + "url": "", + "with_icon": false + }, + "subtitle": "Test Migration is a step where some content types are migrated in a test stack for review. A user can verify the stack and data. If the data is migrated properly then it can proceed with the final Migration Execution process." } diff --git a/ui/src/common/assets/icons.tsx b/ui/src/common/assets/icons.tsx index d20536ea..18a4870b 100644 --- a/ui/src/common/assets/icons.tsx +++ b/ui/src/common/assets/icons.tsx @@ -851,3 +851,20 @@ export const SCHEMA_PREVIEW = ( ); +export const NoDataFound = ( + + + + + + + + + + +); + + diff --git a/ui/src/components/AdvancePropertise/advanceProperties.interface.ts b/ui/src/components/AdvancePropertise/advanceProperties.interface.ts index 8919f1a3..5dd24e3c 100644 --- a/ui/src/components/AdvancePropertise/advanceProperties.interface.ts +++ b/ui/src/components/AdvancePropertise/advanceProperties.interface.ts @@ -234,4 +234,10 @@ export interface StateType { * Indicates if the assets should be embedded. */ embedAssests?: boolean; +} + +export interface optionsType{ + label?:string; + key?:string; + value:string } \ No newline at end of file diff --git a/ui/src/components/AdvancePropertise/index.tsx b/ui/src/components/AdvancePropertise/index.tsx index 3e9631f8..5c387c6a 100644 --- a/ui/src/components/AdvancePropertise/index.tsx +++ b/ui/src/components/AdvancePropertise/index.tsx @@ -18,7 +18,7 @@ import { import { getContentTypes } from '../../services/api/migration.service'; // Interfaces -import { SchemaProps } from './advanceProperties.interface'; +import { optionsType, SchemaProps } from './advanceProperties.interface'; import { ContentType } from '../ContentMapper/contentMapper.interface'; // Styles @@ -70,11 +70,11 @@ const AdvancePropertise = (props: SchemaProps) => { const [showIcon, setShowIcon] = useState(); const filterRef = useRef(null); const [options, setOptions] = useState(props?.value?.options || []); - const [draggedIndex, setDraggedIndex] = useState(null); + const [draggedIndex, setDraggedIndex] = useState(null); useEffect(()=>{ const defaultIndex = toggleStates?.option?.findIndex( - (item: any) => toggleStates?.Default_value === item?.key + (item: optionsType) => toggleStates?.Default_value === item?.key ); if (defaultIndex !== -1) { @@ -90,9 +90,15 @@ const AdvancePropertise = (props: SchemaProps) => { * @param searchText - The search text. */ const fetchContentTypes = async (searchText: string) => { - const { data } = await getContentTypes(props?.projectId ?? '', 0, 10, searchText || ''); //org id will always present + try { + const { data } = await getContentTypes(props?.projectId ?? '', 0, 10, searchText || ''); //org id will always present - setContentTypes(data?.contentTypes); + setContentTypes(data?.contentTypes); + } catch (error) { + return error; + + } + }; /** @@ -213,9 +219,9 @@ const AdvancePropertise = (props: SchemaProps) => { })); } - const handleDefalutValue = (index:number, option:any) => { + const handleDefalutValue = (index:number, option:optionsType) => { setShowIcon(index); - setShowOptions((prev) => ({ + setShowOptions(() => ({ [index]: false, })); @@ -244,9 +250,9 @@ const AdvancePropertise = (props: SchemaProps) => { ); } - const handleRemoveDefalutValue = (index:number, option:any)=>{ + const handleRemoveDefalutValue = (index:number)=>{ setShowIcon(-1); - setShowOptions((prev) => ({ + setShowOptions(() => ({ [index]: false, })); @@ -275,7 +281,7 @@ const AdvancePropertise = (props: SchemaProps) => { ); } - const handleDragStart = (index:any) => { + const handleDragStart = (index: number) => { setDraggedIndex(index); document.querySelectorAll('.element-wrapper').forEach((el, i) => { if (i === index) { @@ -284,7 +290,7 @@ const AdvancePropertise = (props: SchemaProps) => { }); }; - const handleDragOver = (e:any, index:number) => { + const handleDragOver = (e:React.DragEvent, index:number) => { e.preventDefault(); document.querySelectorAll('.element-wrapper').forEach((el, i) => { if (i === index) { @@ -295,7 +301,7 @@ const AdvancePropertise = (props: SchemaProps) => { }); }; - const handleDrop = (index:any) => { + const handleDrop = (index:number) => { if (draggedIndex === null) return; const updatedOptions = [...options]; @@ -363,7 +369,7 @@ const AdvancePropertise = (props: SchemaProps) => { (read only)
- {options?.map((option:any,index)=>( + {options?.map((option: optionsType,index)=>( <>
handleDragStart(index)} @@ -397,7 +403,7 @@ const AdvancePropertise = (props: SchemaProps) => { onClick={()=>handleDefalutValue(index,option)} >Mark as Default : + onClick={()=>handleRemoveDefalutValue(index)} >Remove as Default } @@ -610,9 +616,10 @@ const AdvancePropertise = (props: SchemaProps) => { { - return; - }} - value={selectedStack} - isSearchable={true} - isClearable={true} - placeholder={'Stacks'} - /> -
-
-
- -
-
-
- {selectedStack?.master_locale} -
-
- - - - - - ); -}; - -export default StacksSummary; diff --git a/ui/src/components/DestinationStack/Summary/summary.scss b/ui/src/components/DestinationStack/Summary/summary.scss deleted file mode 100644 index 43f3cb92..00000000 --- a/ui/src/components/DestinationStack/Summary/summary.scss +++ /dev/null @@ -1,30 +0,0 @@ -@import '../../../scss/variables'; - -.summary-title { - font-family: $font-family-primary; - font-style: normal; - font-weight: 700; - font-size: $size-font-medium; - line-height: 135%; - letter-spacing: 0.02em; - color: $color-font-black; - margin-top: 3px; -} - -#Step2 .action-summary-wrapper { - background-color: rgb(247, 249, 252); - border: none; - padding: 0 10px !important; - margin-bottom: 0; - margin-left: 0; - top: 50%; -} - -#Step2 .StepperWrapper__step { - padding: 0 !important; -} - -.col-12 { - text-align: left; - padding: 0; -} diff --git a/ui/src/components/DestinationStack/index.tsx b/ui/src/components/DestinationStack/index.tsx index d40d8790..1de004a8 100644 --- a/ui/src/components/DestinationStack/index.tsx +++ b/ui/src/components/DestinationStack/index.tsx @@ -16,7 +16,8 @@ import { getAllStacksInOrg } from '../../services/api/stacks.service'; import { MigrationResponse, StackResponse } from '../../services/api/service.interface'; import { getCMSDataFromFile } from '../../cmsData/cmsSelector'; import { RootState } from '../../store'; -import { updateMigrationData, updateNewMigrationData } from '../../store/slice/migrationDataSlice'; +import { updateMigrationData } from '../../store/slice/migrationDataSlice'; +import { AutoVerticalStepperRef } from '../LegacyCms'; type DestinationStackComponentProps = { destination_stack: string; @@ -42,7 +43,7 @@ const DestinationStackComponent = ({ const [stepperKey] = useState('destination-Vertical-stepper'); const [internalActiveStepIndex, setInternalActiveStepIndex] = useState(-1); - const autoVerticalStepperComponent = useRef(null); + const autoVerticalStepperComponent = useRef(null); /** ALL CONTEXT HERE */ const migrationData = useSelector((state:RootState)=>state?.migration?.migrationData); @@ -89,48 +90,54 @@ const DestinationStackComponent = ({ //If stack is already selected and exist in backend, then fetch all stack list and filter selected stack. if (!isEmptyString(destination_stack)) { - const stackData: any = await getAllStacksInOrg( - selectedOrganisationData?.value || selectedOrganisation?.value,'' - ); - const stackArray = validateArray(stackData?.data?.stacks) - ? stackData?.data?.stacks?.map((stack: StackResponse) => ({ + try { + const stackData: any = await getAllStacksInOrg( + selectedOrganisationData?.value || selectedOrganisation?.value,'' + ); + const stackArray = validateArray(stackData?.data?.stacks) + ? stackData?.data?.stacks?.map((stack: StackResponse) => ({ + label: stack?.name, + value: stack?.api_key, + uid: stack?.api_key, + master_locale: stack?.master_locale, + locales: stack?.locales, + created_at: stack?.created_at + })) + : []; + + stackArray.sort( + (a: IDropDown, b: IDropDown) => + new Date(b?.created_at)?.getTime() - new Date(a?.created_at)?.getTime() + ); + const stack = + validateArray(stackData?.data?.stacks) && + stackData?.data?.stacks?.find( + (stack: StackResponse) => stack?.api_key === destination_stack + ); + + if (stack) { + selectedStackData = { label: stack?.name, value: stack?.api_key, - uid: stack?.api_key, master_locale: stack?.master_locale, locales: stack?.locales, created_at: stack?.created_at - })) - : []; - - stackArray.sort( - (a: IDropDown, b: IDropDown) => - new Date(b?.created_at)?.getTime() - new Date(a?.created_at)?.getTime() - ); - const stack = - validateArray(stackData?.data?.stacks) && - stackData?.data?.stacks?.find( - (stack: StackResponse) => stack?.api_key === destination_stack - ); - - if (stack) { - selectedStackData = { - label: stack?.name, - value: stack?.api_key, - master_locale: stack?.master_locale, - locales: stack?.locales, - created_at: stack?.created_at + }; + } + const newMigData: IDestinationStack = { + ...newMigrationData?.destination_stack, + selectedOrg: selectedOrganisationData || selectedOrganisation, + selectedStack: selectedStackData, + stackArray: stackArray }; + + } catch (error) { + return error; + } - const newMigData: IDestinationStack = { - ...newMigrationData?.destination_stack, - selectedOrg: selectedOrganisationData || selectedOrganisation, - selectedStack: selectedStackData, - stackArray: stackArray - }; - //dispatch(updateNewMigrationData({ destination_stack: newMigData })); + + } - //Update newMigration Data for destination stack diff --git a/ui/src/components/LegacyCms/Actions/LoadFileFormat.tsx b/ui/src/components/LegacyCms/Actions/LoadFileFormat.tsx index efe596d0..34fcf736 100644 --- a/ui/src/components/LegacyCms/Actions/LoadFileFormat.tsx +++ b/ui/src/components/LegacyCms/Actions/LoadFileFormat.tsx @@ -1,16 +1,11 @@ // Libraries import { useEffect, useState } from 'react'; -import { useParams } from 'react-router'; import { Icon, TextInput } from '@contentstack/venus-components'; import { useDispatch, useSelector } from 'react-redux'; // Utilities import { isEmptyString } from '../../../utilities/functions'; -// Services -import { - fileformatConfirmation -} from '../../../services/api/migration.service'; // Interface import { ICardType} from '../../../components/Common/Card/card.interface'; @@ -23,7 +18,7 @@ import { getConfig } from '../../../services/api/upload.service'; import { ICMSType } from '../../../context/app/app.interface'; interface LoadFileFormatProps { - stepComponentProps: ()=>{}; + stepComponentProps?: ()=>{}; currentStep: number; handleStepChange: (stepIndex: number, closeStep?: boolean) => void; } @@ -31,7 +26,6 @@ interface LoadFileFormatProps { const LoadFileFormat = (props: LoadFileFormatProps) => { const newMigrationData = useSelector((state:RootState)=>state?.migration?.newMigrationData); - const selectedOrganisation = useSelector((state:RootState)=>state?.authentication?.selectedOrganisation); const migrationData = useSelector((state:RootState)=>state?.migration?.migrationData); const dispatch = useDispatch(); @@ -44,8 +38,6 @@ const LoadFileFormat = (props: LoadFileFormatProps) => { const [fileIcon, setFileIcon] = useState(newMigrationData?.legacy_cms?.selectedFileFormat?.title); const [isError, setIsError] = useState(false); const [error, setError] = useState(''); - - const { projectId = '' } = useParams(); /**** ALL METHODS HERE ****/ @@ -74,51 +66,58 @@ const LoadFileFormat = (props: LoadFileFormatProps) => { }; const handleFileFormat = async() =>{ - const {data} = await getConfig(); + try { + const {data} = await getConfig(); - const cmsType = !isEmptyString(newMigrationData?.legacy_cms?.selectedCms?.parent) ? newMigrationData?.legacy_cms?.selectedCms?.parent : data?.cmsType?.toLowerCase(); - const filePath = data?.localPath?.toLowerCase(); - const fileFormat = getFileExtension(filePath); - if(! isEmptyString(selectedCard?.fileformat_id)){ - setFileIcon(selectedCard?.title); - } - else{ - const { all_cms = [] } = migrationData?.legacyCMSData || {}; - let filteredCmsData:ICMSType[] = all_cms; - if (cmsType) { - filteredCmsData = all_cms?.filter((cms) => cms?.parent?.toLowerCase() === cmsType?.toLowerCase()); - } - - const isFormatValid = filteredCmsData[0]?.allowed_file_formats?.find((format:ICardType)=>{ - const isValid = format?.fileformat_id?.toLowerCase() === fileFormat?.toLowerCase(); - return isValid; - }); - - if(!isFormatValid){ - setIsError(true); - setError('File format does not support, please add the correct file format.'); + const cmsType = !isEmptyString(newMigrationData?.legacy_cms?.selectedCms?.parent) ? newMigrationData?.legacy_cms?.selectedCms?.parent : data?.cmsType?.toLowerCase(); + const filePath = data?.localPath?.toLowerCase(); + const fileFormat = getFileExtension(filePath); + if(! isEmptyString(selectedCard?.fileformat_id)){ + setFileIcon(selectedCard?.title); } + else{ + const { all_cms = [] } = migrationData?.legacyCMSData || {}; + let filteredCmsData:ICMSType[] = all_cms; + if (cmsType) { + filteredCmsData = all_cms?.filter((cms) => cms?.parent?.toLowerCase() === cmsType?.toLowerCase()); + } + + const isFormatValid = filteredCmsData[0]?.allowed_file_formats?.find((format:ICardType)=>{ + const isValid = format?.fileformat_id?.toLowerCase() === fileFormat?.toLowerCase(); + return isValid; + }); + + if(!isFormatValid){ + setIsError(true); + setError('File format does not support, please add the correct file format.'); + } + + const selectedFileFormatObj = { + description: "", + fileformat_id: fileFormat, + group_name: fileFormat, + isactive: true, + title: fileFormat === 'zip' ? fileFormat?.charAt(0)?.toUpperCase() + fileFormat?.slice(1) : fileFormat?.toUpperCase() + } + + const newMigrationDataObj = { + ...newMigrationData, + legacy_cms: { + ...newMigrationData?.legacy_cms, + selectedFileFormat: selectedFileFormatObj + } + }; + + setFileIcon(fileFormat === 'zip' ? fileFormat?.charAt(0).toUpperCase() + fileFormat.slice(1) : fileFormat?.toUpperCase()); + dispatch(updateNewMigrationData(newMigrationDataObj)); - const selectedFileFormatObj = { - description: "", - fileformat_id: fileFormat, - group_name: fileFormat, - isactive: true, - title: fileFormat === 'zip' ? fileFormat?.charAt(0)?.toUpperCase() + fileFormat?.slice(1) : fileFormat?.toUpperCase() } - const newMigrationDataObj = { - ...newMigrationData, - legacy_cms: { - ...newMigrationData?.legacy_cms, - selectedFileFormat: selectedFileFormatObj - } - }; - - setFileIcon(fileFormat === 'zip' ? fileFormat?.charAt(0).toUpperCase() + fileFormat.slice(1) : fileFormat?.toUpperCase()); - dispatch(updateNewMigrationData(newMigrationDataObj)); - + } catch (error) { + return error; + } + } /**** ALL USEEffects HERE ****/ diff --git a/ui/src/components/LegacyCms/Actions/LoadPrefix.tsx b/ui/src/components/LegacyCms/Actions/LoadPrefix.tsx index deb0d31f..12de7d9a 100644 --- a/ui/src/components/LegacyCms/Actions/LoadPrefix.tsx +++ b/ui/src/components/LegacyCms/Actions/LoadPrefix.tsx @@ -21,7 +21,7 @@ import { updateNewMigrationData } from '../../../store/slice/migrationDataSlice' import restrictedKeywords from '../restrictedKeywords.json'; interface LoadSelectCmsProps { - stepComponentProps: ()=>{}; + stepComponentProps?: ()=>{}; currentStep: number; handleStepChange: (stepIndex: number, closeStep?: boolean) => void; } @@ -119,6 +119,8 @@ const LoadPreFix = (props: LoadSelectCmsProps) => { version="v2" error={isError} aria-label='affix' + disabled={newMigrationData?.project_current_step > 1} + isReadOnly={newMigrationData?.project_current_step > 1} /> {isError &&

{errorMessage}

} diff --git a/ui/src/components/LegacyCms/Actions/LoadSelectCms.tsx b/ui/src/components/LegacyCms/Actions/LoadSelectCms.tsx index 44c918a0..dbc11d7e 100644 --- a/ui/src/components/LegacyCms/Actions/LoadSelectCms.tsx +++ b/ui/src/components/LegacyCms/Actions/LoadSelectCms.tsx @@ -26,7 +26,7 @@ import { RootState } from '../../../store'; import { updateNewMigrationData } from '../../../store/slice/migrationDataSlice'; interface LoadSelectCmsProps { - stepComponentProps: ()=>{}; + stepComponentProps?: ()=>{}; currentStep: number; handleStepChange: (stepIndex: number, closeStep?: boolean) => void; } @@ -42,7 +42,6 @@ const LoadSelectCms = (props: LoadSelectCmsProps) => { const [cmsData, setCmsData] = useState([]); const [searchText] = useState(''); //const [cmsFilterStatus, setCmsFilterStatus] = useState({}); - const [cmsFilter] = useState([]); const [cmsType, setCmsType] = useState( newMigrationData?.legacy_cms?.selectedCms || defaultCardType ); @@ -86,6 +85,8 @@ const LoadSelectCms = (props: LoadSelectCmsProps) => { // Filter CMS Data const filterCMSData = async (searchText: string) => { + try { + const { all_cms = [] } = migrationData?.legacyCMSData || {}; setSelectedCard(cmsType); setIsLoading(true); @@ -154,6 +155,11 @@ const LoadSelectCms = (props: LoadSelectCmsProps) => { dispatch(updateNewMigrationData(newMigrationDataObj)); props?.handleStepChange(props?.currentStep); } + + } catch (error) { + return error; + + } }; diff --git a/ui/src/components/LegacyCms/Actions/LoadUploadFile.tsx b/ui/src/components/LegacyCms/Actions/LoadUploadFile.tsx index 803eeb2b..4dc09df7 100644 --- a/ui/src/components/LegacyCms/Actions/LoadUploadFile.tsx +++ b/ui/src/components/LegacyCms/Actions/LoadUploadFile.tsx @@ -12,7 +12,7 @@ import { ICardType } from '../../../components/Common/Card/card.interface'; //import progressbar import ProgressBar from '../../../components/Common/ProgressBar'; interface LoadUploadFileProps { - stepComponentProps: ()=>{}; + stepComponentProps?: ()=>{}; currentStep: number; handleStepChange: (stepIndex: number, closeStep: boolean) => void; } @@ -67,7 +67,7 @@ const LoadUploadFile = (props: LoadUploadFileProps) => { /**** ALL HOOKS HERE ****/ const newMigrationData = useSelector((state:RootState)=>state?.migration?.newMigrationData); - //const selectedOrganisation = useSelector((state:RootState)=>state?.authentication?.selectedOrganisation); + const selectedOrganisation = useSelector((state:RootState)=>state?.authentication?.selectedOrganisation); const migrationData = useSelector((state:RootState)=>state?.migration?.migrationData); const newMigrationDataRef = useRef(newMigrationData); @@ -86,6 +86,7 @@ const LoadUploadFile = (props: LoadUploadFileProps) => { const [showProgress, setShowProgress]= useState(false); const [fileFormat, setFileFormat] = useState(newMigrationData?.legacy_cms?.selectedFileFormat?.fileformat_id); const [processing, setProcessing] = useState(''); + const [reValidate, setReValidate] = useState(newMigrationData?.legacy_cms?.uploadedFile?.reValidate || false); //const [isCancelLoading, setIsCancelLoading] = useState(false); //const [setIsFormatValid] = useState(false); @@ -94,109 +95,117 @@ const LoadUploadFile = (props: LoadUploadFileProps) => { //Handle further action on file is uploaded to server const handleOnFileUploadCompletion = async () => { - setIsValidationAttempted(false); - setValidationMessage(''); - setIsLoading(true); - setProgressPercentage(30); - setShowProgress(true); - setProcessing('Processing...30%'); - - await new Promise(resolve => setTimeout(resolve, 1000)); - - const {data, status} = await fileValidation(projectId); - - - setProgressPercentage(70); - setProcessing('Processing...70%'); - - - await new Promise(resolve => setTimeout(resolve, 1000)); - - - const newMigrationDataObj: INewMigration = { - ...newMigrationDataRef?.current, - legacy_cms: { - ...newMigrationDataRef?.current?.legacy_cms, - uploadedFile: { - name: data?.file_details?.localPath || '', - url: data?.file_details?.localPath, - validation: data?.message, - isValidated: status == 200 ? true : false, - file_details: { - isLocalPath: data?.file_details?.isLocalPath, - cmsType: data?.file_details?.cmsType, - localPath: data?.file_details?.localPath, - awsData: { - awsRegion: data?.file_details?.awsData?.awsRegion, - bucketName: data?.file_details?.awsData?.bucketName, - buketKey: data?.file_details?.awsData?.buketKey + try { + setIsValidationAttempted(false); + setValidationMessage(''); + setIsLoading(true); + setProgressPercentage(30); + setShowProgress(true); + setProcessing('Processing...30%'); + + await new Promise(resolve => setTimeout(resolve, 1000)); + + const {data, status} = await fileValidation(projectId); + + + setProgressPercentage(70); + setProcessing('Processing...70%'); + + + await new Promise(resolve => setTimeout(resolve, 1000)); + + + const newMigrationDataObj: INewMigration = { + ...newMigrationDataRef?.current, + legacy_cms: { + ...newMigrationDataRef?.current?.legacy_cms, + uploadedFile: { + name: data?.file_details?.localPath || '', + url: data?.file_details?.localPath, + validation: data?.message, + isValidated: status == 200 ? true : false, + reValidate: false, + file_details: { + isLocalPath: data?.file_details?.isLocalPath, + cmsType: data?.file_details?.cmsType, + localPath: data?.file_details?.localPath, + awsData: { + awsRegion: data?.file_details?.awsData?.awsRegion, + bucketName: data?.file_details?.awsData?.bucketName, + buketKey: data?.file_details?.awsData?.buketKey + } } + } - } + }; + + dispatch(updateNewMigrationData(newMigrationDataObj)); + + if(status === 200){ + setIsValidated(true); + setValidationMessage('Validation is successful'); + + setIsDisabled(true); + + if(! isEmptyString(newMigrationData?.legacy_cms?.affix) && ! isEmptyString(newMigrationData?.legacy_cms?.selectedCms?.cms_id) && ! isEmptyString(newMigrationData?.legacy_cms?.selectedFileFormat?.fileformat_id)){ + props.handleStepChange(props?.currentStep, true); + } + } - }; - - dispatch(updateNewMigrationData(newMigrationDataObj)); - - if(status === 200){ - setIsValidated(true); - setValidationMessage('Validation is successful'); - - setIsDisabled(true); + else if(status === 500){ + setIsValidated(false); + setValidationMessage('File not found'); + setIsValidationAttempted(true); + setProgressPercentage(100); - if(! isEmptyString(newMigrationData?.legacy_cms?.affix) && ! isEmptyString(newMigrationData?.legacy_cms?.selectedCms?.cms_id) && ! isEmptyString(newMigrationData?.legacy_cms?.selectedFileFormat?.fileformat_id)){ - props.handleStepChange(props?.currentStep, true); } - - } - else if(status === 500){ - setIsValidated(false); - setValidationMessage('File not found'); - setIsValidationAttempted(true); - setProgressPercentage(100); - - } - else if(status === 429){ - setIsValidated(false); - setValidationMessage('Rate limit exceeded. Please wait and try again.'); - setIsValidationAttempted(true); - setProgressPercentage(100); - - } - else{ - setIsValidated(false); - setValidationMessage('Validation is failed'); - setIsValidationAttempted(true); + else if(status === 429){ + setIsValidated(false); + setValidationMessage('Rate limit exceeded. Please wait and try again.'); + setIsValidationAttempted(true); + setProgressPercentage(100); + + } + else{ + setIsValidated(false); + setValidationMessage('Validation is failed'); + setIsValidationAttempted(true); + setProgressPercentage(100); + + } + setProgressPercentage(100); - + setProcessing('Processing...100%'); + + await new Promise(resolve => setTimeout(resolve, 1000)); + + setTimeout(() => { + setShowProgress(false); + setShowMessage(true); + }, 1000); + + setIsLoading(false); + saveStateToLocalStorage({ + isLoading, + isConfigLoading, + isValidated, + validationMessgae, + isDisabled, + cmsType, + fileDetails, + fileExtension, + progressPercentage, + showProgress, + fileFormat, + processing + }, projectId); + + } catch (error) { + return error; + } - - setProgressPercentage(100); - setProcessing('Processing...100%'); - - await new Promise(resolve => setTimeout(resolve, 1000)); - - setTimeout(() => { - setShowProgress(false); - setShowMessage(true); - }, 1000); - - setIsLoading(false); - saveStateToLocalStorage({ - isLoading, - isConfigLoading, - isValidated, - validationMessgae, - isDisabled, - cmsType, - fileDetails, - fileExtension, - progressPercentage, - showProgress, - fileFormat, - processing - }, projectId); + @@ -211,7 +220,8 @@ const LoadUploadFile = (props: LoadUploadFileProps) => { //function to get config details const getConfigDetails = async () =>{ - setIsConfigLoading(true); + try { + setIsConfigLoading(true); const {data, status} = await getConfig(); if (!isEmptyString(fileDetails?.localPath) && data?.localPath !== fileDetails?.localPath) { @@ -235,6 +245,7 @@ const LoadUploadFile = (props: LoadUploadFileProps) => { name: data?.localPath, url: data?.localPath, isValidated: newMigrationData?.legacy_cms?.uploadedFile?.isValidated, + reValidate: false, file_details: { isLocalPath: data?.isLocalPath, cmsType: data?.cmsType, @@ -296,6 +307,12 @@ const LoadUploadFile = (props: LoadUploadFileProps) => { setIsDisabled(true); } setIsConfigLoading(false); + + } catch (error) { + return error; + + } + } @@ -360,7 +377,7 @@ const LoadUploadFile = (props: LoadUploadFileProps) => { ]); useEffect(()=>{ - if(newMigrationData?.legacy_cms?.uploadedFile?.isValidated && ! showProgress) + if(newMigrationData?.legacy_cms?.uploadedFile?.isValidated && ! showProgress && !newMigrationData?.legacy_cms?.uploadedFile?.reValidate) { setIsValidated(true); @@ -368,14 +385,19 @@ const LoadUploadFile = (props: LoadUploadFileProps) => { setValidationMessage('Validation is successful'); setIsDisabled(true); ! isEmptyString(newMigrationData?.legacy_cms?.affix) || ! isEmptyString(newMigrationData?.legacy_cms?.selectedCms?.cms_id) || ! isEmptyString(newMigrationData?.legacy_cms?.selectedFileFormat?.fileformat_id) && props.handleStepChange(props?.currentStep, true); - + + } - + if(newMigrationData?.legacy_cms?.uploadedFile?.reValidate){ + setValidationMessage(''); + } + // else{ // setIsValidated(false); // } - - },[isValidated,newMigrationData]) + setReValidate(newMigrationData?.legacy_cms?.uploadedFile?.reValidate || false); + },[isValidated,newMigrationData]); + useEffect(()=>{ if(newMigrationData?.legacy_cms?.selectedFileFormat?.fileformat_id){ diff --git a/ui/src/components/LegacyCms/StepperSteps.ts b/ui/src/components/LegacyCms/StepperSteps.ts index 423bc0ce..e5a470ef 100644 --- a/ui/src/components/LegacyCms/StepperSteps.ts +++ b/ui/src/components/LegacyCms/StepperSteps.ts @@ -4,11 +4,7 @@ import { StepStatus } from '../Stepper/VerticalStepper/AutoVerticalStepper'; import LoadFileFormat from './Actions/LoadFileFormat'; import LoadSelectCms from './Actions/LoadSelectCms'; import LoadUploadFile from './Actions/LoadUploadFile'; -import FileFormatSummary from './Summary/FileFormatSummary'; -import SelectCmsSummary from './Summary/SelectCmsSummary'; -import UploadFileSummary from './Summary/UploadFileSummary'; import LoadPreFix from './Actions/LoadPrefix'; -import PreFixSummary from './Summary/PreFixSummary'; const getComponentObject = ( step: IStep, @@ -22,7 +18,6 @@ const getComponentObject = ( updatedStep = { ...updatedStep, data: LoadSelectCms, - summery: SelectCmsSummary, status: isCompleted ? StepStatus.COMPLETED : StepStatus.ACTIVE, }; break; @@ -33,7 +28,6 @@ const getComponentObject = ( updatedStep = { ...updatedStep, data: LoadPreFix, - summery: PreFixSummary, status: isCompleted ? StepStatus.COMPLETED : StepStatus.DISABLED, }; break; @@ -44,7 +38,6 @@ const getComponentObject = ( updatedStep = { ...updatedStep, data: LoadFileFormat, - summery: FileFormatSummary, status: isCompleted ? StepStatus.COMPLETED : StepStatus.DISABLED, }; break; @@ -55,7 +48,6 @@ const getComponentObject = ( updatedStep = { ...updatedStep, data: LoadUploadFile, - summery: UploadFileSummary, status: isCompleted ? StepStatus.COMPLETED : StepStatus.DISABLED, }; break; diff --git a/ui/src/components/LegacyCms/Summary/FileFormatSummary.tsx b/ui/src/components/LegacyCms/Summary/FileFormatSummary.tsx deleted file mode 100644 index fe7311c2..00000000 --- a/ui/src/components/LegacyCms/Summary/FileFormatSummary.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { useContext } from 'react'; -import { useDispatch,useSelector } from 'react-redux'; -import StepIcon from '../../../components/Stepper/FlowStepper/StepIcon'; -import { AppContext } from '../../../context/app/app.context'; -import { isEmptyString } from '../../../utilities/functions'; -import { IStep } from '../../../context/app/app.interface'; - -import './summary.scss'; -import DocLink from '../../../components/Common/DocLink/DocLink'; -import { ICardType } from '../../Common/Card/card.interface'; -import { RootState } from '../../../store'; - -interface FileFormatSummaryProps { - stepData: IStep; -} - -const FileFormatSummary = ({ stepData }: FileFormatSummaryProps): JSX.Element => { - - const newMigrationData = useSelector((state:RootState)=>state?.migration?.newMigrationData); - const migrationData = useSelector((state:RootState)=>state?.migration?.migrationData); - const { doc_url = { href: '', title: '' }, allowed_file_formats = [] } = - newMigrationData?.legacy_cms?.selectedCms || {}; - - const { file_format_checkbox_text = '' } = migrationData.legacyCMSData; - - return ( -
-
- -
- {!isEmptyString(newMigrationData?.legacy_cms?.selectedFileFormat?.group_name) && - !isEmptyString(newMigrationData?.legacy_cms?.selectedFileFormat?.title) ? ( -
- {allowed_file_formats.map((format: ICardType, index) => ( - <> - {' '} - - {format?.title} - - {' '} - - ))} -
- ) : ( -
- {stepData?.empty_step_placeholder} -
- )} -
- ); -}; - -export default FileFormatSummary; diff --git a/ui/src/components/LegacyCms/Summary/PreFixSummary.tsx b/ui/src/components/LegacyCms/Summary/PreFixSummary.tsx deleted file mode 100644 index de15eebe..00000000 --- a/ui/src/components/LegacyCms/Summary/PreFixSummary.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { useContext } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import StepIcon from '../../../components/Stepper/FlowStepper/StepIcon'; -import { AppContext } from '../../../context/app/app.context'; -import { isEmptyString } from '../../../utilities/functions'; -import { DEFAULT_URL_TYPE, IStep } from '../../../context/app/app.interface'; -import DocLink from '../../../components/Common/DocLink/DocLink'; - -import './summary.scss'; -import { RootState } from '../../../store'; - -interface PreFixSummaryProps { - stepData: IStep; -} - -const PreFixSummary = (props: PreFixSummaryProps): JSX.Element => { - - const newMigrationData = useSelector((state:RootState)=>state?.migration?.newMigrationData); - const migrationData = useSelector((state:RootState)=>state?.migration?.migrationData); - const { restricted_keyword_link = DEFAULT_URL_TYPE, restricted_keyword_checkbox_text = '' } = - migrationData.legacyCMSData; - - return ( -
- - {!isEmptyString(newMigrationData?.legacy_cms?.affix) ? ( -
-
- {newMigrationData?.legacy_cms?.affix || ''} -
-
- ) : ( -
- {props?.stepData?.empty_step_placeholder} -
- )} -
- ); -}; - -export default PreFixSummary; diff --git a/ui/src/components/LegacyCms/Summary/SelectCmsSummary.tsx b/ui/src/components/LegacyCms/Summary/SelectCmsSummary.tsx deleted file mode 100644 index 9d8d4beb..00000000 --- a/ui/src/components/LegacyCms/Summary/SelectCmsSummary.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { useContext } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import StepIcon from '../../../components/Stepper/FlowStepper/StepIcon'; -import { AppContext } from '../../../context/app/app.context'; -import './summary.scss'; -import { isEmptyString } from '../../../utilities/functions'; -import { IStep } from '../../../context/app/app.interface'; -import { RootState } from '../../../store'; - - -interface SelectCmsSummaryProps { - stepData: IStep; -} - -const SelectCmsSummary = (props: SelectCmsSummaryProps): JSX.Element => { - - const newMigrationData = useSelector((state:RootState)=>state?.migration?.newMigrationData); - - return ( -
- {!isEmptyString(newMigrationData?.legacy_cms?.selectedCms?.group_name) && - !isEmptyString(newMigrationData?.legacy_cms?.selectedCms?.title) ? ( -
- {' '} - - {newMigrationData?.legacy_cms?.selectedCms?.title || ''} - -
- ) : ( -
- {props?.stepData?.empty_step_placeholder} -
- )} -
- ); -}; - -export default SelectCmsSummary; diff --git a/ui/src/components/LegacyCms/Summary/UploadFileSummary.tsx b/ui/src/components/LegacyCms/Summary/UploadFileSummary.tsx deleted file mode 100644 index 11a16035..00000000 --- a/ui/src/components/LegacyCms/Summary/UploadFileSummary.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { useContext, useState } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { AppContext } from '../../../context/app/app.context'; -import './summary.scss'; -import { isEmptyString } from '../../../utilities/functions'; -import { IStep } from '../../../context/app/app.interface'; - -interface UploadFileSummaryProps { - stepComponentProps: any; - stepData: IStep; -} - -import { FileDetails } from '../../../context/app/app.interface'; -import { RootState } from '../../../store'; -import { Paragraph } from '@contentstack/venus-components'; - -interface Props { - fileDetails: FileDetails; -} - -export const FileComponent: React.FC = ({ fileDetails }) => { - - return ( -
- {fileDetails?.isLocalPath ? ( -
- - -
- ) : ( -
-

AWS Region: {fileDetails?.awsData?.awsRegion}

-

Bucket Name: {fileDetails?.awsData?.bucketName}

-

Bucket Key: {fileDetails?.awsData?.buketKey}

-
- )} -
- ); -}; - -const UploadFileSummary = ({ - stepComponentProps, - stepData -}: UploadFileSummaryProps): JSX.Element => { - - const newMigrationData = useSelector((state:RootState)=>state?.migration?.newMigrationData); - const [isLoading, setIsLoading] = useState(false); - return ( -
- {!isEmptyString(newMigrationData?.legacy_cms?.uploadedFile?.name) ? ( -
- -

- - {newMigrationData?.legacy_cms?.uploadedFile?.validation} - - - {!newMigrationData?.legacy_cms?.uploadedFile?.isValidated ? ( -

Please upload the correct file

- ) : ( - <> - )} -
- ) : ( -
- {stepData?.empty_step_placeholder} - {!newMigrationData?.legacy_cms?.uploadedFile?.isValidated ? ( -

Please upload the correct file

- ) : ( - <> - )} -
- )} -
- ); -}; - -export default UploadFileSummary; diff --git a/ui/src/components/LegacyCms/Summary/summary.scss b/ui/src/components/LegacyCms/Summary/summary.scss deleted file mode 100644 index bb9cb6e9..00000000 --- a/ui/src/components/LegacyCms/Summary/summary.scss +++ /dev/null @@ -1,47 +0,0 @@ -@import '../../../scss/variables'; -.summary-title { - //font-family: $font-family-primary; - font-style: normal; - font-weight: 700; - font-size: 13px; - line-height: 135%; - letter-spacing: 0.02em; - //color: $color-font-black; - margin-top: 3px; -} - -.Checkbox input:disabled ~ .Checkbox__label { - opacity: 1; -} -.Checkbox.Checkbox--state-checked { - display: flex; -} -.Checkbox .Checkbox__tick svg { - display: block; -} -.Checkbox.Checkbox--state-checked.Checkbox--state-disabled { - color: #212121; - opacity: 1; -} -.Checkbox-wrapper .Checkbox { - display: flex; -} -.Checkbox-wrapper .Checkbox .Checkbox__label { - margin-top: -2px; -} -.Checkbox input:disabled:checked ~ .Checkbox__label, -.Checkbox input:disabled:indeterminate ~ .Checkbox__label { - color: #212121; - opacity: 1; -} -.affix-container { - display: flex; - background-color: $color-base-white-5; - height: $px-40; - width: $px-435; - align-items: center; - text-align: center; - padding: 0 !important; - border: 1px solid #dde3ee; - border-radius: var(--TermCount, 5px); -} diff --git a/ui/src/components/LegacyCms/index.tsx b/ui/src/components/LegacyCms/index.tsx index 699e27b3..b9a04d8d 100644 --- a/ui/src/components/LegacyCms/index.tsx +++ b/ui/src/components/LegacyCms/index.tsx @@ -44,7 +44,7 @@ type LegacyCMSComponentProps = { handleOnAllStepsComplete:(flag : boolean)=>void; }; -interface AutoVerticalStepperRef { +export interface AutoVerticalStepperRef { handleDynamicStepChange: (stepIndex: number, isLastStep?: boolean) => void; } @@ -282,9 +282,6 @@ const LegacyCMSComponent = forwardRef(({ legacyCMSData, isCompleted, handleOnAll isEdit={!isMigrationLocked} isRequired={true} handleOnAllStepsComplete={handleAllStepsComplete} - stepComponentProps={{ - handleDeleteFile: handleOnClickDeleteUploadedFile - }} /> diff --git a/ui/src/components/LegacyCms/legacyCms.scss b/ui/src/components/LegacyCms/legacyCms.scss index 6edc8410..fe9f8f9f 100644 --- a/ui/src/components/LegacyCms/legacyCms.scss +++ b/ui/src/components/LegacyCms/legacyCms.scss @@ -71,7 +71,7 @@ background-color: $color-base-white-5; min-height: 72px; width: 560px; - margin-left: 20px !important; + margin-left: 10px !important; border: 1px solid $color-brand-secondary-lightest; border-radius: var(--TermCount, 5px); @@ -104,7 +104,7 @@ font-size: 12px; } .validation-cta{ - margin: $space-24 $space-20 0; + margin: $space-16 $space-12 0; line-height: 24px; } .success{ @@ -128,7 +128,7 @@ height: 10px; width: 560px; align-items: center; - margin-left: 20px !important; + margin-left: 10px !important; margin-top: 10px; } .file-icon-group{ diff --git a/ui/src/components/LogScreen/index.tsx b/ui/src/components/LogScreen/index.tsx index 1cde4ac1..b1da010e 100644 --- a/ui/src/components/LogScreen/index.tsx +++ b/ui/src/components/LogScreen/index.tsx @@ -25,7 +25,6 @@ const logStyles: { [key: string]: React.CSSProperties } = { type LogsType = { serverPath: string; - isMigrationStarted?: boolean; sendDataToParent?: (isMigrationStarted: boolean) => void | undefined; } @@ -35,7 +34,6 @@ type LogsType = { */ const LogViewer = ({ serverPath, sendDataToParent }: LogsType) => { const [logs, setLogs] = useState(["Loading logs..."]); - const [isMigrationComplete, setIsMigrationComplete] = useState(false); const newMigrationData = useSelector((state: RootState) => state?.migration?.newMigrationData); @@ -147,7 +145,6 @@ const LogViewer = ({ serverPath, sendDataToParent }: LogsType) => { type: 'success' }); sendDataToParent?.(false); - setIsMigrationComplete(true); const newMigrationDataObj: INewMigration = { ...newMigrationData, @@ -155,7 +152,6 @@ const LogViewer = ({ serverPath, sendDataToParent }: LogsType) => { }; dispatch(updateNewMigrationData((newMigrationDataObj))); - } } catch (error) { console.error('Invalid JSON string', error); diff --git a/ui/src/components/MigrationExecution/index.tsx b/ui/src/components/MigrationExecution/index.tsx index 7a8b4d4c..a8f19803 100644 --- a/ui/src/components/MigrationExecution/index.tsx +++ b/ui/src/components/MigrationExecution/index.tsx @@ -7,7 +7,7 @@ import { getCMSDataFromFile } from '../../cmsData/cmsSelector'; // Redux import { RootState } from '../../store'; -import { setMigrationData, updateMigrationData } from '../../store/slice/migrationDataSlice'; +import { updateMigrationData } from '../../store/slice/migrationDataSlice'; // Utilities import { CS_ENTRIES } from '../../utilities/constants'; @@ -22,8 +22,8 @@ import LogViewer from '../LogScreen'; //stylesheet import './index.scss'; + const MigrationExecution = () => { - //const { migrationData, updateMigrationData, newMigrationData } = useContext(AppContext); const dispatch = useDispatch(); const migrationData = useSelector((state:RootState)=>state?.migration?.migrationData); diff --git a/ui/src/components/MigrationFlowHeader/index.tsx b/ui/src/components/MigrationFlowHeader/index.tsx index 8d80a1f5..df1a773d 100644 --- a/ui/src/components/MigrationFlowHeader/index.tsx +++ b/ui/src/components/MigrationFlowHeader/index.tsx @@ -17,10 +17,11 @@ type MigrationFlowHeaderProps = { isLoading: boolean; isCompleted: boolean; legacyCMSRef: React.MutableRefObject; - projectData:MigrationResponse + projectData:MigrationResponse; + finalExecutionStarted?: boolean; }; -const MigrationFlowHeader = ({projectData, handleOnClick, isLoading }: MigrationFlowHeaderProps) => { +const MigrationFlowHeader = ({projectData, handleOnClick, isLoading, finalExecutionStarted }: MigrationFlowHeaderProps) => { const [projectName, setProjectName] = useState(''); const [currentStep, setCurrentStep] = useState(0); @@ -53,7 +54,7 @@ const MigrationFlowHeader = ({projectData, handleOnClick, isLoading }: Migration } else { stepValue = 'Save and Continue'; } - + return (
@@ -69,8 +70,10 @@ const MigrationFlowHeader = ({projectData, handleOnClick, isLoading }: Migration onClick={handleOnClick} version="v2" aria-label='Save and Continue' - isLoading={isLoading} - disabled={params?.stepId === '4' && !newMigrationData?.test_migration?.isMigrationComplete} + 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 + } > {stepValue} diff --git a/ui/src/components/Stepper/HorizontalStepper/HorizontalStepper.tsx b/ui/src/components/Stepper/HorizontalStepper/HorizontalStepper.tsx index b5581d88..33ed4955 100644 --- a/ui/src/components/Stepper/HorizontalStepper/HorizontalStepper.tsx +++ b/ui/src/components/Stepper/HorizontalStepper/HorizontalStepper.tsx @@ -99,7 +99,7 @@ const HorizontalStepper = forwardRef( const stepIndex = parseInt(stepId || '', 10) - 1; if (!Number.isNaN(stepIndex) && stepIndex >= 0 && stepIndex < steps?.length) { - setShowStep(stepIndex); + !newMigrationData?.isprojectMapped && setShowStep(stepIndex); setStepsCompleted(prev => { const updatedStepsCompleted = [...prev]; for (let i = 0; i < stepIndex; i++) { @@ -171,7 +171,7 @@ const HorizontalStepper = forwardRef( }; const setTabStep = (idx: number) => { - if (stepsCompleted?.includes(idx) || stepsCompleted?.length === idx) { + if ((stepsCompleted?.includes(idx) || stepsCompleted?.length === idx) && !newMigrationData?.isprojectMapped) { setShowStep(idx); const url = `/projects/${projectId}/migration/steps/${idx + 1}`; navigate(url, { replace: true }); @@ -190,8 +190,10 @@ const HorizontalStepper = forwardRef( !stepsCompleted.includes(idx) && idx !== showStep && !stepsCompleted?.includes(idx - 1) ? 'disableEvents' : ''; + const disableStep = newMigrationData?.isprojectMapped && stepsCompleted.includes(idx) && idx !== showStep ? 'disableEvents' + : ''; - const completeDisable = stepsCompleted?.includes(idx) && idx < stepIndex - 1 ? 'disableEvents' : ''; + const completeDisable = stepsCompleted?.includes(idx) && idx < stepIndex - 1 && newMigrationData?.test_migration?.isMigrationStarted ? 'disableEvents' : ''; const disableMapper = stepsCompleted?.includes(idx) && idx === 2 && newMigrationData?.test_migration?.isMigrationStarted && !newMigrationData?.test_migration?.isMigrationComplete ? 'disableEvents' : ''; @@ -199,7 +201,7 @@ const HorizontalStepper = forwardRef(
handleTabStep(idx)} >
diff --git a/ui/src/components/Stepper/VerticalStepper/AutoVerticalStepper.scss b/ui/src/components/Stepper/VerticalStepper/AutoVerticalStepper.scss index 6e0dc967..5b9c65cc 100644 --- a/ui/src/components/Stepper/VerticalStepper/AutoVerticalStepper.scss +++ b/ui/src/components/Stepper/VerticalStepper/AutoVerticalStepper.scss @@ -83,11 +83,11 @@ } .action-summary-wrapper { - padding: $space-4 $space-24; - align-items: center; - border: 1px solid $color-base-gray-40; - border-radius: var(--TermCount, 5px); - background: $color-brand-white-base; + padding: $space-4 $space-24; + align-items: center; + //border: 1px solid $color-base-gray-40; + //border-radius: var(--TermCount, 5px); + //background: $color-brand-white-base; } .action-content-wrapper { diff --git a/ui/src/components/Stepper/VerticalStepper/AutoVerticalStepper.tsx b/ui/src/components/Stepper/VerticalStepper/AutoVerticalStepper.tsx index 27eab15d..50e3be90 100644 --- a/ui/src/components/Stepper/VerticalStepper/AutoVerticalStepper.tsx +++ b/ui/src/components/Stepper/VerticalStepper/AutoVerticalStepper.tsx @@ -1,6 +1,7 @@ -import React, { useEffect, useImperativeHandle, useMemo, useState } from 'react'; +import React, { useImperativeHandle, useMemo, useState } from 'react'; import './AutoVerticalStepper.scss'; -import { Heading, Paragraph } from '@contentstack/venus-components'; +import { Heading } from '@contentstack/venus-components'; +import { IStep } from '../../../context/app/app.interface'; export enum StepStatus { @@ -10,10 +11,10 @@ export enum StepStatus { } type AutoVerticalStepperProps = { - steps: any[]; + steps: IStep[]; className?: string; description?: string; - stepComponentProps?: any; + stepComponentProps?: ()=>{}; isEdit: boolean; isRequired:boolean; handleOnAllStepsComplete: (flag: boolean) => void; @@ -34,7 +35,6 @@ const AutoVerticalStepper = React.forwardRef< const { steps, className = '', - description='', stepComponentProps, isEdit = false, handleOnAllStepsComplete = () => { @@ -42,18 +42,13 @@ const AutoVerticalStepper = React.forwardRef< } } = props; - const [stepStatus, setStepStatus] = useState(steps?.map((s: any) => s.status)); - - useEffect(() => { - if (!stepComponentProps?.step?.step_id && !stepComponentProps?.connector?.group_name) { - setStepStatus(steps?.map((s: any) => s.status)); - } - }, [stepComponentProps?.step?.step_id, stepComponentProps?.connector?.group_name]); + const [stepStatus, setStepStatus] = useState(steps?.map((s: IStep) => s.status)); + const handleStepChange = (stepIndex: number, closeStep = false) => { if (closeStep) { - const data = stepStatus.map((s: any, i: number) => { + const data = stepStatus.map((s: string | undefined, i: number) => { if (i === stepIndex) { return StepStatus.COMPLETED; } @@ -63,7 +58,7 @@ const AutoVerticalStepper = React.forwardRef< handleOnAllStepsComplete(true); } else { - const data: string[] = stepStatus.map((s: any, i: number) => { + const data: string[] = stepStatus.map((s: string | undefined, i: number) => { if (i <= stepIndex) { return StepStatus.COMPLETED; } else if (i === stepIndex + 1) { @@ -76,8 +71,8 @@ const AutoVerticalStepper = React.forwardRef< } }; - const StepperStepTitleCreator: (data: any,isRequired:boolean) => JSX.Element = (data: any, isRequired:boolean) => { - const showSpan = data?.title == 'Orgnization' ? (read only) : '' + const StepperStepTitleCreator: (data: IStep,isRequired:boolean) => JSX.Element = (data: IStep, isRequired:boolean) => { + return ( <>
@@ -103,7 +98,7 @@ const AutoVerticalStepper = React.forwardRef< }; const goToStep = (stepIndex: number) => { - const data: string[] = stepStatus.map((s: any, i: number) => { + const data: string[] = stepStatus.map((s: string | undefined, i: number) => { if (s === StepStatus.ACTIVE && i !== stepIndex) { return StepStatus.DISABLED; } @@ -113,19 +108,22 @@ const AutoVerticalStepper = React.forwardRef< if (i === stepIndex) { return StepStatus.ACTIVE; } - return s; + return s !== undefined ? s : '' ; }); setStepStatus(data); }; - const summaryActivateStep = (e: any) => { - const index = e.currentTarget.getAttribute('data-step-index'); + const summaryActivateStep = (e: React.MouseEvent) => { + const index = e?.currentTarget?.getAttribute('data-step-index'); + if(! index) return; handleOnAllStepsComplete(false); + if (!index) return; + const stepIndex = parseInt(index, 10); if (isEdit) { - goToStep(+index); + goToStep(stepIndex); } else { - handleStepChange(index - 1); + handleStepChange(stepIndex - 1); } }; @@ -136,35 +134,13 @@ const AutoVerticalStepper = React.forwardRef< })); return useMemo(() => { - const stepClassNameObject: any = { - [StepStatus.ACTIVE]: 'active', - [StepStatus.COMPLETED]: 'completed', - [StepStatus.DISABLED]: 'disabled', - [`${StepStatus.ACTIVE}__${StepStatus.COMPLETED}`]: 'active__to__completed', - [`${StepStatus.ACTIVE}__${StepStatus.ACTIVE}`]: 'active__to__active', - [`${StepStatus.ACTIVE}__${StepStatus.DISABLED}`]: 'active__to__disabled', - [`${StepStatus.DISABLED}__${StepStatus.COMPLETED}`]: 'disabled__to__completed', - [`${StepStatus.DISABLED}__${StepStatus.ACTIVE}`]: 'disabled__to__active', - [`${StepStatus.DISABLED}__${StepStatus.DISABLED}`]: 'disabled__to__disabled', - [`${StepStatus.COMPLETED}__${StepStatus.COMPLETED}`]: 'completed__to__completed', - [`${StepStatus.COMPLETED}__${StepStatus.ACTIVE}`]: 'completed__to__active', - [`${StepStatus.COMPLETED}__${StepStatus.DISABLED}`]: 'completed__to__disabled' - }; - const getStepStatus = (idx: number) => { - return stepStatus[idx]; - }; return (
{props?.description &&
{props?.description}
}
    - {steps?.map((step: any, index: number) => { - - let stepClassName = stepClassNameObject[getStepStatus(index)]; - if (step?.lock) stepClassName = 'completed'; - const getGridientClass = - stepClassNameObject[`${getStepStatus(index)}__${getStepStatus(index + 1)}`]; - + {steps?.map((step: IStep, index: number) => { + return (
  1. { const [data, setData] = useState({}); const [isLoading, setIsLoading] = useState(newMigrationData?.isprojectMapped); const [isStackLoading, setIsStackLoading] = useState(false); - const [isMigrationStarted, setIsMigrationStarted] = useState(false); + const [disableTestMigration, setdisableTestMigration] = useState(false); + const [disableCreateStack, setDisableCreateStack] = useState(false); const { projectId = '' } = useParams(); const dispatch = useDispatch(); + const { create_stack_cta: createStackCta, subtitle, start_migration_cta: startMigrationCta } = data + /********** ALL USEEFFECT HERE *************/ useEffect(() => { //check if offline CMS data field is set to true, if then read data from cms data file. @@ -54,27 +57,43 @@ 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); + } + + if (newMigrationData?.testStacks.find((stack) => stack?.stackUid === newMigrationData?.test_migration?.stack_api_key)?.isMigrated === true) { + setdisableTestMigration(true); + } + }, [newMigrationData]); + + // Method to create test stack const handleCreateTestStack = async () => { setIsStackLoading(true); //get org plan details - 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') || ''; + 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 max_stack_limit = orgDetails?.data?.organization?.plan?.features[stacks_details_key]?.max_limit; + const max_stack_limit = orgDetails?.data?.organization?.plan?.features[stacks_details_key]?.max_limit; - const stackData = await getAllStacksInOrg(selectedOrganisation?.value, ''); // org id will always be there + const stackData = await getAllStacksInOrg(selectedOrganisation?.value, ''); // org id will always be there - const stack_count = stackData?.data?.stacks?.length; - - if (stack_count >= max_stack_limit) { - // setIsLoading(false); - Notification({ - notificationContent: { text: 'You have reached the maximum limit of stacks for your organization' }, - type: 'warning' - }); - return; + const stack_count = stackData?.data?.stacks?.length; + + if (stack_count >= max_stack_limit) { + // setIsLoading(false); + Notification({ + notificationContent: { text: 'You have reached the maximum limit of stacks for your organization' }, + type: 'warning' + }); + return; + } + } catch (error) { + return error; } const data = { @@ -82,59 +101,73 @@ const TestMigration = () => { description: 'test migration stack', master_locale: newMigrationData?.destination_stack?.selectedStack?.master_locale }; - - const res = await createTestStack( - newMigrationData?.destination_stack?.selectedOrg?.value, - projectId, - data - ); - - if (res?.status === 200) { - setIsStackLoading(false); - Notification({ - notificationContent: { text: 'Test Stack created successfully' }, - notificationProps: { - position: 'bottom-center', - hideProgressBar: true - }, - type: 'success' - }); - - - 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 } - }; - - dispatch(updateNewMigrationData((newMigrationDataObj))); + + try { + const res = await createTestStack( + newMigrationData?.destination_stack?.selectedOrg?.value, + projectId, + data + ); + + if (res?.status === 200) { + setIsStackLoading(false); + setDisableCreateStack(true); + setdisableTestMigration(false) + Notification({ + notificationContent: { text: 'Test Stack created successfully' }, + notificationProps: { + position: 'bottom-center', + hideProgressBar: true + }, + type: 'success' + }); + + + 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 } + }; + dispatch(updateNewMigrationData((newMigrationDataObj))); + } + } catch (err) { + return err; } } + // Method to start test migration const handleTestMigration = async () => { - const testRes = await createTestMigration( - newMigrationData?.destination_stack?.selectedOrg?.value, - projectId - ); - - console.log("testRes", testRes); - - - if (testRes?.status === 200) { - handleMigrationState(true); - Notification({ - notificationContent: { text: 'Test Migration started' }, - notificationProps: { - position: 'bottom-center', - hideProgressBar: false - }, - type: 'message' - }); + try { + const testRes = await createTestMigration( + newMigrationData?.destination_stack?.selectedOrg?.value, + projectId + ); + + if (testRes?.status === 200) { + handleMigrationState(true); + Notification({ + notificationContent: { text: 'Test Migration started' }, + notificationProps: { + position: 'bottom-center', + hideProgressBar: false + }, + type: 'message' + }); + } + } catch (error) { + return error; } + + const newMigrationDataObj: INewMigration = { + ...newMigrationData, + testStacks: [...newMigrationData?.testStacks ?? [], {isMigrated: true, stackUid: newMigrationData?.test_migration?.stack_api_key} ] + }; + dispatch(updateNewMigrationData((newMigrationDataObj))); } // Function to update the parent state const handleMigrationState = (newState: boolean) => { - setIsMigrationStarted(newState); + setDisableCreateStack(newState); + setdisableTestMigration(!newState) const newMigrationDataObj: INewMigration = { ...newMigrationData, @@ -151,17 +184,17 @@ const TestMigration = () => { :
    -

    Test Migration is a step where some content types are migrated in a test stack for review. A user can verify the stack and data. If the data is migrated properly then it can proceed with the final Migration Execution process.

    + {subtitle &&

    {subtitle}

    } - {newMigrationData?.test_migration?.stack_api_key && + {newMigrationData?.test_migration?.stack_api_key && { className="ml-8" onClick={handleTestMigration} version="v2" - disabled={isMigrationStarted} + disabled={disableTestMigration} > - Start Test Migration + {startMigrationCta?.title}
    diff --git a/ui/src/components/TestMigration/testMigration.interface.ts b/ui/src/components/TestMigration/testMigration.interface.ts index cfc3427f..e73bb62a 100644 --- a/ui/src/components/TestMigration/testMigration.interface.ts +++ b/ui/src/components/TestMigration/testMigration.interface.ts @@ -1,6 +1,7 @@ export interface MigrationType { - cta?: CTA; + create_stack_cta?: CTA; subtitle?: string; + start_migration_cta?: CTA; } export interface CTA { diff --git a/ui/src/context/app/app.interface.ts b/ui/src/context/app/app.interface.ts index f540b53a..e66aaade 100644 --- a/ui/src/context/app/app.interface.ts +++ b/ui/src/context/app/app.interface.ts @@ -12,7 +12,7 @@ export interface ICTA { } export type DataProps = { - stepComponentProps: ()=>{}; + stepComponentProps?: ()=>{}; currentStep: number; handleStepChange: (step: number) => void; }; @@ -66,6 +66,7 @@ export interface IFile { validation?: string; file_details?: FileDetails; isValidated: boolean; + reValidate: boolean } export interface ICMSType extends ICardType { @@ -85,6 +86,9 @@ export interface IStep { data?: (props:DataProps) => JSX.Element; summery?: (props: SummaryProps) => JSX.Element; empty_step_placeholder?: string; + ifReadonly?:boolean; + isRequired?: boolean; + titleNote?: string; } export interface IURLType { @@ -132,7 +136,7 @@ export interface MigrationExecution { width: string; } export interface IMigrationExecution { - migration_information: MigrationExecution[]; + migration_information?: MigrationExecution[]; } interface ActionCta { @@ -185,6 +189,8 @@ export interface INewMigration { test_migration: ITestMigration; isprojectMapped: boolean; stackDetails: IDropDown; + migration_execution: IMigrationExecutionStep; + project_current_step: number; } export interface TestStacks { @@ -220,6 +226,10 @@ export interface ITestMigration { isMigrationStarted: boolean; isMigrationComplete: boolean; } + +export interface IMigrationExecutionStep { + migrationStarted: boolean; +} export interface IAppContext { authToken: string; setAuthToken: (token: string) => void; @@ -286,7 +296,8 @@ export const DEFAULT_FILE: IFile = { buketKey: '' } }, - isValidated: false + isValidated: false, + reValidate: false, }; export const DEFAULT_CMS_TYPE: ICMSType = { @@ -334,6 +345,10 @@ export const DEFAULT_TEST_MIGRATION: ITestMigration = { isMigrationComplete: false }; +export const DEFAULT_MIGRATION_EXECUTION_STEP: IMigrationExecutionStep = { + migrationStarted: false +} + export const DEFAULT_NEW_MIGRATION: INewMigration = { mapperKeys: {}, legacy_cms: DEFAULT_LEGACY_CMS, @@ -342,7 +357,9 @@ export const DEFAULT_NEW_MIGRATION: INewMigration = { test_migration: DEFAULT_TEST_MIGRATION, isprojectMapped: false, stackDetails: DEFAULT_DROPDOWN, - testStacks: [] + testStacks: [], + migration_execution: DEFAULT_MIGRATION_EXECUTION_STEP, + project_current_step: 0, }; export const DEFAULT_URL_TYPE: IURLType = { diff --git a/ui/src/pages/Migration/index.tsx b/ui/src/pages/Migration/index.tsx index e1d46747..ab6b1478 100644 --- a/ui/src/pages/Migration/index.tsx +++ b/ui/src/pages/Migration/index.tsx @@ -9,7 +9,7 @@ import { RootState } from '../../store'; import { updateMigrationData, updateNewMigrationData } from '../../store/slice/migrationDataSlice'; // Services -import { getMigrationData, updateCurrentStepData, updateLegacyCMSData, updateDestinationStack, updateAffixData, fileformatConfirmation, updateFileFormatData, affixConfirmation, updateStackDetails, getExistingContentTypes, getExistingGlobalFields } from '../../services/api/migration.service'; +import { getMigrationData, updateCurrentStepData, updateLegacyCMSData, updateDestinationStack, updateAffixData, fileformatConfirmation, updateFileFormatData, affixConfirmation, updateStackDetails, getExistingContentTypes, getExistingGlobalFields, startMigration } from '../../services/api/migration.service'; import { getCMSDataFromFile } from '../../cmsData/cmsSelector'; // Utilities @@ -50,7 +50,8 @@ const Migration = () => { const [curreentStepIndex, setCurrentStepIndex] = useState(0); const [isCompleted, setIsCompleted] = useState(false); const [isProjectMapper, setIsProjectMapper] = useState(false); - const [isModalOpen, setIsModalOpen] = useState(false); + + const [migrationStarted, setMigrationStarted] = useState(false); const params: Params = useParams(); const { projectId = '' } = useParams(); @@ -80,19 +81,33 @@ const Migration = () => { // Function to get exisiting content types list const fetchExistingContentTypes = async () => { - const { data, status } = await getExistingContentTypes(projectId); - if (status === 201) { - return data?.contentTypes; + try { + const { data, status } = await getExistingContentTypes(projectId); + if (status === 201) { + return data?.contentTypes; + } + + } catch (error) { + return error; + } + }; // Function to get exisiting global fields list const fetchExistingGlobalFields = async () => { - const { data, status } = await getExistingGlobalFields(projectId); + try { + const { data, status } = await getExistingGlobalFields(projectId); - if (status === 201) { - return data?.globalFields; + if (status === 201) { + return data?.globalFields; + } + + } catch (error) { + return error; + } + } const fetchData = async () => { @@ -167,6 +182,7 @@ const Migration = () => { isNewStack: false }; + selectedStackData = { label: projectData?.stackDetails?.label, value: projectData?.stackDetails?.value, @@ -198,7 +214,8 @@ const Migration = () => { }, isLocalPath: projectData?.legacy_cms?.is_localPath }, - isValidated: projectData?.legacy_cms?.is_fileValid + isValidated: projectData?.legacy_cms?.is_fileValid, + reValidate: newMigrationData?.legacy_cms?.uploadedFile?.reValidate }, isFileFormatCheckboxChecked: true, isRestictedKeywordCheckboxChecked: true, @@ -222,8 +239,12 @@ const Migration = () => { isMigrationStarted: false, isMigrationComplete: false }, + migration_execution: { + migrationStarted: migrationStarted + }, stackDetails: projectData?.stackDetails, - testStacks: projectData?.test_stacks + testStacks: projectData?.test_stacks, + project_current_step: projectData?.current_step, }; dispatch(updateNewMigrationData(projectMapper)); @@ -255,7 +276,7 @@ const Migration = () => { title:'Destination Stack' }, { - data: , + data: , id:'3', title:'Content Mapping' }, @@ -394,14 +415,14 @@ const Migration = () => { }; const handleOnClickContentMapper = async (event: MouseEvent) => { - setIsModalOpen(true); + // setIsModalOpen(true); if(newMigrationData?.content_mapping?.isDropDownChanged) { return cbModal({ component: (props: ModalObj) => ( { @@ -442,6 +463,37 @@ const Migration = () => { handleStepChange(4); } + const handleOnClickMigrationExecution = async () => { + setIsLoading(true); + + try { + const migrationRes = await startMigration(newMigrationData?.destination_stack?.selectedOrg?.value, projectId); + + console.log("migrationRes", migrationRes); + + if (migrationRes?.status === 200) { + setIsLoading(false); + setMigrationStarted(true) + Notification({ + notificationContent: { text: 'Migration Execution process started' }, + notificationProps: { + position: 'bottom-center', + hideProgressBar: false + }, + type: 'message' + }); + } + } catch (error) { + return error; + } + + const newMigrationDataObj: INewMigration = { + ...newMigrationData, + migration_execution: {migrationStarted: true} + }; + dispatch(updateNewMigrationData((newMigrationDataObj))); + } + const changeDropdownState = () =>{ const newMigrationDataObj: INewMigration = { ...newMigrationData, @@ -455,7 +507,8 @@ const Migration = () => { handleOnClickLegacyCms, handleOnClickDestinationStack, handleOnClickContentMapper, - handleOnClickTestMigration + handleOnClickTestMigration, + handleOnClickMigrationExecution ]; return ( diff --git a/ui/src/pages/Projects/index.tsx b/ui/src/pages/Projects/index.tsx index 76979842..62cbc3d4 100644 --- a/ui/src/pages/Projects/index.tsx +++ b/ui/src/pages/Projects/index.tsx @@ -62,12 +62,18 @@ const Projects = () => { const fetchProjects = async () => { setLoadStatus(true); if (selectedOrganisation?.value) { - const { data, status } = await getAllProjects(selectedOrganisation?.value || ''); //org id will always present - if (status === 200) { - setLoadStatus(false); - setProjects(data); - setAllProjects(data); + try { + const { data, status } = await getAllProjects(selectedOrganisation?.value || ''); //org id will always present + if (status === 200) { + setLoadStatus(false); + setProjects(data); + setAllProjects(data); + } + + } catch (error) { + return error } + } }; diff --git a/ui/src/services/api/migration.service.ts b/ui/src/services/api/migration.service.ts index f13ee155..ea164008 100644 --- a/ui/src/services/api/migration.service.ts +++ b/ui/src/services/api/migration.service.ts @@ -311,4 +311,13 @@ export const createTestMigration = async (orgId: string, projectId: string) => { } catch (error) { return error; } +}; + +export const startMigration = async (orgId: string, projectId: string) => { + try { + return await postCall( + `${API_VERSION}/migration/start/${orgId}/${projectId}`, {}, options); + } catch (error) { + return error; + } }; \ No newline at end of file diff --git a/ui/src/store/slice/authSlice.tsx b/ui/src/store/slice/authSlice.tsx index c202891b..6542abe6 100644 --- a/ui/src/store/slice/authSlice.tsx +++ b/ui/src/store/slice/authSlice.tsx @@ -1,9 +1,8 @@ //redux dependencies import { createAsyncThunk, createSlice} from '@reduxjs/toolkit'; -import { useNavigate } from 'react-router'; //initial values from app interface -import { DEFAULT_ORGANISATION,DEFAULT_USER, IDropDown, DEFAULT_DROPDOWN } from './../../context/app/app.interface'; +import { DEFAULT_USER, IDropDown, DEFAULT_DROPDOWN } from './../../context/app/app.interface'; //utilities import { clearLocalStorage, getDataFromLocalStorage, isEmptyString, validateArray } from '../../utilities/functions'; @@ -81,7 +80,7 @@ const authSlice = createSlice({ }, setUser : (state, action) => { state.user = { - ...state.user, + ...state?.user, ...action?.payload, }; }, @@ -103,10 +102,10 @@ const authSlice = createSlice({ }, extraReducers: (builder) => { - builder.addCase(getUserDetails?.fulfilled,(state, action:any)=>{ + builder.addCase(getUserDetails?.fulfilled,(state, action)=>{ state.user = { - ...state.user, - ...action.payload.user, + ...state?.user, + ...action?.payload?.user, }; state.organisationsList = action?.payload?.organisationsList; state.selectedOrganisation = action?.payload?.selectedOrganisation ; diff --git a/upload-api/.env b/upload-api/.env index 13675414..2640e916 100644 --- a/upload-api/.env +++ b/upload-api/.env @@ -1,3 +1,3 @@ PORT=4002 -NODE_BACKEND_API =http://localhost:5001 +NODE_BACKEND_API =http://localhost:5000 diff --git a/upload-api/src/controllers/sitecore/index.ts b/upload-api/src/controllers/sitecore/index.ts index 69358c10..c78bdd37 100644 --- a/upload-api/src/controllers/sitecore/index.ts +++ b/upload-api/src/controllers/sitecore/index.ts @@ -32,6 +32,7 @@ const createSitecoreMapper = async (filePath: string = "", projectId: string | s fieldMapping.contentTypes.push(element); } } + // console.log("🚀 ~ createSitecoreMapper ~ fieldMapping:", fieldMapping) const config = { method: 'post', maxBodyLength: Infinity,