diff --git a/.talismanrc b/.talismanrc index 6e5cfee23..0252a7bb7 100644 --- a/.talismanrc +++ b/.talismanrc @@ -1,7 +1,13 @@ -fileignoreconfig: -- filename: .github/workflows/secrets-scan.yml - ignore_detectors: - - filecontent -- filename: remove-broken-imports.js - checksum: d9d3ca95b2f4df855c8811c73b5714e80b31e5e84b46affa0cb514dcfcc145bf +ignoreconfig: + - filename: .github/workflows/secrets-scan.yml + ignore_detectors: + - filecontent + + - filename: remove-broken-imports.js + checksum: d9d3ca95b2f4df855c8811c73b5714e80b31e5e84b46affa0cb514dcfcc145bf + + - filename: ui/package-lock.json + ignore_detectors: + - Base64Detector + version: "1.0" diff --git a/api/package.json b/api/package.json index 780df4f7a..384cf8420 100644 --- a/api/package.json +++ b/api/package.json @@ -48,7 +48,7 @@ "p-limit": "^6.2.0", "path-to-regexp": "^8.2.0", "router": "^2.0.0", - "shelljs": "^0.8.5", + "shelljs": "^0.9.0", "socket.io": "^4.7.5", "uuid": "^9.0.1", "winston": "^3.11.0" diff --git a/api/src/constants/index.ts b/api/src/constants/index.ts index 79e68beb7..c64b4136e 100644 --- a/api/src/constants/index.ts +++ b/api/src/constants/index.ts @@ -177,7 +177,7 @@ export const LOCALE_MAPPER: any = { masterLocale: { "en-us": "en", }, - locales:{fr: "fr-fr",} + locales: { fr: "fr-fr", } }; export const CHUNK_SIZE = 1048576; @@ -272,3 +272,13 @@ export const MIGRATION_DATA_CONFIG = { EXPORT_INFO_FILE: "export-info.json", }; + +export const GET_AUDIT_DATA = { + MIGRATION: "migration-v2", + API_DIR: "api", + MIGRATION_DATA_DIR: "migration-data", + LOGS_DIR: "logs", + AUDIT_DIR: "audit", + AUDIT_REPORT: "audit-report", + FILTERALL: "all", +} diff --git a/api/src/controllers/migration.controller.ts b/api/src/controllers/migration.controller.ts index fbbc75996..fabb36795 100644 --- a/api/src/controllers/migration.controller.ts +++ b/api/src/controllers/migration.controller.ts @@ -49,10 +49,13 @@ const deleteTestStack = async (req: Request, res: Response): Promise => { const resp = await migrationService.deleteTestStack(req); res.status(200).json(resp); }; - +const getAuditData = async (req: Request, res: Response): Promise => { + const resp = await migrationService.getAuditData(req); + res.status(resp?.status).json(resp); +}; const getLogs = async (req: Request, res: Response): Promise => { const resp = await migrationService.getLogs(req); - res.status(200).json(resp); + res.status(resp?.status).json(resp); }; const saveLocales = async (req: Request, res: Response): Promise => { @@ -72,5 +75,6 @@ export const migrationController = { startMigration, getLogs, saveLocales, - saveMappedLocales + saveMappedLocales, + getAuditData }; diff --git a/api/src/routes/migration.routes.ts b/api/src/routes/migration.routes.ts index 6bcf58783..db10c99e3 100644 --- a/api/src/routes/migration.routes.ts +++ b/api/src/routes/migration.routes.ts @@ -20,7 +20,10 @@ router.post( "/test-stack/:orgId/:projectId", asyncRouter(migrationController.startTestMigration) ); - +router.get( + "/get_audit_data/:orgId/:projectId/:stackId/:moduleName/:skip/:limit/:startIndex/:stopIndex/:searchText/:filter", + asyncRouter(migrationController.getAuditData) +) /** * Route for deleting a test stack. * @route POST /test-stack/:projectId diff --git a/api/src/services/migration.service.ts b/api/src/services/migration.service.ts index 88b5be057..f1aaf1d02 100644 --- a/api/src/services/migration.service.ts +++ b/api/src/services/migration.service.ts @@ -7,6 +7,7 @@ import https from "../utils/https.utils.js"; import { LoginServiceType } from "../models/types.js"; import getAuthtoken from "../utils/auth.utils.js"; import logger from "../utils/logger.js"; +import { GET_AUDIT_DATA } from "../constants/index.js"; import { HTTP_TEXTS, HTTP_CODES, @@ -118,12 +119,10 @@ const createTestStack = async (req: Request): Promise => { } return { data: { - data: res.data, - url: `${ - config.CS_URL[token_payload?.region as keyof typeof config.CS_URL] - }/stack/${res.data.stack.api_key}/dashboard`, + data: res?.data, + url: `${config?.CS_URL[token_payload?.region as keyof typeof config.CS_URL]}/stack/${res?.data?.stack?.api_key}/dashboard`, }, - status: res.status, + status: res?.status, }; } catch (error: any) { logger.error( @@ -135,13 +134,158 @@ const createTestStack = async (req: Request): Promise => { ) ); + throw new ExceptionFunction( + error?.message || HTTP_TEXTS?.INTERNAL_ERROR, + error?.statusCode || error?.status || HTTP_CODES.SERVER_ERROR + ); + } +}; + +const getAuditData = async (req: Request): Promise => { + const projectId = path?.basename(req?.params?.projectId); + const stackId = path?.basename(req?.params?.stackId); + const moduleName = path.basename(req?.params?.moduleName); + const limit = parseInt(req?.params?.limit); + const startIndex = parseInt(req?.params?.startIndex); + const stopIndex = startIndex + limit; + const searchText = req?.params?.searchText; + const filter = req?.params?.filter; + const srcFunc = "getAuditData"; + + if (projectId?.includes('..') || stackId?.includes('..') || moduleName?.includes('..')) { + throw new BadRequestError("Invalid projectId, stackId, or moduleName"); + } + + try { + const mainPath = process?.cwd()?.split?.(GET_AUDIT_DATA?.MIGRATION)?.[0]; + const logsDir = path.join(mainPath, GET_AUDIT_DATA?.MIGRATION, GET_AUDIT_DATA?.API_DIR, GET_AUDIT_DATA?.MIGRATION_DATA_DIR); + + const stackFolders = fs.readdirSync(logsDir); + + const stackFolder = stackFolders?.find(folder => folder?.startsWith?.(stackId)); + if (!stackFolder) { + throw new BadRequestError("Migration data not found for this stack"); + } + + const auditLogPath = path?.resolve(logsDir, stackFolder, GET_AUDIT_DATA?.LOGS_DIR, GET_AUDIT_DATA?.AUDIT_DIR, GET_AUDIT_DATA?.AUDIT_REPORT); + if (!fs.existsSync(auditLogPath)) { + throw new BadRequestError("Audit log path not found"); + } + + + // Read and parse the JSON file for the module + const filePath = path?.resolve(auditLogPath, `${moduleName}.json`); + let fileData; + if (fs?.existsSync(filePath)) { + const fileContent = await fsPromises.readFile(filePath, 'utf8'); + try { + if (typeof fileContent === 'string') { + fileData = JSON?.parse(fileContent); + } + } catch (error) { + logger.error(`Error parsing JSON from file ${filePath}:`, error); + throw new BadRequestError('Invalid JSON format in audit file'); + } + } + + if (!fileData) { + throw new BadRequestError(`No audit data found for module: ${moduleName}`); + } + let transformedData = transformAndFlattenData(fileData); + if (filter != GET_AUDIT_DATA?.FILTERALL) { + const filters = filter?.split("-"); + moduleName === 'Entries_Select_feild' ? transformedData = transformedData?.filter((log) => { + return filters?.some((filter) => { + return ( + log?.display_type?.toLowerCase()?.includes(filter?.toLowerCase()) + ); + }); + }) : transformedData = transformedData?.filter((log) => { + return filters?.some((filter) => { + return ( + log?.data_type?.toLowerCase()?.includes(filter?.toLowerCase()) + ); + }); + }); + + } + if (searchText && searchText !== null && searchText !== "null") { + transformedData = transformedData?.filter((item: any) => { + return Object?.values(item)?.some(value => + value && + typeof value === 'string' && + value?.toLowerCase?.()?.includes(searchText?.toLowerCase()) + ); + }); + } + const paginatedData = transformedData?.slice?.(startIndex, stopIndex); + + return { + data: paginatedData, + totalCount: transformedData?.length, + status: HTTP_CODES?.OK + }; + + } catch (error: any) { + logger.error( + getLogMessage( + srcFunc, + `Error getting audit log data for module: ${moduleName}`, + error + ) + ); throw new ExceptionFunction( error?.message || HTTP_TEXTS.INTERNAL_ERROR, error?.statusCode || error?.status || HTTP_CODES.SERVER_ERROR ); } }; +/** + * Transforms and flattens nested data structure into an array of items + * with sequential tuid values + */ +const transformAndFlattenData = (data: any): Array<{ [key: string]: any, id: number }> => { + try { + const flattenedItems: Array<{ [key: string]: any }> = []; + // Handle the data based on its structure + if (Array.isArray(data)) { + // If data is already an array, use it directly + data?.forEach((item, index) => { + flattenedItems?.push({ + ...item ?? {}, + uid: item?.uid || `item-${index}` + }); + }); + } else if (typeof data === 'object' && data !== null) { + Object?.entries?.(data)?.forEach(([key, value]) => { + if (Array.isArray(value)) { + value?.forEach((item, index) => { + flattenedItems?.push({ + ...item ?? {}, + parentKey: key, + uid: item?.uid || `${key}-${index}` + }); + }); + } else if (typeof value === 'object' && value !== null) { + flattenedItems?.push({ + ...value, + key, + uid: (value as any)?.uid || key + }); + } + }); + } + + return flattenedItems?.map((item, index) => ({ + ...item ?? {}, + id: index + 1 + })); + } catch (error) { + console.error('Error transforming data:', error); + return []; + } +}; /** * Deletes a test stack. * @param req - The request object. @@ -782,7 +926,6 @@ const startMigration = async (req: Request): Promise => { } }; - const getLogs = async (req: Request): Promise => { const projectId = req?.params?.projectId ? path?.basename(req.params.projectId) : ""; const stackId = req?.params?.stackId ? path?.basename(req.params.stackId) : ""; @@ -791,9 +934,7 @@ const getLogs = async (req: Request): Promise => { const stopIndex = startIndex + limit; const searchText = req?.params?.searchText ?? null; const filter = req?.params?.filter ?? "all"; - const srcFunc = "getLogs"; - if ( !projectId || !stackId || @@ -802,40 +943,44 @@ const getLogs = async (req: Request): Promise => { ) { throw new BadRequestError("Invalid projectId or stackId"); } - try { const mainPath = process?.cwd()?.split("migration-v2")?.[0]; if (!mainPath) { throw new BadRequestError("Invalid application path"); } - const logsDir = path?.join(mainPath, "migration-v2", "api", "logs"); const loggerPath = path?.join(logsDir, projectId, `${stackId}.log`); const absolutePath = path?.resolve(loggerPath); - if (!absolutePath?.startsWith(logsDir)) { throw new BadRequestError("Access to this file is not allowed."); } - if (fs.existsSync(absolutePath)) { + let index = 0; const logs = await fs.promises.readFile(absolutePath, "utf8"); let logEntries = logs ?.split("\n") ?.map((line) => { try { - return line ? JSON?.parse(line) : null; + const parsedLine = JSON?.parse(line) + parsedLine['id'] = index; + ++index; + return parsedLine ? parsedLine : null; } catch (error) { return null; } }) ?.filter?.((entry) => entry !== null); - if (!logEntries?.length) { return { logs: [], total: 0 }; } - + const filterOptions = Array?.from(new Set(logEntries?.map((log) => log?.level))); + const auditStartIndex = logEntries?.findIndex?.(log => log?.message?.includes("Starting audit process")); + const auditEndIndex = logEntries?.findIndex?.(log => log?.message?.includes("Audit process completed")); + logEntries = [ + ...logEntries.slice(0, auditStartIndex), + ...logEntries.slice(auditEndIndex + 1) + ] logEntries = logEntries?.slice?.(1, logEntries?.length - 2); - if (filter !== "all") { const filters = filter?.split("-") ?? []; logEntries = logEntries?.filter((log) => { @@ -846,17 +991,17 @@ const getLogs = async (req: Request): Promise => { }); }); } - if (searchText && searchText !== "null") { logEntries = logEntries?.filter?.((log) => matchesSearchText(log, searchText) ); } - const paginatedLogs = logEntries?.slice?.(startIndex, stopIndex) ?? []; return { logs: paginatedLogs, total: logEntries?.length ?? 0, + filterOptions: filterOptions, + status: HTTP_CODES?.OK }; } else { logger.error(getLogMessage(srcFunc, HTTP_TEXTS.LOGS_NOT_FOUND)); @@ -971,4 +1116,5 @@ export const migrationService = { getLogs, createSourceLocales, updateLocaleMapper, + getAuditData }; diff --git a/ui/package-lock.json b/ui/package-lock.json index 86db83adc..29d38584e 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "dependencies": { "@contentstack/json-rte-serializer": "^2.0.12", - "@contentstack/venus-components": "^2.2.5", + "@contentstack/venus-components": "^3.0.0", "@reduxjs/toolkit": "^2.4.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^13.4.0", @@ -1944,35 +1944,36 @@ } }, "node_modules/@contentstack/venus-components": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/@contentstack/venus-components/-/venus-components-2.2.5.tgz", - "integrity": "sha512-SLL8fixWaiHadmbU8J9foJYqc9XoOZr1+hd5BppkHYLuXT7DGuaXMhMpT8xXGUCRMipp8TJykw6YPBpg1+n2Qg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@contentstack/venus-components/-/venus-components-3.0.0.tgz", + "integrity": "sha512-BsXmDl15iSKOwNZHI/3F5wA1TrDwleu300I3BOMyOIXjQEsXCV6I6kZ2C1nb1T+dL0bbmbsoaz5g5QncyeqIFg==", "dependencies": { "@emotion/css": "^11.1.3", "@testing-library/react-hooks": "^8.0.1", "@tippyjs/react": "^4.1.0", "array-move": "^3.0.1", - "axios": "^1.6.1", + "axios": "^1.8.4", + "block-elements": "^1.2.0", "browser-detect": "^0.2.28", "cache": "^3.0.0", "clean": "^4.0.2", - "collapse-whitespace": "^1.1.7", "detect-browser": "^5.3.0", - "dompurify": "^2.4.1", + "dompurify": "^3.2.4", "is-hotkey": "^0.2.0", "is-url": "^1.2.4", "jest-fetch-mock": "^3.0.3", + "js-beautify": "1.15.1", "lodash": "^4.17.21", "moment": "^2.29.1", "path-browserify": "^1.0.1", "pretty": "^2.0.0", "prism-react-renderer": "^1.1.1", - "prismjs": "^1.26.0", + "prismjs": "^1.30.0", "re-resizable": "^6.9.0", "react-ace": "^9.1.4", "react-beautiful-dnd": "^13.0.0", "react-color": "^2.19.3", - "react-datepicker": "^4.25.0", + "react-datepicker": "npm:@contentstack/react-datepicker@^4.25.5", "react-dnd-11": "npm:react-dnd@^11.1.3", "react-dnd-html5-backend": "^11.1.3", "react-dropzone": "^11.2.4", @@ -1981,15 +1982,15 @@ "react-mentions": "^4.4.7", "react-modal": "^3.11.2", "react-redux": "^7.2.1", - "react-select": "^3.2.0", + "react-select": "npm:@contentstack/react-select@^3.2.5", "react-select-async-paginate": "^0.4.0", "react-simple-code-editor": "^0.11.0", "react-sortable-hoc": "^1.11.0", - "react-style-tag": "^2.0.3", + "react-style-tag": "^3.0.1", "react-table": "^7.5.0", - "react-test-renderer": "^16.13.1", + "react-test-renderer": "^18.3.1", "react-tiktok": "^1.0.0", - "react-toastify": "6.1.0", + "react-toastify": "npm:@contentstack/react-toastify@^6.1.5", "react-treebeard": "^3.2.4", "react-virtualized": "^9.22.5", "react-virtualized-auto-sizer": "1.0.5", @@ -2007,24 +2008,59 @@ "storybook-addon-whats-new": "^1.0.3", "systemjs": "^6.10.2", "user-event": "^4.0.0", - "uuid": "^8.3.2" + "uuid": "^8.3.2", + "void-elements": "^3.1.0" + }, + "engines": { + "node": ">=18.0.0 <19.0.0", + "npm": ">=8.19.3" }, "peerDependencies": { - "react": "^16.8.6 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.6 || ^17.0.0 || ^18.0.0" + "react": "^16.8.6 || ^17 || ^18 || ^19", + "react-dom": "^16.8.6 || ^17 || ^18 || ^19" } }, "node_modules/@contentstack/venus-components/node_modules/@emotion/cache": { - "version": "10.0.29", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz", - "integrity": "sha512-fU2VtSVlHiF27empSbxi1O2JFdNWZO+2NFHfwO0pxgTep6Xa3uGb+3pVKfLww2l/IBGLNEZl5Xf/++A4wAYDYQ==", + "version": "10.0.9", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.9.tgz", + "integrity": "sha512-f7MblpE2xoimC4fCMZ9pivmsIn7hyWRIvY75owMDi8pdOSeh+w5tH3r4hBJv/LLrwiMM7cTQURqTPcYoL5pWnw==", "dependencies": { - "@emotion/sheet": "0.9.4", - "@emotion/stylis": "0.8.5", + "@emotion/sheet": "0.9.2", + "@emotion/stylis": "0.8.3", + "@emotion/utils": "0.11.1", + "@emotion/weak-memoize": "0.2.2" + } + }, + "node_modules/@contentstack/venus-components/node_modules/@emotion/core": { + "version": "10.0.9", + "resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.0.9.tgz", + "integrity": "sha512-v5a77dV+uWGoy9w6R3MXZG01lqHcXgoy/jGmJqPDGhPjmpWg26LWXAphYZxpZffFUwDUlDdYDiX5HtaKphvJnQ==", + "dependencies": { + "@emotion/cache": "^10.0.9", + "@emotion/css": "^10.0.9", + "@emotion/serialize": "^0.11.6", + "@emotion/sheet": "0.9.2", + "@emotion/utils": "0.11.1" + }, + "peerDependencies": { + "react": ">=16.3.0" + } + }, + "node_modules/@contentstack/venus-components/node_modules/@emotion/core/node_modules/@emotion/css": { + "version": "10.0.27", + "resolved": "https://registry.npmjs.org/@emotion/css/-/css-10.0.27.tgz", + "integrity": "sha512-6wZjsvYeBhyZQYNrGoR5yPMYbMBNEnanDrqmsqS1mzDm1cOTu12shvl2j4QHNS36UaTE0USIJawCH9C8oW34Zw==", + "dependencies": { + "@emotion/serialize": "^0.11.15", "@emotion/utils": "0.11.3", - "@emotion/weak-memoize": "0.2.5" + "babel-plugin-emotion": "^10.0.27" } }, + "node_modules/@contentstack/venus-components/node_modules/@emotion/core/node_modules/@emotion/css/node_modules/@emotion/utils": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.3.tgz", + "integrity": "sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==" + }, "node_modules/@contentstack/venus-components/node_modules/@emotion/hash": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", @@ -2047,15 +2083,25 @@ "csstype": "^2.5.7" } }, + "node_modules/@contentstack/venus-components/node_modules/@emotion/serialize/node_modules/@emotion/utils": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.3.tgz", + "integrity": "sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==" + }, "node_modules/@contentstack/venus-components/node_modules/@emotion/serialize/node_modules/csstype": { "version": "2.6.21", "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz", "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==" }, "node_modules/@contentstack/venus-components/node_modules/@emotion/sheet": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-0.9.4.tgz", - "integrity": "sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA==" + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-0.9.2.tgz", + "integrity": "sha512-pVBLzIbC/QCHDKJF2E82V2H/W/B004mDFQZiyo/MSR+VC4pV5JLG0TF/zgQDFvP3fZL/5RTPGEmXlYJBMUuJ+A==" + }, + "node_modules/@contentstack/venus-components/node_modules/@emotion/stylis": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.3.tgz", + "integrity": "sha512-M3nMfJ6ndJMYloSIbYEBq6G3eqoYD41BpDOxreE8j0cb4fzz/5qvmqU9Mb2hzsXcCnIlGlWhS03PCzVGvTAe0Q==" }, "node_modules/@contentstack/venus-components/node_modules/@emotion/unitless": { "version": "0.7.5", @@ -2063,14 +2109,14 @@ "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" }, "node_modules/@contentstack/venus-components/node_modules/@emotion/utils": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.3.tgz", - "integrity": "sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==" + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.1.tgz", + "integrity": "sha512-8M3VN0hetwhsJ8dH8VkVy7xo5/1VoBsDOk/T4SJOeXwTO1c4uIqVNx2qyecLFnnUWD5vvUqHQ1gASSeUN6zcTg==" }, "node_modules/@contentstack/venus-components/node_modules/@emotion/weak-memoize": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz", - "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.2.tgz", + "integrity": "sha512-n/VQ4mbfr81aqkx/XmVicOLjviMuy02eenSdJY33SVA7S2J42EU0P1H0mOogfYedb3wXA0d/LVtBrgTSm04WEA==" }, "node_modules/@contentstack/venus-components/node_modules/@testing-library/react-hooks": { "version": "8.0.1", @@ -2122,6 +2168,31 @@ "url": "https://opencollective.com/immer" } }, + "node_modules/@contentstack/venus-components/node_modules/js-beautify": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.1.tgz", + "integrity": "sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==", + "dependencies": { + "config-chain": "^1.1.13", + "editorconfig": "^1.0.4", + "glob": "^10.3.3", + "js-cookie": "^3.0.5", + "nopt": "^7.2.0" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@contentstack/venus-components/node_modules/memoize-one": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.0.0.tgz", + "integrity": "sha512-7g0+ejkOaI9w5x6LvQwmj68kUj6rxROywPSCqmclG/HBacmFnZqhVscQ8kovkn9FBCNJmOz6SY42+jnvZzDWdw==" + }, "node_modules/@contentstack/venus-components/node_modules/react-ace": { "version": "9.5.0", "resolved": "https://registry.npmjs.org/react-ace/-/react-ace-9.5.0.tgz", @@ -2168,18 +2239,18 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, "node_modules/@contentstack/venus-components/node_modules/react-select": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/react-select/-/react-select-3.2.0.tgz", - "integrity": "sha512-B/q3TnCZXEKItO0fFN/I0tWOX3WJvi/X2wtdffmwSQVRwg5BpValScTO1vdic9AxlUgmeSzib2hAZAwIUQUZGQ==", + "name": "@contentstack/react-select", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@contentstack/react-select/-/react-select-3.2.5.tgz", + "integrity": "sha512-Q/IE5+BGh4X1/1vFqEaNXdB42JYpewdTs2OyFKeEZ9FemgUWMe/O783cyhrDbXfor4s6J41o9/SLmkzfDD7MjQ==", "dependencies": { - "@babel/runtime": "^7.4.4", - "@emotion/cache": "^10.0.9", - "@emotion/core": "^10.0.9", - "@emotion/css": "^10.0.9", - "memoize-one": "^5.0.0", - "prop-types": "^15.6.0", - "react-input-autosize": "^3.0.0", - "react-transition-group": "^4.3.0" + "@babel/runtime": "7.4.4", + "@emotion/cache": "10.0.9", + "@emotion/core": "10.0.9", + "@emotion/css": "10.0.9", + "memoize-one": "5.0.0", + "react-input-autosize": "3.0.0", + "react-transition-group": "4.3.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0", @@ -2234,14 +2305,22 @@ "object-assign": "^4.1.1" } }, + "node_modules/@contentstack/venus-components/node_modules/react-select/node_modules/@babel/runtime": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.4.tgz", + "integrity": "sha512-w0+uT71b6Yi7i5SE0co4NioIpSYS6lLiXvCzWzGSKvpK5vdQtCbICHMj+gbAKAOtxiV6HsVh/MBdaF9EQ6faSg==", + "dependencies": { + "regenerator-runtime": "^0.13.2" + } + }, "node_modules/@contentstack/venus-components/node_modules/react-select/node_modules/@emotion/css": { - "version": "10.0.27", - "resolved": "https://registry.npmjs.org/@emotion/css/-/css-10.0.27.tgz", - "integrity": "sha512-6wZjsvYeBhyZQYNrGoR5yPMYbMBNEnanDrqmsqS1mzDm1cOTu12shvl2j4QHNS36UaTE0USIJawCH9C8oW34Zw==", + "version": "10.0.9", + "resolved": "https://registry.npmjs.org/@emotion/css/-/css-10.0.9.tgz", + "integrity": "sha512-jtHhUSWw+L7yxYgNtC+KJ3Ory90/jiAtpG1qT+gTQQ/RR5AMiigs9/lDHu/vnwljaq2S48FoKb/FZZMlJcC4bw==", "dependencies": { - "@emotion/serialize": "^0.11.15", - "@emotion/utils": "0.11.3", - "babel-plugin-emotion": "^10.0.27" + "@emotion/serialize": "^0.11.6", + "@emotion/utils": "0.11.1", + "babel-plugin-emotion": "^10.0.9" } }, "node_modules/@contentstack/venus-components/node_modules/react-select/node_modules/react-input-autosize": { @@ -2270,30 +2349,6 @@ "react-dom": "^0.14.0 || ^15.0.0 || ^16.0.0" } }, - "node_modules/@contentstack/venus-components/node_modules/react-style-tag": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/react-style-tag/-/react-style-tag-2.0.5.tgz", - "integrity": "sha512-9v2ghHCKrUObmr++BwQ/V1HYmJDvhDWoCHylRwJFWZ+3E8rNX0KDWnigfSEvF00nIUaiR6lFt58at9KnXot2SA==", - "dependencies": { - "cssbeautify": "^0.3.1", - "react-parm": "^1.0.0", - "stylis": "^3.5.0" - }, - "peerDependencies": { - "prop-types": "^15.6.1", - "react": "^15.3.0 || ^16.0.0 || ^17.0.0", - "react-dom": "^15.3.0 || ^16.0.0 || ^17.0.0" - } - }, - "node_modules/@contentstack/venus-components/node_modules/react-style-tag/node_modules/react-parm": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/react-parm/-/react-parm-1.1.1.tgz", - "integrity": "sha512-fei1OHIwwYWTdAeiJSCz8VHCIIeZTI65fmtFRtoejwYMOQGgJcim0K1RTz69kTnvwviQI+a9vU3t0MZR52hSLQ==", - "peerDependencies": { - "react": "^15.3.0 || ^16.0.0", - "react-dom": "^15.3.0 || ^16.0.0" - } - }, "node_modules/@contentstack/venus-components/node_modules/react-test-renderer": { "version": "16.14.0", "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.14.0.tgz", @@ -2308,6 +2363,21 @@ "react": "^16.14.0" } }, + "node_modules/@contentstack/venus-components/node_modules/react-transition-group": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.3.0.tgz", + "integrity": "sha512-1qRV1ZuVSdxPlPf4O8t7inxUGpdyO5zG9IoNfJxSO0ImU2A1YWkEQvFPuIPZmMLkg5hYs7vv5mMOyfgSkvAwvw==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/@contentstack/venus-components/node_modules/react-virtualized-auto-sizer": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.5.tgz", @@ -2345,6 +2415,11 @@ "@babel/runtime": "^7.9.2" } }, + "node_modules/@contentstack/venus-components/node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, "node_modules/@contentstack/venus-components/node_modules/scheduler": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", @@ -2386,11 +2461,6 @@ } } }, - "node_modules/@contentstack/venus-components/node_modules/stylis": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.4.tgz", - "integrity": "sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q==" - }, "node_modules/@csstools/normalize.css": { "version": "12.1.1", "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-12.1.1.tgz", @@ -7070,15 +7140,6 @@ "node": ">=4" } }, - "node_modules/collapse-whitespace": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/collapse-whitespace/-/collapse-whitespace-1.1.7.tgz", - "integrity": "sha512-24up1hbQSsnaDSGHPOvGQT84vmxvG0QUrI8tguiQpo9I5irrnypCKwddXindXMyXhoTe+9V6LYj3aFIhTQ4UCg==", - "dependencies": { - "block-elements": "^1.0.0", - "void-elements": "^2.0.1" - } - }, "node_modules/collect-v8-coverage": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", @@ -7627,17 +7688,6 @@ "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==" }, - "node_modules/cssbeautify": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cssbeautify/-/cssbeautify-0.3.1.tgz", - "integrity": "sha512-ljnSOCOiMbklF+dwPbpooyB78foId02vUrTDogWzu6ca2DCNB7Kc/BHEGBnYOlUYtwXvSW0mWTwaiO2pwFIoRg==", - "bin": { - "cssbeautify": "bin/cssbeautify" - }, - "engines": { - "node": "*" - } - }, "node_modules/cssdb": { "version": "7.11.2", "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.11.2.tgz", @@ -8393,9 +8443,12 @@ } }, "node_modules/dompurify": { - "version": "2.5.8", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.8.tgz", - "integrity": "sha512-o1vSNgrmYMQObbSSvF/1brBYEQPHhV1+gsmrusO7/GXtp1T9rCS8cXFqVxK/9crT1jA6Ccv+5MTSjBNqr7Sovw==" + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz", + "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } }, "node_modules/domutils": { "version": "3.2.2", @@ -15823,15 +15876,15 @@ } }, "node_modules/react-datepicker": { - "version": "4.25.0", - "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.25.0.tgz", - "integrity": "sha512-zB7CSi44SJ0sqo8hUQ3BF1saE/knn7u25qEMTO1CQGofY1VAKahO8k9drZtp0cfW1DMfoYLR3uSY1/uMvbEzbg==", + "name": "@contentstack/react-datepicker", + "version": "4.25.5", + "resolved": "https://registry.npmjs.org/@contentstack/react-datepicker/-/react-datepicker-4.25.5.tgz", + "integrity": "sha512-kPeg7kDqZkNCn0Vr01bRlrz9VxLxpgnSBeV49r0rNjSQpLMWBTzVw0ZdFafEzxjix54ltFiKH9MiUixglUIPBQ==", "dependencies": { "@popperjs/core": "^2.11.8", "classnames": "^2.2.6", "date-fns": "^2.30.0", "prop-types": "^15.7.2", - "react-onclickoutside": "^6.13.0", "react-popper": "^2.3.0" }, "peerDependencies": { @@ -16132,19 +16185,6 @@ "react-dom": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18 || ^19" } }, - "node_modules/react-onclickoutside": { - "version": "6.13.2", - "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.13.2.tgz", - "integrity": "sha512-h6Hbf1c8b7tIYY4u90mDdBLY4+AGQVMFtIE89HgC0DtVCh/JfKl477gYqUtGLmjZBKK3MJxomP/lFiLbz4sq9A==", - "funding": { - "type": "individual", - "url": "https://github.com/Pomax/react-onclickoutside/blob/master/FUNDING.md" - }, - "peerDependencies": { - "react": "^15.5.x || ^16.x || ^17.x || ^18.x", - "react-dom": "^15.5.x || ^16.x || ^17.x || ^18.x" - } - }, "node_modules/react-popper": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", @@ -16338,6 +16378,18 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/react-style-tag": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/react-style-tag/-/react-style-tag-3.0.1.tgz", + "integrity": "sha512-XeiiTQtG04uT+Lrtr0YvA5I2qcbUzHMCtS40iH3/bzP98xhPUm1qzRUOoOXgMfyJRPwHh36MH8ebj1C7nuaolg==", + "dependencies": { + "stylis": "^4.1.1" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/react-table": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/react-table/-/react-table-7.8.0.tgz", @@ -16364,9 +16416,10 @@ } }, "node_modules/react-toastify": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-6.1.0.tgz", - "integrity": "sha512-Ne+wIoO9A+jZlaqGqgeuXDC/DQfqTuJdyoc7G5SsuCHsr8mNRx7W26417YKtHRH0LcnFFd5ii76tGnmm0cMlLg==", + "name": "@contentstack/react-toastify", + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/@contentstack/react-toastify/-/react-toastify-6.1.5.tgz", + "integrity": "sha512-ibY34/1kdHytblf7PRI7QBSPaUV31bVP/WPVJMqTdpjDX1AEz+ZczbFBb9l3modcBMPBos+bDgbMsuOkiK00dw==", "dependencies": { "clsx": "^1.1.1", "prop-types": "^15.7.2", @@ -19540,9 +19593,9 @@ } }, "node_modules/void-elements": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", - "integrity": "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", "engines": { "node": ">=0.10.0" } diff --git a/ui/package.json b/ui/package.json index 54a60f985..b88d8da30 100644 --- a/ui/package.json +++ b/ui/package.json @@ -4,7 +4,7 @@ "private": true, "dependencies": { "@contentstack/json-rte-serializer": "^2.0.12", - "@contentstack/venus-components": "^2.2.5", + "@contentstack/venus-components": "^3.0.0", "@reduxjs/toolkit": "^2.4.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^13.4.0", diff --git a/ui/src/cmsData/setting.json b/ui/src/cmsData/setting.json index 692a38c09..4b02e001d 100644 --- a/ui/src/cmsData/setting.json +++ b/ui/src/cmsData/setting.json @@ -28,6 +28,9 @@ "execution_logs": { "title": "Execution Logs" }, + "audit_logs": { + "title": "Audit Logs" + }, "tags": [], "locale": "en-us", "uid": "blt96cda740c3157d20", @@ -37,4 +40,4 @@ "updated_at": "2024-03-11T06:03:23.420Z", "_version": 3, "_in_progress": false -} +} \ No newline at end of file diff --git a/ui/src/components/AuditFilterModal/auditlog.interface.ts b/ui/src/components/AuditFilterModal/auditlog.interface.ts new file mode 100644 index 000000000..43cad689d --- /dev/null +++ b/ui/src/components/AuditFilterModal/auditlog.interface.ts @@ -0,0 +1,11 @@ +import { FilterOption } from "../AuditLogs/auditLogs.interface"; + +export type AuditFilterModalProps = { + isOpen: boolean; + closeModal: () => void; + updateValue: (params: { value: FilterOption; isChecked: boolean }) => void; + onApply: () => void; + selectedLevels: FilterOption[]; + setFilterValue: (levels: FilterOption[]) => void; + selectedFileType: string; +}; diff --git a/ui/src/components/AuditFilterModal/index.scss b/ui/src/components/AuditFilterModal/index.scss new file mode 100644 index 000000000..646860218 --- /dev/null +++ b/ui/src/components/AuditFilterModal/index.scss @@ -0,0 +1,85 @@ +@import '../../scss/variables'; + +.tableFilterModalStories { + position: absolute; + z-index: 1000; + width: 350px; + background-color: $color-brand-white-base; + border-radius: 12px; + box-shadow: 0 8px 24px $color-base-black-31; + display: flex; + flex-direction: column; + max-height: 350px; + overflow: hidden; + font-family: 'Inter', sans-serif; +} + +.tableFilterModalStories__header { + padding: 16px 16px; + color: #3d3f4c; + border-bottom: 1px solid #e5e7eb; + display: flex; + align-items: center; + justify-content: space-between; +} + +.tableFilterModalStories__suggestion-item { + padding: 8px 16px; +} + +.Checkbox { + display: flex; + align-items: center; + cursor: pointer; +} + +.Checkbox .Checkbox__tick svg { + display: block !important; +} + +.Checkbox .Checkbox__label { + font-size: $size-font-medium; + line-height: $line-height-reset; + color: #3d3f4c; + text-transform: capitalize; +} + +.tableFilterModalStories__footer { + display: flex; + justify-content: flex-end; + align-items: center; + gap: 16px; + padding: 16px 8px; + border-top: 1px solid #e5e7eb; + background: #fff; + font-size: $size-font-medium; + line-height: $line-height-reset; +} + +.close-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; +} + +.close-btn:hover { + box-shadow: 0 4px 12px $color-base-black-31; + transform: scale(1.05); + border-radius: 8px; + background-color: $color-base-black-31 ; + cursor: pointer; + transition: + box-shadow 0.2s ease, + transform 0.2s ease; +} + +.close-btn:active { + transform: scale(0.95); +} + +.text-size { + font-size: $size-font-medium; + line-height: $line-height-reset; +} \ No newline at end of file diff --git a/ui/src/components/AuditFilterModal/index.tsx b/ui/src/components/AuditFilterModal/index.tsx new file mode 100644 index 000000000..bb5b67f74 --- /dev/null +++ b/ui/src/components/AuditFilterModal/index.tsx @@ -0,0 +1,121 @@ +import React, { useEffect, useRef } from 'react'; +import { + Button, + ButtonGroup, + Checkbox, + Icon +} from '@contentstack/venus-components'; +import './index.scss'; +import { FilterOption } from '../AuditLogs/auditLogs.interface'; +import { auditLogsConstants } from '../../utilities/constants'; +import { AuditFilterModalProps } from './auditlog.interface'; + +const AuditFilterModal = ({ + isOpen, + closeModal, + updateValue, + onApply, + selectedLevels, + setFilterValue, + selectedFileType +}: AuditFilterModalProps) => { + const modalRef = useRef(null); + + const getFilterOptions = (): FilterOption[] => { + if (!selectedFileType) return []; + + if (selectedFileType?.includes?.('content-types') || selectedFileType?.includes?.('global-fields')) { + return [ + { label: 'global_field', value: 'global_field' }, + { label: 'reference', value: 'reference' }, + { label: 'group', value: 'group' }, + ]; + } + + if (selectedFileType?.includes?.('Entries')) { + return [{ label: 'dropdown', value: 'dropdown' }]; + } + + return []; + }; + + const filterOptions = getFilterOptions(); + + const clearAll = () => { + setFilterValue([]); + }; + + useEffect(() => { + if (isOpen && modalRef?.current) { + const modalElement = modalRef?.current; + const rect = modalElement?.getBoundingClientRect(); + const viewportHeight = window?.innerHeight; + const viewportWidth = window?.innerWidth; + + if (rect.bottom > viewportHeight) { + modalElement?.classList?.add('position-bottom'); + } + + if (rect.right > viewportWidth) { + modalElement?.classList?.add('position-right'); + } + } + }, [isOpen]); + + if (!isOpen) return
; + + return ( +
+
+ + {selectedFileType?.includes?.('Entries') ? 'Display Type' : 'Field Type'} + +
+ +
+
+ +
+ {filterOptions?.length > 0 ? ( + filterOptions.map((item, index) => ( +
+
+ v?.value === item?.value)} + onChange={(e: React.ChangeEvent) => + updateValue({ value: item, isChecked: e?.target?.checked }) + } + version="v2" + label={item?.label} + className="text-size" + /> +
+
+ )) + ) : ( +
+ {auditLogsConstants?.filterModal?.noFilterAvailabe} +
+ )} +
+ +
+ + + + +
+
+ ); +}; + +export default AuditFilterModal; diff --git a/ui/src/components/AuditLogs/auditLogs.interface.ts b/ui/src/components/AuditLogs/auditLogs.interface.ts new file mode 100644 index 000000000..6d45dd56e --- /dev/null +++ b/ui/src/components/AuditLogs/auditLogs.interface.ts @@ -0,0 +1,54 @@ +// src/components/AuditLogs/interfaces.ts + + +export interface FileData { + fileData: any; + [key: string]: any; +} + +export interface StackOption { + label: string; + value: string; + name?: string; + stackName?: string; + stackUid?: string; + [key: string]: any; +} + +export interface FileOption { + label: string; + value: string; +} + +export interface TableDataItem { + uid?: string; + name?: string; + display_name?: string; + display_type?: string; + data_type?: string; + missingRefs?: string[] | string; + treeStr?: string; + fixStatus?: string; + missingCTSelectFieldValues?: string; + parentKey?: string; + ct_uid?: string; + [key: string]: any; +} +export type DropdownOption = { + label: string; + value: string; +}; + +export type FilterOption = { + label: string; + value: string; +}; +export interface TableColumn { + Header: string; + accessor: (data: TableDataItem) => JSX.Element; + addToColumnSelector: boolean; + disableSortBy: boolean; + disableResizing: boolean; + canDragDrop: boolean; + width: number; +} \ No newline at end of file diff --git a/ui/src/components/AuditLogs/index.scss b/ui/src/components/AuditLogs/index.scss new file mode 100644 index 000000000..244c81764 --- /dev/null +++ b/ui/src/components/AuditLogs/index.scss @@ -0,0 +1,36 @@ +.select-container { + display: flex; + + .select-wrapper { + display: flex; + margin-left: 1rem; + + } +} + +.Search-input-show { + margin-bottom: 4px; +} + +.PageLayout--primary .PageLayout__leftSidebar+.PageLayout__content .PageLayout__body { + width: calc(100% - 15rem); +} + +.PageLayout__body { + .table-height { + .Table { + height: calc(100vh - 12.75rem) !important; + } + + .Table.TableWithPaginated { + .Table__body { + height: calc(100vh - 20rem) !important; + } + } + } +} + +.TablePagination { + position: sticky; + bottom: 0px; +} \ No newline at end of file diff --git a/ui/src/components/AuditLogs/index.tsx b/ui/src/components/AuditLogs/index.tsx new file mode 100644 index 000000000..c2b9e8149 --- /dev/null +++ b/ui/src/components/AuditLogs/index.tsx @@ -0,0 +1,466 @@ +import React, { useState, useEffect } from 'react'; +import { useSelector } from 'react-redux'; +import { useParams } from 'react-router'; +import { Button, EmptyState, InfiniteScrollTable, Select } from '@contentstack/venus-components'; +// Redux +import { RootState } from '../../store'; +// Service +import { getAuditData } from '../../services/api/project.service'; +// Interfaces +import { + StackOption, + FileOption, + TableDataItem, + FilterOption +} from './auditLogs.interface'; +import './index.scss'; +import { auditLogsConstants } from '../../utilities/constants'; +import AuditFilterModal from '../AuditFilterModal'; +const AuditLogs: React.FC = () => { + const params = useParams<{ projectId?: string }>(); + const [loading, setLoading] = useState(false); + const [selectedStack, setSelectedStack] = useState(null); + const [stackOptions, setStackOptions] = useState([]); + const [selectedFile, setSelectedFile] = useState(null); + const [fileOptions, setFileOptions] = useState([]); + const [searchText, setSearchText] = useState(''); + const [tableData, setTableData] = useState([]); + const [totalCounts, setTotalCounts] = useState(0); + const [tableKey, setTableKey] = useState(0); + const [filterOption, setFilterOption] = useState('all'); + const [filterValue, setFilterValue] = useState([]); + const [isCursorInside, setIsCursorInside] = useState(true); + const [isFilterApplied, setIsFilterApplied] = useState(false); + const [isFilterDropdownOpen, setIsFilterDropdownOpen] = useState(false); + const [dropDownOptions, setDropDownOptions] = useState(); + // const [tableHeight, setTableHeight] = useState(window.innerHeight); + const selectedOrganisation = useSelector( + (state: RootState) => state?.authentication?.selectedOrganisation + ); + const stacks = useSelector((state: RootState) => state?.migration?.newMigrationData?.testStacks); + const isMigDone = useSelector((state: RootState) => state?.migration?.newMigrationData?.migration_execution?.migrationCompleted); + const label1 = useSelector((state: RootState) => state?.migration?.newMigrationData?.stackDetails?.label); + const value1 = useSelector((state: RootState) => state?.migration?.newMigrationData?.stackDetails?.value); + useEffect(() => { + if (stacks && stacks?.length > 0) { + const formattedOptions: StackOption[] = stacks.map((stack: any) => ({ + label: stack?.stackName, + value: stack?.stackUid, + ...stack + })); + if (isMigDone && label1 && value1) { + formattedOptions.push({ + label: label1, + value: value1, + ...stacks + }); + } + setStackOptions(formattedOptions); + if (!selectedStack) { + setSelectedStack(formattedOptions[stacks.length - 1]); + updateFileOptionsForStack(formattedOptions[0]); + } + } + }, [stacks]); + const updateFileOptionsForStack = (stack: StackOption | null) => { + if (stack && selectedOrganisation?.value) { + const predefinedOptions: FileOption[] = [ + { label: 'Content Types', value: 'content-types' }, + { label: 'Global Fields', value: 'global-fields' }, + { label: 'Entries', value: 'Entries_Select_feild' } + ]; + setFileOptions(predefinedOptions); + } + }; + const handleStackChange = async (selectedOption: StackOption | null) => { + setSelectedStack(selectedOption); + resetFileSelection(); + if (selectedOption) { + updateFileOptionsForStack(selectedOption); + } + }; + const resetFileSelection = () => { + setSelectedFile(null); + setTableData([]); + setSearchText(''); + setTotalCounts(0); + setFilterValue([]); + setFilterOption('all'); + setIsFilterApplied(false); + }; + // useEffect(() => { + // const handleResize = () => { + // setTableHeight(window.innerHeight - 275); + // }; + // // console.info(window.innerHeight) + // // console.info(tableHeight) + // window.addEventListener("resize", handleResize); + // handleResize(); + // return () => window.removeEventListener("resize", handleResize); + // }, [window.innerHeight]); + const fetchTableData = async ({ + skip = 0, + limit = 30, + startIndex = 0, + stopIndex = 30, + searchText = 'null', + filter = filterOption + }) => { + if (!selectedStack || !selectedFile || !selectedOrganisation?.value) { + return { data: [], count: 0 }; + } + searchText = searchText === '' ? 'null' : searchText; + setLoading(true); + try { + const response = await getAuditData( + selectedOrganisation.value, + params?.projectId ?? '', + selectedStack.value, + selectedFile.value, + skip, + limit, + startIndex, + stopIndex, + searchText, + filter + ); + if (response?.data) { + setTableData(response?.data?.data || []); + setTotalCounts(response?.data?.totalCount || 0); + return { + data: response?.data?.data || [], + count: response?.data?.totalCount || 0 + }; + } + return { data: [], count: 0 }; + } catch (error) { + console.error('Error fetching audit data:', error); + if (startIndex === 0) { + setTableData([]); + setTotalCounts(0); + } + return { data: [], count: 0 }; + } finally { + setLoading(false); + } + }; + const handleFileChange = async (selectedOption: FileOption | null) => { + setSelectedFile(selectedOption); + console.info('selectedOption', selectedOption); + setDropDownOptions(selectedOption?.value); + setSearchText(''); + setFilterValue([]); + setFilterOption('all'); + setIsFilterApplied(false); + if (selectedOption) { + + setTableKey((prevKey) => prevKey + 1); + } + }; + const handleSearchChange = (value: string) => { + setSearchText(value); + setTableKey((prevKey) => prevKey + 1); + }; + const ColumnFilter = () => { + const closeModal = () => { + console.info(isFilterDropdownOpen); + setIsFilterDropdownOpen(false); + }; + const openFilterDropdown = () => { + if (!isFilterDropdownOpen) { + console.info('openFilterDropdown'); + setIsFilterDropdownOpen(true); + } + setIsFilterDropdownOpen(true); + }; + + const iconProps = { + className: isFilterApplied + ? auditLogsConstants.filterIcon.filterOn + : auditLogsConstants.filterIcon.filterOff, + withTooltip: true, + tooltipContent: 'Filter', + tooltipPosition: 'left' + }; + // Method to update filter value + const updateValue = ({ value, isChecked }: { value: FilterOption; isChecked: boolean }) => { + try { + let filterValueCopy = [...filterValue]; + if (!filterValueCopy.length && isChecked) { + filterValueCopy.push(value); + } else if (isChecked) { + const updatedFilter = filterValueCopy.filter((v) => v.value !== value.value); + filterValueCopy = [...updatedFilter, value]; + } else if (!isChecked) { + filterValueCopy = filterValueCopy.filter((v) => v.value !== value.value); + } + setFilterValue(filterValueCopy); + } catch (error) { + // console.error('Error updating filter value:', error); + } + }; + const handleClickOutside = () => { + if (!isCursorInside) { + closeModal && closeModal(); + } + }; + const onApply = () => { + try { + if (!filterValue.length) { + const newFilter = 'all'; + setFilterOption(newFilter); + fetchTableData({ filter: newFilter }); + closeModal(); + setIsFilterApplied(false); + return; + } + const usersQueryArray = filterValue.map((item) => item.value); + const newFilter = + usersQueryArray.length > 1 ? usersQueryArray.join('-') : usersQueryArray[0]; + setFilterOption(newFilter); + fetchTableData({ filter: newFilter }); + setIsFilterApplied(true); + closeModal(); + } catch (error) { + console.error('Error applying filter:', error); + } + }; + useEffect(() => { + document.addEventListener('click', handleClickOutside, false); + return () => { + document.removeEventListener('click', handleClickOutside, false); + }; + }, [isCursorInside]); + return ( +
{ + setIsCursorInside(true); + }} + onMouseLeave={() => { + setIsCursorInside(false); + }}> +
+ ); + }; + const renderCell = (value: any) =>
{value ?? '-'}
; + const contentTypeHeader = [ + { + Header: 'Title', + accessor: (data: TableDataItem) => renderCell(data.name), + addToColumnSelector: true, + disableSortBy: true, + disableResizing: false, + canDragDrop: true, + width: 150 + }, + { + Header: 'Field Name', + accessor: (data: TableDataItem) => renderCell(data.display_name), + addToColumnSelector: true, + disableSortBy: true, + disableResizing: false, + canDragDrop: true, + width: 200 + }, + { + Header: 'Field Type', + accessor: (data: TableDataItem) => renderCell(data.data_type), + addToColumnSelector: true, + disableSortBy: true, + disableResizing: false, + canDragDrop: true, + width: 200, + filter: ColumnFilter + }, + { + Header: 'Missing Reference', + accessor: (data: TableDataItem) => { + const missing = Array.isArray(data.missingRefs) + ? data.missingRefs.join(', ') + : typeof data.missingRefs === 'string' + ? data.missingRefs + : '-'; + + return renderCell(missing); + }, + addToColumnSelector: true, + disableSortBy: true, + disableResizing: false, + canDragDrop: true, + width: 200 + }, + { + Header: 'Tree Structure', + accessor: (data: TableDataItem) => renderCell(data.treeStr), + addToColumnSelector: true, + disableSortBy: true, + disableResizing: false, + canDragDrop: true, + width: 200 + }, + { + Header: 'Fix Status', + accessor: (data: TableDataItem) => renderCell(data.fixStatus), + addToColumnSelector: true, + disableSortBy: true, + disableResizing: false, + canDragDrop: true, + width: 200 + } + ]; + const entryHeader = [ + { + Header: 'Entry UID', + accessor: (data: TableDataItem) => renderCell(data.uid), + addToColumnSelector: true, + disableSortBy: true, + disableResizing: false, + canDragDrop: true, + width: 350 + }, + { + Header: 'Name', + accessor: (data: TableDataItem) => renderCell(data.name), + addToColumnSelector: true, + disableSortBy: true, + disableResizing: false, + canDragDrop: true, + width: 200 + }, + { + Header: 'Display Name', + accessor: (data: TableDataItem) => renderCell(data.display_name), + addToColumnSelector: true, + disableSortBy: true, + disableResizing: false, + canDragDrop: true, + width: 200 + }, + { + Header: 'Display Type', + accessor: (data: TableDataItem) => renderCell(data.display_type), + addToColumnSelector: true, + disableSortBy: true, + disableResizing: false, + canDragDrop: true, + width: 200, + filter: ColumnFilter + }, + { + Header: 'Missing Select Value', + accessor: (data: TableDataItem) => renderCell(data.missingCTSelectFieldValues), + addToColumnSelector: true, + disableSortBy: true, + disableResizing: false, + canDragDrop: true, + width: 200 + }, + { + Header: 'Tree Structure', + accessor: (data: TableDataItem) => renderCell(data.treeStr ?? '-'), + addToColumnSelector: true, + disableSortBy: true, + disableResizing: false, + canDragDrop: true, + width: 250 + } + ]; + + const exportCtaComponent = ( +
+
+ +
+
+ ); + return ( +
+ + } + /> +
+ ); +} +export default AuditLogs; \ No newline at end of file diff --git a/ui/src/components/Common/Settings/Settings.scss b/ui/src/components/Common/Settings/Settings.scss index 4cefaab17..a592df415 100644 --- a/ui/src/components/Common/Settings/Settings.scss +++ b/ui/src/components/Common/Settings/Settings.scss @@ -3,18 +3,22 @@ #setting-page { display: flex; width: 100%; + .SectionHeader { color: $color-stepper-title; font-weight: $font-weight-semi-bold; margin-bottom: 2rem; } + .PageLayout { width: 100%; } + .PageLayout__head, .PageLayout__leftSidebar { - border-top: 0 none; + border-top: 1; } + .PageLayout--primary { .PageLayout__content { .PageLayout__body { @@ -24,10 +28,12 @@ } } } + .action-component-title { justify-content: space-between; width: calc(100vw - 326px); } + .content-block { margin-right: 0; padding: 1.5rem; @@ -97,6 +103,7 @@ .PageLayout__leftSidebar { background-color: #f7f9fc !important; } + .ListRowV2--active { background-color: #fff; border-left: 0.125rem solid; @@ -104,7 +111,7 @@ font-weight: 600; } -.back-button{ +.back-button { cursor: pointer; - margin-bottom: 20px ; + margin-bottom: 20px; } \ No newline at end of file diff --git a/ui/src/components/Common/Settings/index.tsx b/ui/src/components/Common/Settings/index.tsx index 82f334f9d..3ba2ac8f4 100644 --- a/ui/src/components/Common/Settings/index.tsx +++ b/ui/src/components/Common/Settings/index.tsx @@ -35,6 +35,7 @@ import { useDispatch } from 'react-redux'; import { updateNewMigrationData } from '../../../store/slice/migrationDataSlice'; import { DEFAULT_NEW_MIGRATION } from '../../../context/app/app.interface'; import ExecutionLog from '../../../components/ExecutionLogs'; +import AuditLogs from '../../AuditLogs'; /** * Renders the Settings component. @@ -261,8 +262,12 @@ const Settings = () => { )} {active === cmsData?.execution_logs?.title && ( - + )} + {active === cmsData?.audit_logs?.title && + + + } ) }; @@ -315,6 +320,17 @@ const Settings = () => { }} version="v2" /> + } + onClick={() => { + setActive(cmsData?.audit_logs?.title); + setCurrentHeader(cmsData?.audit_logs?.title); + }} + version="v2" + /> ) }; diff --git a/ui/src/components/Common/Settings/setting.interface.ts b/ui/src/components/Common/Settings/setting.interface.ts index 78a3ac06d..acd3627c0 100644 --- a/ui/src/components/Common/Settings/setting.interface.ts +++ b/ui/src/components/Common/Settings/setting.interface.ts @@ -21,7 +21,7 @@ interface CTA { title: string; url: string; with_icon: boolean; - icon: string + icon: string } interface IExecutionLogs { title: string; @@ -33,5 +33,6 @@ interface IExecutionLogs { export interface Setting { project?: IProject; execution_logs?: IExecutionLogs; + audit_logs?: IExecutionLogs; title?: string; } diff --git a/ui/src/components/ExecutionLogs/index.scss b/ui/src/components/ExecutionLogs/index.scss index 325927294..e90794b27 100644 --- a/ui/src/components/ExecutionLogs/index.scss +++ b/ui/src/components/ExecutionLogs/index.scss @@ -1,7 +1,3 @@ -.Search-input-show { - margin-bottom: 8px; - width: 300px; -} .Search .Search__search-icon { top: calc(50% - 10px); width: 20px; @@ -15,7 +11,7 @@ margin-bottom: 8px; } -.PageLayout--primary .PageLayout__leftSidebar + .PageLayout__content .PageLayout__body { +.PageLayout--primary .PageLayout__leftSidebar+.PageLayout__content .PageLayout__body { width: calc(100% - 15.4rem); } @@ -25,15 +21,10 @@ justify-content: space-between; } -.Table__head{ +.Table__head { height: auto; } -.Table:has(.custom-empty-state) { - height: 40.5rem; -} - - .custom-empty-state { .Icon--original { width: 207px !important; @@ -47,3 +38,19 @@ .select-container { margin-bottom: 8px; } + + + +.PageLayout__body { + .table-height { + .Table { + height: calc(100vh - 20rem) !important; + } + + .Table.TableWithPaginated { + .Table__body { + height: calc(100vh - 25rem) !important; + } + } + } +} \ No newline at end of file diff --git a/ui/src/components/ExecutionLogs/index.tsx b/ui/src/components/ExecutionLogs/index.tsx index 4c51547ca..e8f389644 100644 --- a/ui/src/components/ExecutionLogs/index.tsx +++ b/ui/src/components/ExecutionLogs/index.tsx @@ -26,9 +26,14 @@ const ExecutionLogs = ({ projectId }: { projectId: string }) => { const [filterValue, setFilterValue] = useState('all'); const selectedOrganisation = useSelector( + + (state: RootState) => state?.authentication?.selectedOrganisation ); + + + const testStacks = useSelector( (state: RootState) => state?.migration?.newMigrationData?.testStacks ); @@ -55,8 +60,8 @@ const ExecutionLogs = ({ projectId }: { projectId: string }) => { const [selectedStack, setSelectedStack] = useState( { - label: stackIds?.[stackIds?.length - 1]?.label ?? '' , - value: stackIds?.[stackIds?.length - 1]?.value ?? '' + label: stackIds?.[stackIds?.length - 1]?.label ?? '', + value: stackIds?.[stackIds?.length - 1]?.value ?? '' } ); @@ -276,63 +281,62 @@ const ExecutionLogs = ({ projectId }: { projectId: string }) => { }; return ( -
+
setSearchText(value)} - withExportCta={{ - component: ( - { + setSelectedStack({ + label: s?.label ?? '', + value: s?.value ?? '' + }); + setSearchText(''); + }} + /> + ), + showExportCta: true + }} + customEmptyState={ + } - moduleIcon={searchText === '' ? EXECUTION_LOGS_UI_TEXT.EMPTY_STATE_ICON.NO_LOGS : EXECUTION_LOGS_UI_TEXT.EMPTY_STATE_ICON.NO_MATCH} - type="secondary" - className="custom-empty-state" - /> - } />
); diff --git a/ui/src/services/api/project.service.ts b/ui/src/services/api/project.service.ts index 1ca18c813..cf8b75ad3 100644 --- a/ui/src/services/api/project.service.ts +++ b/ui/src/services/api/project.service.ts @@ -20,7 +20,17 @@ export const getAllProjects = async (orgId: string) => { } } }; - +export const getAuditData = async (orgId: string, projectId: string, stackId: string, moduleName: string, skip: number, limit: number, startIndex: number, stopIndex: number, searchText: string, filter: string) => { + try { + return await getCall(`${API_VERSION}/migration/get_audit_data/${orgId}/${projectId}/${stackId}/${moduleName}/${skip}/${limit}/${startIndex}/${stopIndex}/${searchText}/${filter}`, options()); + } catch (error) { + if (error instanceof Error) { + throw new Error(`Error in userSession: ${error?.message}`); + } else { + throw new Error('Unknown error in userSession'); + } + } +}; export const getProject = async (orgId: string, projectId: string) => { try { return await getCall(`${API_VERSION}/org/${orgId}/project/${projectId}`, options()); diff --git a/ui/src/utilities/constants.ts b/ui/src/utilities/constants.ts index 74152f713..5175b6579 100644 --- a/ui/src/utilities/constants.ts +++ b/ui/src/utilities/constants.ts @@ -145,7 +145,7 @@ export const EXECUTION_LOGS_UI_TEXT = { NO_MATCH: 'No Matching Result Found' }, EMPTY_STATE_ICON: { - NO_LOGS:'NoDataEmptyState', + NO_LOGS: 'NoDataEmptyState', NO_MATCH: 'NoSearchResult' }, FILTER_ICON: { @@ -157,3 +157,30 @@ export const EXECUTION_LOGS_UI_TEXT = { export const EXECUTION_LOGS_ERROR_TEXT = { ERROR: 'Error in Getting Migration Logs' } +export const auditLogsConstants = { + executeTestMigration: 'Try executing Test Migration', + selectModuleMessage: 'Select Module to See the Logs', + queryChangeMessage: 'Try Changing the Search Query to find what you are looking for', + noResult: 'No Matching Result Found', + noLogs: 'No Logs Found', + filterIcon: { + filterOn: 'filterWithAppliedIcon Icon--v2 Icon--medium', + filterOff: 'filterWithAppliedIcon Icon--v2 Icon--medium Icon--disabled' + }, + + placeholders: { + selectStack: 'Select Stack', + selectModule: 'Select Module', + searchLogs: 'Search Audit Logs', + }, + + emptyStateIcon: { + noLogs: 'NoDataEmptyState', + noMatch: 'NoSearchResult' + }, + filterModal: { + noFilterAvailabe: 'No Filters Available', + clearAll: 'Clear All', + apply: 'Apply' + } +};