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 iteration of E2602 hardens and extends the Student Task View: consolidating the student dashboard and detail pages into single-call 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

  • Eliminated N+1 queries in the score heatgrid (get_team_scores, get_my_scores_data, accumulate_round_scores, get_answer) via .includes(responses: { scores: :item }) and in-memory filtering instead of re-querying preloaded associations
  • Converted ReviewTable's stylesheet to CSS Modules, fixing a Bootstrap class collision and scoping color classes (c1c5) correctly
  • Redesigned the score/feedback heatgrid with sticky #/Question columns and a sticky, full-width SectionHeader row that stays fixed during horizontal scroll
  • Added hover tooltips on score cells showing the reviewer's written feedback
  • Rewrote StudentTasks from a (mis-implemented) topic sign-up sheet into a proper course-grouped assignment dashboard
  • Added participant-level can_submit / can_review permission checks, hiding "Your work" / "Others' work" task links accordingly
  • Refactored review grade calculation: ResponseMap#review_grade now weights each round by its 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 numbering 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

Acceptance Criteria:

  • GET /student_tasks/list returns all tasks for the logged-in student in one call
  • Tasks are grouped by course on the dashboard
  • Each task shows current stage and days remaining

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

Acceptance Criteria:

  • GET /student_tasks/show/:id returns the task plus a unified due_dates timeline
  • The timeline visually distinguishes completed, current, and pending stages
  • GET /student_tasks/team returns teammates grouped by course

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

Acceptance Criteria:

  • The heatgrid loads from a single GET /grades/:id/view_our_scores call
  • SectionHeaders are excluded from the # column numbering
  • SectionHeader rows and the #/Question columns stay fixed during horizontal scroll
  • Hovering a score with a comment shows the reviewer's feedback as a tooltip

As a student who isn't permitted to submit or review, I should 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 review grades to reflect each rubric's configured weight

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

No dedicated StudentTask database table exists. StudentTask is a plain Ruby object composed from AssignmentParticipant and its associated Assignment, Course, DueDate, SignedUpTeam/ProjectTopic, and permission fields (can_submit, can_review, permission_granted).

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
  • Selecting an assignment navigates to the detail route → frontend calls GET /student_tasks/show/:id
  • StudentTask.get_events_for_assignment merges due dates with submitted review/feedback activity into one sorted timeline
  • For the score heatgrid: frontend calls GET /grades/:id/view_our_scoresGradesController#get_team_scores eager-loads responses: { scores: :item } and calls insert_section_headers to splice header sentinels into the response

Score Aggregation Flow

  • 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

StudentTasksController

  • list — all StudentTask objects for the logged-in user
  • show — single task by participant ID with the unified timeline
  • team — teammates grouped by course name (StudentTask.all_teammates)

GradesController — N+1 elimination

get_team_scores and get_my_scores_data eager-load responses: { scores: :item } on the ReviewResponseMap/TeammateReviewResponseMap queries. accumulate_round_scores filters SectionHeader scores 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

Splices { type: "header", txt: "..." } sentinel hashes into a round's scores array at positions matching each SectionHeader item in the questionnaire's sequence, so the frontend can render heading rows between score rows without those rows affecting numbering.

Review Grade Calculation

  • ResponseMap#review_grade — latest submitted response per round, normalized (score/max), weighted by that round's questionnaire_weight
  • ResponseMap.compute_average_reviewer_score / ResponseMap.reviewer_reputation_for — shared weighted-average logic; reviewer reputation currently defaults to 1.0 (placeholder for a future review-quality grader integration)
  • Questionnaire#total_item_weight — pre-calculates the sum of scored item weights, excluding SectionHeader items

Comprehensive Testing

  • Model specs for StudentTask.all_teammates, create_from_participant, get_events_for_assignment
  • Request specs for student_tasks/list, student_tasks/show/:id, student_tasks/team, and grades/:id/view_our_scores
  • Routing specs verifying the above routes resolve correctly

Frontend Implementation

