CSC/ECE 517 Spring 2026 - E2602. Reimplement student task view

From Expertiza_Wiki
Jump to navigation Jump to search

CSC/ECE 517 Spring 2026 - E2602. Reimplement Student Task View

About Expertiza

Expertiza is a robust, open-source web platform designed to empower educational environments that emphasize project-based learning. It streamlines collaborative activities including peer reviews, surveys, and team assignments — giving instructors tools to manage assignments and track performance, and giving students a single place to submit work, review peers, and track feedback.

Introduction

This page documents the reimplementation of the Student Task view. It supersedes the earlier E2602 team's version. All searches for E2602 will hit here. This iteration of E2602 hardens and extends the Student Task View: consolidating the student dashboard and detail pages into REST endpoints, eliminating N+1 queries in the peer-review score heatgrid, and fixing several display/permission bugs surfaced by manual QA against real assignment data.

Tech Stack

  • Backend: Ruby on Rails (API-only)
  • Frontend: React + TypeScript
  • Database: MySQL
  • Backend Testing: RSpec
  • Frontend Testing: Jest, React Testing Library

Key Improvements over the Previous Implementation and the Existing Expertiza System

The following improvements were made both over the earlier student_task_controller in the existing Expertiza system and over the initial E2602 skeleton handed off to this team:

  • Rewrote StudentTasks.tsx from a mis-implemented topic sign-up sheet into a proper course-grouped assignment dashboard, loaded from a single GET /student_tasks/list call
  • Built a new StudentTaskDetail page with a visual progress timeline — fetches task summary and timeline events in parallel via GET /student_tasks/show/:id and GET /participants/:id/timeline; timeline nodes are colour-coded by stage status (completed/current/pending)
  • Added StudentTasksList sidebar showing tasks not yet started, assignments in revision, and a course-grouped list of past teammates
  • Added AssignmentParticipant#timeline_events to build a chronologically sorted array of due dates and submitted review/feedback activity for a participant
  • Added AssignmentParticipant.all_teammates to return teammates grouped by course, excluding calibration assignments and the requesting user; fixed a pre-existing bug where assignment.is_calibrated always returned nil due to an attr_accessor shadowing ActiveRecord's column reader
  • Added GET /participants/teammates and GET /participants/:id/timeline endpoints to ParticipantsController
  • Added participant-level can_submit/can_review permission checks, hiding "Your work" and "Others' work" task links when the student lacks those permissions
  • Eliminated N+1 queries in the score heatgrid via .includes(responses: { scores: :item }) and in-memory filtering with .reject instead of re-querying preloaded associations
  • Converted ViewTeamGrades.module.scss to CSS Modules, fixing a Bootstrap class collision and correctly scoping c1c5 color classes
  • Redesigned the score/feedback heatgrid with sticky Question column and section header rows during horizontal scroll
  • Added hover tooltips on score cells showing the reviewer's written feedback
  • Refactored review grade calculation: ResponseMap#review_grade weights each round by its configured questionnaire_weight; ResponseMap.compute_average_reviewer_score centralizes the weighted-average logic previously duplicated across AssignmentTeam and AssignmentParticipant

Problem Statement

The previous Student Task View had several concrete defects:

  • The score heatgrid fired one SQL query per response/score across all reviewers and rounds, producing hundreds of queries for a single page load
  • SectionHeader rubric rows (non-scorable dividers) were incorrectly counted in the # column and lost their fixed position during horizontal scroll
  • StudentTasks.tsx was previously implemented as a topic sign-up sheet, not a task dashboard, and required a full rewrite
  • The page did not respect participant-level can_submit/can_review flags — all task links were always shown regardless of permission
  • Review grade calculation treated every reviewer and every round equally, ignoring each rubric's configured questionnaire_weight

User Stories

As a student, I want to see all my assignments in one place

This is the home view for a student. It shows all of the student's assignments, arranged by the course they pertain to.

Student View Assignments Page

