From 7bdcd23668665b072d2680ec018ceb3d31a5348b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 21 Nov 2025 18:08:58 +0000 Subject: [PATCH 1/4] Initial plan From dfa924044fc3a1315bb681fa133f6ad9d2a015d8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 21 Nov 2025 18:15:31 +0000 Subject: [PATCH 2/4] Add browse_history endpoint for assignment groups Co-authored-by: acbart <897227+acbart@users.noreply.github.com> --- controllers/endpoints/assignment_groups.py | 56 ++++++++++++++++++- .../assignment_groups/browse_history.html | 43 ++++++++++++++ 2 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 templates/assignment_groups/browse_history.html diff --git a/controllers/endpoints/assignment_groups.py b/controllers/endpoints/assignment_groups.py index 11fe0c47..138f1900 100644 --- a/controllers/endpoints/assignment_groups.py +++ b/controllers/endpoints/assignment_groups.py @@ -6,7 +6,7 @@ from common.maybe import maybe_bool from controllers.auth import get_user from controllers.helpers import (require_request_parameters, require_course_instructor, login_required, - require_course_adopter, + require_course_adopter, ajax_failure, check_resource_exists, get_select_menu_link, get_course_id, ajax_success, maybe_int, require_course_grader, make_log_entry) from models import Course, Submission, AssignmentLog @@ -16,6 +16,8 @@ from models.assignment_group import AssignmentGroup from models.assignment_group_membership import AssignmentGroupMembership from models.data_formats.portation import export_bundle, import_bundle, export_zip, export_pdf_zip +from models.log_tables import SubmissionLog as Log +from models.user import User blueprint_assignment_group = Blueprint('assignment_group', __name__, url_prefix='/assignment_group') @@ -377,3 +379,55 @@ def export_submissions(): filename = course.get_url_or_id() + '-' + assignment_group.get_filename(extension='.zip') return Response(bundle, mimetype='application/zip', headers={'Content-Disposition': 'attachment;filename={}'.format(filename)}) + + +@blueprint_assignment_group.route('/browse_history/', methods=['GET', 'POST']) +@blueprint_assignment_group.route('/browse_history', methods=['GET', 'POST']) +@require_request_parameters('assignment_group_id', 'course_id') +def browse_history(): + """ + View the history of all submissions in a single assignment group. + Returns a page with the watcher component to view submission histories. + """ + # Get parameters + assignment_group_id = maybe_int(request.values.get('assignment_group_id')) + course_id = maybe_int(request.values.get('course_id')) + user_id = maybe_int(request.values.get('user_id', None)) + embed = maybe_bool(request.values.get('embed')) + user, viewer_id = get_user() + + # Get resources + assignment_group = AssignmentGroup.by_id(assignment_group_id) + check_resource_exists(assignment_group, "AssignmentGroup", assignment_group_id) + course = Course.by_id(course_id) + check_resource_exists(course, "Course", course_id) + + # Verify permissions - only graders can view history for an assignment group + if not user.is_grader(course_id): + return ajax_failure("Only graders can see logs for assignment groups.") + + # Get all assignments in this group + assignments = assignment_group.get_assignments() + assignment_ids = ','.join(str(a.id) for a in assignments) + + # Get all users who have submissions in this course for these assignments + if user_id is None: + # Get all users with submissions in these assignments + user_ids_list = (Log.query + .filter(Log.course_id == course_id) + .filter(Log.assignment_id.in_([a.id for a in assignments])) + .with_entities(Log.subject_id) + .distinct() + .all()) + user_ids = ','.join(str(uid[0]) for uid in user_ids_list) + else: + user_ids = str(user_id) + + return render_template('assignment_groups/browse_history.html', + assignment_group=assignment_group, + course=course, + course_id=course_id, + assignment_ids=assignment_ids, + user_ids=user_ids, + user=user, + embed=embed) diff --git a/templates/assignment_groups/browse_history.html b/templates/assignment_groups/browse_history.html new file mode 100644 index 00000000..a26f5bdd --- /dev/null +++ b/templates/assignment_groups/browse_history.html @@ -0,0 +1,43 @@ +{% set reduced_layout = embed or 'iframe' == request.form.get('launch_presentation_document_target') %} +{% extends 'helpers/layout.html' %} +{% import 'helpers/navigation.html' as navigation %} + +{% block title %} +Browse Assignment Group History +{% endblock %} + +{% block statusbar %} +{% endblock %} + +{% block extrahead %} + +{% endblock %} + +{% block body %} + +