Student View Assignments Page
Student View Assignment Detail Page
Student View Assignment View Team Grades
Student View Assignment View Feedbacks

New/Updated Files

File Purpose
StudentTasks.tsx Course-grouped assignment dashboard (rewritten from a topic sign-up sheet)
StudentTasksList.tsx Sidebar — not-started tasks, revisions, teammates by course
StudentTaskDetail.tsx Per-assignment detail page — timeline, task links, conditional can_submit/can_review rendering
ViewTeamGrades.module.scss CSS Modules conversion; scoped c1c5 color classes, sticky-column and tooltip styles
ReviewTable.tsx Single-call heatgrid; Scores/Feedback toggle; sticky SectionHeader rows
ReviewTableRow.tsx Sticky #/Question columns; reduced row height; hover tooltip via data-question
FeedbackTable.tsx Feedback-mode table mirroring ReviewTable's sticky-column layout
heatgridUtils.ts RoundRow/isHeader() types; fixed # numbering to skip SectionHeader sentinels

StudentTasks (List View)

  • Single GET /student_tasks/list call, grouped by course
  • Fixed: "Assignments" heading was rendering as body text because its class was referenced as a plain string instead of the CSS Modules export (styles['assignments-title'])

StudentTaskDetail (Detail View)

  • Visual timeline with progress line and stage nodes (completed/current/pending)
  • Task links list — "Your work" and "Others' work" conditionally rendered based on can_submit/can_review
  • "Send Email To Reviewers" button repositioned to align with the task list rather than floating disconnected at the page top

ViewTeamGrades (Heatgrid)

  • Consolidated ~6 sequential API calls into one GET /grades/:id/view_our_scores
  • SectionHeader rows split into a sticky label cell + scrolling spacer cell so they stay fixed during horizontal scroll
  • Score cells show reviewer feedback as a hover tooltip
  • 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_review_grade and ReviewAggregator#compute_average_review_score was consolidated into a single ResponseMap.compute_average_reviewer_score, and the now-dead ReviewAggregator concern was deleted.

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 StudentTask.all_teammates groups teammates by course Returns {course_name => [fullnames]}
2 all_teammates excludes calibrated assignments Calibrated-assignment teammates excluded
3 all_teammates excludes the user themself Solo team returns empty hash
4 GET /student_tasks/list returns all tasks for user 200 OK with task array
5 GET /student_tasks/show/:id returns due_dates timeline 200 OK with due_dates array
6 GET /student_tasks/team groups teammates by course 200 OK with grouped hash
7 Heatgrid # column excludes SectionHeaders from numbering Sequential numbering skips header rows
8 accumulate_round_scores filters SectionHeaders without re-querying No additional SQL fired against preloaded association
9 AssignmentQuestionnaire weight validation questionnaire_weight must be 0 when rubric has no scored questions
10 ResponseMap.compute_average_reviewer_score Weighted average matches Σ(grade × weight) / Σ(weight)

Frontend Tests

# Test Expected Result
1 StudentTasks renders "Assignments" heading with correct styling Heading uses module class, renders bold and large
2 StudentTaskDetail hides "Your work" when can_submit is false List item not rendered
3 StudentTaskDetail hides "Others' work" when can_review is false List item not rendered
4 heatgridUtils itemNumber skips SectionHeader rows Numbering matches scored items only
5 ReviewTable SectionHeader row stays fixed during horizontal scroll Header cell position: sticky
6 ReviewTableRow shows tooltip only when a comment exists No tooltip box when data-question is undefined
7 Heatgrid column borders render as #ddd No black borders after tbl_heat class applied

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/requests/api/v1/student_tasks_controller_spec.rb
bundle exec rspec spec/requests/api/v1/grades_controller_spec.rb

Frontend

Setup & Run

npm install
npm start

Running Frontend Tests

npm test -- StudentTaskDetail   # run a single suite
npm test                        # run full suite

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 for every reviewer.

Email-the-authors integration

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

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