-
Notifications
You must be signed in to change notification settings - Fork 8
Feature/execution log #664
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
84ff0ee
48726a2
c308e7a
4d02b52
7dedfd1
3be3fc3
6ba63c1
1e6bc2d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -634,36 +634,78 @@ const startMigration = async (req: Request): Promise<any> => { | |
| }; | ||
|
|
||
| const getLogs = async (req: Request): Promise<any> => { | ||
| const projectId = path.basename(req?.params?.projectId); | ||
| const stackId = path.basename(req?.params?.stackId); | ||
| const srcFunc = 'getLogs'; | ||
|
|
||
| if (projectId.includes('..') || stackId.includes('..')) { | ||
| throw new BadRequestError('Invalid projectId or stackId'); | ||
| const projectId = req?.params?.projectId ? path.basename(req.params.projectId) : ''; | ||
| const stackId = req?.params?.stackId ? path.basename(req.params.stackId) : ''; | ||
| const limit = req?.params?.limit ? parseInt(req.params.limit) : 10; | ||
| const startIndex = req?.params?.startIndex ? parseInt(req.params.startIndex) : 0; | ||
| const stopIndex = startIndex + limit; | ||
| const searchText = req?.params?.searchText ?? null; | ||
| const filter = req?.params?.filter ?? 'all'; | ||
|
|
||
| const srcFunc = "getLogs"; | ||
|
|
||
| if (!projectId || !stackId || projectId.includes("..") || stackId.includes("..")) { | ||
| throw new BadRequestError("Invalid projectId or stackId"); | ||
| } | ||
|
|
||
| try { | ||
| const logsDir = path.join(process.cwd(), 'logs'); | ||
| const mainPath = process.cwd()?.split("migration-v2")?.[0]; | ||
| if (!mainPath) { | ||
| throw new BadRequestError("Invalid application path"); | ||
| } | ||
|
|
||
| const logsDir = path.join(mainPath, "migration-v2", "api", "logs"); | ||
| const loggerPath = path.join(logsDir, projectId, `${stackId}.log`); | ||
| const absolutePath = path.resolve(loggerPath); // Resolve the absolute path | ||
| const absolutePath = path.resolve(loggerPath); | ||
|
|
||
| if (!absolutePath.startsWith(logsDir)) { | ||
| throw new BadRequestError('Access to this file is not allowed.'); | ||
| throw new BadRequestError("Access to this file is not allowed."); | ||
| } | ||
|
|
||
| if (fs.existsSync(absolutePath)) { | ||
| const logs = await fs.promises.readFile(absolutePath, 'utf8'); | ||
| const logEntries = logs | ||
| .split('\n') | ||
| .map((line) => { | ||
| const logs = await fs.promises.readFile(absolutePath, "utf8"); | ||
| let logEntries = logs | ||
| ?.split("\n") | ||
| ?.map((line) => { | ||
| try { | ||
| return JSON.parse(line); | ||
| return line ? JSON.parse(line) : null; | ||
| } catch (error) { | ||
| return null; | ||
| } | ||
| }) | ||
| .filter((entry) => entry !== null); | ||
| return logEntries; | ||
| ?.filter((entry) => entry !== null); | ||
|
||
|
|
||
| if (!logEntries?.length) { | ||
| return { logs: [], total: 0 }; | ||
| } | ||
|
|
||
| logEntries = logEntries?.slice(1, logEntries.length - 2); | ||
|
||
|
|
||
| if (filter !== "all") { | ||
| const filters = filter?.split("-") ?? []; | ||
| logEntries = logEntries?.filter((log) => { | ||
| return filters?.some((filter) => { | ||
| return log?.level?.toLowerCase()?.includes(filter?.toLowerCase() ?? ''); | ||
| }); | ||
| }); | ||
| } | ||
|
|
||
| if (searchText !== "null") { | ||
| logEntries = logEntries?.filter((log) => { | ||
| return ( | ||
| log?.level?.toLowerCase()?.includes(searchText?.toLowerCase() ?? '') || | ||
|
||
| log?.message?.toLowerCase()?.includes(searchText?.toLowerCase() ?? '') || | ||
| log?.methodName?.toLowerCase()?.includes(searchText?.toLowerCase() ?? '') || | ||
| log?.timestamp?.toLowerCase()?.includes(searchText?.toLowerCase() ?? '') | ||
| ); | ||
| }); | ||
| } | ||
|
|
||
| const paginatedLogs = logEntries?.slice(startIndex, stopIndex) ?? []; | ||
| return { | ||
| logs: paginatedLogs, | ||
| total: logEntries?.length ?? 0, | ||
| }; | ||
| } else { | ||
| logger.error(getLogMessage(srcFunc, HTTP_TEXTS.LOGS_NOT_FOUND)); | ||
| throw new BadRequestError(HTTP_TEXTS.LOGS_NOT_FOUND); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -103,3 +103,8 @@ | |
| color: #6c5ce7 !important; | ||
| font-weight: 600; | ||
| } | ||
|
|
||
| .back-button{ | ||
| cursor: pointer; | ||
| margin-bottom: 20px ; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,3 @@ | ||
| // Libraries | ||
| import React, { useEffect, useState } from 'react'; | ||
| import { useSelector } from 'react-redux'; | ||
| import { Params, useNavigate, useParams } from 'react-router'; | ||
|
|
@@ -12,7 +11,8 @@ import { | |
| Textarea, | ||
| PageLayout, | ||
| Notification, | ||
| cbModal | ||
| cbModal, | ||
| ClipBoard | ||
| } from '@contentstack/venus-components'; | ||
|
|
||
| // Redux | ||
|
|
@@ -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 ExecutionLog from '../../../components/ExecutionLogs'; | ||
|
|
||
| /** | ||
| * Renders the Settings component. | ||
|
|
@@ -47,14 +48,19 @@ const Settings = () => { | |
| const [active, setActive] = useState<string>(); | ||
| const [currentHeader, setCurrentHeader] = useState<string>(); | ||
| const [projectName, setProjectName] = useState(''); | ||
| const [projectId, setProjectId] = useState(''); | ||
| const [projectDescription, setProjectDescription] = useState(''); | ||
|
|
||
| const selectedOrganisation = useSelector( | ||
| (state: RootState) => state?.authentication?.selectedOrganisation | ||
| ); | ||
|
|
||
| const currentStep = useSelector( | ||
| (state: RootState) => state?.migration?.newMigrationData?.project_current_step | ||
| ); | ||
|
|
||
| const navigate = useNavigate(); | ||
| const dispatch = useDispatch() | ||
| const dispatch = useDispatch(); | ||
|
|
||
| useEffect(() => { | ||
| const fetchData = async () => { | ||
|
|
@@ -78,6 +84,7 @@ const Settings = () => { | |
| if (status === 200) { | ||
|
||
| setProjectName(data?.name); | ||
| setProjectDescription(data?.description); | ||
| setProjectId(params?.projectId ?? ''); | ||
| } | ||
| }; | ||
|
|
||
|
|
@@ -124,29 +131,34 @@ const Settings = () => { | |
| }); | ||
| } | ||
| }; | ||
| const handleDeleteProject = async (closeModal: ()=> void): Promise<void> => { | ||
| //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<void> => { | ||
| //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 handleBack = () => { | ||
| navigate(`/projects/${params?.projectId}/migration/steps/${currentStep}`); | ||
| } | ||
|
|
||
| const handleClick = () => { | ||
| cbModal({ | ||
|
|
@@ -178,15 +190,13 @@ const Settings = () => { | |
| class="Button Button--secondary Button--size-large Button--icon-alignment-left Button--v2" | ||
| aria-label="Delete Project for deleting project" | ||
| type="button" | ||
| onClick={handleClick} | ||
| > | ||
| onClick={handleClick}> | ||
| <div className="flex-center"> | ||
| <div className="flex-v-center Button__mt-regular Button__visible"> | ||
| <Icon | ||
| icon="Delete" | ||
| version="v2" | ||
| data={cmsData?.project?.delete_project?.title} | ||
| ></Icon> | ||
| data={cmsData?.project?.delete_project?.title}></Icon> | ||
| </div> | ||
| </div> | ||
| </Button> | ||
|
|
@@ -214,8 +224,7 @@ const Settings = () => { | |
| aria-label="projectname" | ||
| version="v2" | ||
| value={projectName} | ||
| onChange={handleProjectNameChange} | ||
| ></TextInput> | ||
| onChange={handleProjectNameChange}></TextInput> | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
@@ -244,16 +253,19 @@ const Settings = () => { | |
| icon={'v2-Save'} | ||
|
||
| autoClose={5000} | ||
| label={'Success'} | ||
| onClick={handleUpdateProject} | ||
| > | ||
| onClick={handleUpdateProject}> | ||
| {cmsData?.project?.save_project?.title} | ||
| </Button> | ||
| </div> | ||
| </form> | ||
| </div> | ||
| </div> | ||
| )} | ||
| {active === cmsData?.execution_logs?.title && <div></div>} | ||
| {active === cmsData?.execution_logs?.title && ( | ||
| <div> | ||
|
||
| <ExecutionLog projectId={projectId} /> | ||
| </div> | ||
| )} | ||
| </div> | ||
| ) | ||
| }; | ||
|
|
@@ -265,8 +277,21 @@ const Settings = () => { | |
| data-testid="cs-section-header" | ||
| className="SectionHeader SectionHeader--extra-bold SectionHeader--medium SectionHeader--black SectionHeader--v2" | ||
| aria-label={cmsData?.title} | ||
| aria-level={1} | ||
| > | ||
| aria-level={1}> | ||
| <div> | ||
| <Icon | ||
| version="v2" | ||
| icon={'LeftArrow'} | ||
|
||
| size="medium" | ||
| onClick={() => { | ||
| handleBack(); | ||
| }} | ||
| withTooltip={true} | ||
| tooltipContent={'Back'} | ||
| tooltipPosition="right" | ||
| className='back-button' | ||
| /> | ||
| </div> | ||
| {cmsData?.title} | ||
| </div> | ||
|
|
||
|
|
@@ -281,6 +306,18 @@ const Settings = () => { | |
| }} | ||
| version="v2" | ||
| /> | ||
|
|
||
| <ListRow | ||
| rightArrow={true} | ||
| active={active === cmsData?.execution_logs?.title} | ||
| content={cmsData?.execution_logs?.title} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we also this v2 to at constant so we don’t need to change every time in each component |
||
| leftIcon={<Icon icon="ExecutionLog" version="v2" />} | ||
| onClick={() => { | ||
| setActive(cmsData?.execution_logs?.title); | ||
| setCurrentHeader(cmsData?.execution_logs?.title); | ||
| }} | ||
| version="v2" | ||
| /> | ||
| </div> | ||
| ) | ||
| }; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| export type LogEntry = { | ||
| level: string; | ||
| message: string; | ||
| methodName: string; | ||
| timestamp: string; | ||
| }; | ||
|
|
||
| export type StackIds = { | ||
| stackUid?: string; | ||
| stackName?: string; | ||
| isMigrated?: boolean; | ||
| }; | ||
|
|
||
|
|
||
| export type DropdownOption = { | ||
| label: string ; | ||
| value: string; | ||
| }; | ||
|
|
||
| export type FilterOption = { | ||
| label: string; | ||
| value: string; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| .Search-input-show { | ||
| margin-bottom: 8px; | ||
| width: 300px; | ||
| } | ||
| .Search .Search__search-icon { | ||
| top: calc(50% - 10px); | ||
| width: 20px; | ||
| } | ||
|
|
||
| .Search .Search__input.regular-corners { | ||
| margin-left: 8px; | ||
| } | ||
|
|
||
| .dropdown-wrapper { | ||
| margin-bottom: 8px; | ||
| } | ||
|
|
||
| .PageLayout--primary .PageLayout__leftSidebar + .PageLayout__content .PageLayout__body { | ||
| width: calc(100% - 15.4rem); | ||
| } | ||
|
|
||
| .Table__head__column { | ||
| align-items: center; | ||
| display: flex; | ||
| justify-content: space-between; | ||
| } | ||
|
|
||
| .Table__head{ | ||
| height: auto; | ||
| } | ||
|
|
||
| .Table:has(.custom-empty-state) { | ||
| height: 40.5rem; | ||
| } | ||
|
|
||
|
|
||
| .custom-empty-state { | ||
| .Icon--original { | ||
| width: 207px !important; | ||
| height: auto !important; | ||
| max-width: 100%; | ||
| display: block; | ||
| margin: 0 auto; | ||
| } | ||
| } | ||
|
|
||
| .select-container { | ||
| margin-bottom: 8px; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.