Acceptance Criteria:

  • GET /student_tasks/list returns all tasks for the authenticated student in a single call — no sequential per-assignment requests
  • Each task object in the response contains: assignment (name), course, topic, current_stage, stage_deadline, permission_granted, active_round, submission_updated, started, and a nested participant object
  • Results are pre-sorted by [course, assignment, stage_deadline] so the frontend can render them in a stable order without additional sorting
  • The frontend groups the sorted list by course name to produce the course-grouped dashboard view
  • Each task displays its current stage and next deadline; tasks with no upcoming due date show "Finished"
  • Stage deadline is adjusted to the student's configured timezone preference where available
  • GET /participants/teammates returns a hash of { course_name => [teammate_full_names] } for all assignment teams the student has been part of
  • Each course's teammate list is sorted alphabetically
  • The current user is excluded from their own teammate lists
  • Calibration assignments are excluded — they are practice exercises, not real team collaborations
  • Returns an empty hash if the student has no past teammates

As a student, I want to view a single assignment's deadlines and details

A student can perform several tasks for each assignment, depending upon the role they play (e.g., whether they are a submitter, a reviewer, or both.

Student View Assignment Detail Page

Acceptance Criteria:

  • GET /student_tasks/show/:id (where :id is the participant ID) returns the task summary: assignment name, current_stage, stage_deadline, permission_granted, and a nested participant object containing can_submit, can_review, and parent_id
  • GET /participants/:id/timeline returns a chronologically sorted array of timeline events — each with id, name, date, type, and round. Events include due dates (with id: null) and submitted peer reviews / author feedback responses (with real IDs that link to /responses/:id)
  • Both requests are made in parallel on page load; the task summary renders immediately while the timeline loads independently
  • The timeline visually distinguishes completed, current, and pending stages
  • "Your work" and "Others' work" links are conditionally shown based on can_submit and can_review respectively
  • "Your feedback" links to /view-team-grades?assignmentId=X using parent_id from the participant object in the show response
  • Both endpoints return 403 Forbidden if the authenticated user does not own the requested participant; 404 if the participant does not exist

As a student, I want my team's score heatgrid to load fast and stay readable while scrolling

The heatgrid shows all of the reviews that have been written of the team's submission (or, the student's submission, if this is not a team assignment). The user can hover over any cell with a score that is underlined, meaning that the reviewer has made a comment to go along with the score. Hovering over a cell will show the comments it contains.

Student View Assignment View Team Grades


Clicking on "Feedback" will show all of the feedback along with the scores.

Student View Assignment View Feedbacks


Acceptance Criteria:

  • The heatgrid loads from a single GET /grades/:id/view_our_scores call
  • The first two columns (item number "#" and Item) remain fixed during horizontal scroll, along with section headers
  • Hovering over a score with a comment shows the corresponding reviewer's feedback as a tooltip

A student who is not permitted to submit or review will not see those task links

Acceptance Criteria:

  • "Your work" is hidden when can_submit is false
  • "Others' work" is hidden when can_review is false

As an instructor, I want calculated review grades to reflect each rubric's configured weight

Grades are calculated in Response.rb (score for a particular review by a specific reviewer), ResponseMap.rb (composite score for reviews in all rounds by the same reviewer) and AssignmentTeam.rb (combined score for all reviews by all reviewers).

Acceptance Criteria:

  • ResponseMap#review_grade weights each round's normalized score by that round's questionnaire_weight
  • AssignmentTeam#aggregate_reviewer_score and AssignmentParticipant#aggregate_teammate_review_grade both compute their averages through the same shared ResponseMap.compute_average_reviewer_score

Design

Student Task Overview

A StudentTask is a task that a student performs in the course of doing an assignment, e.g., forming a team, choosing a topic, submitting work, or reviewing work. No dedicated StudentTask database table exists. A StudentTask is a plain Ruby object composed from AssignmentParticipant and its associated Assignment, Course, DueDate, SignedUpTeam/ProjectTopic, and permission fields (permission_granted). The per-participant submission and review permissions (can_submit, can_review) are DB columns on the participants table and are surfaced to the frontend through the nested participant object in the show response.

