Skip to content

Commit

Permalink
CHG make autograde page submission tests same as what students see
Browse files Browse the repository at this point in the history
  • Loading branch information
wabscale committed Feb 21, 2024
1 parent 3a3fc32 commit 44eb39e
Show file tree
Hide file tree
Showing 4 changed files with 231 additions and 374 deletions.
306 changes: 226 additions & 80 deletions web/src/components/core/Submission/SubmissionTests.jsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
import React from 'react';
import React, {useState} from 'react';

import makeStyles from '@mui/styles/makeStyles';
import Typography from '@mui/material/Typography';
import Accordion from '@mui/material/Accordion';
import AccordionSummary from '@mui/material/AccordionSummary';
import AccordionDetails from '@mui/material/AccordionDetails';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import CircularProgress from '@mui/material/CircularProgress';
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import green from '@mui/material/colors/green';
import CancelIcon from '@mui/icons-material/Cancel';
import Fab from '@mui/material/Fab';
import AssessmentIcon from '@mui/icons-material/Assessment';
import HighlightOffIcon from '@mui/icons-material/HighlightOff';
import {Tooltip} from '@mui/material';
import IconButton from '@mui/material/IconButton';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import DeleteForeverIcon from '@mui/icons-material/DeleteForever';

import {useHistory} from 'react-router-dom';
import {useSnackbar} from 'notistack';
import axios from 'axios';
import clsx from 'clsx';

import SubmissionTestExpanded from '../SubmissionTestExpanded/SubmissionTestExpanded';
import SubmissionContent from '../SubmissionContent/SubmissionContent';
import SubmissionTest from '../SubmissionTest/SubmissionTest';
import SubmissionHeader from '../SubmissionHeader/SubmissionHeader';
import {submissionUpdateSubscribe} from '../../../constant';
import standardStatusHandler from '../../../utils/standardStatusHandler';
import {translateSubmission} from '../../../utils/submission';
import standardErrorHandler from '../../../utils/standardErrorHandler';


