From 735b46037aefff51de1f900c0fbfa59e76ffb0e6 Mon Sep 17 00:00:00 2001 From: samarp-jain Date: Tue, 22 Apr 2025 16:07:33 +0530 Subject: [PATCH 01/37] feat: add audit data retrieval endpoint and integrate AuditLogs component --- api/src/controllers/migration.controller.ts | 8 +- api/src/routes/migration.routes.ts | 5 +- api/src/services/migration.service.ts | 160 +++++- .../AuditLogs/auditLogs.interface.ts | 46 ++ ui/src/components/AuditLogs/index.scss | 24 + ui/src/components/AuditLogs/index.tsx | 517 ++++++++++++++++++ ui/src/components/Common/Settings/index.tsx | 64 ++- ui/src/services/api/project.service.ts | 12 +- upload-api/src/config/index.ts | 4 +- 9 files changed, 807 insertions(+), 33 deletions(-) create mode 100644 ui/src/components/AuditLogs/auditLogs.interface.ts create mode 100644 ui/src/components/AuditLogs/index.scss create mode 100644 ui/src/components/AuditLogs/index.tsx diff --git a/api/src/controllers/migration.controller.ts b/api/src/controllers/migration.controller.ts index fbbc75996..3261f8080 100644 --- a/api/src/controllers/migration.controller.ts +++ b/api/src/controllers/migration.controller.ts @@ -49,7 +49,10 @@ 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(200).json(resp); +}; const getLogs = async (req: Request, res: Response): Promise => { const resp = await migrationService.getLogs(req); res.status(200).json(resp); @@ -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 fdcefaee6..2d079e7da 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", + 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 3ea443587..7339d129c 100644 --- a/api/src/services/migration.service.ts +++ b/api/src/services/migration.service.ts @@ -118,9 +118,8 @@ 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`, + url: `${config.CS_URL[token_payload?.region as keyof typeof config.CS_URL] + }/stack/${res.data.stack.api_key}/dashboard`, }, status: res.status, }; @@ -141,6 +140,160 @@ const createTestStack = async (req: Request): Promise => { } }; +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 srcFunc = "getAuditData"; + + if (projectId.includes('..') || stackId.includes('..') || moduleName.includes('..')) { + throw new BadRequestError("Invalid projectId, stackId, or moduleName"); + } + + try { + const mainPath = process.cwd().split("migration-v2")[0]; + const logsDir = path.join(mainPath, "migration-v2", "api", "migration-data"); + + 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.join(logsDir, stackFolder, "logs", "audit", "audit-report"); + if (!fs.existsSync(auditLogPath)) { + throw new BadRequestError("Audit log path not found"); + } + + const files = fs.readdirSync(auditLogPath); + const jsonFiles = files.filter(file => file.endsWith('.json')); + + let fileData = null; + let fileName = null; + + for (const file of jsonFiles) { + const fileModuleName = file.replace('.json', ''); + + if (fileModuleName === moduleName) { + const filePath = path.join(auditLogPath, file); + try { + const content = fs.readFileSync(filePath, 'utf8'); + fileData = JSON.parse(content); + fileName = file; + break; + } catch (err) { + logger.warn( + getLogMessage( + srcFunc, + `Failed to parse JSON file for module ${moduleName}: ${file}` + ) + ); + fileData = { + error: 'Failed to parse file', + }; + fileName = file; + break; + } + } + } + + // If no matching module was found + if (!fileData) { + throw new BadRequestError(`No audit data found for module: ${moduleName}`); + } + + // Transform and flatten the data with sequential tuid + let transformedData = transformAndFlattenData(fileData); + + // Apply search filter if searchText is provided and not "null" + if (searchText && searchText !== "null") { + transformedData = transformedData.filter((item: any) => { + // Adjust these fields based on your actual audit data structure + return Object.values(item).some(value => + value && + typeof value === 'string' && + value.toLowerCase().includes(searchText.toLowerCase()) + ); + }); + } + + // Apply pagination + const paginatedData = transformedData.slice(startIndex, stopIndex); + + return { + data: paginatedData, + totalCount: transformedData.length + }; + + } 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) { + // Process object data + Object.entries(data).forEach(([key, value]) => { + if (Array.isArray(value)) { + // If property contains an array, flatten each item + value.forEach((item, index) => { + flattenedItems.push({ + ...item, + parentKey: key, + uid: item.uid || `${key}-${index}` + }); + }); + } else if (typeof value === 'object' && value !== null) { + // If property contains an object, add it as an item + flattenedItems.push({ + ...value, + key, + uid: (value as any).uid || key + }); + } + }); + } + + // Add sequential tuid to each item + 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. @@ -777,4 +930,5 @@ export const migrationService = { getLogs, createSourceLocales, updateLocaleMapper, + getAuditData }; diff --git a/ui/src/components/AuditLogs/auditLogs.interface.ts b/ui/src/components/AuditLogs/auditLogs.interface.ts new file mode 100644 index 000000000..91fe1eaea --- /dev/null +++ b/ui/src/components/AuditLogs/auditLogs.interface.ts @@ -0,0 +1,46 @@ +// 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 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..4fe0e4a0c --- /dev/null +++ b/ui/src/components/AuditLogs/index.scss @@ -0,0 +1,24 @@ +.select-box { + + margin-bottom: 4px; + width: 220px; +} + +.Search-input-show { + margin-bottom: 4px; +} + +.PageLayout--primary .PageLayout__leftSidebar+.PageLayout__content .PageLayout__body { + width: calc(100% - 15rem); +} + + +.custom-empty-state { + .Icon--original { + width: 207px !important; + height: auto !important; + max-width: 100%; + display: block; + margin: 0 auto; + } +} \ 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..809c01962 --- /dev/null +++ b/ui/src/components/AuditLogs/index.tsx @@ -0,0 +1,517 @@ +import React, { useState, useEffect } from 'react'; +import { useSelector } from 'react-redux'; +import { useParams } from 'react-router'; +import { EmptyState, InfiniteScrollTable, Select } from '@contentstack/venus-components'; +// Redux +import { RootState } from '../../store'; +// Service +import { getAuditData } from '../../services/api/project.service'; +// Interfaces +import { + FileData, + StackOption, + FileOption, + TableDataItem, + TableColumn +} from './auditLogs.interface'; + +// Import NoDataSvg similar to ExecutionLogs component +import { NoDataFound } from '../../common/assets' +import './index.scss'; + +const AuditLogs: React.FC = () => { + const params = useParams<{ projectId?: string }>(); + const [loading, setLoading] = useState(false); + const [initialLoadComplete, setInitialLoadComplete] = useState(false); + const [selectedStack, setSelectedStack] = useState(null); + const [stackOptions, setStackOptions] = useState([]); + const [selectedFile, setSelectedFile] = useState(null); + const [fileOptions, setFileOptions] = useState([]); + const [fileContent, setFileContent] = useState(null); + const [searchText, setSearchText] = useState(''); + const [tableColumns, setTableColumns] = useState([]); + const [tableData, setTableData] = useState([]); + const [totalCounts, setTotalCounts] = useState(0); + const [currentPage, setCurrentPage] = useState(1); + const [tableKey, setTableKey] = useState(0); + + const selectedOrganisation = useSelector( + (state: RootState) => state?.authentication?.selectedOrganisation + ); + const stacks = useSelector((state: RootState) => state?.migration?.newMigrationData?.testStacks); + + useEffect(() => { + if (stacks && stacks.length > 0) { + const formattedOptions: StackOption[] = stacks.map((stack: any) => ({ + label: stack.name || stack.stackName || stack.stackUid || '', + value: stack.stackUid || '', + ...stack + })); + setStackOptions(formattedOptions); + + if (!selectedStack) { + setSelectedStack(formattedOptions[0]); + 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); + setInitialLoadComplete(true); + } + }; + + const handleStackChange = async (selectedOption: StackOption | null) => { + setSelectedStack(selectedOption); + resetFileSelection(); + + if (selectedOption) { + updateFileOptionsForStack(selectedOption); + } + }; + + const resetFileSelection = () => { + setSelectedFile(null); + setFileContent(null); + setTableData([]); + setTableColumns([]); + setCurrentPage(1); + setSearchText(''); + setTotalCounts(0); + }; + + const handleFileChange = async (selectedOption: FileOption | null) => { + setSelectedFile(selectedOption); + setCurrentPage(1); + setSearchText(''); + + if (selectedOption) { + const columns = generateColumnsForFile(selectedOption.value); + setTableColumns(columns); + + setTableData([]); + + setTableKey(prevKey => prevKey + 1); + } else { + setFileContent(null); + setTableData([]); + setTableColumns([]); + } + }; + + useEffect(() => { + if (selectedStack && selectedFile && selectedOrganisation?.value) { + fetchInitialData(); + } + }, [selectedStack, selectedFile]); + + const handleSearchChange = (value: string) => { + setSearchText(value); + setCurrentPage(1); + setTableKey(prevKey => prevKey + 1); + }; + + // Function to fetch initial data when selections change + const fetchInitialData = async () => { + if (!selectedStack || !selectedFile || !selectedOrganisation?.value) { + return; + } + + setLoading(true); + try { + const searchParam = searchText === '' ? 'null' : searchText; + + const response = await getAuditData( + selectedOrganisation.value, + params?.projectId ?? '', + selectedStack.value, + selectedFile.value, + 0, + 15, + 0, + 15, + searchParam + ); + + if (response.data) { + setTableData(response.data.data || []); + setTotalCounts(response.data.totalCount || 0); + } else { + setTableData([]); + setTotalCounts(0); + } + } catch (error) { + console.error('Error fetching initial audit data:', error); + setTableData([]); + setTotalCounts(0); + } finally { + setLoading(false); + } + }; + + const generateColumnsForFile = (fileName: string): TableColumn[] => { + const renderCell = (value: any) => ( +
{value}
+ ); + + if (fileName.includes('content-types') || fileName.includes('global-fields')) { + return [ + { + Header: 'Title', + accessor: (data: TableDataItem) => renderCell(data.name || data.ct_uid || '-'), + 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 + }, + { + Header: 'Missing Reference', + accessor: (data: TableDataItem) => { + const missing = data.missingRefs ? + (typeof data.missingRefs === 'string' ? data.missingRefs : data.missingRefs.join(', ')) : + '-'; + 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) => { + const status = data.fixStatus || 'Fixed'; + const statusClass = status === 'Fixed' ? 'status-fixed' : 'status-not-fixed'; + return ( +
+
{status}
+
+ ); + }, + addToColumnSelector: true, + disableSortBy: true, + disableResizing: false, + canDragDrop: true, + width: 200 + } + ]; + } + else if (fileName.includes('Entries')) { + return [ + { + Header: 'Entry UID', + accessor: (data: TableDataItem) => renderCell(data.uid || '-'), + addToColumnSelector: true, + disableSortBy: true, + disableResizing: false, + canDragDrop: true, + width: 150 + }, + { + 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 + }, + { + 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: 200 + }, + { + Header: 'Fix Status', + accessor: (data: TableDataItem) => { + const status = data.fixStatus || 'Fixed'; + return ( +
{status}
+ ); + }, + addToColumnSelector: true, + disableSortBy: true, + disableResizing: false, + canDragDrop: true, + width: 200 + } + ]; + } + else { + // Default columns for other file types + return [ + { + Header: 'Name', + accessor: (data: TableDataItem) => renderCell(data.name || data.uid || '-'), + addToColumnSelector: true, + disableSortBy: true, + disableResizing: false, + canDragDrop: true, + width: 150 + }, + { + Header: 'Type', + accessor: (data: TableDataItem) => renderCell(data.type || data.data_type || '-'), + addToColumnSelector: true, + disableSortBy: true, + disableResizing: false, + canDragDrop: true, + width: 200 + }, + { + Header: 'Details', + accessor: (data: TableDataItem) => renderCell(data.details || data.description || '-'), + addToColumnSelector: true, + disableSortBy: true, + disableResizing: false, + canDragDrop: true, + width: 200 + } + ]; + } + }; + + // Fetch table data function for pagination and other table operations + const fetchTableData = async ({ + skip = 0, + limit = 30, + startIndex = 0, + stopIndex = 30, + searchText: tableSearchText = null, + }) => { + if (!selectedStack || !selectedFile || !selectedOrganisation?.value) { + return { data: [], count: 0 }; + } + + // Use component's searchText state if tableSearchText isn't provided + const finalSearchText = tableSearchText !== null ? tableSearchText : searchText; + const searchParam = finalSearchText === '' ? 'null' : finalSearchText; + + setLoading(true); + try { + const response = await getAuditData( + selectedOrganisation.value, + params?.projectId ?? '', + selectedStack.value, + selectedFile.value, + skip, + limit, + startIndex, + stopIndex, + searchParam + ); + + if (response.data) { + // Only update the state if this is not being called as part of table's internal operations + 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 EmptyObj = { + heading: !selectedStack ? `execute test migraiton` : selectedStack && !selectedFile ? `select module` : '', + forPage: "emptyStateV2", + description: ( + <> + + {!selectedStack ? `execute test migraiton` : selectedStack && !selectedFile ? `select module` : ''} + + } + // img={NoDataFound} + version="v2" + className="emptycomponent" + /> + + ), + } + // Create the export CTA component for the table with both stack and file selectors + const exportCtaComponent = ( +
+
+ +
+
+ ); + + const defaultEmptyColumns: TableColumn[] = [ + { + Header: 'Name', + accessor: (data: TableDataItem) =>
{data.name || '-'}
, + addToColumnSelector: true, + disableSortBy: true, + disableResizing: false, + canDragDrop: true, + width: 200 + }, + { + Header: 'Type', + accessor: (data: TableDataItem) =>
{data.type || '-'}
, + addToColumnSelector: true, + disableSortBy: true, + disableResizing: false, + canDragDrop: true, + width: 200 + }, + { + Header: 'Details', + accessor: (data: TableDataItem) =>
{data.details || '-'}
, + addToColumnSelector: true, + disableSortBy: true, + disableResizing: false, + canDragDrop: true, + width: 200 + } + ]; + + return ( + 0 ? tableColumns : defaultEmptyColumns} + uniqueKey={"id"} + fetchTableData={fetchTableData} + totalCounts={totalCounts} + loading={loading} + rowPerPageOptions={[10, 30, 50, 100]} + minBatchSizeToFetch={30} + v2Features={{ + pagination: true, + isNewEmptyState: true + }} + isResizable={false} + isRowSelect={false} + columnSelector={false} + canSearch={true} + searchPlaceholder={'Search Audit Logs'} + searchValue={searchText} + onSearchChangeEvent={handleSearchChange} + withExportCta={{ + component: exportCtaComponent, + showExportCta: true + }} + customEmptyState={ + + } + /> + ); +}; + +export default AuditLogs; \ 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 63beb0bea..8b6e96662 100644 --- a/ui/src/components/Common/Settings/index.tsx +++ b/ui/src/components/Common/Settings/index.tsx @@ -35,6 +35,7 @@ import './Settings.scss'; import { useDispatch } from 'react-redux'; import { updateNewMigrationData } from '../../../store/slice/migrationDataSlice'; import { DEFAULT_NEW_MIGRATION } from '../../../context/app/app.interface'; +import AuditLogs from '../../AuditLogs'; /** * Renders the Settings component. @@ -124,29 +125,29 @@ const Settings = () => { }); } }; - const handleDeleteProject = async (closeModal: ()=> void): Promise => { - //setIsLoading(true); - const response = await deleteProject(selectedOrganisation?.value, params?.projectId ?? ''); - - if (response?.status === 200) { - //setIsLoading(false); - closeModal(); - dispatch(updateNewMigrationData(DEFAULT_NEW_MIGRATION)); - setTimeout(() => { - navigate('/projects'); - }, 800); - setTimeout(() => { - Notification({ - notificationContent: { text: response?.data?.data?.message }, - notificationProps: { - position: 'bottom-center', - hideProgressBar: true - }, - type: 'success' - }); - }, 1200); - } - }; + const handleDeleteProject = async (closeModal: () => void): Promise => { + //setIsLoading(true); + const response = await deleteProject(selectedOrganisation?.value, params?.projectId ?? ''); + + if (response?.status === 200) { + //setIsLoading(false); + closeModal(); + dispatch(updateNewMigrationData(DEFAULT_NEW_MIGRATION)); + setTimeout(() => { + navigate('/projects'); + }, 800); + setTimeout(() => { + Notification({ + notificationContent: { text: response?.data?.data?.message }, + notificationProps: { + position: 'bottom-center', + hideProgressBar: true + }, + type: 'success' + }); + }, 1200); + } + }; const handleClick = () => { cbModal({ @@ -253,7 +254,11 @@ const Settings = () => { )} - {active === cmsData?.execution_logs?.title &&
} + {active === "AuditLogs" && +
+ +
+ } ) }; @@ -281,6 +286,17 @@ const Settings = () => { }} version="v2" /> + } + onClick={() => { + setActive("AuditLogs"); + setCurrentHeader("AuditLogs"); + }} + version="v2" + /> ) }; diff --git a/ui/src/services/api/project.service.ts b/ui/src/services/api/project.service.ts index 1ca18c813..a380ae388 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) => { + try { + return await getCall(`${API_VERSION}/migration/get_audit_data/${orgId}/${projectId}/${stackId}/${moduleName}/${skip}/${limit}/${startIndex}/${stopIndex}/${searchText}`, 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/upload-api/src/config/index.ts b/upload-api/src/config/index.ts index f2eb4db96..e6934ca85 100644 --- a/upload-api/src/config/index.ts +++ b/upload-api/src/config/index.ts @@ -2,7 +2,7 @@ export default { plan: { dropdown: { optionLimit: 100 } }, - cmsType: 'contentful', + cmsType: 'sitecore', isLocalPath: true, awsData: { awsRegion: 'us-east-2', @@ -13,5 +13,5 @@ export default { bucketKey: '' }, // localPath: '/Users/sayali.joshi/Downloads/contentfulDummyEmbedData.json' //package 45.zip' - localPath: '/Users/shobhit.upadhyay/Downloads/asic.json' + localPath: '/Users/samarp.jain/Desktop/package 45.zip', }; From 211f5ba4510c36c3c9fd228a677932822ab3408e Mon Sep 17 00:00:00 2001 From: samarp-jain Date: Tue, 22 Apr 2025 19:25:22 +0530 Subject: [PATCH 02/37] feat: enhance AuditLogs component with improved empty state handling and table column widths --- ui/src/components/AuditLogs/index.scss | 5 +- ui/src/components/AuditLogs/index.tsx | 101 +++++++++++-------------- 2 files changed, 47 insertions(+), 59 deletions(-) diff --git a/ui/src/components/AuditLogs/index.scss b/ui/src/components/AuditLogs/index.scss index 4fe0e4a0c..3f5e00274 100644 --- a/ui/src/components/AuditLogs/index.scss +++ b/ui/src/components/AuditLogs/index.scss @@ -12,10 +12,13 @@ width: calc(100% - 15rem); } +.Table:has(.custom-empty-state) { + height: 40.5rem; +} .custom-empty-state { .Icon--original { - width: 207px !important; + width: 240x !important; height: auto !important; max-width: 100%; display: block; diff --git a/ui/src/components/AuditLogs/index.tsx b/ui/src/components/AuditLogs/index.tsx index 809c01962..7e337a472 100644 --- a/ui/src/components/AuditLogs/index.tsx +++ b/ui/src/components/AuditLogs/index.tsx @@ -241,7 +241,7 @@ const AuditLogs: React.FC = () => { disableSortBy: true, disableResizing: false, canDragDrop: true, - width: 150 + width: 350 }, { Header: 'Name', @@ -286,7 +286,7 @@ const AuditLogs: React.FC = () => { disableSortBy: true, disableResizing: false, canDragDrop: true, - width: 200 + width: 250 }, { Header: 'Fix Status', @@ -391,25 +391,6 @@ const AuditLogs: React.FC = () => { setLoading(false); } }; - const EmptyObj = { - heading: !selectedStack ? `execute test migraiton` : selectedStack && !selectedFile ? `select module` : '', - forPage: "emptyStateV2", - description: ( - <> - - {!selectedStack ? `execute test migraiton` : selectedStack && !selectedFile ? `select module` : ''} - - } - // img={NoDataFound} - version="v2" - className="emptycomponent" - /> - - ), - } - // Create the export CTA component for the table with both stack and file selectors const exportCtaComponent = (
@@ -474,43 +455,47 @@ const AuditLogs: React.FC = () => { ]; return ( - 0 ? tableColumns : defaultEmptyColumns} - uniqueKey={"id"} - fetchTableData={fetchTableData} - totalCounts={totalCounts} - loading={loading} - rowPerPageOptions={[10, 30, 50, 100]} - minBatchSizeToFetch={30} - v2Features={{ - pagination: true, - isNewEmptyState: true - }} - isResizable={false} - isRowSelect={false} - columnSelector={false} - canSearch={true} - searchPlaceholder={'Search Audit Logs'} - searchValue={searchText} - onSearchChangeEvent={handleSearchChange} - withExportCta={{ - component: exportCtaComponent, - showExportCta: true - }} - customEmptyState={ - - } - /> +
+ + 0 ? tableColumns : defaultEmptyColumns} + uniqueKey={"id"} + fetchTableData={fetchTableData} + totalCounts={totalCounts} + loading={loading} + rowPerPageOptions={[10, 30, 50, 100]} + minBatchSizeToFetch={30} + v2Features={{ + pagination: true, + isNewEmptyState: true + }} + isResizable={false} + isRowSelect={false} + columnSelector={false} + canSearch={true} + searchPlaceholder={'Search Audit Logs'} + searchValue={searchText} + onSearchChangeEvent={handleSearchChange} + withExportCta={{ + component: exportCtaComponent, + showExportCta: true + }} + customEmptyState={ + + } + /> +
+ ); }; From b50694c3ba6980b40f5935a2d88d19a7a685d685 Mon Sep 17 00:00:00 2001 From: samarp-jain Date: Wed, 23 Apr 2025 15:27:10 +0530 Subject: [PATCH 03/37] refactor: update AuditLogs component styles and improve empty state messaging --- ui/src/components/AuditLogs/index.scss | 19 ++++++++--- ui/src/components/AuditLogs/index.tsx | 44 ++++---------------------- 2 files changed, 21 insertions(+), 42 deletions(-) diff --git a/ui/src/components/AuditLogs/index.scss b/ui/src/components/AuditLogs/index.scss index 3f5e00274..c2fd9c69d 100644 --- a/ui/src/components/AuditLogs/index.scss +++ b/ui/src/components/AuditLogs/index.scss @@ -1,7 +1,14 @@ -.select-box { +.select-container { + display: flex; - margin-bottom: 4px; - width: 220px; + .select-wrapper { + display: flex; + margin-left: 1rem; + + .select-box { + margin-bottom: 4px; + } + } } .Search-input-show { @@ -18,10 +25,14 @@ .custom-empty-state { .Icon--original { - width: 240x !important; + width: 240px !important; height: auto !important; max-width: 100%; display: block; margin: 0 auto; } +} + +.TablePanel__list-count flex-v-center { + display: none; } \ No newline at end of file diff --git a/ui/src/components/AuditLogs/index.tsx b/ui/src/components/AuditLogs/index.tsx index 7e337a472..5c48d14af 100644 --- a/ui/src/components/AuditLogs/index.tsx +++ b/ui/src/components/AuditLogs/index.tsx @@ -50,7 +50,7 @@ const AuditLogs: React.FC = () => { setStackOptions(formattedOptions); if (!selectedStack) { - setSelectedStack(formattedOptions[0]); + setSelectedStack(formattedOptions[stacks.length - 1]); updateFileOptionsForStack(formattedOptions[0]); } } @@ -96,12 +96,9 @@ const AuditLogs: React.FC = () => { const columns = generateColumnsForFile(selectedOption.value); setTableColumns(columns); - setTableData([]); - setTableKey(prevKey => prevKey + 1); } else { setFileContent(null); - setTableData([]); setTableColumns([]); } }; @@ -306,35 +303,7 @@ const AuditLogs: React.FC = () => { } else { // Default columns for other file types - return [ - { - Header: 'Name', - accessor: (data: TableDataItem) => renderCell(data.name || data.uid || '-'), - addToColumnSelector: true, - disableSortBy: true, - disableResizing: false, - canDragDrop: true, - width: 150 - }, - { - Header: 'Type', - accessor: (data: TableDataItem) => renderCell(data.type || data.data_type || '-'), - addToColumnSelector: true, - disableSortBy: true, - disableResizing: false, - canDragDrop: true, - width: 200 - }, - { - Header: 'Details', - accessor: (data: TableDataItem) => renderCell(data.details || data.description || '-'), - addToColumnSelector: true, - disableSortBy: true, - disableResizing: false, - canDragDrop: true, - width: 200 - } - ]; + return []; } }; @@ -392,8 +361,8 @@ const AuditLogs: React.FC = () => { } }; const exportCtaComponent = ( -
-
+
+
{ return (
- { customEmptyState={ Date: Thu, 24 Apr 2025 16:05:47 +0530 Subject: [PATCH 04/37] refactor: simplify AuditLogs component by removing unused data fetching logic and optimizing status rendering --- ui/src/components/AuditLogs/index.tsx | 64 ++++----------------------- 1 file changed, 8 insertions(+), 56 deletions(-) diff --git a/ui/src/components/AuditLogs/index.tsx b/ui/src/components/AuditLogs/index.tsx index 5c48d14af..17131cad7 100644 --- a/ui/src/components/AuditLogs/index.tsx +++ b/ui/src/components/AuditLogs/index.tsx @@ -15,8 +15,6 @@ import { TableColumn } from './auditLogs.interface'; -// Import NoDataSvg similar to ExecutionLogs component -import { NoDataFound } from '../../common/assets' import './index.scss'; const AuditLogs: React.FC = () => { @@ -103,11 +101,7 @@ const AuditLogs: React.FC = () => { } }; - useEffect(() => { - if (selectedStack && selectedFile && selectedOrganisation?.value) { - fetchInitialData(); - } - }, [selectedStack, selectedFile]); + const handleSearchChange = (value: string) => { setSearchText(value); @@ -115,44 +109,6 @@ const AuditLogs: React.FC = () => { setTableKey(prevKey => prevKey + 1); }; - // Function to fetch initial data when selections change - const fetchInitialData = async () => { - if (!selectedStack || !selectedFile || !selectedOrganisation?.value) { - return; - } - - setLoading(true); - try { - const searchParam = searchText === '' ? 'null' : searchText; - - const response = await getAuditData( - selectedOrganisation.value, - params?.projectId ?? '', - selectedStack.value, - selectedFile.value, - 0, - 15, - 0, - 15, - searchParam - ); - - if (response.data) { - setTableData(response.data.data || []); - setTotalCounts(response.data.totalCount || 0); - } else { - setTableData([]); - setTotalCounts(0); - } - } catch (error) { - console.error('Error fetching initial audit data:', error); - setTableData([]); - setTotalCounts(0); - } finally { - setLoading(false); - } - }; - const generateColumnsForFile = (fileName: string): TableColumn[] => { const renderCell = (value: any) => (
{value}
@@ -213,12 +169,10 @@ const AuditLogs: React.FC = () => { { Header: 'Fix Status', accessor: (data: TableDataItem) => { - const status = data.fixStatus || 'Fixed'; - const statusClass = status === 'Fixed' ? 'status-fixed' : 'status-not-fixed'; + const status = data.fixStatus; + return ( -
-
{status}
-
+
{status}
); }, addToColumnSelector: true, @@ -288,7 +242,7 @@ const AuditLogs: React.FC = () => { { Header: 'Fix Status', accessor: (data: TableDataItem) => { - const status = data.fixStatus || 'Fixed'; + const status = data.fixStatus; return (
{status}
); @@ -313,15 +267,13 @@ const AuditLogs: React.FC = () => { limit = 30, startIndex = 0, stopIndex = 30, - searchText: tableSearchText = null, + searchText = 'null', }) => { if (!selectedStack || !selectedFile || !selectedOrganisation?.value) { return { data: [], count: 0 }; } - // Use component's searchText state if tableSearchText isn't provided - const finalSearchText = tableSearchText !== null ? tableSearchText : searchText; - const searchParam = finalSearchText === '' ? 'null' : finalSearchText; + searchText = searchText === '' ? 'null' : searchText; setLoading(true); try { @@ -334,7 +286,7 @@ const AuditLogs: React.FC = () => { limit, startIndex, stopIndex, - searchParam + searchText ); if (response.data) { From 728020cd4b74b5c1b56d536523a2633bfedc2279 Mon Sep 17 00:00:00 2001 From: samarp-jain Date: Thu, 24 Apr 2025 22:23:46 +0530 Subject: [PATCH 05/37] feat: add filter functionality to audit data retrieval and implement AuditFilterModal component --- api/src/routes/migration.routes.ts | 2 +- api/src/services/migration.service.ts | 1 + .../AuditFilterModal/auditlog.interface.ts | 11 ++ ui/src/components/AuditFilterModal/index.scss | 85 ++++++++++ ui/src/components/AuditFilterModal/index.tsx | 123 ++++++++++++++ .../AuditLogs/auditLogs.interface.ts | 8 + ui/src/components/AuditLogs/index.tsx | 160 +++++++++++++++--- ui/src/services/api/project.service.ts | 4 +- 8 files changed, 366 insertions(+), 28 deletions(-) create mode 100644 ui/src/components/AuditFilterModal/auditlog.interface.ts create mode 100644 ui/src/components/AuditFilterModal/index.scss create mode 100644 ui/src/components/AuditFilterModal/index.tsx diff --git a/api/src/routes/migration.routes.ts b/api/src/routes/migration.routes.ts index 2d079e7da..6b02ccd71 100644 --- a/api/src/routes/migration.routes.ts +++ b/api/src/routes/migration.routes.ts @@ -21,7 +21,7 @@ router.post( asyncRouter(migrationController.startTestMigration) ); router.get( - "/get_audit_data/:orgId/:projectId/:stackId/:moduleName/:skip/:limit/:startIndex/:stopIndex/:searchText", + "/get_audit_data/:orgId/:projectId/:stackId/:moduleName/:skip/:limit/:startIndex/:stopIndex/:searchText/:filter", asyncRouter(migrationController.getAuditData) ) /** diff --git a/api/src/services/migration.service.ts b/api/src/services/migration.service.ts index 7339d129c..bbf764a90 100644 --- a/api/src/services/migration.service.ts +++ b/api/src/services/migration.service.ts @@ -148,6 +148,7 @@ const getAuditData = async (req: Request): Promise => { 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('..')) { diff --git a/ui/src/components/AuditFilterModal/auditlog.interface.ts b/ui/src/components/AuditFilterModal/auditlog.interface.ts new file mode 100644 index 000000000..d83fe97bf --- /dev/null +++ b/ui/src/components/AuditFilterModal/auditlog.interface.ts @@ -0,0 +1,11 @@ +import { FilterOption } from "../AuditLogs/auditLogs.interface"; + +export type FilterModaleProps = { + 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..1c09cebbf --- /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: #ffffff; + border-radius: 12px; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); + 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 rgba(0, 0, 0, 0.15); + transform: scale(1.05); + border-radius: 8px; + background-color: #f0f1f3; + 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..edec02e81 --- /dev/null +++ b/ui/src/components/AuditFilterModal/index.tsx @@ -0,0 +1,123 @@ +import React from 'react'; +import { Button, ButtonGroup, Checkbox, Icon } from '@contentstack/venus-components'; +import './index.scss'; +import { FilterOption } from '../AuditLogs/auditLogs.interface'; + +interface AuditFilterModalProps { + isOpen: boolean; + closeModal: () => void; + updateValue: ({ value, isChecked }: { value: FilterOption; isChecked: boolean }) => void; + onApply: () => void; + selectedLevels: FilterOption[]; + setFilterValue: React.Dispatch>; + selectedFileType?: string; +} + +const AuditFilterModal = ({ + isOpen, + closeModal, + updateValue, + onApply, + selectedLevels, + setFilterValue, + selectedFileType +}: AuditFilterModalProps) => { + // Generate filter options based on selected file type + const getFilterOptions = () => { + if (!selectedFileType) return []; + + if (selectedFileType.includes('content-types') || selectedFileType.includes('global-fields')) { + return [ + { label: 'Single Line Textbox', value: 'text' }, + { label: 'Multi Line Textbox', value: 'textarea' }, + { label: 'Rich Text Editor', value: 'rte' }, + { label: 'Markdown', value: 'markdown' }, + { label: 'Select', value: 'select' }, + { label: 'Reference', value: 'reference' }, + { label: 'File', value: 'file' }, + { label: 'Number', value: 'number' }, + { label: 'Boolean', value: 'boolean' }, + { label: 'Date', value: 'date' }, + { label: 'Group', value: 'group' } + ]; + } else if (selectedFileType.includes('Entries')) { + return [ + { label: 'Text', value: 'text' }, + { label: 'Reference', value: 'reference' }, + { label: 'File', value: 'file' }, + { label: 'Select', value: 'select' }, + { label: 'Group', value: 'group' } + ]; + } + + return []; + }; + + const filterOptions = getFilterOptions(); + + const clearAll = () => { + setFilterValue([]); + }; + + if (!isOpen) return null; + + return ( +
+ {/* Modal header */} +
+ + {selectedFileType?.includes('Entries') ? 'Display Type' : 'Field Type'} + +
+ +
+
+ + {/* Modal Body */} +
    + {filterOptions.length > 0 ? ( + filterOptions.map((item) => ( +
  • +
    + v.value === item.value)} + onChange={(e: React.ChangeEvent) => + updateValue({ value: item, isChecked: e.target.checked }) + } + version="v2" + label={item.label} + className="text-size" + /> +
    +
  • + )) + ) : ( +
  • +
    + No filter options available for this selection +
    +
  • + )} +
