Skip to content
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

Draft: Release 0.3.0 #188

Closed
wants to merge 35 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
cea5dbf
Do not show "Extra Files" warning if assignment allows files by students
meffmadd Oct 31, 2023
e055844
Fixed assignment file list warnings when user is logged in as student
meffmadd Nov 1, 2023
1e5270b
Added logs in edit view of a submission.
mpetojevic Nov 7, 2023
d8ee91c
Corrected how number of days is shown in deadline extension
mpetojevic Nov 7, 2023
f5b68ef
Number of submissions in assigment view (student & instructor) update…
mpetojevic Nov 7, 2023
b3e1c90
Added "tooltip" message to the cog icon for editing lecture.
mpetojevic Nov 7, 2023
a1f53b1
Instructors can set assignment deadline in past now, but a snackbar w…
mpetojevic Nov 8, 2023
0d7b131
Assignment creation form on assignments overview allows selecting dat…
mpetojevic Nov 8, 2023
c1ec5c9
Redesigned detail submission view. Now if the submission is not autom…
mpetojevic Nov 12, 2023
ed6f30b
Don't let number of submissions left go to negative for instructors. …
mpetojevic Nov 13, 2023
5f96b51
Updated setting submissions left.
mpetojevic Nov 13, 2023
e2e3941
feedback view is now scrollable.
mpetojevic Nov 20, 2023
3d14d67
Fixed bug regarding creating new directories for 'create submission'.…
mpetojevic Nov 27, 2023
a5b5a95
Disabled feedback generation for submissions that aren't automaticall…
mpetojevic Nov 27, 2023
d550c80
Tree structure for FilesList
mpetojevic Dec 6, 2023
8823967
Solved spacing "problem" for file icon from previous commit
mpetojevic Dec 7, 2023
1438b40
Remove null character from grading log #165
meffmadd Dec 13, 2023
b1bd8a5
Updated folder structure for files list
mpetojevic Dec 14, 2023
05b21e4
Merge pull request #170 from TU-Wien-dataLAB/165-submissions-log-with…
meffmadd Dec 20, 2023
8f5e141
Added some comments/TODOs for the current missing extra file implemen…
meffmadd Dec 20, 2023
aa37700
Fixed missing file generation.
mpetojevic Dec 27, 2023
5511783
Merge pull request #179 from TU-Wien-dataLAB/release-lab-0.2.13
mpetojevic Jan 8, 2024
dc37696
chore: removed unused import
florian-jaeger Jan 8, 2024
88924ed
fix: deleted custom write_error function to use the default one
florian-jaeger Jan 8, 2024
8a9347a
refactor: removed directory deletion from assignment delete handler
florian-jaeger Jan 8, 2024
1d94def
refactor: refactored custom write_error function in server extension
florian-jaeger Jan 8, 2024
a0185d8
fix: use reason instead of message
florian-jaeger Jan 8, 2024
57c56a1
chore: removed old unused code from settings
florian-jaeger Jan 9, 2024
903b7b9
Merge pull request #182 from TU-Wien-dataLAB/fix/error-handling
florian-jaeger Jan 10, 2024
2647bac
Merge branch 'release-0.3.0' into 155-ui-improvements-details-grader_…
mpetojevic Jan 10, 2024
cdb966c
Allow dedline in the past.
mpetojevic Jan 10, 2024
2de4a4d
Merge branch '155-ui-improvements-details-grader_labextension' of git…
mpetojevic Jan 10, 2024
1f7682b
Disabled finish manual grading if assignment status is not "automatic…
mpetojevic Jan 10, 2024
cdf50e9
Merge pull request #187 from TU-Wien-dataLAB/155-ui-improvements-deta…
mpetojevic Jan 10, 2024
ff44db0
Merge branch 'main' into release-0.3.0
mpetojevic Jan 10, 2024
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
7 changes: 2 additions & 5 deletions examples/dev_environment/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,12 @@ python -m pip install --upgrade pip
pip install jupyterhub jupyterlab

echo "Installing grader_convert..."
pip install -r ../../grader_convert/requirements.txt
pip install --no-use-pep517 ../../grader_convert
pip install ../../grader_convert

