diff --git a/grader_labextension/grader_labextension/handlers/version_control.py b/grader_labextension/grader_labextension/handlers/version_control.py index 7c8074ec..85520011 100644 --- a/grader_labextension/grader_labextension/handlers/version_control.py +++ b/grader_labextension/grader_labextension/handlers/version_control.py @@ -303,14 +303,8 @@ async def put(self, lecture_id: int, assignment_id: int, repo: str): self.log.error(e.response) raise HTTPError(e.code, reason=e.response.reason) - # TODO: similar logic for push instruction submission - # however we cannot push to edit repository when no submission exists - # reverse order: - # first create submission (with fake commit hash since autograder does not need any commit hash if submission is set to edited), - # then set submission to edited and update username to student username -> need to be able to set this later (in PUT?) -> also refactor SubmissionEditHandler? -> create new endpoint? - # then push to edit repository directly (not using SubmissionEditHandler), - - # differentiate between "normal" edit and "create" edit by sub_id -> if it is None we know we are in submission creation mode instead of edit mode + # differentiate between "normal" edit and "create" edit by sub_id -> if it is None we know we are in + # submission creation mode instead of edit mode if repo == "edit" and sub_id is None: submission = Submission(commit_hash="0" * 40) response: dict = await self.request_service.request( @@ -443,6 +437,7 @@ async def put(self, lecture_id: int, assignment_id: int, repo: str): git_service.push(f"grader_{repo}", force=True) except GitError as e: self.log.error("GitError:\n" + e.error) + git_service.undo_commit() raise HTTPError(HTTPStatus.INTERNAL_SERVER_ERROR, reason=str(e.error)) if submit and repo == "assignment": diff --git a/grader_labextension/grader_labextension/services/git.py b/grader_labextension/grader_labextension/services/git.py index e00d18a2..c0b64751 100644 --- a/grader_labextension/grader_labextension/services/git.py +++ b/grader_labextension/grader_labextension/services/git.py @@ -140,6 +140,12 @@ def go_to_commit(self, commit_hash): self.log.info(f"Show commit with hash {commit_hash}") self._run_command(f"git checkout {commit_hash}", cwd=self.path) + def undo_commit(self, n: int = 1) -> None: + self.log.info(f"Undoing {n} commit(s)") + self._run_command(f"git reset --hard HEAD~{n}", cwd=self.path) + self._run_command(f"git gc", cwd=self.path) + + def pull(self, origin: str, branch="main", force=False): """Pulls a repository diff --git a/grader_labextension/src/components/coursemanage/assignment.tsx b/grader_labextension/src/components/coursemanage/assignment.tsx deleted file mode 100644 index 4087d3df..00000000 --- a/grader_labextension/src/components/coursemanage/assignment.tsx +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright (c) 2022, TU Wien -// All rights reserved. -// -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. - -import * as React from 'react'; -import { - Box, - Card, - CardActionArea, - CardContent, - Divider, - Typography -} from '@mui/material'; - -import { Assignment } from '../../model/assignment'; -import { Lecture } from '../../model/lecture'; -import { - getAllSubmissions, - getProperties -} from '../../services/submissions.service'; -import { - getAssignment, - getAssignmentProperties -} from '../../services/assignments.service'; -import { AssignmentModalComponent } from './assignment-modal'; -import { DeadlineComponent } from '../util/deadline'; -import { blue } from '@mui/material/colors'; -import { getFiles, lectureBasePath } from '../../services/file.service'; -import { openBrowser } from './overview/util'; -import { CardDescriptor } from '../util/card-descriptor'; -import { enqueueSnackbar } from 'notistack'; -import { - deleteKey, - loadNumber, - storeNumber -} from '../../services/storage.service'; -import { Link } from 'react-router-dom'; - -/** - * Props for AssignmentComponent. - */ -export interface IAssignmentComponentProps { - lecture: Lecture; - assignment: Assignment; - root: HTMLElement; - users: any; - onDeleted: () => void; -} - -/** - * Renders an assignment card which opens onclick the assignment modal. - * @param props Props of assignment functional component - * @constructor - */ -export const AssignmentComponent = (props: IAssignmentComponentProps) => { - const [assignment, setAssignment] = React.useState(props.assignment); - const [displaySubmissions, setDisplaySubmissions] = React.useState( - loadNumber('cm-opened-assignment') === props.assignment.id || false - ); - const [files, setFiles] = React.useState([]); - const onSubmissionClose = async () => { - setDisplaySubmissions(false); - deleteKey('cm-opened-assignment'); - setAssignment(await getAssignment(props.lecture.id, assignment.id)); - props.onDeleted(); - }; - - const [allSubmissions, setAllSubmissions] = React.useState([]); - const [latestSubmissions, setLatestSubmissions] = React.useState([]); - const [numAutoGraded, setNumAutoGraded] = React.useState(0); - const [numManualGraded, setNumManualGraded] = React.useState(0); - React.useEffect(() => { - getAllSubmissions(props.lecture.id, assignment.id, 'none', true).then( - response => { - setAllSubmissions(response); - let auto = 0; - const autoUserSet = new Set(); - let manual = 0; - const manualUserSet = new Set(); - for (const submission of response) { - if ( - submission.auto_status === 'automatically_graded' && - !autoUserSet.has(submission.username) - ) { - autoUserSet.add(submission.username); - auto++; - } - if ( - submission.manual_status === 'manually_graded' && - !manualUserSet.has(submission.username) - ) { - manualUserSet.add(submission.username); - manual++; - } - } - setNumAutoGraded(auto); - setNumManualGraded(manual); - }, - (error: Error) => { - enqueueSnackbar(error.message, { - variant: 'error' - }); - } - ); - - getAllSubmissions(props.lecture.id, assignment.id, 'latest', true).then( - response => { - setLatestSubmissions(response); - } - ); - - getFiles(`${lectureBasePath}${props.lecture.code}/source/${assignment.id}`).then(files => { - setFiles(files); - }); - }, [assignment]); - - return ( - - { - await openBrowser(`${lectureBasePath}${props.lecture.code}/source/${assignment.id}`); - setDisplaySubmissions(true); - storeNumber('cm-opened-assignment', assignment.id); - }} - > - - - - {assignment.name} - - - {files.length + ' File' + (files.length === 1 ? '' : 's')} - - {assignment.status} - - - - - - - - - - - - - - ); -}; diff --git a/grader_labextension/src/components/coursemanage/lecture.tsx b/grader_labextension/src/components/coursemanage/lecture.tsx index 618e00ba..2b25fa9b 100644 --- a/grader_labextension/src/components/coursemanage/lecture.tsx +++ b/grader_labextension/src/components/coursemanage/lecture.tsx @@ -13,7 +13,7 @@ import { Grid, LinearProgress, Stack, TableCell, TableRow, Typography, - Box + Box, Tooltip } from '@mui/material'; import * as React from 'react'; import { Assignment } from '../../model/assignment'; @@ -22,14 +22,13 @@ import { createAssignment, deleteAssignment, getAllAssignments } from '../../services/assignments.service'; -import { AssignmentComponent } from './assignment'; import { CreateDialog, EditLectureDialog } from '../util/dialog'; import { getLecture, getUsers, updateLecture } from '../../services/lectures.service'; -import { red } from '@mui/material/colors'; +import { red, grey } from '@mui/material/colors'; import { enqueueSnackbar } from 'notistack'; import { deleteKey, @@ -88,41 +87,49 @@ const AssignmentTable = (props: IAssignmentTableProps) => { - { - showDialog( - 'Delete Assignment', - 'Do you wish to delete this assignment?', - async () => { - try { - await deleteAssignment( - props.lecture.id, - row.id - ); - enqueueSnackbar('Successfully Deleted Assignment', { - variant: 'success' + + {/* span because of https://mui.com/material-ui/react-tooltip/#disabled-elements */} + { + showDialog( + 'Delete Assignment', + 'Do you wish to delete this assignment?', + async () => { + try { + await deleteAssignment( + props.lecture.id, + row.id + ); + enqueueSnackbar('Successfully Deleted Assignment', { + variant: 'success' + }); + props.setAssignments(props.rows.filter(a => a.id !== row.id)); + } catch (error: any) { + enqueueSnackbar(error.message, { + variant: 'error' + }); + } }); - props.setAssignments(props.rows.filter(a => a.id !== row.id)); - } catch (error: any) { - enqueueSnackbar(error.message, { - variant: 'error' - }); - } - }); - e.stopPropagation(); - }} - > - - + e.stopPropagation(); + }} + > + + + + ); }} /> - ); }; diff --git a/grader_labextension/src/components/coursemanage/settings/settings.tsx b/grader_labextension/src/components/coursemanage/settings/settings.tsx index 9b6a4972..3361cfa3 100644 --- a/grader_labextension/src/components/coursemanage/settings/settings.tsx +++ b/grader_labextension/src/components/coursemanage/settings/settings.tsx @@ -6,7 +6,7 @@ import { Box, Button, Checkbox, - FormControlLabel, + FormControlLabel, IconButton, InputLabel, MenuItem, Stack, @@ -25,12 +25,14 @@ import { enqueueSnackbar } from 'notistack'; import { Lecture } from '../../../model/lecture'; import * as yup from 'yup'; import { SectionTitle } from '../../util/section-title'; -import { useRouteLoaderData } from 'react-router-dom'; +import { useNavigate, useRouteLoaderData } from 'react-router-dom'; import { getLateSubmissionInfo, ILateSubmissionInfo, LateSubmissionForm } from './late-submission-form'; import { FormikValues } from 'formik/dist/types'; import { SubmissionPeriod } from '../../../model/submissionPeriod'; import moment from 'moment'; import { red } from '@mui/material/colors'; +import { showDialog } from '../../util/dialog-provider'; +import CloseIcon from '@mui/icons-material/Close'; const gradingBehaviourHelp = `Specifies the behaviour when a students submits an assignment.\n No Automatic Grading: No action is taken on submit.\n @@ -62,6 +64,7 @@ const validationSchema = yup.object({ //} export const SettingsComponent = () => { + const navigate = useNavigate(); const { lecture, assignments } = useRouteLoaderData('lecture') as { lecture: Lecture, @@ -341,9 +344,52 @@ export const SettingsComponent = () => { Group */} - + + + + + {/* span because of https://mui.com/material-ui/react-tooltip/#disabled-elements */} + + + + + ); diff --git a/grader_labextension/src/widgets/assignmentmanage.tsx b/grader_labextension/src/widgets/assignmentmanage.tsx index 4b75ab7f..d8475110 100644 --- a/grader_labextension/src/widgets/assignmentmanage.tsx +++ b/grader_labextension/src/widgets/assignmentmanage.tsx @@ -26,7 +26,7 @@ export class AssignmentManageView extends ReactWidget { constructor(options: AssignmentManageView.IOptions = {}) { super(); - this.id = options.id; + this.id = options.id || "assignment-view"; this.addClass('GradingWidget'); const savedPath = loadString('assignment-manage-react-router-path'); @@ -50,6 +50,8 @@ export class AssignmentManageView extends ReactWidget { (