+ + {/* Modal Footer */} +
+ + + + +
+
+ ); +}; + +export default AuditFilterModal; \ No newline at end of file diff --git a/ui/src/components/AuditLogs/auditLogs.interface.ts b/ui/src/components/AuditLogs/auditLogs.interface.ts index 91fe1eaea..6d45dd56e 100644 --- a/ui/src/components/AuditLogs/auditLogs.interface.ts +++ b/ui/src/components/AuditLogs/auditLogs.interface.ts @@ -34,7 +34,15 @@ export interface TableDataItem { 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; diff --git a/ui/src/components/AuditLogs/index.tsx b/ui/src/components/AuditLogs/index.tsx index 17131cad7..b9bb45123 100644 --- a/ui/src/components/AuditLogs/index.tsx +++ b/ui/src/components/AuditLogs/index.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from 'react'; import { useSelector } from 'react-redux'; import { useParams } from 'react-router'; -import { EmptyState, InfiniteScrollTable, Select } from '@contentstack/venus-components'; +import { Button, EmptyState, InfiniteScrollTable, Select } from '@contentstack/venus-components'; // Redux import { RootState } from '../../store'; // Service @@ -12,10 +12,12 @@ import { StackOption, FileOption, TableDataItem, - TableColumn + TableColumn, + FilterOption } from './auditLogs.interface'; import './index.scss'; +import AuditFilterModal from '../AuditFilterModal'; const AuditLogs: React.FC = () => { const params = useParams<{ projectId?: string }>(); @@ -32,6 +34,11 @@ const AuditLogs: React.FC = () => { const [totalCounts, setTotalCounts] = useState(0); const [currentPage, setCurrentPage] = useState(1); const [tableKey, setTableKey] = useState(0); + const [filterV, setFilterV] = useState('all'); + const [filterValue, setFilterValue] = useState([]); + const [isCursorInside, setIsCursorInside] = useState(false); + const [isFilterApplied, setIsFilterApplied] = useState(false); + const [isFilterDropdownOpen, setIsFilterDropdownOpen] = useState(false); const selectedOrganisation = useSelector( (state: RootState) => state?.authentication?.selectedOrganisation @@ -83,12 +90,20 @@ const AuditLogs: React.FC = () => { setCurrentPage(1); setSearchText(''); setTotalCounts(0); + // Reset filter values when file selection changes + setFilterValue([]); + setFilterV('all'); + setIsFilterApplied(false); }; const handleFileChange = async (selectedOption: FileOption | null) => { setSelectedFile(selectedOption); setCurrentPage(1); setSearchText(''); + // Reset filter values when file selection changes + setFilterValue([]); + setFilterV('all'); + setIsFilterApplied(false); if (selectedOption) { const columns = generateColumnsForFile(selectedOption.value); @@ -101,15 +116,13 @@ const AuditLogs: React.FC = () => { } }; - - const handleSearchChange = (value: string) => { setSearchText(value); setCurrentPage(1); setTableKey(prevKey => prevKey + 1); }; - const generateColumnsForFile = (fileName: string): TableColumn[] => { + const generateColumnsForFile = (fileName: string) => { const renderCell = (value: any) => (
{value}
); @@ -141,7 +154,8 @@ const AuditLogs: React.FC = () => { disableSortBy: true, disableResizing: false, canDragDrop: true, - width: 200 + width: 200, + filter: ColumnFilter }, { Header: 'Missing Reference', @@ -210,7 +224,7 @@ const AuditLogs: React.FC = () => { disableSortBy: true, disableResizing: false, canDragDrop: true, - width: 200 + width: 200, }, { Header: 'Display Type', @@ -219,7 +233,8 @@ const AuditLogs: React.FC = () => { disableSortBy: true, disableResizing: false, canDragDrop: true, - width: 200 + width: 200, + filter: ColumnFilter }, { Header: 'Missing Select Value', @@ -238,20 +253,6 @@ const AuditLogs: React.FC = () => { disableResizing: false, canDragDrop: true, width: 250 - }, - { - Header: 'Fix Status', - accessor: (data: TableDataItem) => { - const status = data.fixStatus; - return ( -
{status}
- ); - }, - addToColumnSelector: true, - disableSortBy: true, - disableResizing: false, - canDragDrop: true, - width: 200 } ]; } @@ -261,6 +262,114 @@ const AuditLogs: React.FC = () => { } }; + const ColumnFilter = () => { + const closeModal = () => { + setIsFilterDropdownOpen(false); + }; + + const openFilterDropdown = () => { + if (!isFilterDropdownOpen) { + setIsFilterDropdownOpen(true); + } + }; + + const handleClickOutside = () => { + if (!isCursorInside) { + closeModal(); + } + }; + + useEffect(() => { + document.addEventListener('click', handleClickOutside, false); + return () => { + document.removeEventListener('click', handleClickOutside, false); + }; + }, [isCursorInside]); + + const iconProps = { + className: isFilterApplied + ? 'filterWithAppliedIcon Icon--v2 Icon--medium' + : 'defaultFilterIcon Icon--v2 Icon--medium', + 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) { + // Remove the old value and keep updated one in case old value exists + 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); + } + }; + + // Method to apply filter + const onApply = () => { + try { + if (!filterValue.length) { + const newFilter = 'all'; + setFilterV(newFilter); + fetchTableData({ filter: newFilter }); + closeModal(); + setIsFilterApplied(false); + return; + } + + const usersQueryArray = filterValue.map((item) => item.value); + const newFilter = usersQueryArray.length > 1 ? usersQueryArray.join('-') : usersQueryArray[0]; + + setFilterV(newFilter); + fetchTableData({ filter: newFilter }); + setIsFilterApplied(true); + closeModal(); + } catch (error) { + console.error('Error applying filter:', error); + } + }; + + return ( +
{ + setIsCursorInside(true); + }} + onMouseLeave={() => { + setIsCursorInside(false); + }}> +
+ ); + }; + // Fetch table data function for pagination and other table operations const fetchTableData = async ({ skip = 0, @@ -268,6 +377,7 @@ const AuditLogs: React.FC = () => { startIndex = 0, stopIndex = 30, searchText = 'null', + filter = filterV, }) => { if (!selectedStack || !selectedFile || !selectedOrganisation?.value) { return { data: [], count: 0 }; @@ -286,7 +396,8 @@ const AuditLogs: React.FC = () => { limit, startIndex, stopIndex, - searchText + searchText, + filter ); if (response.data) { @@ -407,7 +518,7 @@ const AuditLogs: React.FC = () => { customEmptyState={ { } />
- ); }; diff --git a/ui/src/services/api/project.service.ts b/ui/src/services/api/project.service.ts index a380ae388..9fa4bd82d 100644 --- a/ui/src/services/api/project.service.ts +++ b/ui/src/services/api/project.service.ts @@ -20,9 +20,9 @@ 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) => { +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}`, options()); + 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}`); From 6381d51379a30e17a8bcde5c7fce3c226738315f Mon Sep 17 00:00:00 2001 From: samarp-jain Date: Mon, 28 Apr 2025 13:27:08 +0530 Subject: [PATCH 06/37] feat: implement filter functionality in audit data retrieval and enhance AuditFilterModal component --- api/src/services/migration.service.ts | 10 ++++++++ ui/src/components/AuditFilterModal/index.tsx | 7 ++++-- ui/src/components/AuditLogs/index.scss | 10 ++++++++ ui/src/components/AuditLogs/index.tsx | 26 +++++++++----------- 4 files changed, 36 insertions(+), 17 deletions(-) diff --git a/api/src/services/migration.service.ts b/api/src/services/migration.service.ts index bbf764a90..93e1294ec 100644 --- a/api/src/services/migration.service.ts +++ b/api/src/services/migration.service.ts @@ -210,7 +210,17 @@ const getAuditData = async (req: Request): Promise => { // Transform and flatten the data with sequential tuid let transformedData = transformAndFlattenData(fileData); + if (filter != "all") { + const filters = filter.split("-"); + transformedData = transformedData.filter((log) => { + return filters.some((filter) => { + return ( + log?.level?.toLowerCase()?.includes(filter?.toLowerCase()) + ); + }); + }); + } // Apply search filter if searchText is provided and not "null" if (searchText && searchText !== "null") { transformedData = transformedData.filter((item: any) => { diff --git a/ui/src/components/AuditFilterModal/index.tsx b/ui/src/components/AuditFilterModal/index.tsx index edec02e81..e34f8fb35 100644 --- a/ui/src/components/AuditFilterModal/index.tsx +++ b/ui/src/components/AuditFilterModal/index.tsx @@ -59,10 +59,13 @@ const AuditFilterModal = ({ setFilterValue([]); }; - if (!isOpen) return null; + console.info("Filter modal render - isOpen:", isOpen); + console.info("Selected file type:", selectedFileType); + console.info("Filter options:", filterOptions); + // Always render the component, let the parent control visibility return ( -
+
{/* Modal header */}
diff --git a/ui/src/components/AuditLogs/index.scss b/ui/src/components/AuditLogs/index.scss index c2fd9c69d..590625fec 100644 --- a/ui/src/components/AuditLogs/index.scss +++ b/ui/src/components/AuditLogs/index.scss @@ -19,6 +19,16 @@ width: calc(100% - 15rem); } +.Table__head__column { + align-items: center; + display: flex; + justify-content: space-between; +} + +.Table__head { + height: auto; +} + .Table:has(.custom-empty-state) { height: 40.5rem; } diff --git a/ui/src/components/AuditLogs/index.tsx b/ui/src/components/AuditLogs/index.tsx index b9bb45123..7b3b984d6 100644 --- a/ui/src/components/AuditLogs/index.tsx +++ b/ui/src/components/AuditLogs/index.tsx @@ -275,17 +275,9 @@ const AuditLogs: React.FC = () => { const handleClickOutside = () => { if (!isCursorInside) { - closeModal(); + closeModal && closeModal(); } }; - - useEffect(() => { - document.addEventListener('click', handleClickOutside, false); - return () => { - document.removeEventListener('click', handleClickOutside, false); - }; - }, [isCursorInside]); - const iconProps = { className: isFilterApplied ? 'filterWithAppliedIcon Icon--v2 Icon--medium' @@ -338,16 +330,20 @@ const AuditLogs: React.FC = () => { } 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); - }}> + + > - - - + + + +
-
- ); + ) :
); }; export default AuditFilterModal; \ No newline at end of file diff --git a/ui/src/components/AuditLogs/index.tsx b/ui/src/components/AuditLogs/index.tsx index 7b3b984d6..e2c3a6eb3 100644 --- a/ui/src/components/AuditLogs/index.tsx +++ b/ui/src/components/AuditLogs/index.tsx @@ -15,10 +15,8 @@ import { TableColumn, FilterOption } from './auditLogs.interface'; - import './index.scss'; import AuditFilterModal from '../AuditFilterModal'; - const AuditLogs: React.FC = () => { const params = useParams<{ projectId?: string }>(); const [loading, setLoading] = useState(false); @@ -36,15 +34,14 @@ const AuditLogs: React.FC = () => { const [tableKey, setTableKey] = useState(0); const [filterV, setFilterV] = useState('all'); const [filterValue, setFilterValue] = useState([]); - const [isCursorInside, setIsCursorInside] = useState(false); + const [isCursorInside, setIsCursorInside] = useState(true); const [isFilterApplied, setIsFilterApplied] = useState(false); const [isFilterDropdownOpen, setIsFilterDropdownOpen] = useState(false); - + const [dropDownOptions, setDropDownOptions] = useState(); const selectedOrganisation = useSelector( (state: RootState) => state?.authentication?.selectedOrganisation ); const stacks = useSelector((state: RootState) => state?.migration?.newMigrationData?.testStacks); - useEffect(() => { if (stacks && stacks.length > 0) { const formattedOptions: StackOption[] = stacks.map((stack: any) => ({ @@ -53,35 +50,30 @@ const AuditLogs: React.FC = () => { ...stack })); 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' }, + { label: 'Entries', value: 'Entries_Select_feild' } ]; setFileOptions(predefinedOptions); setInitialLoadComplete(true); } }; - const handleStackChange = async (selectedOption: StackOption | null) => { setSelectedStack(selectedOption); resetFileSelection(); - if (selectedOption) { updateFileOptionsForStack(selectedOption); } }; - const resetFileSelection = () => { setSelectedFile(null); setFileContent(null); @@ -95,189 +87,95 @@ const AuditLogs: React.FC = () => { setFilterV('all'); setIsFilterApplied(false); }; - + const fetchTableData = async ({ + skip = 0, + limit = 30, + startIndex = 0, + stopIndex = 30, + searchText = 'null', + filter = filterV + }) => { + 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) { + // Only update the state if this is not being called as part of table's internal operations + 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); setCurrentPage(1); setSearchText(''); // Reset filter values when file selection changes setFilterValue([]); setFilterV('all'); setIsFilterApplied(false); - if (selectedOption) { - const columns = generateColumnsForFile(selectedOption.value); - setTableColumns(columns); - - setTableKey(prevKey => prevKey + 1); + // const columns = generateColumnsForFile(selectedOption.value); + // setTableColumns(columns); + setTableKey((prevKey) => prevKey + 1); } else { setFileContent(null); setTableColumns([]); } }; - const handleSearchChange = (value: string) => { setSearchText(value); setCurrentPage(1); - setTableKey(prevKey => prevKey + 1); - }; - - const generateColumnsForFile = (fileName: string) => { - const renderCell = (value: any) => ( -
{value}
- ); - - if (fileName.includes('content-types') || fileName.includes('global-fields')) { - return [ - { - Header: 'Title', - accessor: (data: TableDataItem) => renderCell(data.name || data.ct_uid || '-'), - 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 = data.missingRefs ? - (typeof data.missingRefs === 'string' ? data.missingRefs : data.missingRefs.join(', ')) : - '-'; - 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) => { - const status = data.fixStatus; - - return ( -
{status}
- ); - }, - addToColumnSelector: true, - disableSortBy: true, - disableResizing: false, - canDragDrop: true, - width: 200 - } - ]; - } - else if (fileName.includes('Entries')) { - return [ - { - 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 - } - ]; - } - else { - // Default columns for other file types - return []; - } + setTableKey((prevKey) => prevKey + 1); }; - const ColumnFilter = () => { const closeModal = () => { + console.info(isFilterDropdownOpen); setIsFilterDropdownOpen(false); }; - const openFilterDropdown = () => { if (!isFilterDropdownOpen) { + console.info('openFilterDropdown'); setIsFilterDropdownOpen(true); } + setIsFilterDropdownOpen(true); + console.info('isFilterDropdownOpen', isFilterDropdownOpen); }; - - const handleClickOutside = () => { - if (!isCursorInside) { - closeModal && closeModal(); - } - }; + // const handleClickOutside = () => { + // if (!isCursorInside) { + // closeModal && closeModal(); + // } + // }; const iconProps = { className: isFilterApplied ? 'filterWithAppliedIcon Icon--v2 Icon--medium' @@ -286,12 +184,10 @@ const AuditLogs: React.FC = () => { 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) { @@ -301,13 +197,16 @@ const AuditLogs: React.FC = () => { } 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(); + } + }; // Method to apply filter const onApply = () => { try { @@ -319,10 +218,9 @@ const AuditLogs: React.FC = () => { setIsFilterApplied(false); return; } - const usersQueryArray = filterValue.map((item) => item.value); - const newFilter = usersQueryArray.length > 1 ? usersQueryArray.join('-') : usersQueryArray[0]; - + const newFilter = + usersQueryArray.length > 1 ? usersQueryArray.join('-') : usersQueryArray[0]; setFilterV(newFilter); fetchTableData({ filter: newFilter }); setIsFilterApplied(true); @@ -330,20 +228,21 @@ const AuditLogs: React.FC = () => { } catch (error) { console.error('Error applying filter:', error); } - }; useEffect(() => { document.addEventListener('click', handleClickOutside, false); return () => { document.removeEventListener('click', handleClickOutside, false); }; - } - , [isCursorInside]); - + }, [isCursorInside]); return (
+ onMouseEnter={() => { + setIsCursorInside(true); + }} + onMouseLeave={() => { + setIsCursorInside(false); + }}>
); }; - - // Fetch table data function for pagination and other table operations - const fetchTableData = async ({ - skip = 0, - limit = 30, - startIndex = 0, - stopIndex = 30, - searchText = 'null', - filter = filterV, - }) => { - if (!selectedStack || !selectedFile || !selectedOrganisation?.value) { - return { data: [], count: 0 }; + const renderCell = (value: any) =>
{value}
; + const contentTypeHeader = [ + { + Header: 'Title', + accessor: (data: TableDataItem) => renderCell(data.name || data.ct_uid || '-'), + 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 = data.missingRefs + ? typeof data.missingRefs === 'string' + ? data.missingRefs + : data.missingRefs.join(', ') + : '-'; + 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) => { + const status = data.fixStatus; + return
{status}
; + }, + addToColumnSelector: true, + disableSortBy: true, + disableResizing: false, + canDragDrop: true, + width: 200 } - - 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) { - // Only update the state if this is not being called as part of table's internal operations - 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 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 = (
@@ -451,37 +422,6 @@ const AuditLogs: React.FC = () => {
); - - const defaultEmptyColumns: TableColumn[] = [ - { - Header: 'Name', - accessor: (data: TableDataItem) =>
{data.name || '-'}
, - addToColumnSelector: true, - disableSortBy: true, - disableResizing: false, - canDragDrop: true, - width: 200 - }, - { - Header: 'Type', - accessor: (data: TableDataItem) =>
{data.type || '-'}
, - addToColumnSelector: true, - disableSortBy: true, - disableResizing: false, - canDragDrop: true, - width: 200 - }, - { - Header: 'Details', - accessor: (data: TableDataItem) =>
{data.details || '-'}
, - addToColumnSelector: true, - disableSortBy: true, - disableResizing: false, - canDragDrop: true, - width: 200 - } - ]; - return (
{ tableHeight={570} itemSize={80} data={tableData} - columns={tableColumns.length > 0 ? tableColumns : defaultEmptyColumns} - uniqueKey={"id"} + columns={dropDownOptions == 'content-types' || dropDownOptions == 'global-fields' ? contentTypeHeader : entryHeader} + uniqueKey={'id'} fetchTableData={fetchTableData} totalCounts={totalCounts} loading={loading} @@ -513,9 +453,19 @@ const AuditLogs: React.FC = () => { }} customEmptyState={ @@ -523,6 +473,5 @@ const AuditLogs: React.FC = () => { />
); -}; - +} export default AuditLogs; \ No newline at end of file From 89768bb5768783cdfab8acb1dc3890c5002e34c7 Mon Sep 17 00:00:00 2001 From: samarp-jain Date: Mon, 19 May 2025 14:31:03 +0530 Subject: [PATCH 08/37] refactor: streamline AuditLogs component by renaming filter state and enhancing data rendering logic --- ui/src/components/AuditLogs/index.tsx | 92 +++++++++++---------------- 1 file changed, 38 insertions(+), 54 deletions(-) diff --git a/ui/src/components/AuditLogs/index.tsx b/ui/src/components/AuditLogs/index.tsx index e2c3a6eb3..823e4a2e7 100644 --- a/ui/src/components/AuditLogs/index.tsx +++ b/ui/src/components/AuditLogs/index.tsx @@ -8,11 +8,9 @@ import { RootState } from '../../store'; import { getAuditData } from '../../services/api/project.service'; // Interfaces import { - FileData, StackOption, FileOption, TableDataItem, - TableColumn, FilterOption } from './auditLogs.interface'; import './index.scss'; @@ -20,19 +18,15 @@ import AuditFilterModal from '../AuditFilterModal'; const AuditLogs: React.FC = () => { const params = useParams<{ projectId?: string }>(); const [loading, setLoading] = useState(false); - const [initialLoadComplete, setInitialLoadComplete] = useState(false); const [selectedStack, setSelectedStack] = useState(null); const [stackOptions, setStackOptions] = useState([]); const [selectedFile, setSelectedFile] = useState(null); const [fileOptions, setFileOptions] = useState([]); - const [fileContent, setFileContent] = useState(null); const [searchText, setSearchText] = useState(''); - const [tableColumns, setTableColumns] = useState([]); const [tableData, setTableData] = useState([]); const [totalCounts, setTotalCounts] = useState(0); - const [currentPage, setCurrentPage] = useState(1); const [tableKey, setTableKey] = useState(0); - const [filterV, setFilterV] = useState('all'); + const [filterOption, setFilterOption] = useState('all'); const [filterValue, setFilterValue] = useState([]); const [isCursorInside, setIsCursorInside] = useState(true); const [isFilterApplied, setIsFilterApplied] = useState(false); @@ -42,13 +36,23 @@ const AuditLogs: React.FC = () => { (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.name || stack.stackName || stack.stackUid || '', - value: stack.stackUid || '', + 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]); @@ -64,7 +68,6 @@ const AuditLogs: React.FC = () => { { label: 'Entries', value: 'Entries_Select_feild' } ]; setFileOptions(predefinedOptions); - setInitialLoadComplete(true); } }; const handleStackChange = async (selectedOption: StackOption | null) => { @@ -76,15 +79,11 @@ const AuditLogs: React.FC = () => { }; const resetFileSelection = () => { setSelectedFile(null); - setFileContent(null); setTableData([]); - setTableColumns([]); - setCurrentPage(1); setSearchText(''); setTotalCounts(0); - // Reset filter values when file selection changes setFilterValue([]); - setFilterV('all'); + setFilterOption('all'); setIsFilterApplied(false); }; const fetchTableData = async ({ @@ -93,7 +92,7 @@ const AuditLogs: React.FC = () => { startIndex = 0, stopIndex = 30, searchText = 'null', - filter = filterV + filter = filterOption }) => { if (!selectedStack || !selectedFile || !selectedOrganisation?.value) { return { data: [], count: 0 }; @@ -138,24 +137,17 @@ const AuditLogs: React.FC = () => { setSelectedFile(selectedOption); console.info('selectedOption', selectedOption); setDropDownOptions(selectedOption?.value); - setCurrentPage(1); setSearchText(''); - // Reset filter values when file selection changes setFilterValue([]); - setFilterV('all'); + setFilterOption('all'); setIsFilterApplied(false); if (selectedOption) { - // const columns = generateColumnsForFile(selectedOption.value); - // setTableColumns(columns); + setTableKey((prevKey) => prevKey + 1); - } else { - setFileContent(null); - setTableColumns([]); } }; const handleSearchChange = (value: string) => { setSearchText(value); - setCurrentPage(1); setTableKey((prevKey) => prevKey + 1); }; const ColumnFilter = () => { @@ -171,11 +163,7 @@ const AuditLogs: React.FC = () => { setIsFilterDropdownOpen(true); console.info('isFilterDropdownOpen', isFilterDropdownOpen); }; - // const handleClickOutside = () => { - // if (!isCursorInside) { - // closeModal && closeModal(); - // } - // }; + const iconProps = { className: isFilterApplied ? 'filterWithAppliedIcon Icon--v2 Icon--medium' @@ -191,7 +179,6 @@ const AuditLogs: React.FC = () => { if (!filterValueCopy.length && isChecked) { filterValueCopy.push(value); } else if (isChecked) { - // Remove the old value and keep updated one in case old value exists const updatedFilter = filterValueCopy.filter((v) => v.value !== value.value); filterValueCopy = [...updatedFilter, value]; } else if (!isChecked) { @@ -207,12 +194,11 @@ const AuditLogs: React.FC = () => { closeModal && closeModal(); } }; - // Method to apply filter const onApply = () => { try { if (!filterValue.length) { const newFilter = 'all'; - setFilterV(newFilter); + setFilterOption(newFilter); fetchTableData({ filter: newFilter }); closeModal(); setIsFilterApplied(false); @@ -221,7 +207,7 @@ const AuditLogs: React.FC = () => { const usersQueryArray = filterValue.map((item) => item.value); const newFilter = usersQueryArray.length > 1 ? usersQueryArray.join('-') : usersQueryArray[0]; - setFilterV(newFilter); + setFilterOption(newFilter); fetchTableData({ filter: newFilter }); setIsFilterApplied(true); closeModal(); @@ -259,16 +245,16 @@ const AuditLogs: React.FC = () => { onApply={onApply} selectedLevels={filterValue} setFilterValue={setFilterValue} - selectedFileType={selectedFile?.value || ''} + selectedFileType={selectedFile?.value} />
); }; - const renderCell = (value: any) =>
{value}
; + const renderCell = (value: any) =>
{value ?? '-'}
; const contentTypeHeader = [ { Header: 'Title', - accessor: (data: TableDataItem) => renderCell(data.name || data.ct_uid || '-'), + accessor: (data: TableDataItem) => renderCell(data.name), addToColumnSelector: true, disableSortBy: true, disableResizing: false, @@ -277,7 +263,7 @@ const AuditLogs: React.FC = () => { }, { Header: 'Field Name', - accessor: (data: TableDataItem) => renderCell(data.display_name || '-'), + accessor: (data: TableDataItem) => renderCell(data.display_name), addToColumnSelector: true, disableSortBy: true, disableResizing: false, @@ -286,7 +272,7 @@ const AuditLogs: React.FC = () => { }, { Header: 'Field Type', - accessor: (data: TableDataItem) => renderCell(data.data_type || '-'), + accessor: (data: TableDataItem) => renderCell(data.data_type), addToColumnSelector: true, disableSortBy: true, disableResizing: false, @@ -297,11 +283,12 @@ const AuditLogs: React.FC = () => { { Header: 'Missing Reference', accessor: (data: TableDataItem) => { - const missing = data.missingRefs - ? typeof data.missingRefs === 'string' + const missing = Array.isArray(data.missingRefs) + ? data.missingRefs.join(', ') + : typeof data.missingRefs === 'string' ? data.missingRefs - : data.missingRefs.join(', ') - : '-'; + : '-'; + return renderCell(missing); }, addToColumnSelector: true, @@ -312,7 +299,7 @@ const AuditLogs: React.FC = () => { }, { Header: 'Tree Structure', - accessor: (data: TableDataItem) => renderCell(data.treeStr || '-'), + accessor: (data: TableDataItem) => renderCell(data.treeStr), addToColumnSelector: true, disableSortBy: true, disableResizing: false, @@ -321,10 +308,7 @@ const AuditLogs: React.FC = () => { }, { Header: 'Fix Status', - accessor: (data: TableDataItem) => { - const status = data.fixStatus; - return
{status}
; - }, + accessor: (data: TableDataItem) => renderCell(data.fixStatus), addToColumnSelector: true, disableSortBy: true, disableResizing: false, @@ -335,7 +319,7 @@ const AuditLogs: React.FC = () => { const entryHeader = [ { Header: 'Entry UID', - accessor: (data: TableDataItem) => renderCell(data.uid || '-'), + accessor: (data: TableDataItem) => renderCell(data.uid), addToColumnSelector: true, disableSortBy: true, disableResizing: false, @@ -344,7 +328,7 @@ const AuditLogs: React.FC = () => { }, { Header: 'Name', - accessor: (data: TableDataItem) => renderCell(data.name || '-'), + accessor: (data: TableDataItem) => renderCell(data.name), addToColumnSelector: true, disableSortBy: true, disableResizing: false, @@ -353,7 +337,7 @@ const AuditLogs: React.FC = () => { }, { Header: 'Display Name', - accessor: (data: TableDataItem) => renderCell(data.display_name || '-'), + accessor: (data: TableDataItem) => renderCell(data.display_name), addToColumnSelector: true, disableSortBy: true, disableResizing: false, @@ -362,7 +346,7 @@ const AuditLogs: React.FC = () => { }, { Header: 'Display Type', - accessor: (data: TableDataItem) => renderCell(data.display_type || '-'), + accessor: (data: TableDataItem) => renderCell(data.display_type), addToColumnSelector: true, disableSortBy: true, disableResizing: false, @@ -372,7 +356,7 @@ const AuditLogs: React.FC = () => { }, { Header: 'Missing Select Value', - accessor: (data: TableDataItem) => renderCell(data.missingCTSelectFieldValues || '-'), + accessor: (data: TableDataItem) => renderCell(data.missingCTSelectFieldValues), addToColumnSelector: true, disableSortBy: true, disableResizing: false, @@ -381,7 +365,7 @@ const AuditLogs: React.FC = () => { }, { Header: 'Tree Structure', - accessor: (data: TableDataItem) => renderCell(data.treeStr || '-'), + accessor: (data: TableDataItem) => renderCell(data.treeStr ?? '-'), addToColumnSelector: true, disableSortBy: true, disableResizing: false, From 4a873cc0a485733621a0a1cce7a5935da5b14501 Mon Sep 17 00:00:00 2001 From: samarp-jain Date: Tue, 20 May 2025 18:37:11 +0530 Subject: [PATCH 09/37] feat: introduce auditLogsConstants for improved readability and maintainability in AuditLogs component --- ui/src/components/AuditLogs/index.tsx | 26 +++++++++++++------------- ui/src/utilities/constants.ts | 22 ++++++++++++++++++++++ 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/ui/src/components/AuditLogs/index.tsx b/ui/src/components/AuditLogs/index.tsx index 823e4a2e7..642df6e21 100644 --- a/ui/src/components/AuditLogs/index.tsx +++ b/ui/src/components/AuditLogs/index.tsx @@ -14,6 +14,7 @@ import { 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 }>(); @@ -161,13 +162,12 @@ const AuditLogs: React.FC = () => { setIsFilterDropdownOpen(true); } setIsFilterDropdownOpen(true); - console.info('isFilterDropdownOpen', isFilterDropdownOpen); }; const iconProps = { className: isFilterApplied - ? 'filterWithAppliedIcon Icon--v2 Icon--medium' - : 'defaultFilterIcon Icon--v2 Icon--medium', + ? auditLogsConstants.filterIcon.filterOn + : auditLogsConstants.filterIcon.filterOff, withTooltip: true, tooltipContent: 'Filter', tooltipPosition: 'left' @@ -186,7 +186,7 @@ const AuditLogs: React.FC = () => { } setFilterValue(filterValueCopy); } catch (error) { - console.error('Error updating filter value:', error); + // console.error('Error updating filter value:', error); } }; const handleClickOutside = () => { @@ -383,7 +383,7 @@ const AuditLogs: React.FC = () => { options={stackOptions} width="220px" maxWidth="220px" - placeholder="Select Stack" + placeholder={auditLogsConstants.placeholders.selectStack} isSearchable version="v2" isDisabled={loading} @@ -397,7 +397,7 @@ const AuditLogs: React.FC = () => { options={fileOptions} width="220px" maxWidth="220px" - placeholder="Select Module" + placeholder={auditLogsConstants.placeholders.selectModule} isSearchable version="v2" isDisabled={loading || !selectedStack || fileOptions.length === 0} @@ -428,7 +428,7 @@ const AuditLogs: React.FC = () => { isRowSelect={false} columnSelector={false} canSearch={true} - searchPlaceholder={'Search Audit Logs'} + searchPlaceholder={auditLogsConstants.placeholders.searchLogs} searchValue={searchText} onSearchChangeEvent={handleSearchChange} withExportCta={{ @@ -437,18 +437,18 @@ const AuditLogs: React.FC = () => { }} customEmptyState={ Date: Wed, 21 May 2025 11:20:02 +0530 Subject: [PATCH 10/37] style: remove unnecessary margin from select-box in AuditLogs component --- ui/src/components/AuditLogs/index.scss | 3 --- 1 file changed, 3 deletions(-) diff --git a/ui/src/components/AuditLogs/index.scss b/ui/src/components/AuditLogs/index.scss index 590625fec..9e0ef0d91 100644 --- a/ui/src/components/AuditLogs/index.scss +++ b/ui/src/components/AuditLogs/index.scss @@ -5,9 +5,6 @@ display: flex; margin-left: 1rem; - .select-box { - margin-bottom: 4px; - } } } From 31b982a0dc11a0e6826b6aed8d3e6e6e14cc6f99 Mon Sep 17 00:00:00 2001 From: samarp-jain Date: Wed, 21 May 2025 13:15:24 +0530 Subject: [PATCH 11/37] chore: update shelljs dependency to version 0.9.0 --- api/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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" From 3e001062066bd76353c65620d74ba7d6f7ce7f4f Mon Sep 17 00:00:00 2001 From: samarp-jain Date: Wed, 21 May 2025 14:58:04 +0530 Subject: [PATCH 12/37] fix: resolve audit log path using path.resolve for improved reliability --- api/src/services/migration.service.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/api/src/services/migration.service.ts b/api/src/services/migration.service.ts index 26a711ff0..1705dd505 100644 --- a/api/src/services/migration.service.ts +++ b/api/src/services/migration.service.ts @@ -167,7 +167,7 @@ const getAuditData = async (req: Request): Promise => { throw new BadRequestError("Migration data not found for this stack"); } - const auditLogPath = path.join(logsDir, stackFolder, "logs", "audit", "audit-report"); + const auditLogPath = path.resolve(logsDir, stackFolder, "logs", "audit", "audit-report"); if (!fs.existsSync(auditLogPath)) { throw new BadRequestError("Audit log path not found"); } @@ -182,7 +182,11 @@ const getAuditData = async (req: Request): Promise => { const fileModuleName = file.replace('.json', ''); if (fileModuleName === moduleName) { - const filePath = path.join(auditLogPath, file); + const filePath = path.resolve(auditLogPath, file); // Resolve path + if (!filePath.startsWith(auditLogPath)) { + throw new BadRequestError("Invalid file path"); + } + try { const content = fs.readFileSync(filePath, 'utf8'); fileData = JSON.parse(content); @@ -204,6 +208,7 @@ const getAuditData = async (req: Request): Promise => { } } + // If no matching module was found if (!fileData) { throw new BadRequestError(`No audit data found for module: ${moduleName}`); From bb3e96efc89bc43b74e3fdbc63051ca02af93716 Mon Sep 17 00:00:00 2001 From: samarp-jain Date: Wed, 21 May 2025 15:36:10 +0530 Subject: [PATCH 13/37] refactor: simplify audit data retrieval by directly resolving file path for module --- api/src/services/migration.service.ts | 40 ++++----------------------- 1 file changed, 6 insertions(+), 34 deletions(-) diff --git a/api/src/services/migration.service.ts b/api/src/services/migration.service.ts index 1705dd505..9b1022f1a 100644 --- a/api/src/services/migration.service.ts +++ b/api/src/services/migration.service.ts @@ -172,43 +172,15 @@ const getAuditData = async (req: Request): Promise => { throw new BadRequestError("Audit log path not found"); } - const files = fs.readdirSync(auditLogPath); - const jsonFiles = files.filter(file => file.endsWith('.json')); - let fileData = null; - let fileName = null; - - for (const file of jsonFiles) { - const fileModuleName = file.replace('.json', ''); - - if (fileModuleName === moduleName) { - const filePath = path.resolve(auditLogPath, file); // Resolve path - if (!filePath.startsWith(auditLogPath)) { - throw new BadRequestError("Invalid file path"); - } - - try { - const content = fs.readFileSync(filePath, 'utf8'); - fileData = JSON.parse(content); - fileName = file; - break; - } catch (err) { - logger.warn( - getLogMessage( - srcFunc, - `Failed to parse JSON file for module ${moduleName}: ${file}` - ) - ); - fileData = { - error: 'Failed to parse file', - }; - fileName = file; - break; - } - } + // Read and parse the JSON file for the module + const filePath = path.resolve(auditLogPath, `${moduleName}.json`); + let fileData; + if (fs.existsSync(filePath)) { + const fileContent = fs.readFileSync(filePath, 'utf8'); + fileData = JSON.parse(fileContent); } - // If no matching module was found if (!fileData) { throw new BadRequestError(`No audit data found for module: ${moduleName}`); From efcc858f5deded70d9b6cb84caa0192d8fdb6cf1 Mon Sep 17 00:00:00 2001 From: samarp-jain Date: Wed, 21 May 2025 15:42:27 +0530 Subject: [PATCH 14/37] fix: use fs.promises to read audit log file asynchronously --- api/src/services/migration.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/services/migration.service.ts b/api/src/services/migration.service.ts index 9b1022f1a..b96027048 100644 --- a/api/src/services/migration.service.ts +++ b/api/src/services/migration.service.ts @@ -177,7 +177,7 @@ const getAuditData = async (req: Request): Promise => { const filePath = path.resolve(auditLogPath, `${moduleName}.json`); let fileData; if (fs.existsSync(filePath)) { - const fileContent = fs.readFileSync(filePath, 'utf8'); + const fileContent = await fsPromises.readFile(filePath, 'utf8'); fileData = JSON.parse(fileContent); } From 5f8db6f7e2a4d12f74d0851084f1fcdc5d2e7797 Mon Sep 17 00:00:00 2001 From: samarp-jain Date: Fri, 23 May 2025 12:25:25 +0530 Subject: [PATCH 15/37] feat: enhance audit filter modal with new constants and improved styling and added null checks --- api/src/constants/index.ts | 12 +- api/src/controllers/migration.controller.ts | 2 +- api/src/services/migration.service.ts | 80 ++++----- .../AuditFilterModal/auditlog.interface.ts | 2 +- ui/src/components/AuditFilterModal/index.scss | 39 ++-- ui/src/components/AuditFilterModal/index.tsx | 168 +++++++----------- ui/src/components/AuditLogs/index.scss | 17 -- ui/src/components/AuditLogs/index.tsx | 21 ++- ui/src/components/Common/Settings/index.tsx | 7 +- ui/src/utilities/constants.ts | 5 + 10 files changed, 159 insertions(+), 194 deletions(-) diff --git a/api/src/constants/index.ts b/api/src/constants/index.ts index 79e68beb7..e87582114 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_AUDT_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 3261f8080..fdc53148e 100644 --- a/api/src/controllers/migration.controller.ts +++ b/api/src/controllers/migration.controller.ts @@ -51,7 +51,7 @@ const deleteTestStack = async (req: Request, res: Response): Promise => { }; const getAuditData = async (req: Request, res: Response): Promise => { const resp = await migrationService.getAuditData(req); - res.status(200).json(resp); + res.status(resp?.status).json(resp); }; const getLogs = async (req: Request, res: Response): Promise => { const resp = await migrationService.getLogs(req); diff --git a/api/src/services/migration.service.ts b/api/src/services/migration.service.ts index b96027048..2d04d9a59 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_AUDT_DATA } from "../constants/index.js"; import { HTTP_TEXTS, HTTP_CODES, @@ -118,11 +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( @@ -142,8 +142,8 @@ const createTestStack = async (req: Request): Promise => { }; const getAuditData = async (req: Request): Promise => { - const projectId = path.basename(req?.params?.projectId); - const stackId = path.basename(req?.params?.stackId); + 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); @@ -157,8 +157,8 @@ const getAuditData = async (req: Request): Promise => { } try { - const mainPath = process.cwd().split("migration-v2")[0]; - const logsDir = path.join(mainPath, "migration-v2", "api", "migration-data"); + const mainPath = process?.cwd()?.split?.(GET_AUDT_DATA?.MIGRATION)?.[0]; + const logsDir = path.join(mainPath, GET_AUDT_DATA?.MIGRATION, GET_AUDT_DATA?.API_DIR, GET_AUDT_DATA?.MIGRATION_DATA_DIR); const stackFolders = fs.readdirSync(logsDir); @@ -167,67 +167,55 @@ const getAuditData = async (req: Request): Promise => { throw new BadRequestError("Migration data not found for this stack"); } - const auditLogPath = path.resolve(logsDir, stackFolder, "logs", "audit", "audit-report"); + const auditLogPath = path.resolve(logsDir, stackFolder, GET_AUDT_DATA?.LOGS_DIR, GET_AUDT_DATA?.AUDIT_DIR, GET_AUDT_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`); + const filePath = path?.resolve(auditLogPath, `${moduleName}.json`); let fileData; if (fs.existsSync(filePath)) { const fileContent = await fsPromises.readFile(filePath, 'utf8'); - fileData = JSON.parse(fileContent); + 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 no matching module was found if (!fileData) { throw new BadRequestError(`No audit data found for module: ${moduleName}`); } - // Transform and flatten the data with sequential tuid - const filterKey = moduleName === 'Entries_Select_feild' ? 'display_type' : 'data_type'; - console.info("🚀 ~ getAuditData ~ filterKey:", filterKey) - let transformedData = transformAndFlattenData(fileData); - // console.info(transformedData) - if (filter != "all") { + if (filter != GET_AUDT_DATA?.FILTERALL) { const filters = filter.split("-"); - moduleName === 'Entries_Select_feild' ? transformedData = transformedData.filter((log) => { + transformedData = transformedData.filter((log) => { return filters.some((filter) => { - //eslint-disable-next-line - console.log("🚀 ~ getAuditData ~ filter:", log) return ( log?.display_type?.toLowerCase()?.includes(filter?.toLowerCase()) ); }); - }) : transformedData = transformedData.filter((log) => { - return filters.some((filter) => { - return ( - log?.data_type?.toLowerCase()?.includes(filter?.toLowerCase()) - ); - }); }); - } - // Apply search filter if searchText is provided and not "null" - if (searchText && searchText !== "null") { - transformedData = transformedData.filter((item: any) => { - // Adjust these fields based on your actual audit data structure + 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()) + value?.toLowerCase?.()?.includes(searchText.toLowerCase()) ); }); } - - // Apply pagination - const paginatedData = transformedData.slice(startIndex, stopIndex); + const paginatedData = transformedData?.slice?.(startIndex, stopIndex); return { data: paginatedData, - totalCount: transformedData.length + totalCount: transformedData?.length }; } catch (error: any) { @@ -257,36 +245,32 @@ const transformAndFlattenData = (data: any): Array<{ [key: string]: any, id: num // If data is already an array, use it directly data.forEach((item, index) => { flattenedItems.push({ - ...item, - uid: item.uid || `item-${index}` + ...item ?? {}, + uid: item?.uid || `item-${index}` }); }); } else if (typeof data === 'object' && data !== null) { - // Process object data Object.entries(data).forEach(([key, value]) => { if (Array.isArray(value)) { - // If property contains an array, flatten each item value.forEach((item, index) => { - flattenedItems.push({ - ...item, + flattenedItems?.push({ + ...item ?? {}, parentKey: key, uid: item.uid || `${key}-${index}` }); }); } else if (typeof value === 'object' && value !== null) { - // If property contains an object, add it as an item - flattenedItems.push({ + flattenedItems?.push({ ...value, key, - uid: (value as any).uid || key + uid: (value as any)?.uid || key }); } }); } - // Add sequential tuid to each item return flattenedItems.map((item, index) => ({ - ...item, + ...item ?? {}, id: index + 1 })); } catch (error) { diff --git a/ui/src/components/AuditFilterModal/auditlog.interface.ts b/ui/src/components/AuditFilterModal/auditlog.interface.ts index d83fe97bf..43cad689d 100644 --- a/ui/src/components/AuditFilterModal/auditlog.interface.ts +++ b/ui/src/components/AuditFilterModal/auditlog.interface.ts @@ -1,6 +1,6 @@ import { FilterOption } from "../AuditLogs/auditLogs.interface"; -export type FilterModaleProps = { +export type AuditFilterModalProps = { isOpen: boolean; closeModal: () => void; updateValue: (params: { value: FilterOption; isChecked: boolean }) => void; diff --git a/ui/src/components/AuditFilterModal/index.scss b/ui/src/components/AuditFilterModal/index.scss index 1c09cebbf..501fbb44d 100644 --- a/ui/src/components/AuditFilterModal/index.scss +++ b/ui/src/components/AuditFilterModal/index.scss @@ -3,19 +3,28 @@ .tableFilterModalStories { position: absolute; z-index: 1000; - width: 350px; + width: 250px; background-color: #ffffff; - border-radius: 12px; - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); - display: flex; - flex-direction: column; - max-height: 350px; - overflow: hidden; + border: 1px solid #e0e0e0; + border-radius: 4px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + display: block; + margin-top: 5px; font-family: 'Inter', sans-serif; } +.tableFilterModalStories.position-bottom { + top: auto !important; + bottom: 0px !important; +} + +.tableFilterModalStories.position-right { + left: auto !important; + right: 0px !important; +} + .tableFilterModalStories__header { - padding: 16px 16px; + padding: 16px; color: #3d3f4c; border-bottom: 1px solid #e5e7eb; display: flex; @@ -27,6 +36,12 @@ padding: 8px 16px; } +.tableFilterModalStories__no-data { + padding: 8px 16px; + color: #6b7280; + font-size: $size-font-medium; +} + .Checkbox { display: flex; align-items: center; @@ -62,6 +77,10 @@ justify-content: center; width: 32px; height: 32px; + cursor: pointer; + transition: + box-shadow 0.2s ease, + transform 0.2s ease; } .close-btn:hover { @@ -69,10 +88,6 @@ transform: scale(1.05); border-radius: 8px; background-color: #f0f1f3; - cursor: pointer; - transition: - box-shadow 0.2s ease, - transform 0.2s ease; } .close-btn:active { diff --git a/ui/src/components/AuditFilterModal/index.tsx b/ui/src/components/AuditFilterModal/index.tsx index ce1f7df8e..917778a00 100644 --- a/ui/src/components/AuditFilterModal/index.tsx +++ b/ui/src/components/AuditFilterModal/index.tsx @@ -1,17 +1,14 @@ import React, { useEffect, useRef } from 'react'; -import { Button, ButtonGroup, Checkbox, Icon } from '@contentstack/venus-components'; +import { + Button, + ButtonGroup, + Checkbox, + Icon +} from '@contentstack/venus-components'; import './index.scss'; import { FilterOption } from '../AuditLogs/auditLogs.interface'; - -interface AuditFilterModalProps { - isOpen: boolean; - closeModal: () => void; - updateValue: ({ value, isChecked }: { value: FilterOption; isChecked: boolean }) => void; - onApply: () => void; - selectedLevels: FilterOption[]; - setFilterValue: React.Dispatch>; - selectedFileType?: string; -} +import { auditLogsConstants } from '../../utilities/constants'; +import { AuditFilterModalProps } from './auditlog.interface'; const AuditFilterModal = ({ isOpen, @@ -23,21 +20,19 @@ const AuditFilterModal = ({ selectedFileType }: AuditFilterModalProps) => { const modalRef = useRef(null); - console.info("in modal", isOpen) - // Generate filter options based on selected file type - const getFilterOptions = () => { + + const getFilterOptions = (): FilterOption[] => { if (!selectedFileType) return []; - if (selectedFileType.includes('content-types') || selectedFileType.includes('global-fields')) { + if (selectedFileType?.includes?.('content-types') || selectedFileType?.includes?.('global-fields')) { return [ - { label: "global_field", value: "global_field" }, - { label: "reference", value: "reference" }, + { label: 'global_field', value: 'global_field' }, + { label: 'reference', value: 'reference' } ]; - } else if (selectedFileType.includes('Entries')) { - return [ - { label: 'dropdown', value: 'dropdown' }, + } - ]; + if (selectedFileType?.includes?.('Entries')) { + return [{ label: 'dropdown', value: 'dropdown' }]; } return []; @@ -49,102 +44,77 @@ const AuditFilterModal = ({ setFilterValue([]); }; - // Position the modal properly when it opens useEffect(() => { if (isOpen && modalRef.current) { - // Ensure the modal is positioned correctly - const modalElement = modalRef.current; - const rect = modalElement.getBoundingClientRect(); - const viewportHeight = window.innerHeight; - const viewportWidth = window.innerWidth; + const modalElement = modalRef?.current; + const rect = modalElement?.getBoundingClientRect(); + const viewportHeight = window?.innerHeight; + const viewportWidth = window?.innerWidth; - // Ensure the modal stays within the viewport if (rect.bottom > viewportHeight) { - modalElement.style.top = 'auto'; - modalElement.style.bottom = '0px'; + modalElement.classList.add('position-bottom'); } if (rect.right > viewportWidth) { - modalElement.style.left = 'auto'; - modalElement.style.right = '0px'; + modalElement.classList.add('position-right'); } } }, [isOpen]); + if (!isOpen) return
; + return ( - isOpen ? ( -
- {/* Modal header */} -
- - {selectedFileType?.includes('Entries') ? 'Display Type' : 'Field Type'} - -
- -
+
+
+ + {selectedFileType?.includes?.('Entries') ? 'Display Type' : 'Field Type'} + +
+
+
- {/* Modal Body */} -
    - {filterOptions.length > 0 ? ( - filterOptions.map((item) => ( -
  • -
    - v.value === item.value)} - onChange={(e: React.ChangeEvent) => - updateValue({ value: item, isChecked: e.target.checked }) - } - version="v2" - label={item.label} - className="text-size" - /> -
    -
  • - )) - ) : ( -
  • -
    - No filter options available for this selection +
      + {filterOptions?.length > 0 ? ( + filterOptions.map((item) => ( +
    • +
      + 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} +
    + )} +
- {/* Modal Footer */} -
- + + - - - -
+
- ) :
); +
+ ); }; -export default AuditFilterModal; \ No newline at end of file +export default AuditFilterModal; diff --git a/ui/src/components/AuditLogs/index.scss b/ui/src/components/AuditLogs/index.scss index 9e0ef0d91..b289e9acb 100644 --- a/ui/src/components/AuditLogs/index.scss +++ b/ui/src/components/AuditLogs/index.scss @@ -22,24 +22,7 @@ justify-content: space-between; } -.Table__head { - height: auto; -} .Table:has(.custom-empty-state) { height: 40.5rem; -} - -.custom-empty-state { - .Icon--original { - width: 240px !important; - height: auto !important; - max-width: 100%; - display: block; - margin: 0 auto; - } -} - -.TablePanel__list-count flex-v-center { - display: none; } \ No newline at end of file diff --git a/ui/src/components/AuditLogs/index.tsx b/ui/src/components/AuditLogs/index.tsx index 642df6e21..8708ebcf8 100644 --- a/ui/src/components/AuditLogs/index.tsx +++ b/ui/src/components/AuditLogs/index.tsx @@ -41,10 +41,10 @@ const AuditLogs: React.FC = () => { 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) { + if (stacks && stacks?.length > 0) { const formattedOptions: StackOption[] = stacks.map((stack: any) => ({ - label: stack.stackName, - value: stack.stackUid, + label: stack?.stackName, + value: stack?.stackUid, ...stack })); if (isMigDone && label1 && value1) { @@ -113,13 +113,12 @@ const AuditLogs: React.FC = () => { searchText, filter ); - if (response.data) { - // Only update the state if this is not being called as part of table's internal operations - setTableData(response.data.data || []); - setTotalCounts(response.data.totalCount || 0); + if (response?.data) { + setTableData(response?.data?.data || []); + setTotalCounts(response?.data?.totalCount || 0); return { - data: response.data.data || [], - count: response.data.totalCount || 0 + data: response?.data?.data || [], + count: response?.data?.totalCount || 0 }; } return { data: [], count: 0 }; @@ -245,7 +244,7 @@ const AuditLogs: React.FC = () => { onApply={onApply} selectedLevels={filterValue} setFilterValue={setFilterValue} - selectedFileType={selectedFile?.value} + selectedFileType={selectedFile?.value ?? ''} />
); @@ -410,7 +409,7 @@ const AuditLogs: React.FC = () => {
{
)} {active === cmsData?.execution_logs?.title && ( - + )} {active === "AuditLogs" && -
- -
+ + }
) diff --git a/ui/src/utilities/constants.ts b/ui/src/utilities/constants.ts index 7754b8a0a..5175b6579 100644 --- a/ui/src/utilities/constants.ts +++ b/ui/src/utilities/constants.ts @@ -177,5 +177,10 @@ export const auditLogsConstants = { emptyStateIcon: { noLogs: 'NoDataEmptyState', noMatch: 'NoSearchResult' + }, + filterModal: { + noFilterAvailabe: 'No Filters Available', + clearAll: 'Clear All', + apply: 'Apply' } }; From db6b964fc48dbda58cfc65e5a89302699cce73ae Mon Sep 17 00:00:00 2001 From: samarp-jain Date: Fri, 23 May 2025 12:29:38 +0530 Subject: [PATCH 16/37] refactor: streamline modal element access and update filter options rendering to use divs --- ui/src/components/AuditFilterModal/index.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ui/src/components/AuditFilterModal/index.tsx b/ui/src/components/AuditFilterModal/index.tsx index 917778a00..cbb4060de 100644 --- a/ui/src/components/AuditFilterModal/index.tsx +++ b/ui/src/components/AuditFilterModal/index.tsx @@ -46,10 +46,10 @@ const AuditFilterModal = ({ useEffect(() => { if (isOpen && modalRef.current) { - const modalElement = modalRef?.current; - const rect = modalElement?.getBoundingClientRect(); - const viewportHeight = window?.innerHeight; - const viewportWidth = window?.innerWidth; + 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'); @@ -74,10 +74,10 @@ const AuditFilterModal = ({
-
    +
    {filterOptions?.length > 0 ? ( filterOptions.map((item) => ( -
  • +
    v?.value === item?.value)} @@ -89,14 +89,14 @@ const AuditFilterModal = ({ className="text-size" />
    -
  • +
    )) ) : (
    {auditLogsConstants?.filterModal?.noFilterAvailabe}
    )} -
+