Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
45282b2
orbit: add automated feedback based on gradable status
theyoyojo May 22, 2025
ddb25fe
mailman: factor out git.Repo.init() and git.Repo() into callers
theyoyojo May 22, 2025
5cf3bd9
mailman: factor out author args into short config blocks
theyoyojo May 22, 2025
36a9cef
mailman: add status strings to email ID tags
theyoyojo May 22, 2025
e54a0ba
orbit: read assignment grade from grading repo with git notes
theyoyojo May 22, 2025
303ee89
orbit: read peer review grade grade from grading repo with git notes
theyoyojo May 22, 2025
d9c01d1
orbit: cache the result of get_grade() in AsmtTable
theyoyojo May 22, 2025
e31e2a9
orbit: calculate total score from final score and review scores
theyoyojo May 22, 2025
732b01d
denis: add notes tied to initial and final submissions
theyoyojo May 22, 2025
ff073de
denis,orbit: jank implementation of automatic 0
theyoyojo May 22, 2025
3bb3f22
denis: verify submitted and calculated diffstat
theyoyojo May 23, 2025
1ef626b
denis: verify DCO in each patch in patchset
theyoyojo May 23, 2025
45a7aba
denis: check subject tag format
theyoyojo May 30, 2025
962c4d9
orbit: dashboard: implement human feedback section
theyoyojo May 30, 2025
ac1e771
denis/configure: inform user when dumping dirty
theyoyojo May 30, 2025
fb5311e
mailman: reject patchsets modifying files outside student directory
theyoyojo May 31, 2025
8edab5a
denis: automatic zero for peer review when no submission is made
theyoyojo May 31, 2025
2e26bec
mailman,denis: add simple patchset rubric based rejection support
theyoyojo May 31, 2025
a3cdb8b
test-sub.sh: a new testing script
theyoyojo May 22, 2025
ad12ee4
CI: enhance PINPing by running test-sub.sh
theyoyojo May 30, 2025
5729e46
test-sub-check.sh: add script to check results of test-sub.sh
theyoyojo May 31, 2025
53ee0de
treewide: s/DOCKER/PODMAN/g
theyoyojo May 31, 2025
31cc50b
test: consolidate repetitive setup in test-lib
theyoyojo May 31, 2025
cf1d3b5
test: add more rubric tests cleanly by consolidating common testing code
theyoyojo Jun 2, 2025
140c3f5
denis,mailman,orbit: rename Gradable.{status => auto_feedback}
theyoyojo Jun 2, 2025
334651c
test: add test for whether only initial submission gets autozero
theyoyojo Jun 2, 2025
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
5 changes: 4 additions & 1 deletion Containerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ RUN dnf update -y && \
python-flake8 \
python-virtualenv \
python-pip \
git
gawk \
socat \
git \
git-email

RUN sed -i 's/log_driver = "journald"/log_driver = "json-file"/' /usr/share/containers/containers.conf

Expand Down
1 change: 1 addition & 0 deletions container-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ services:
- orbit_source=./orbit
environment:
TZ: ${SINGULARITY_TIMEZONE}
HOSTNAME: ${SINGULARITY_HOSTNAME}
volumes:
- type: volume
source: denis-db
Expand Down
24 changes: 24 additions & 0 deletions create_rubric.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/env python

from argparse import ArgumentParser as ap
import git
import os


def main():
parser = ap(prog='create_rubric', description='create rubric')
parser.add_argument('-n', type=int, help='number of patches to examine',
required=True)

args = parser.parse_args()

repo = git.Repo(os.getcwd())
for i in range(args.n):
patch = repo.git.execute(['git', 'show', f'HEAD~{args.n-i-1}'])
changelines = list(filter(lambda line: line.startswith('--- ') or line.startswith('+++ '), patch.split('\n')))
changeline_pairs = {(fromfile, tofile): 0 for fromfile, tofile in zip(changelines[::2], changelines[1::2])}
print(f'{"[" if i == 0 else ""}{changeline_pairs}{"," if i < args.n - 1 else "]"}')


if __name__ == '__main__':
main()
47 changes: 43 additions & 4 deletions denis/configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from datetime import datetime
from argparse import ArgumentParser as ap
from pathlib import Path
import os

import db

Expand Down Expand Up @@ -32,19 +34,26 @@ def add_final(parser, required=True):
help='Final submission due date timetamp',
required=required)

def add_rubric(parser):
parser.add_argument('-r', '--rubric',
type=str,
help='Rubric to enforce on incoming patchsets')

command_parsers = parser.add_subparsers(dest='command', required=True)

create_parser = command_parsers.add_parser('create')
add_assignment(create_parser)
add_initial(create_parser)
add_peer_review(create_parser)
add_final(create_parser)
add_rubric(create_parser)

