From 857e86a7ebc72a4409bc03310d9a08735f9e624f Mon Sep 17 00:00:00 2001 From: Marijana Petojevic Date: Tue, 19 Nov 2024 21:59:31 +0100 Subject: [PATCH 1/2] feat: add component for submission logs and present logs clearly and well formatted, use black github style background, so they are also readable in dark theme, invalidate submission logs when submission gets autograded --- .../coursemanage/grading/edit-submission.tsx | 35 +---- .../coursemanage/grading/grading.tsx | 82 ++++-------- .../coursemanage/grading/manual-grading.tsx | 8 ++ .../coursemanage/grading/table-toolbar.tsx | 4 +- src/components/util/submission-logs.tsx | 125 ++++++++++++++++++ 5 files changed, 168 insertions(+), 86 deletions(-) create mode 100644 src/components/util/submission-logs.tsx diff --git a/src/components/coursemanage/grading/edit-submission.tsx b/src/components/coursemanage/grading/edit-submission.tsx index a0f3eb6..4f6c37b 100644 --- a/src/components/coursemanage/grading/edit-submission.tsx +++ b/src/components/coursemanage/grading/edit-submission.tsx @@ -14,7 +14,6 @@ import { Assignment } from '../../../model/assignment'; import { Submission } from '../../../model/submission'; import { createOrOverrideEditRepository, - getLogs, getSubmission, pullSubmissionFiles, pushSubmissionFiles @@ -28,6 +27,7 @@ import Toolbar from '@mui/material/Toolbar'; import { showDialog } from '../../util/dialog-provider'; import { GraderLoadingButton } from '../../util/loading-button'; import { useQuery } from '@tanstack/react-query'; +import { SubmissionLogs } from '../../util/submission-logs'; export const EditSubmission = () => { const navigate = useNavigate(); @@ -51,10 +51,6 @@ export const EditSubmission = () => { getSubmission(lecture.id, assignment.id, manualGradeSubmission.id, true) }); - const { data: logs, refetch: refetchLogs } = useQuery({ - queryKey: ['logs', lecture.id, assignment.id, manualGradeSubmission.id], - queryFn: () => getLogs(lecture.id, assignment.id, manualGradeSubmission.id) - }); const { data: submissionFiles, refetch: refetchSubmissionFiles } = useQuery({ queryKey: ['submissionFiles'], @@ -67,11 +63,6 @@ export const EditSubmission = () => { await refetchSubmission(); }; - const openLogs = async () => { - setShowLogs(true); - await refetchLogs(); - }; - const pushEditedFiles = async () => { await pushSubmissionFiles(lecture, assignment, submission).then( response => { @@ -167,7 +158,7 @@ export const EditSubmission = () => { sx={{ mr: 2 }} variant="outlined" size="small" - onClick={openLogs} + onClick={() => setShowLogs(true)} > Show Logs @@ -228,25 +219,13 @@ export const EditSubmission = () => { Back - setShowLogs(false)} - aria-labelledby="alert-dialog-title" - aria-describedby="alert-dialog-description" - > - {'Logs'} - - - {logs || 'No logs available'} - - - - - - + lectureId={lecture.id} + assignmentId={assignment.id} + submissionId={manualGradeSubmission.id} + /> ); }; diff --git a/src/components/coursemanage/grading/grading.tsx b/src/components/coursemanage/grading/grading.tsx index 967d49d..6b51e75 100644 --- a/src/components/coursemanage/grading/grading.tsx +++ b/src/components/coursemanage/grading/grading.tsx @@ -7,7 +7,6 @@ import TableHead from '@mui/material/TableHead'; import TablePagination from '@mui/material/TablePagination'; import TableRow from '@mui/material/TableRow'; import TableSortLabel from '@mui/material/TableSortLabel'; -import Typography from '@mui/material/Typography'; import Checkbox from '@mui/material/Checkbox'; import { visuallyHidden } from '@mui/utils'; import { Lecture } from '../../../model/lecture'; @@ -15,22 +14,9 @@ import { Assignment } from '../../../model/assignment'; import { Outlet, useNavigate, useOutletContext } from 'react-router-dom'; import { Submission } from '../../../model/submission'; import { utcToLocalFormat } from '../../../services/datetime.service'; -import { - Button, - Chip, - Dialog, - DialogActions, - DialogContent, - DialogTitle, - IconButton, - Stack -} from '@mui/material'; +import { Chip, IconButton, Stack } from '@mui/material'; import { SectionTitle } from '../../util/section-title'; -import { enqueueSnackbar } from 'notistack'; -import { - getAllSubmissions, - getLogs -} from '../../../services/submissions.service'; +import { getAllSubmissions } from '../../../services/submissions.service'; import { EnhancedTableToolbar } from './table-toolbar'; import EditNoteOutlinedIcon from '@mui/icons-material/EditNoteOutlined'; import { green } from '@mui/material/colors'; @@ -44,6 +30,7 @@ import { getAssignment } from '../../../services/assignments.service'; import { useQuery } from '@tanstack/react-query'; import { getLecture } from '../../../services/lectures.service'; import { extractIdsFromBreadcrumbs } from '../../util/breadcrumbs'; +import { SubmissionLogs } from '../../util/submission-logs'; /** * Calculates chip color based on submission status. @@ -305,33 +292,21 @@ export default function GradingTable() { | 'best' ); - const [showLogs, setShowLogs] = React.useState(false); - const [logs, setLogs] = React.useState(undefined); - const [search, setSearch] = React.useState(''); React.useEffect(() => { updateSubmissions(shownSubmissions); }, []); - /** - * Opens log dialog which contain autograded logs from grader service. - * @param event the click event - * @param submissionId submission for which to show logs - */ - const openLogs = (event: React.MouseEvent, submissionId: number) => { - getLogs(lecture.id, assignment.id, submissionId).then( - logs => { - setLogs(logs); - setShowLogs(true); - }, - error => { - enqueueSnackbar('No logs for submission', { - variant: 'error' - }); - } - ); - event.stopPropagation(); + const [open, setOpen] = React.useState(false); + const [submissionId, setSubmissionId] = React.useState(null); + const handleOpenLogs = (event: React.MouseEvent, id: number) => { + setSubmissionId(id); + setOpen(true); + }; + const handleCloseLogs = () => { + setSubmissionId(null); + setOpen(false); }; const updateSubmissions = (filter: 'none' | 'latest' | 'best') => { @@ -513,7 +488,10 @@ export default function GradingTable() { label={row.auto_status.split('_').join(' ')} color={getColor(row.auto_status)} clickable={true} - onClick={event => openLogs(event, row.id)} + onClick={event => { + event.stopPropagation(); // prevents the event from bubbling up to the TableRow + handleOpenLogs(event, row.id); + }} /> {getManualChip(row)} @@ -546,6 +524,15 @@ export default function GradingTable() { )} + {submissionId && ( + + )} - setShowLogs(false)} - aria-labelledby="alert-dialog-title" - aria-describedby="alert-dialog-description" - > - {'Logs'} - - - {logs} - - - - - - ); } diff --git a/src/components/coursemanage/grading/manual-grading.tsx b/src/components/coursemanage/grading/manual-grading.tsx index 637ab12..5b9ba00 100644 --- a/src/components/coursemanage/grading/manual-grading.tsx +++ b/src/components/coursemanage/grading/manual-grading.tsx @@ -172,6 +172,14 @@ export const ManualGrading = () => { try { await autogradeSubmission(lecture, assignment, submission).then(() => { refetchSubmission(); + queryClient.invalidateQueries({ + queryKey: [ + 'submissionLogs', + lecture.id, + assignment.id, + submission.id + ] + }); }); enqueueSnackbar('Autograding submission!', { variant: 'success' diff --git a/src/components/coursemanage/grading/table-toolbar.tsx b/src/components/coursemanage/grading/table-toolbar.tsx index 7f67e65..59f6fcd 100644 --- a/src/components/coursemanage/grading/table-toolbar.tsx +++ b/src/components/coursemanage/grading/table-toolbar.tsx @@ -31,7 +31,6 @@ import { Submission } from '../../../model/submission'; import { showDialog } from '../../util/dialog-provider'; import AddIcon from '@mui/icons-material/Add'; import { Link } from 'react-router-dom'; -import { openBrowser } from '../overview/util'; import SearchIcon from '@mui/icons-material/Search'; import ClearIcon from '@mui/icons-material/Clear'; import { queryClient } from '../../../widgets/assignmentmanage'; @@ -159,6 +158,9 @@ export function EnhancedTableToolbar(props: EnhancedTableToolbarProps) { const row = rows.find(value => value.id === id); row.auto_status = 'pending'; await autogradeSubmission(lecture, assignment, row); + await queryClient.invalidateQueries({ + queryKey: ['submissionLogs', lecture.id, assignment.id, row.id] + }); }) ); enqueueSnackbar(`Autograding ${numSelected} submissions!`, { diff --git a/src/components/util/submission-logs.tsx b/src/components/util/submission-logs.tsx new file mode 100644 index 0000000..5a804ff --- /dev/null +++ b/src/components/util/submission-logs.tsx @@ -0,0 +1,125 @@ +import { + Button, + Card, + CardActions, + CardContent, + CardHeader, + LinearProgress, + Modal, + Paper, + Typography +} from '@mui/material'; +import * as React from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { getLogs } from '../../services/submissions.service'; + +interface SubmissionLogsProps { + lectureId: number; + assignmentId: number; + submissionId: number; + onClose: () => void; + open: boolean; +} + +const formatLogs = (logs: string): JSX.Element[] => { + return logs.split('\n').map((line, index) => { + const timestampMatch = line.match( + /^\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}]/ + ); + const logLevelMatch = line.match(/\[(INFO|WARNING|ERROR|DEBUG)]/); + + const timestamp = timestampMatch ? timestampMatch[0] : ''; + const logLevel = logLevelMatch ? logLevelMatch[0] : ''; + const message = line.replace(timestamp, '').replace(logLevel, '').trim(); + + let logLevelColor = 'white'; + if (logLevelMatch) { + switch (logLevelMatch[1]) { + case 'INFO': + logLevelColor = 'lightGreen'; + break; + case 'WARNING': + logLevelColor = 'orange'; + break; + case 'ERROR': + logLevelColor = 'red'; + break; + case 'DEBUG': + logLevelColor = 'lightBlue'; + break; + default: + logLevelColor = 'white'; + } + } + + return ( +
+ {timestamp && {timestamp}}{' '} + {logLevel && {logLevel}}{' '} + {message} +
+ ); + }); +}; + +export const SubmissionLogs = (props: SubmissionLogsProps) => { + const { data: logs, isLoading: isLoadingLogs } = useQuery({ + queryKey: [ + 'submissionLogs', + props.lectureId, + props.assignmentId, + props.submissionId + ], + queryFn: () => + getLogs(props.lectureId, props.assignmentId, props.submissionId) + }); + + if (isLoadingLogs) { + return ( +
+ + + +
+ ); + } + return ( + + + + + {logs && logs.length > 0 ? ( + formatLogs(logs) + ) : ( + No Logs found. + )} + + + {/* Card Actions */} + + + + + + ); +}; From 1d7efbf6bfffc414b2952f695967b7618cc99cce Mon Sep 17 00:00:00 2001 From: Marijana Petojevic Date: Wed, 20 Nov 2024 18:18:08 +0100 Subject: [PATCH 2/2] fix: reload assignment active status everytime submission count changes --- src/components/assignment/assignment.tsx | 2 +- src/components/util/submission-logs.tsx | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/components/assignment/assignment.tsx b/src/components/assignment/assignment.tsx index 6d33ef1..e43761b 100644 --- a/src/components/assignment/assignment.tsx +++ b/src/components/assignment/assignment.tsx @@ -155,7 +155,7 @@ export const AssignmentComponent = () => { refetchSubleft(); }); } - }, [lecture, assignment]); + }, [lecture, assignment, submissions.length]); if ( isLoadingAssignment || diff --git a/src/components/util/submission-logs.tsx b/src/components/util/submission-logs.tsx index 5a804ff..aedf058 100644 --- a/src/components/util/submission-logs.tsx +++ b/src/components/util/submission-logs.tsx @@ -91,8 +91,7 @@ export const SubmissionLogs = (props: SubmissionLogsProps) => { maxHeight: '80%', margin: '5% auto', padding: '16px', - overflowY: 'auto', - fontFamily: "'Roboto Mono', monospace" + overflowY: 'auto' }} > @@ -113,7 +112,6 @@ export const SubmissionLogs = (props: SubmissionLogsProps) => { )} - {/* Card Actions */}