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
4 changes: 3 additions & 1 deletion denis/final.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@

print(f'final subs for {assignment} released')

utilities.update_tags(assignment, 'final')
tags = utilities.update_tags(assignment, 'final')

utilities.run_automated_checks(tags)
4 changes: 3 additions & 1 deletion denis/initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,6 @@

print(f'initial subs for {assignment} released')

utilities.update_tags(assignment, 'initial')
tags = utilities.update_tags(assignment, 'initial')

utilities.run_automated_checks(tags)
29 changes: 26 additions & 3 deletions denis/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ def release_subs(sub_ids):
input=journal_data, check=True)


def configure_repo(repo):
with repo.config_writer() as config:
config.set_value('user', 'name', 'denis')
config.set_value('user', 'email', 'denis@denis')


def update_tags(assignment, component):
grd_tbl = mailman.db.Gradeable
subs = (grd_tbl.select()
Expand All @@ -47,15 +53,15 @@ def update_tags(assignment, component):
with tempfile.TemporaryDirectory() as repo_path:
repo = git.Repo.clone_from(PULL_URL, repo_path)
repo.create_remote(REMOTE_NAME, PUSH_URL)
repo.config_writer().set_value('user', 'name', 'denis').release()
(repo.config_writer().set_value('user', 'email', 'denis@denis')
.release())
configure_repo(repo)
if 'EMPTY' not in repo.tags:
repo.git.commit('--allow-empty', '-m', 'No gradeable submission.')
repo.create_tag('EMPTY')

updated_tags = []
for user in orbit.db.User.select():
new_tag_name = f'{assignment}_{component}_{user.username}'
updated_tags.append(new_tag_name)
if new_tag_name in repo.tags:
print('Potential issue? Attempted to create duplicate tag '
f'{new_tag_name}')
Expand All @@ -70,3 +76,20 @@ def update_tags(assignment, component):
repo.create_tag(new_tag_name, ref=to_promote.commit, message=msg)

repo.git.push(REMOTE_NAME, tags=True)

return updated_tags


def run_automated_checks(tags):
with tempfile.TemporaryDirectory() as repo_path:
repo = git.Repo.clone_from(PULL_URL, repo_path)

remote = repo.create_remote(REMOTE_NAME, PUSH_URL)
remote.fetch('refs/notes/*:refs/notes/*')
configure_repo(repo)

for tag in tags:
msg = 'Automated tests by denis'
repo.git.execute(['git', 'notes', '--ref=denis', 'add', tag, '-m', msg])

remote.push('refs/notes/*:refs/notes/*')
34 changes: 18 additions & 16 deletions mailman/patchset.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,9 @@ def try_or_false(do, exc):
return False


def tag_and_push(repo_path, tag_name):
def tag_and_push(repo, tag_name, msg=None):
try:
repo = git.Repo(repo_path)
repo.create_tag(tag_name)
repo.create_tag(tag_name, message=msg)
repo.create_remote('grading', REMOTE_PUSH_URL)
repo.git.push('grading', tags=True)
return True
Expand All @@ -31,14 +30,11 @@ def tag_and_push(repo_path, tag_name):
return False


author_args = ['-c', 'user.name=mailman', '-c',
'user.email=mailman@mailman']
git_am_args = ['git', '-c', 'advice.mergeConflict=false',
*author_args, 'am', '--keep']
'am', '--keep']


def do_check(repo_path, cover_letter, patches):
repo = git.Repo.init(repo_path)
def do_check(repo, cover_letter, patches):
whitespace_errors = []

def am_cover_letter(keep_empty=True):
Expand All @@ -51,7 +47,7 @@ def am_cover_letter(keep_empty=True):
git.GitCommandError):
return "missing cover letter!"

repo.git.execute(["git", *author_args, "am", "--abort"])
repo.git.execute(["git", "am", "--abort"])
if not try_or_false(lambda: am_cover_letter(keep_empty=True),
git.GitCommandError):
return ("missing cover letter and "
Expand All @@ -69,7 +65,7 @@ def do_git_am(extra_args=[]):
git.GitCommandError):
continue

