Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/components/assignment/assignment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ export const AssignmentComponent = () => {
refetchSubleft();
});
}
}, [lecture, assignment]);
}, [lecture, assignment, submissions.length]);

if (
isLoadingAssignment ||
Expand Down
35 changes: 7 additions & 28 deletions src/components/coursemanage/grading/edit-submission.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { Assignment } from '../../../model/assignment';
import { Submission } from '../../../model/submission';
import {
createOrOverrideEditRepository,
getLogs,
getSubmission,
pullSubmissionFiles,
pushSubmissionFiles
Expand All @@ -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();
Expand All @@ -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'],
Expand All @@ -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 => {
Expand Down Expand Up @@ -167,7 +158,7 @@ export const EditSubmission = () => {
sx={{ mr: 2 }}
variant="outlined"
size="small"
onClick={openLogs}
onClick={() => setShowLogs(true)}
>
Show Logs
</Button>
Expand Down Expand Up @@ -228,25 +219,13 @@ export const EditSubmission = () => {
Back
</Button>
</Toolbar>
<Dialog
<SubmissionLogs
open={showLogs}
onClose={() => setShowLogs(false)}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">{'Logs'}</DialogTitle>
<DialogContent>
<Typography
id="alert-dialog-description"
sx={{ fontSize: 10, fontFamily: "'Roboto Mono', monospace" }}
>
{logs || 'No logs available'}
</Typography>
</DialogContent>
<DialogActions>
<Button onClick={() => setShowLogs(false)}>Close</Button>
</DialogActions>
</Dialog>
lectureId={lecture.id}
assignmentId={assignment.id}
submissionId={manualGradeSubmission.id}
/>
</Stack>
);
};
82 changes: 25 additions & 57 deletions src/components/coursemanage/grading/grading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,16 @@ 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';
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';
Expand All @@ -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.
Expand Down Expand Up @@ -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<unknown>, 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<number | null>(null);
const handleOpenLogs = (event: React.MouseEvent<unknown>, id: number) => {
setSubmissionId(id);
setOpen(true);
};
const handleCloseLogs = () => {
setSubmissionId(null);
setOpen(false);
};

const updateSubmissions = (filter: 'none' | 'latest' | 'best') => {
Expand Down Expand Up @@ -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);
}}
/>
</TableCell>
<TableCell align="left">{getManualChip(row)}</TableCell>
Expand Down Expand Up @@ -546,6 +524,15 @@ export default function GradingTable() {
)}
</TableBody>
</Table>
{submissionId && (
<SubmissionLogs
lectureId={lecture.id}
assignmentId={assignment.id}
submissionId={submissionId}
open={open}
onClose={handleCloseLogs}
/>
)}
</Box>
<TablePagination
rowsPerPageOptions={[10, 25, 50]}
Expand All @@ -556,25 +543,6 @@ export default function GradingTable() {
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
/>
<Dialog
open={showLogs}
onClose={() => setShowLogs(false)}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">{'Logs'}</DialogTitle>
<DialogContent>
<Typography
id="alert-dialog-description"
sx={{ fontSize: 10, fontFamily: "'Roboto Mono', monospace" }}
>
{logs}
</Typography>
</DialogContent>
<DialogActions>
<Button onClick={() => setShowLogs(false)}>Close</Button>
</DialogActions>
</Dialog>
</Stack>
);
}
Expand Down
8 changes: 8 additions & 0 deletions src/components/coursemanage/grading/manual-grading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
4 changes: 3 additions & 1 deletion src/components/coursemanage/grading/table-toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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!`, {
Expand Down
123 changes: 123 additions & 0 deletions src/components/util/submission-logs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
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 (
<div key={index} style={{ color: 'white', fontFamily: 'monospace' }}>
{timestamp && <span style={{ color: 'gray' }}>{timestamp}</span>}{' '}
{logLevel && <span style={{ color: logLevelColor }}>{logLevel}</span>}{' '}
{message}
</div>
);
});
};

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 (
<div>
<Card>
<LinearProgress />
</Card>
</div>
);
}
return (
<Modal open={props.open} onClose={props.onClose}>
<Card
style={{
width: '80%',
maxHeight: '80%',
margin: '5% auto',
padding: '16px',
overflowY: 'auto'
}}
>
<CardHeader title="Logs" />
<CardContent
style={{
backgroundColor: 'black',
borderRadius: '8px',
overflowY: 'auto',
maxHeight: '60vh',
color: 'white',
padding: '16px'
}}
>
{logs && logs.length > 0 ? (
formatLogs(logs)
) : (
<Typography style={{ color: 'red' }}>No Logs found.</Typography>
)}
</CardContent>

<CardActions style={{ justifyContent: 'flex-end' }}>
<Button variant="contained" onClick={props.onClose}>
Close
</Button>
</CardActions>
</Card>
</Modal>
);
};
Loading