echo "Installing grader_service..."
pip install -r ../../grader_service/requirements.txt
pip install --no-use-pep517 ../../grader_service
pip install ../../grader_service

echo "Installing grader_labextension..."
#pip install -r ../../grader_labextension/requirements.txt
pip install ../../grader_labextension # no development install for grader_labextension

jupyter server extension enable grader_labextension
Expand Down
4 changes: 2 additions & 2 deletions examples/dev_environment/jupyter_hub_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
c.JupyterHub.authenticator_class = 'jupyterhub.auth.DummyAuthenticator'
c.DummyAuthenticator.password = "admin"
c.Authenticator.admin_users = {'user1'}
c.Authenticator.allowed_users = {"user1", "user2", "user3"}
c.Authenticator.allowed_users = {"user1", "user2", "user3", "user4", "user5", "user6", "user7"}

## simple setup
c.JupyterHub.ip = '127.0.0.1'
Expand All @@ -31,7 +31,7 @@

c.JupyterHub.load_groups = {
"23wsle2:instructor": {'users': ["user1", "user2"]},
"23wsle2:student": {'users': ["user3"]},
"23wsle2:student": {'users': ["user3", "user4", "user5", "user6", "user7"]},
"23wsle1:instructor": {'users': ["user1", "user2"]},
"23wsle1:student": {'users': ["user3"]},
}
Expand Down
15 changes: 1 addition & 14 deletions grader_labextension/grader_labextension/handlers/assignment.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,28 +185,15 @@ async def delete(self, lecture_id: int, assignment_id: int):
"""

try:
assignment = await self.request_service.request(
method="GET",
endpoint=f"{self.service_base_url}/lectures/{lecture_id}/assignments/{assignment_id}",
header=self.grader_authentication_header,
)
lecture = await self.request_service.request(
"GET",
f"{self.service_base_url}/lectures/{lecture_id}",
header=self.grader_authentication_header,
)
await self.request_service.request(
method="DELETE",
endpoint=f"{self.service_base_url}/lectures/{lecture_id}/assignments/{assignment_id}",
header=self.grader_authentication_header,
decode_response=False
)
except HTTPClientError as e:
self.log.error(e.response)
raise HTTPError(e.code, reason=e.response.reason)

self.log.warn(f'Deleting directory {self.root_dir}/{lecture["code"]}/assignments/{assignment["id"]}')
shutil.rmtree(os.path.expanduser(f'{self.root_dir}/{lecture["code"]}/assignments/{assignment["id"]}'), ignore_errors=True)

self.write("OK")


Expand Down
44 changes: 22 additions & 22 deletions grader_labextension/grader_labextension/handlers/base_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree.
import functools
from http.client import responses
import json
import traceback
from typing import Optional, Awaitable
Expand Down Expand Up @@ -86,7 +87,7 @@ def grader_authentication_header(self):

@property
def user_name(self):
return self.current_user.name
return self.current_user['name']

async def get_lecture(self, lecture_id) -> dict:
try:
Expand All @@ -111,24 +112,23 @@ async def get_assignment(self, lecture_id, assignment_id):
except HTTPClientError as e:
self.log.error(e.response)
raise HTTPError(e.code, reason=e.response.reason)

def write_error(self, status_code: int, **kwargs) -> None:
self.set_header('Content-Type', 'application/json')
self.set_status(status_code)
_, e, _ = kwargs.get("exc_info", (None, None, None))
error = httputil.responses.get(status_code, "Unknown")
reason = kwargs.get("reason", None)
if e and isinstance(e, HTTPError) and e.reason:
reason = e.reason
if self.settings.get("serve_traceback") and "exc_info" in kwargs:
# in debug mode, try to send a traceback
lines = []
for line in traceback.format_exception(*kwargs["exc_info"]):
lines.append(line)
self.finish(json.dumps(
ErrorMessage(status_code, error, self.request.path, reason,
traceback=json.dumps(lines)).to_dict()))
else:
self.finish(json.dumps(ErrorMessage(status_code, error,
self.request.path,
reason).to_dict()))

def write_error(self, status_code, **kwargs):
"""APIHandler errors are JSON, not human pages"""
self.set_header("Content-Type", "application/json")
message = responses.get(status_code, "Unknown HTTP Error")
reply: dict = {
"message": message,
}
exc_info = kwargs.get("exc_info")
if exc_info:
e = exc_info[1]
if isinstance(e, HTTPError):
reply["message"] = e.log_message or message
reply["reason"] = e.reason
else:
reply["message"] = "Unhandled error"
reply["reason"] = None
reply["traceback"] = "".join(traceback.format_exception(*exc_info))
self.log.warning("wrote error: %r", reply["message"], exc_info=True)
self.finish(json.dumps(reply))
69 changes: 53 additions & 16 deletions grader_labextension/src/components/assignment/assignment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import * as React from 'react';
import { Lecture } from '../../model/lecture';
import { Assignment } from '../../model/assignment';
import { Submission } from '../../model/submission';
import { Box, Button, Chip, Stack, Tooltip, Typography } from '@mui/material';

import { Box, Button, Chip, IconButton, Stack, Tooltip, Typography } from '@mui/material';
import ReplayIcon from '@mui/icons-material/Replay';
import { SubmissionList } from './submission-list';
import { AssignmentStatus } from './assignment-status';
import { Files } from './files/files';
Expand Down Expand Up @@ -51,6 +51,21 @@ const calculateActiveStep = (submissions: Submission[]) => {
return 0;
};

interface ISubmissionsLeft{
subLeft: number;
}
const SubmissionsLeftChip = (props: ISubmissionsLeft) =>{
const output = props.subLeft + ' submission' + (props.subLeft === 1 ? ' left' : 's left');
return(
<Chip
sx={{ ml: 2 }}
size='medium'
icon={<WarningIcon />}
label={output}
/>
)
}

/**
* Renders the components available in the extended assignment modal view
*/
Expand Down Expand Up @@ -79,14 +94,17 @@ export const AssignmentComponent = () => {

/* Now we can divvy this into a useReducer */
const [allSubmissions, setSubmissions] = React.useState(submissions);

const [files, setFiles] = React.useState([]);
const [activeStatus, setActiveStatus] = React.useState(0);
const [subLeft, setSubLeft] = React.useState(0);


React.useEffect(() => {
getAllSubmissions(lecture.id, assignment.id, 'none', false).then(
response => {
setSubmissions(response);
if(assignment.max_submissions - response.length < 0) setSubLeft(0);
else setSubLeft(assignment.max_submissions - response.length);
}
);
getFiles(path).then(files => {
Expand Down Expand Up @@ -149,6 +167,8 @@ export const AssignmentComponent = () => {
response => {
console.log('Submitted');
setSubmissions([response, ...allSubmissions]);
if(subLeft - 1 < 0) setSubLeft(0);
else setSubLeft(subLeft - 1);
enqueueSnackbar('Successfully Submitted Assignment', {
variant: 'success'
});
Expand Down Expand Up @@ -242,7 +262,11 @@ export const AssignmentComponent = () => {
const scope = permissions[lecture.code];
return scope >= Scope.tutor;
};
const [reloadFilesToggle, setReloadFiles] = React.useState(false);

const reloadFiles = () => {
setReloadFiles(!reloadFilesToggle);
};

return (
<Box sx={{ flex: 1, overflow: 'auto' }}>
Expand All @@ -260,9 +284,16 @@ export const AssignmentComponent = () => {
<DeadlineDetail due_date={assignment.due_date} late_submissions={assignment.settings.late_submission || []} />
</Box>
<Box sx={{ mt: 4 }}>
<Typography variant={'h6'} sx={{ ml: 2 }}>
<Stack direction={'row'} justifyContent={'flex-start'} alignItems={'center'} spacing={2} sx={{ ml: 2 }} >
<Typography variant={'h6'} sx={{ ml: 2 }}>
Files
</Typography>
<Tooltip title='Reload Files'>
<IconButton aria-label='reload' onClick={() => reloadFiles()}>
<ReplayIcon />
</IconButton>
</Tooltip>
</Stack>
<Files
lecture={lecture}
assignment={assignment}
Expand Down Expand Up @@ -359,18 +390,24 @@ export const AssignmentComponent = () => {
<Box sx={{ mt: 4 }}>
<Typography variant={'h6'} sx={{ ml: 2, mt: 3 }}>
Submissions
{assignment.max_submissions !== null ? (
<Chip
sx={{ ml: 2 }}
size='medium'
icon={<WarningIcon />}
label={
assignment.max_submissions -
submissions.length +
' submissions left'
}
/>
) : null}

{ assignment.max_submissions !== null
? ( hasPermissions()
? (
<Stack direction={'row'}>
<SubmissionsLeftChip subLeft={subLeft}/>
<Chip sx={{ml: 2}}
color="success" variant="outlined"
label={'As instructor you have unlimited submissions'}/>
</Stack>
)
: (
<SubmissionsLeftChip subLeft={subLeft}/>
)
)
: null
}

</Typography>
<SubmissionList
submissions={allSubmissions}
Expand Down
5 changes: 3 additions & 2 deletions grader_labextension/src/components/assignment/feedback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export const Feedback = () => {
}, [lecture, assignment, submission]);

return (
<Box>
<Box sx={{overflow: 'auto'}}>
<SectionTitle title={'Feedback for ' + assignment.name} />
<Box sx={{ m: 2, mt: 12 }}>
<Stack direction='row' spacing={2} sx={{ ml: 2 }}>
Expand Down Expand Up @@ -143,7 +143,8 @@ export const Feedback = () => {
Feedback Files
</Typography>

<FilesList path={path} sx={{ m: 2 }} />
<FilesList path={path} sx={{ m: 2, overflow: 'auto'}} />

<Stack direction={'row'} spacing={2} sx={{ m: 2 }}>
<Button variant='outlined' component={Link as any} to={assignmentLink}>Back</Button>
<Button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const Files = (
openBrowser(path);
return (
<div>
<FilesList path={path} sx={{ m: 2, mt: 1 }} />
<FilesList path={path} sx={{ m: 2, mt: 1 }} lecture={props.lecture} shouldContain={props.files} assignment={props.assignment} />
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ export const Files = (props: IFilesProps) => {
<Tab label='Source' value='source' />
<Tab label='Release' value='release' />
</Tabs>
<Box height={200}>
<Box>
<FilesList
path={`${lectureBasePath}${props.lecture.code}/${selectedDir}/${props.assignment.id}`}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { SectionTitle } from '../../util/section-title';
import {
Box,
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
IconButton,
Stack,
Tooltip,
Expand All @@ -13,6 +17,7 @@ import { Assignment } from '../../../model/assignment';
import { Submission } from '../../../model/submission';
import {
createOrOverrideEditRepository,
getLogs,
getProperties,
pullSubmissionFiles,
pushSubmissionFiles,
Expand Down Expand Up @@ -52,6 +57,24 @@ export const EditSubmission = () => {
const [submission, setSubmission] = React.useState(manualGradeSubmission);
const [loading, setLoading] = React.useState(false);

const [showLogs, setShowLogs] = React.useState(false);
const [logs, setLogs] = React.useState(undefined);

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 pushEditedFiles = async () => {
await pushSubmissionFiles(lecture, assignment, submission).then(
response => {
Expand Down Expand Up @@ -137,7 +160,17 @@ export const EditSubmission = () => {
</Stack>
</Stack>
</Box>
<Stack direction = {'row'} justifyContent='space-between'>
<Typography sx={{ m: 2, mb: 0 }}>Submission Files</Typography>
<Button
sx = {{mr: 2}}
variant='outlined'
size="small"
onClick={(event) => openLogs(event, manualGradeSubmission.id)}>
Show Logs
</Button>
</Stack>

<FilesList path={path} sx={{ m: 2 }} />

<Stack direction={'row'} sx={{ ml: 2 }} spacing={2}>
Expand Down Expand Up @@ -191,6 +224,25 @@ export const EditSubmission = () => {
<Toolbar>
<Button variant='outlined' component={Link as any} to={submissionsLink}>Back</Button>
</Toolbar>
<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>
);
};
Loading
Loading