repo.git.execute(["git", *author_args, "am", "--abort"])
repo.git.execute(["git", "am", "--abort"])

# Try again, if we succeed, count this patch as a whitespace error
if try_or_false(lambda: do_git_am(), git.GitCommandError):
Expand All @@ -80,21 +76,24 @@ def do_git_am(extra_args=[]):
return f'patch {i+1} failed to apply!'

if whitespace_errors:
return ('whitespace error patch(es) '
return (f'whitespace error patch{"es" if len(whitespace_errors) > 1 else ""} '
f'{",".join(whitespace_errors)}?')
else:
return 'patchset applies.'


def check(cover_letter, patches, submission_id):
with tempfile.TemporaryDirectory() as repo_path:
status = do_check(repo_path, cover_letter, patches)
repo = git.Repo.init(repo_path)
with repo.config_writer() as config:
config.set_value('user', 'name', 'mailman')
config.set_value('user', 'email', 'mailman@mailman')
status = do_check(repo, cover_letter, patches)
if status[-1] == '!':
repo = git.Repo(repo_path)
for patch in patches:
patch_abspath = str(maildir / patch.msg_id)
repo.git.execute(['git', *author_args, 'commit', '--allow-empty', '-F', patch_abspath])
tag_and_push(repo_path, submission_id)
repo.git.execute(['git', 'commit', '--allow-empty', '-F', patch_abspath])
tag_and_push(repo, submission_id, msg=status)
return status


Expand All @@ -110,8 +109,11 @@ def apply_peer_review(email, submission_id, review_id):
multi_options=[f'--branch={review_id}',
'--single-branch',
'--no-tags'])
with repo.config_writer() as config:
config.set_value('user', 'name', 'mailman')
config.set_value('user', 'email', 'mailman@mailman')
repo.git.execute([*args, patch_abspath])
tag_and_push(repo_path, submission_id)
tag_and_push(repo, submission_id)
except git.GitCommandError as e:
print(e, file=sys.stderr)
status = 'failed to apply peer review'
Expand Down
1 change: 1 addition & 0 deletions orbit/Containerfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ FROM alpine:3.20 AS orbit

RUN apk update && apk upgrade && apk add \
py3-bcrypt \
py3-gitpython \
py3-peewee \
py3-markdown \
uwsgi-python3 \
Expand Down
82 changes: 69 additions & 13 deletions orbit/radius.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import base64
import bcrypt
import git
import html
import markdown
import os
Expand Down Expand Up @@ -425,16 +426,19 @@ class OopsStatus:

class AsmtTable:
def __init__(self, assignment, oopsieness, peer1, peer2, init,
review1, review2, final):
review1, review1_grade, review2, review2_grade, final, final_grade):
self.assignment = assignment
self.name = assignment.name
self.oopsieness = oopsieness
self.peer1 = peer1
self.peer2 = peer2
self.init = init
self.review1 = review1
self.review1_grade = review1_grade
self.review2 = review2
self.review2_grade = review2_grade
self.final = final
self.final_grade = final_grade

def oops_button_hover(self):
match self.oopsieness:
Expand All @@ -448,6 +452,48 @@ def oops_button_hover(self):
case OopsStatus.UNAVAILABLE:
return "You have already used your oopsie"

def get_automated_feedback(self, attr):
if attr not in ['init', 'final'] or (gbl := getattr(self, attr)) is None:
return 'No submission'

match attr:
case 'init':
due_date = int(self.assignment.initial_due_date)
case 'final':
due_date = int(self.assignment.final_due_date)

if due_date < int(datetime.now().timestamp()):
return gbl.status

match gbl.status[-1]:
case '.':
return 'Submission accepted'
case '?':
return 'Issues detected in patchset'
case '!':
return 'Submission rejected'
case _:
Comment thread
charliemirabile marked this conversation as resolved.
return '---'

def get_total_score(self):
weighted_sum = 0
sum_of_weights = 0
try:
weighted_sum += 0.8 * int(self.final_grade)
sum_of_weights += 0.8
if self.peer1 is not None:
weighted_sum += 0.1 * int(self.review1_grade)
sum_of_weights += 0.1
if self.peer2 is not None:
weighted_sum += 0.1 * int(self.review2_grade)
sum_of_weights += 0.1
# if any of the grades are None, attempting to cast to int throws a TypeError
except TypeError:
return '-'
except ValueError:
return '???'
return f'{weighted_sum/sum_of_weights:.1f}'

def gradeable_row(self, item_name, gradeable, rightmost_col):
return f"""
<tr>
Expand Down Expand Up @@ -480,8 +526,8 @@ def body(self):
return f"""
{self.gradeable_row('Final Submission', self.final, self.oopsie_button())}
<tr>
<th>Comments</th>
<td colspan="3">-</td>
<th>Automated Feedback</th>
<td colspan="3">{self.get_automated_feedback('final')}</td>
</tr>
"""
if (not self.init or
Expand All @@ -491,27 +537,27 @@ def body(self):
{self.gradeable_row('Initial Submission', self.init, self.oopsie_button())}
<tr>
<th>Automated Feedback</th>
<td colspan="3">-</td>
<td colspan="3">{self.get_automated_feedback('init')}</td>
</tr>
"""
return f"""
{self.gradeable_row('Initial Submission', self.init, self.oopsie_button())}
<tr>
<th>Automated Feedback</th>
<td colspan="3">-</td>
<td colspan="3">{self.get_automated_feedback('init')}</td>
</tr>
<tr>
<th></th>
<th>Timestamp</th>
<th>Submission ID</th>
<th>Score</th>
</tr>
{self.gradeable_row(self.peer1 + ' Peer Review', self.review1, '-') if self.peer1 else ''}
{self.gradeable_row(self.peer2 + ' Peer Review', self.review2, '-') if self.peer2 else ''}
{self.gradeable_row('Final Submission', self.final, '-')}
{self.gradeable_row(self.peer1 + ' Peer Review', self.review1, self.review1_grade if self.review1_grade else '-') if self.peer1 else ''}
{self.gradeable_row(self.peer2 + ' Peer Review', self.review2, self.review2_grade if self.review2_grade else '-') if self.peer2 else ''}
{self.gradeable_row('Final Submission', self.final, self.final_grade if self.final_grade else '-')}
<tr>
<th>Comments</th>
<td colspan="3">-</td>
<th>Automated Feedback</th>
<td colspan="3">{self.get_automated_feedback('final')}</td>
</tr>
"""

Expand All @@ -520,7 +566,7 @@ def __str__(self):
<table>
<caption><h3>{self.name}</h3></caption>
<tr>
<th>Total Score: -</th>
<th>Total Score: {self.get_total_score()}</th>
<th>Timestamp</th>
<th>Submission ID</th>
<th>Request an 'Oopsie'</th>
Expand Down Expand Up @@ -593,8 +639,18 @@ def handle_dashboard(rocket):
rev1 = asn_gradeables.where(grd_tbl.component == 'review1').first()
rev2 = asn_gradeables.where(grd_tbl.component == 'review2').first()
final = asn_gradeables.where(grd_tbl.component == 'final').first()
ret += str(AsmtTable(assignment, oopsieness, peer1, peer2, init, rev1,
rev2, final))

repo = git.Repo('/var/lib/git/grading.git')
grades = {}
for component in ['review1', 'review2', 'final']:
tag = f'{assignment.name}_{component}_{rocket.session.username}'
try:
grades[component] = repo.git.execute(['git', 'notes', '--ref=grade', 'show', tag])
except git.GitCommandError:
grades[component] = None

ret += str(AsmtTable(assignment, oopsieness, peer1, peer2, init,
rev1, grades['review1'], rev2, grades['review2'], final, grades['final']))
return rocket.respond(ret + '</form>', 'Dashboard')


Expand Down