Request / Response Flow

  • Student navigates to /student_tasks → frontend calls GET /student_tasks/list
  • StudentTask.tasks(user) preloads assignment: :course and :user associations, then maps each AssignmentParticipant through StudentTask.create_from_participant, and sorts by [course, assignment, stage_deadline]
  • Selecting an assignment navigates to the detail route → frontend fires two requests in parallel:
    • GET /student_tasks/show/:id — returns task summary (assignment, current_stage, stage_deadline, permission_granted, nested participant with can_submit, can_review, parent_id)
    • GET /participants/:id/timeline — calls AssignmentParticipant#timeline_events, which merges the assignment's due dates with timestamps of the participant's submitted peer reviews and author feedback into one chronologically sorted array
  • For the heatgrid: frontend calls GET /grades/:id/view_our_scoresGradesController#get_team_scores eager-loads responses: { scores: :item } and calls insert_section_headers to splice unscored item markers into the response before it is returned to the frontend

Aggregation of Scores Assigned by a Reviewer

  • Response#aggregate_questionnaire_score — item-weighted score for one submitted response
  • ResponseMap#review_grade — normalizes a map's score per round and weights it by that round's questionnaire_weight
  • ResponseMap.compute_average_reviewer_score(maps) — weighted average across multiple maps, the single implementation shared by AssignmentTeam#aggregate_reviewer_score and AssignmentParticipant#aggregate_teammate_review_grade

Backend Implementation

The backend is structured around three models (AssignmentParticipant, StudentTask, and Assignment) and two controllers. The controllers expose REST endpoints consumed by the React frontend; the models encapsulate business logic including timeline generation, teammate aggregation, and grade computation.

StudentTasksController

Handles two endpoints, both requiring student privileges:

  • list (GET /student_tasks/list) — calls StudentTask.tasks(current_user), which preloads assignment: :course and :user associations on all of the user's AssignmentParticipant records, maps each through StudentTask.create_from_participant, and sorts the result by [course, assignment, stage_deadline]
  • show (GET /student_tasks/show/:id) — delegates to StudentTask.from_participant_id(params[:id]), which preloads the same associations as tasks; returns 404 if the participant does not exist, 403 if the participant belongs to a different user

ParticipantsController

Two new actions were added to the existing ParticipantsController:

  • teammates (GET /participants/teammates) — delegates to AssignmentParticipant.all_teammates(current_user) and returns a { course_name => [sorted_full_names] } hash. Calibration assignments are excluded using assignment[:is_calibrated] rather than assignment.is_calibratedAssignment has an attr_accessor :is_calibrated that shadows ActiveRecord's column reader, returning nil after a DB load; reading via [] bypasses the accessor and reads directly from AR's attribute hash
  • timeline (GET /participants/:id/timeline) — calls AssignmentParticipant#timeline_events on the located participant; returns 404 if not found, 403 if the participant belongs to a different user

AssignmentParticipant model

Two class/instance methods were added:

  • all_teammates(user) — iterates the user's AssignmentTeam memberships, skips calibration assignments, and accumulates teammates (excluding the user themselves) grouped by course name, with each course list sorted alphabetically
  • timeline_events — builds a chronologically sorted array combining the assignment's due dates (with id: nil) and any submitted ReviewResponseMap / FeedbackResponseMap responses (with real IDs). Previously this logic lived in StudentTask; it was moved here because it operates on participant-level data rather than task summaries

GradesController

get_team_scores and get_my_scores_data eager-load responses: { scores: :item } on the ReviewResponseMap/TeammateReviewResponseMap queries. accumulate_round_scores filters unscored item rows via Ruby .reject instead of .joins/.where — calling .where on an already-preloaded association silently bypasses the cache and re-queries the database. get_answer now accepts the parent response directly instead of re-fetching it via score.response.

insert_section_headers

Inserts { type: "header", txt: "..." } marker objects into the scores array at positions derived from each unscored item's (text areas and text fields) sequence order, so the frontend renders section heading rows between score rows without those rows affecting item numbering.

Review Grade Calculation

For details on how peer review grades are computed, see Score Calculation.