alter_parser = command_parsers.add_parser('alter')
add_assignment(alter_parser)
add_initial(alter_parser, required=False)
add_peer_review(alter_parser, required=False)
add_final(alter_parser, required=False)
add_rubric(alter_parser)

remove_parser = command_parsers.add_parser('remove')
add_assignment(remove_parser)
Expand All @@ -67,31 +76,53 @@ def add_final(parser, required=True):
subparser_func(**kwargs)


def create(assignment, initial, peer_review, final):
def dirty():
Path('/tmp/dirty').touch()


# since the rubric is copied into the container by the wrapper script,
# we always place it in /tmp/rubric to simplify things
def load_rubric():
try:
with open('/tmp/rubric', 'r') as rubric_file:
os.unlink('/tmp/rubric')
return rubric_file.read()
except FileNotFoundError:
return None


def create(assignment, initial, peer_review, final, rubric):
rubric = load_rubric()
try:
db.Assignment.create(name=assignment,
initial_due_date=initial,
peer_review_due_date=peer_review,
final_due_date=final)
final_due_date=final,
rubric=rubric)
dirty()
except db.peewee.IntegrityError:
print('cannot create assignment with duplicate name')


def alter(assignment, initial, peer_review, final):
def alter(assignment, initial, peer_review, final, rubric):
alterations = {}
if initial is not None:
alterations[db.Assignment.initial_due_date] = initial
if peer_review is not None:
alterations[db.Assignment.peer_review_due_date] = peer_review
if final is not None:
alterations[db.Assignment.final_due_date] = final
if (rubric := load_rubric()) is not None:
alterations[db.Assignment.rubric] = rubric
if not alterations:
return print('At least one new date must be specified')
query = (db.Assignment
.update(alterations)
.where(db.Assignment.name == assignment))
if query.execute() < 1:
print(f'no such assignment {assignment}')
else:
dirty()


def remove(assignment):
Expand All @@ -100,25 +131,33 @@ def remove(assignment):
.where(db.Assignment.name == assignment))
if query.execute() < 1:
print(f'no such assignment {assignment}')
else:
dirty()


def dump(fmt_iso):
def timestamp_to_formatted(timestamp):
dt = datetime.fromtimestamp(timestamp).astimezone()
return dt.isoformat() if fmt_iso else dt.strftime('%a %b %d %Y %T %Z (%z)')

if os.path.exists('/tmp/dirty'):
print('WARNING: Denis database is dirty, reload to update waiters')

print(' --- Assignments ---')
for asn in db.Assignment.select():
print(f'''{asn.name}:
\tInitial:\t{timestamp_to_formatted(asn.initial_due_date)}
\tPeer Review:\t{timestamp_to_formatted(asn.peer_review_due_date)}
\tFinal:\t\t{timestamp_to_formatted(asn.final_due_date)}''')
\tFinal:\t\t{timestamp_to_formatted(asn.final_due_date)}
{"\tRubric:\n" + str(asn.rubric) if asn.rubric else ""}''')


def reload():
import os
import signal
os.kill(1, signal.SIGUSR1)
if os.path.exists('/tmp/dirty'):
os.remove('/tmp/dirty')


if __name__ == '__main__':
Expand Down
24 changes: 24 additions & 0 deletions denis/configure.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,31 @@
#!/bin/sh
set -e

PODMAN=${PODMAN:-podman}
COMPOSE=${COMPOSE:-podman-compose}

# a rubric is passed as a path to a file,
# but the main script is executed inside the container
# so copy any rubric into the container,
# if one is specified.
get_next=
rubric_file=
for arg in "$@"
do
if [ "$arg" = '-r' ]
then
get_next=yes
elif [ -n "$get_next" ]
then
rubric_file=$arg
fi
done

if [ -n "$rubric_file" ]
then
${PODMAN} cp "$rubric_file" singularity_denis_1:/tmp/rubric
fi


${COMPOSE} exec denis ./configure.py "$@"

1 change: 1 addition & 0 deletions denis/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class Assignment(BaseModel):
initial_due_date = peewee.IntegerField()
peer_review_due_date = peewee.IntegerField()
final_due_date = peewee.IntegerField()
rubric = peewee.TextField(null=True)


class PeerReviewAssignment(BaseModel):
Expand Down
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)
6 changes: 4 additions & 2 deletions denis/peer_review.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,7 @@

print(f'peer review subs for {assignment} released')

utilities.update_tags(assignment, 'review1')
utilities.update_tags(assignment, 'review2')
tags1 = utilities.update_tags(assignment, 'review1')
tags2 = utilities.update_tags(assignment, 'review2')

utilities.run_automated_checks(tags1 + tags2, peer=True)
Loading