Skip to content

feat: instructors can appear as graders#8060

Merged
david-yz-liu merged 13 commits into
MarkUsProject:masterfrom
YheChen:feat/assign-instructors-as-graders
Jul 23, 2026
Merged

feat: instructors can appear as graders#8060
david-yz-liu merged 13 commits into
MarkUsProject:masterfrom
YheChen:feat/assign-instructors-as-graders

Conversation

@YheChen

@YheChen YheChen commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Proposed Changes

(Describe your changes here. Also describe the motivation for your changes: what problem do they solve, or how do they improve the application or codebase? If this pull request fixes an open issue, use a keyword to link this pull request to the issue.)

This PR allows course instructors to be assigned as graders for assignments, alongside TAs.

  • Displays instructors in the assignment Graders tab.
  • Supports assigning and unassigning instructors for groups and criteria.
  • Supports instructor assignment through random and section-based assignment actions.
  • Allows instructor usernames in group and criterion grader-mapping CSV imports.
  • Shares grader statistics behavior between instructors and TAs so assignment summaries continue to work.
  • Prevents unsupported roles and cross-assignment groups from being included in bulk grader assignments.
  • Adds controller and model test coverage for the new behavior.

This is the first incremental part of #1221.
Ref #1221

Screenshots of your changes (if applicable) image

Type of Change

(Write an X or a brief description next to the type or types that best describe your changes.)

Type Applies?
🚨 Breaking change (fix or feature that would cause existing functionality to change)
New feature (non-breaking change that adds functionality) X
🐛 Bug fix (non-breaking change that fixes an issue)
🎨 User interface change (change to user interface; provide screenshots) X
♻️ Refactoring (internal change to codebase, without changing functionality) X
🚦 Test update (change that only adds or modifies tests)
📦 Dependency update (change that updates a dependency)
📖 Documentation update (change that updates documentation)
🔧 Internal (change that only affects developers or continuous integration)

Checklist

(Complete each of the following items for your pull request. Indicate that you have completed an item by changing the [ ] into a [x] in the raw text, or by clicking on the checkbox in the rendered description on GitHub.)

Before opening your pull request:

  • I have performed a self-review of my changes.
    • Check that all changed files included in this pull request are intentional changes.
    • Check that all changes are relevant to the purpose of this pull request, as described above.
  • I have added tests for my changes, if applicable.
    • This is required for all bug fixes and new features.
  • I have updated the project documentation, if applicable.
    • This is required for new features.
  • If this is my first contribution, I have added myself to the list of contributors.

After opening your pull request:

  • I have updated the project Changelog (this is required for all changes).
  • I have verified that the pre-commit.ci checks have passed.
  • I have verified that the CI tests have passed.
  • I have reviewed the test coverage changes reported by Coveralls.
  • I have requested a review from a project maintainer.

Questions and Comments

(Include any questions or comments you have regarding your changes.)

@coveralls

coveralls commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Coverage Report for CI Build 29963024176

Coverage increased (+0.02%) to 90.429%

Details

  • Coverage increased (+0.02%) from the base build.
  • Patch coverage: 119 of 119 lines across 15 files are fully covered (100%).
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 51372
Covered Lines: 47471
Line Coverage: 92.41%
Relevant Branches: 2470
Covered Branches: 1218
Branch Coverage: 49.31%
Branches in Coverage %: Yes
Coverage Strength: 127.86 hits per line

💛 - Coveralls

@YheChen YheChen changed the title WIP: feat: instructors can appear as graders feat: instructors can appear as graders Jul 16, 2026
@YheChen
YheChen requested a review from david-yz-liu July 16, 2026 00:57
Comment thread app/models/grouping.rb
# is a list of grouping IDs involved in the unassignment. The memberships
# and groupings must belong to the given assignment +assignment+.
def self.unassign_tas(ta_membership_ids, grouping_ids, assignment)
grouping_ids = assignment.groupings.where(id: grouping_ids).ids

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a separate bug unrelated to the current task. It's good to fix, but please do so in a separate PR.

Comment thread app/models/grouping.rb
ta_ids = Ta.where(id: ta_ids).ids
grouping_ids = Grouping.where(id: grouping_ids).ids
ta_ids = assignment.course.graders.where(id: ta_ids).ids
grouping_ids = assignment.groupings.where(id: grouping_ids).ids

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a separate bug unrelated to the current task. It's good to fix, but please do so in a separate PR.

Comment thread app/models/course.rb Outdated
has_many :roles
has_many :instructors
has_many :tas
has_many :graders, -> { where(type: %w[Instructor Ta]) }, class_name: 'Role', inverse_of: :course

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming here is quite challenging. The current application uses "TAs" and "Graders" as somewhat synonymous, both internally and externally. That's not ideal, but some historical baggage we have to live with now.