Comprehensive Testing

  • Model specs for AssignmentParticipant.all_teammates (grouping, calibration exclusion, self-exclusion, alphabetical sort) and AssignmentParticipant#timeline_events (due dates, submitted reviews, author feedback, sort order)
  • Request specs for GET /student_tasks/list, GET /student_tasks/show/:id, GET /participants/teammates, GET /participants/:id/timeline, and GET /grades/:id/view_our_scores
  • Routing specs verifying the above routes resolve correctly

Frontend Implementation

New/Updated Files

File Purpose
StudentTasks.tsx Course-grouped assignment dashboard rewritten from a topic sign-up sheet; uses TanStack Table with per-course grouping, a "Review Grade" tooltip column, and a "Show as Example?" client-side toggle
StudentTasks.module.css CSS Modules stylesheet scoping the dashboard layout and course-section headings
StudentTasksList.tsx Sidebar ("StudentTasksBox") with three sections: tasks not yet started, revisions (links to /student_review/list/:participantId), and teammates by course fetched independently via GET /participants/teammates
StudentTasksList.module.css CSS Modules stylesheet for the sidebar layout and teammate count badges
StudentTaskDetail.tsx Per-assignment detail page — fires GET /student_tasks/show/:id and GET /participants/:id/timeline in parallel; renders a visual progress timeline and conditionally shows task links based on can_submit/can_review
StudentTaskDetail.module.css CSS Modules stylesheet for the timeline track line, stage nodes, and deadline label rows
ViewTeamGrades.module.scss CSS Modules conversion of the heatgrid stylesheet; scopes c1c5 color classes, sticky-column positioning, and hover tooltip styles
ReviewTable.tsx Heatgrid loaded from a single GET /grades/:id/view_our_scores call; Scores/Feedback toggle; sticky SectionHeader rows
ReviewTableRow.tsx Sticky #/Question columns; reduced row height; hover tooltip on score cells that have reviewer comments
FeedbackTable.tsx Feedback-mode table mirroring ReviewTable's sticky-column layout
heatgridUtils.ts RoundRow/isHeader() types; normalizeReviewData() adapter translating the legacy questionNumber/questionText field names to itemNumber/itemText; fixed # numbering that skips SectionHeader sentinels

StudentTasks (List View)

  • Single GET /student_tasks/list call; parseStudentTasks() normalises the raw response into a typed Task[] with ?? fallbacks to handle both flat and nested response shapes
  • tasksGroupedByCourse groups the sorted task list by course name, rendering a separate TanStack Table per course
  • "Assignment" column links to /student_task_detail/:participantId, passing task summary via router state so the detail page can render the header before the API responds
  • "Review Grade" column renders a ToolTip when a grade exists; shows "NA" otherwise
  • Sidebar (StudentTasksList) receives a Revision[] derived from the same task list; fetches teammates independently via GET /participants/teammates
  • Fixed: "Assignments" heading was rendering as unstyled body text because its class was referenced as a plain string instead of the CSS Modules export (styles['assignments-title'])

StudentTaskDetail (Detail View)

  • Fires two requests in parallel on mount: GET /student_tasks/show/:id for task summary and GET /participants/:id/timeline for timeline events; task summary renders immediately while the timeline loads independently
  • Timeline events are sorted by date and rendered as three aligned rows: date labels, a progress track line with coloured stage nodes (completed=red filled, current=pulsing red, pending=grey outline), and deadline name labels
  • Progress track line uses a CSS linear-gradient driven by progressPercent, calculated as the midpoint of the current stage node so the red fill ends at the centre of the active dot
  • Date strings from the API are normalised: ISO 8601 strings are used as-is; legacy dd-mm-yyyy strings are reformatted to yyyy-mm-ddTHH:MM:SS before parsing
  • "Your work" and "Others' work" links are conditionally rendered based on can_submit and can_review flags from the nested participant object in the show response
  • "Your feedback" links to /view-team-grades?assignmentId=X using parent_id from the participant object, falling back to assignmentId passed via router state

ViewTeamGrades (Heatgrid)

  • ReviewTable.tsx consolidates ~6 sequential API calls into one GET /grades/:id/view_our_scores; team_members is now embedded directly in the response so no secondary participant-lookup calls are needed
  • SectionHeader rows are split into a sticky label cell and a scrolling spacer cell so section headings stay fixed during horizontal scroll
  • Score cells show reviewer feedback as a hover tooltip when a comment exists
  • Round headings simplified from "Review (Round: 1 of 2)" to "Round 1"