Browse Assignment Group History

+ {{ navigation.navigate_course(course_id) }} + +

Assignment Group: {{ assignment_group.name }}

+

Course: {{ course.name }}

+ + + +{% endblock %} From 1c7a8a02e4dbf33dea8d2354cfe6723cfdaf3e21 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 21 Nov 2025 18:17:57 +0000 Subject: [PATCH 3/4] Address code review feedback: improve readability and efficiency Co-authored-by: acbart <897227+acbart@users.noreply.github.com> --- controllers/endpoints/assignment_groups.py | 23 ++++++++++++------- .../assignment_groups/browse_history.html | 8 ++++++- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/controllers/endpoints/assignment_groups.py b/controllers/endpoints/assignment_groups.py index 138f1900..0d5b803a 100644 --- a/controllers/endpoints/assignment_groups.py +++ b/controllers/endpoints/assignment_groups.py @@ -408,18 +408,25 @@ def browse_history(): # Get all assignments in this group assignments = assignment_group.get_assignments() - assignment_ids = ','.join(str(a.id) for a in assignments) + # Extract assignment IDs once for efficiency + assignment_id_list = [a.id for a in assignments] if assignments else [] + # Format as comma-separated string (expected by watcher component) + assignment_ids = ','.join(str(aid) for aid in assignment_id_list) # Get all users who have submissions in this course for these assignments if user_id is None: # Get all users with submissions in these assignments - user_ids_list = (Log.query - .filter(Log.course_id == course_id) - .filter(Log.assignment_id.in_([a.id for a in assignments])) - .with_entities(Log.subject_id) - .distinct() - .all()) - user_ids = ','.join(str(uid[0]) for uid in user_ids_list) + if assignment_id_list: + user_ids_list = (Log.query + .filter(Log.course_id == course_id) + .filter(Log.assignment_id.in_(assignment_id_list)) + .with_entities(Log.subject_id) + .distinct() + .all()) + # Format as comma-separated string (expected by watcher component) + user_ids = ','.join(str(uid[0]) for uid in user_ids_list) if user_ids_list else '' + else: + user_ids = '' else: user_ids = str(user_id) diff --git a/templates/assignment_groups/browse_history.html b/templates/assignment_groups/browse_history.html index a26f5bdd..0d0bd172 100644 --- a/templates/assignment_groups/browse_history.html +++ b/templates/assignment_groups/browse_history.html @@ -17,12 +17,18 @@ users: [loggedInUser] }); + // WatchMode enum values + const WatchMode = { + SUMMARY: 0, + FULL: 1 + }; + let mainModel = { server: server, courseId: {{ course_id|tojson }}, assignmentIds: {{ assignment_ids|tojson }}, userIds: {{ user_ids|tojson }}, - defaultWatchMode: 0 // WatchMode.SUMMARY + defaultWatchMode: WatchMode.SUMMARY }; ko.applyBindings(mainModel); From 4d3bd693d27bcba0a63798a1cb94a6f946207acd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 21 Nov 2025 18:20:39 +0000 Subject: [PATCH 4/4] Add link to browse assignment group history from group report page Co-authored-by: acbart <897227+acbart@users.noreply.github.com> --- templates/reports/group.html | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/templates/reports/group.html b/templates/reports/group.html index ff9389fd..de3f5467 100644 --- a/templates/reports/group.html +++ b/templates/reports/group.html @@ -55,6 +55,15 @@

Overview

+ {% if is_grader %} +

+ + Browse All Submission History + +

+ {% endif %} +

{% if reduced_layout %} Below, you can see your current submission for this assignment.