const useStyles = makeStyles((theme) => ({
heading: {
Expand All @@ -37,83 +44,222 @@ const useStyles = makeStyles((theme) => ({
left: -6,
zIndex: 1,
},
submissionContentContainer: {
width: '100%',
marginTop: theme.spacing(2),
},
}));

const regrade = (
{submission, setSubmission, setStep, setErrorStop},
continueSubscribe,
enqueueSnackbar,
) => () => {
if (!submission.processed) {
return enqueueSnackbar('Submission must first finish tests before regrading.', {variant: 'warning'});
}

axios
.get(`/api/public/submissions/regrade/${submission.commit}`)
.then((response) => {
const data = standardStatusHandler(response, enqueueSnackbar);
if (data) {
setErrorStop(false);
setStep(-1);
setSubmission(null);
continueSubscribe();
enqueueSnackbar('Regrading submission', {variant: 'success'});
} else {
enqueueSnackbar(`Unable to regrade`, {variant: 'error'});
}
})
.catch((error) => {
enqueueSnackbar(error.toString(), {variant: 'error'});
});
};


export default function SubmissionTests({tests, stop}) {
export default function SubmissionTests({submissionId}) {
const classes = useStyles();
const [step, setStep] = useState(0);
const [modalTest, setModalTest] = useState(null);
const [isExpanded, setIsExpanded] = useState(false);
const [submission, setSubmission] = useState(null);
const {enqueueSnackbar} = useSnackbar();
const history = useHistory();

const continueSubscribe = () => setTimeout(() => {
if (step < submissionUpdateSubscribe) {
setStep((state) => ++state);
}
}, 1000);

React.useEffect(() => {
if (!submissionId) {
return;
}
axios.get(
`/api/public/submissions/get/${submissionId}`,
).then((response) => {
const data = standardStatusHandler(response, enqueueSnackbar);
if (!data) {
return;
}

const newSubmission = translateSubmission(data.submission);

// sort all the tests in Alpha Order
newSubmission.tests.sort(function(a, b) {
return a.test.order > b.test.order;
});

if (!tests) {
setSubmission(newSubmission);


if (!submission) {
return continueSubscribe();
}

if (submission.build.passed !== newSubmission.build.passed) {
if (newSubmission.build.passed === true) {
enqueueSnackbar('Build passed', {variant: 'success'});
} else if (newSubmission.build.passed === false) {
return enqueueSnackbar('Build failed', {variant: 'error'});
}
}

for (let index = 0; index < submission.tests.length; index++) {
const oldTest = submission.tests[index];
const newTest = newSubmission.tests[index];

if (oldTest.result.passed === null && newTest.result.passed !== null) {
enqueueSnackbar(
`${newTest.test.name} ${newTest.result.passed ? 'passed' : 'failed'}`,
{variant: (newTest.result.passed ? 'success' : 'error')});
}
}

if (!submission.processed) {
continueSubscribe();
}
}).catch((error) => enqueueSnackbar(error.toString(), {variant: 'error'}));
}, [step, submissionId]);

const expandModal = (test) => {
setModalTest(test);
setIsExpanded(true);
};

const closeModal = () => {
setIsExpanded(false);
setModalTest(null);
};

if (!submissionId || !submission) {
return null;
}

const pipelineLogTest = {
test: {
name: 'Pipeline Log',
},
result: {
test_name: 'Pipeline Log',
passed: !!submission?.build?.passed,
message: 'Not Visible to Students',
output_type: 'text',
output: submission?.pipeline_log ?? null,
},
};

const buildTest = {
test: {
name: 'Build',
},
result: {
test_name: 'Build',
passed: !!submission?.build?.passed,
message: !!submission?.build?.passed ? 'Build Succeeded' : 'Build Failed',
output_type: 'text',
output: submission?.build?.stdout ?? null,
},
};


return (
<React.Fragment>
{tests.map((test, index) => (
<Accordion key={`test-${index}`}>
<AccordionSummary
expandIcon={<ExpandMoreIcon/>}
aria-controls="panel2a-content"
id="panel2a-header"
>
<div className={classes.wrapper}>
<Fab
aria-label="save"
color={stop ? 'error' : (test.result.passed === false ? 'error' : 'primary')}
>
{stop ? (
<CancelIcon/>
) : (
<React.Fragment>
{test.result.passed === null ? (
<AssessmentIcon/>
) : (test.result.passed === true) ? (
<CheckCircleIcon/>
) : (test.result.passed === false) ? (
<CancelIcon/>
) : null}
</React.Fragment>
)}
</Fab>
{stop ? null : (
test.result.passed === null && <CircularProgress size={68} className={classes.fabProgress}/>
)}
</div>
<Typography className={classes.heading}>{test.test.name}</Typography>
{test.test.hidden ? (
<div className={classes.hiddenIcon}>
<Tooltip title={'Test hidden to students'}>
<IconButton size="large">
<HighlightOffIcon/>
</IconButton>
</Tooltip>
</div>
) : null}
</AccordionSummary>
<AccordionDetails>
<div>
<Typography key={'message'} variant={'h5'} className={classes.heading}>
{test.result.message}
</Typography>
{test.result.passed !== null && !!test.result.stdout ?
test.result.stdout.trim().split('\n')
.map((line, index) => (
line.trim().length !== 0 ?
<Typography
variant={'body1'}
color={'textSecondary'}
width={100}
key={`line-${index}`}
>
{line}
</Typography> :
<br/>
)) :
null}
</div>
</AccordionDetails>
</Accordion>
))}
{!!submission?.pipeline_log && (
<Box sx={{m: 1}}>
<Tooltip title="Delete submission forever from Anubis">
<Button
variant={'contained'}
color={'error'}
startIcon={<DeleteForeverIcon/>}
className={clsx(classes.dataItem)}
onClick={() => {
axios.delete(`/api/admin/submissions/delete/${submissionId}`).then((response) => {
const data = standardStatusHandler(response, enqueueSnackbar);
if (data) {
history.go(-1);
}
}).catch(standardErrorHandler(enqueueSnackbar));
}}
>
Delete
</Button>
</Tooltip>
</Box>
)}
<Box className={classes.headerContainer}>
<SubmissionHeader
admin={!!submission?.pipeline_log}
regrade={regrade(
{submission, setSubmission, setStep},
continueSubscribe,
enqueueSnackbar,
)}
{...submission}
/>
</Box>
<Box className={classes.submissionContentContainer}>
<SubmissionContent submission={submission}>
{submission?.pipeline_log && (
<SubmissionTest
test={pipelineLogTest}
processing={submission.processing}
expandModal={() => expandModal(pipelineLogTest)}
/>
)}
{!submission.commit.startsWith('fake-') && (
<SubmissionTest
test={buildTest}
processing={submission.processing}
expandModal={() => expandModal(buildTest)}
/>
)}
{submission?.tests && submission.tests.map((test, index) => (
<SubmissionTest
key={index}
test={test}
processing={submission.processing}
expandModal={() => expandModal(test)}
/>
))}
</SubmissionContent>
</Box>
{modalTest &&
<SubmissionTestExpanded
open={isExpanded}
submissionID={submission.commit}
assignmentName={submission.assignment_name}
testName={modalTest.result.test_name}
testSuccess={modalTest.result.passed}
testOutputType={modalTest.result.output_type}
testOutput={modalTest.result.output}
testMessage={modalTest.result.message}
onClose={() => closeModal()}
/>
}
</React.Fragment>
);
}
10 changes: 2 additions & 8 deletions web/src/pages/core/admin/Autograde/Submission.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,8 @@ export default function Submission() {
<Grid item xs={12}>
<QuestionsCard questions={questions}/>
</Grid>
<Grid item xs={12} md={4}>
<SubmissionSummary submission={submission} regrade={null}/>
</Grid>
<Grid item xs={12} md={4}>
<SubmissionBuild build={submission?.build}/>
</Grid>
<Grid item xs={12} md={4}>
<SubmissionTests tests={submission?.tests}/>
<Grid item xs={12}>
<SubmissionTests submissionId={submission?.id}/>
</Grid>
</Grid>
</Grid>
Expand Down
Loading

0 comments on commit 44eb39e

Please sign in to comment.