Design Principles

Single Responsibility

Score aggregation is layered: Response owns item-level scoring, ResponseMap owns per-map/per-round weighting, ResponseMap.compute_average_reviewer_score owns cross-map averaging — each level only knows about the one below it.

Don't Repeat Yourself

The weighted-average logic that was duplicated between AssignmentTeam#aggregate_reviewer_score and AssignmentParticipant#aggregate_teammate_review_grade was consolidated into a single shared implementation: ResponseMap.compute_average_reviewer_score(maps). Both callers now delegate to that one method instead of each maintaining their own weighted-average calculation.

Performance by Design

Eager loading (.includes) and in-memory filtering (.reject) were chosen deliberately over additional database round-trips after profiling showed the heatgrid endpoint firing hundreds of queries under the old implementation.

Test Plan

Backend Tests

# Test Expected Result
1 AssignmentParticipant.all_teammates groups teammates by course Returns { course_name => [full_names] }
2 all_teammates excludes calibrated assignments Calibrated-assignment teammates not included in result
3 all_teammates excludes the user themselves Solo team returns empty hash
4 all_teammates returns teammates sorted alphabetically Names within each course are in alphabetical order
5 AssignmentParticipant#timeline_events includes due dates with id: nil Due-date entries have id: null in the response
6 timeline_events includes submitted peer review responses with a real id Submitted review entries carry the response's DB id
7 timeline_events excludes unsubmitted/draft peer review responses Draft reviews do not appear in the timeline
8 timeline_events captures both round 1 and round 2 responses for the same map Multi-round reviews each appear as separate timeline entries
9 timeline_events excludes unsubmitted author feedback responses Draft feedback does not appear in the timeline
10 timeline_events returns entries sorted by date Timeline array is in ascending chronological order
11 GET /student_tasks/list returns all tasks for user 200 OK with task array
12 GET /student_tasks/show/:id returns task summary 200 OK with assignment, current_stage, stage_deadline, permission_granted, and nested participant fields
13 GET /participants/:id/timeline returns sorted timeline events 200 OK with array of { id, name, date, type, round }; due-date entries have id: null
14 GET /participants/teammates groups teammates by course 200 OK with { course_name => [sorted_full_names] }; calibration assignments and requesting user excluded
15 Heatgrid # column excludes SectionHeaders from numbering Sequential numbering skips header rows
16 accumulate_round_scores filters SectionHeaders without re-querying No additional SQL fired against preloaded association
17 AssignmentQuestionnaire weight validation questionnaire_weight must be 0 when rubric has no scored questions
18 ResponseMap.compute_average_reviewer_score Weighted average matches Σ(grade × weight) / Σ(weight)

Relevant Links

Repos

Pull Requests

Demo Video

  • (add link once recorded)

Developer Guidance

Backend

Setup & Run (via Docker)

docker compose up

Within the container

bundle exec rspec spec/models/student_task_spec.rb
bundle exec rspec spec/models/assignment_participant_spec.rb
bundle exec rspec spec/requests/api/v1/student_tasks_controller_spec.rb
bundle exec rspec spec/requests/api/v1/participants_controller_spec.rb
bundle exec rspec spec/requests/api/v1/grades_controller_spec.rb

Frontend

Setup & Running

npm install
npm start

Running Frontend Tests

npm test

Future Scope

Reviewer reputation-weighted grading

Integrate an external review-quality grader (e.g. an LLM-based reviewer grader) so ResponseMap.reviewer_reputation_for reflects actual review quality instead of the current 1.0 placeholder weight for every reviewer.

Email-the-authors integration

Wire "Send Email To Reviewers" to a real backend endpoint instead of the current /email_the_author placeholder.

Dedicated StudentTask abstraction

StudentTask remains a composed, non-persisted object. A future dedicated table could centralize derived state and simplify queries if the feature's scope grows further.

Mentor

Koushik Gudipelly

Members

Ravi Goparaju Xiangjun Mi

Completed by

Bestin Lalu