Please rename this association to course_staff. It's in some sense more generic (not specifically about grading) but has a fairly intuitive meaning.

Carry this renaming throughout; for example, rename GraderRole to CourseStaffRole

Comment thread app/models/grouping.rb Outdated
ta_ids.each_with_index do |group, index|
weightings[group] = (weightings_arr[index].to_f / total * grouping_ids.length).round
end
weightings = Array(ta_ids).map(&:to_i).zip(weightings_arr).to_h

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes in this method are unrelated to the current task, and (unlike the ones below) I do not see what problem this is trying to solve. Revert these changes and message me separately about this method.

@YheChen
YheChen requested a review from david-yz-liu July 18, 2026 17:48
Comment thread app/models/criterion_ta_association.rb
if role && !role.is_a?(Ta)
errors.add(:base, :not_ta)
def must_be_a_grader
if role && !role.is_a?(Ta) && !role.instance_of?(Instructor)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to my above comment, use is_a?

Comment thread app/models/ta_membership.rb Outdated
# rubocop:enable Layout/LineLength, Lint/RedundantCopDisableDirective
class TaMembership < Membership
validate :must_be_a_ta
validate :must_be_a_grader

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename to must_be_course_staff

models:
ta_membership:
not_ta: User must be a TA
not_grader: User must be a TA or instructor

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename this key to not_course_staff

## Graders Tab

The graders tab can be used to assign graders to specific students. Once you assign a grader to a student, they will be able to perform all the same actions as the instructor account (including releasing marks). However, they may only perform these actions on students that have been assigned to them.
The Graders tab can be used to assign graders to specific student groups and marking criteria. For assignments, both course graders and course instructors are available for assignment. Assigning an instructor can be useful for distributing and tracking marking work, but does not restrict the access they already have as an instructor.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"course graders" -> "course TAs"

The graders tab can be used to assign graders to specific students. Once you assign a grader to a student, they will be able to perform all the same actions as the instructor account (including releasing marks). However, they may only perform these actions on students that have been assigned to them.
The Graders tab can be used to assign graders to specific student groups and marking criteria. For assignments, both course graders and course instructors are available for assignment. Assigning an instructor can be useful for distributing and tracking marking work, but does not restrict the access they already have as an instructor.

For marks spreadsheets, graders can be assigned to individual students, but instructors are not included as assignable graders.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make this an inset "NOTE" (see how we do this elsewhere in the docs). Also, replace "graders" with "TAs"

### Table Info

When you arrive at the "Manage Graders" page you will see a table of graders:
When you arrive at the "Manage Graders" page you will see a table of assignable graders. For assignments, this table contains both graders and instructors:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"graders and instructors" -> "TAs and instructors"

## Setting up a Grader Account

If anyone other than the course instructor wants to be able to grade a student (or group's) work, they must have a "Grader" account. This means if a course has 4 TAs and everyone wants to be able to grade work, you must set up 4 separate "Grader" accounts - one for each TA. The course instructor does not require a separate account for grading as all marking can also be done from the admin account.
Anyone other than a course instructor who needs to grade student work must have a "Grader" account. For example, if a course has 4 TAs who will all grade work, you must set up 4 separate Grader accounts—one for each TA.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Grader" account -> "TA" account (and same for "Grader accounts")

Comment thread app/models/criterion_ta_association.rb Outdated
has_one :course, through: :assignment

validate :courses_should_match
validate :must_be_a_grader

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename to must_be_course_staff

Comment thread app/models/criterion_ta_association.rb Outdated
course_tas = assignment.course.tas
unless ta_user_names.all? { |g| course_tas.joins(:user).exists?('users.user_name': g) }
course_staff = assignment.course.course_staff
unless ta_user_names.all? { |g| course_staff.joins(:user).exists?('users.user_name': g) }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please rename ta_user_names to staff_user_names

Comment thread app/models/criterion_ta_association.rb Outdated
private

def must_be_course_staff
errors.add(:ta, :invalid) if ta && !ta.is_a?(Ta) && !ta.is_a?(Instructor)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry, I forgot that AdminRole is a subclass of Instructor. We don't want "admins" to be assigned to be graders. Please revert to instance_of? and the additional tests. But also leave a comment explaining that we use instance_of? to exclude AdminRole.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh good point

@YheChen
YheChen requested a review from david-yz-liu July 22, 2026 22:42

@david-yz-liu david-yz-liu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work, @YheChen!

@david-yz-liu
david-yz-liu merged commit 22f49a7 into MarkUsProject:master Jul 23, 2026
8 checks passed
@YheChen
YheChen deleted the feat/assign-instructors-as-graders branch July 23, 2026 21:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants