CSC/ECE 517 Spring 2026 - E2619. Student Quizzes Frontend

From Expertiza_Wiki
Jump to navigation Jump to search

E2619: Student Quizzes — Design Document

Introduction

Expertiza is an educational web application collaboratively developed and maintained by students and faculty at NCSU. As an open-source project built on the Ruby on Rails platform, its code is accessible on GitHub. The platform enables students to provide peer reviews and refine their work based on feedback.

This design document describes the implementation of the E2619 Student Quizzes project, covering both the frontend (React/TypeScript) and backend (Ruby on Rails API). The project builds on the E2607 questionnaire rendering code and extends it with quiz-specific capabilities: directing students to quizzes/reviews, scoring quiz responses, specifying correct answers, and handling fill-in-the-blank questions.

Project Overview

Quizzes in Expertiza are designed to ensure that reviewers comprehend the material they are evaluating. When an assignment includes quizzes (require_quiz = true), submitting teams create quizzes based on their submissions. Reviewers must complete the quizzes before reviewing to demonstrate their understanding. If a reviewer performs poorly, their review can be discounted to maintain quality.

Previous Implementation

The prior codebase provided the data models, CRUD controllers, and UI components needed for assignments, questionnaires, and peer reviews. However, the quiz workflow was incomplete in several key ways:

  • Response#aggregate_questionnaire_score computed scores as answer * weight using only the numeric answer column, with no correctness check against a stored correct answer. There was no way to distinguish a quiz response from a regular review response.
  • The Item model had no correct_answer column; there was nowhere to persist the expected answer for a quiz item.
  • The StudentTask model did not carry quiz gateway fields (require_quiz, quiz_taken, has_quiz_questionnaire, quiz_questionnaire_id), so the frontend had no structured way to know whether a student must take a quiz before reviewing.
  • The Team model had no quiz_questionnaire_id. Quiz questionnaire ownership was expected to flow through the assignment's questionnaire list, which required instructor configuration and did not support per-team quizzes.
  • The ResponseMapsController had no index action that filtered out quiz maps, so the frontend could not safely list a student's review assignments without also receiving spurious self-referential quiz entries.
  • No QuizResponseMapsController existed; there was no endpoint to create a QuizResponseMap before a student started a quiz.
  • The questionnaire editor rendered no "Correct answer" field for any item type when the questionnaire type was set to "Quiz".
  • The student task page had no "Take Quiz" button or any flow to route a student from a quiz gate to the quiz form and back to their review.

Project Goals

Based on the gaps above, this project aimed to:

  1. Add a correct_answer string column to quiz items so submitting teams can specify expected answers at quiz-creation time.
  2. Extend Response#aggregate_questionnaire_score to detect quiz responses by the reviewer_id == reviewee_id invariant and score text/choice items from the comments column using case-insensitive equality.
  3. Extend StudentTask with four quiz gateway fields so the frontend can render the correct button (Take Quiz / Start Review) without extra API calls.
  4. Add a quiz_questionnaire_id column to Team so each submitting team directly owns its quiz questionnaire, enabling per-team quizzes on the same assignment.
  5. Implement POST /quiz_response_maps so the frontend can create (or reuse) a QuizResponseMap before navigating a student to the quiz form.
  6. Update ResponseMapsController#index to exclude quiz maps and include per-row quiz state.
  7. Add a "Correct answer" row in the questionnaire editor for every quiz item type.
  8. Implement an instructor "Create Quiz" button on the reviewer-assignment page that opens the questionnaire editor pre-linked to the team.
  9. Implement a "Take Quiz" button in the student task list that calls POST /quiz_response_maps and navigates to the existing review form in quiz mode.
  10. Add GET /questions/quiz_types so the frontend can fetch the allowed quiz item types dynamically.

Design

Architecture Overview

┌─────────────────────────────────────────────────────────────┐
│                     Frontend (React/TS)                     │
│                                                             │
│  AssignedReviews ──→ [Take Quiz] ──→ TeammateReview         │
│       │               (quiz mode)        │                  │
│       │                                  └──→ [redirect]    │
│       └──→ [Start/Open Review] ──→ TeammateReview           │
│                                     (review mode)           │
│  QuestionnaireEditor ──→ correct_answer fields per type     │
└──────────────────────────┬──────────────────────────────────┘
                           │ REST API
┌──────────────────────────┴──────────────────────────────────┐
│                     Backend (Rails API)                     │
│                                                             │
│  QuizResponseMapsController  (POST /quiz_response_maps)     │
│  ResponsesController         (create / update / submit)     │
│  ResponseMapsController      (index — filters quiz maps)    │
│  Response model              (aggregate_questionnaire_score) │
│  StudentTask model           (quiz gateway fields)          │
└─────────────────────────────────────────────────────────────┘

UML Design

┌───────────────────────────────────────┐
│              Assignment               │
├───────────────────────────────────────┤
│ +boolean require_quiz                 │
│ +int num_quiz_questions               │
│ +has_many questionnaires (via AQ)     │
└────────────────────┬──────────────────┘
                     │ has_many (via AQ)
                     ▼
┌───────────────────────────────────────┐     ┌─────────────────────────────┐
│            Questionnaire              │     │        Team  [UPDATED]      │
├───────────────────────────────────────┤     ├─────────────────────────────┤
│ +String name                          │     │ +int quiz_questionnaire_id  │◄─── NEW
│ +String questionnaire_type            │◄────│ +belongs_to                 │
│   ("Quiz" for quiz questionnaires)    │     │   quiz_questionnaire        │
│ +int min_question_score               │     └─────────────────────────────┘
│ +int max_question_score               │
│ +int instructor_id                    │
└────────────────────┬──────────────────┘
                     │ has_many
                     ▼
┌───────────────────────────────────────┐
│            Item  [UPDATED]            │
├───────────────────────────────────────┤
│ +String txt                           │
│ +String question_type                 │
│ +int weight                           │
│ +String correct_answer               ◄│─── NEW
│ +QUIZ_ITEM_TYPES : Array             ◄│─── NEW
│ +is_quiz_item?() : boolean           ◄│─── NEW
└────────────────────┬──────────────────┘
                     │ has_many answers
                     ▼
┌───────────────────────────────────────┐
│               Answer                  │
├───────────────────────────────────────┤
│ +int item_id                          │
│ +int response_id                      │
│ +int answer                           │
│ +String comments  (quiz answer here)  │
└────────────────────┬──────────────────┘
                     │ belongs_to
                     ▼
┌───────────────────────────────────────┐
│          Response  [UPDATED]          │
├───────────────────────────────────────┤
│ +int map_id                           │
│ +boolean is_submitted                 │
│ +int round                            │
│ +aggregate_questionnaire_score()     ◄│─── quiz-aware: checks
│                                       │    reviewer_id == reviewee_id
└────────────────────┬──────────────────┘
                     │ belongs_to
                     ▼
┌───────────────────────────────────────┐
│      QuizResponseMap  [UPDATED]       │
├───────────────────────────────────────┤
│ +int reviewer_id                      │
│ +int reviewee_id                      │
│   (reviewer_id == reviewee_id        ◄│─── self-referential invariant
│    identifies quiz maps)              │    distinguishes quiz from review
│ +int reviewed_object_id              ◄│─── quiz questionnaire id
│ +mappings_for_reviewer()              │
└───────────────────────────────────────┘

Screenshots

Take Quiz Button (Assigned Reviews)

Questionnaire Editor – Correct Answer Fields

Quiz Submission and Score Display

Changes Implemented

1) Database Schema Additions

Two new columns support the quiz feature. The correct_answer string column on items stores the expected answer for each quiz question. It is only exposed in API responses when is_quiz_item? is true, preventing accidental leakage on peer-review rubric items. The quiz_questionnaire_id integer column on teams lets each submitting team directly own its quiz questionnaire — a key change that makes per-team quizzes possible and replaces a previous approach that required manual wiring at the assignment level.

Two migrations were added to support the quiz features:

# db/migrate/20260423000001_add_correct_answer_to_items.rb
class AddCorrectAnswerToItems < ActiveRecord::Migration[8.0]
  def change
    add_column :items, :correct_answer, :string
  end
end

# db/migrate/20260424000001_add_quiz_questionnaire_id_to_teams.rb
class AddQuizQuestionnaireIdToTeams < ActiveRecord::Migration[8.0]
  def change
    add_column :teams, :quiz_questionnaire_id, :integer, null: true, default: nil
    add_index  :teams, :quiz_questionnaire_id
  end
end

The correct_answer column stores the expected answer for each quiz item. It is only exposed in API responses when is_quiz_item? is true, preventing accidental leakage on peer-review rubric items.

The quiz_questionnaire_id column on teams allows each submitting team to directly own its quiz questionnaire. This replaces a previous approach that required the instructor to manually wire a questionnaire to the assignment, and is the key change that makes per-team quizzes possible.

2) Quiz-Aware Scoring in Response Model

Quiz responses are identified by the invariant reviewer_id == reviewee_id on the associated ResponseMap — no STI type column is needed. For text and choice quiz items the student's answer is stored in the comments column (not the numeric answer column), so scoring uses case-insensitive string equality. Both spaced names ("Text field", "Multiple choice") and CamelCase names ("TextField", "MultipleChoiceRadio") are handled because the frontend historically used both conventions. The final score is returned in the PATCH /responses/:id/submit response body as total_score and displayed to the student before the redirect.

The core scoring logic in Response#aggregate_questionnaire_score was extended to handle quiz responses:

def aggregate_questionnaire_score
  sum = 0
  is_quiz = map.reviewer_id == map.reviewee_id
  comment_scored_types = %w[
    TextField MultipleChoiceRadio MultipleChoiceCheckbox
    Text\ field Multiple\ choice Multiple\ choice\ checkbox
  ].freeze

  scores.each do |s|
    if is_quiz && comment_scored_types.include?(s.item.question_type)
      correct        = s.item.correct_answer.to_s.strip.downcase
      student_answer = s.comments.to_s.strip.downcase
      sum += (student_answer == correct && correct.present? ? 1 : 0) * (s.item.weight || 1)
    else
      sum += s.answer * (s.item.weight || 1) unless s.answer.nil?
    end
  end
  sum
end

Both spaced names ("Text field", "Multiple choice") and CamelCase names ("TextField", "MultipleChoiceRadio") are handled because the frontend historically used both conventions.

3) StudentTask Quiz Gateway Fields

Four fields were added to StudentTask so the frontend can make gatekeeping decisions without additional API calls: require_quiz, has_quiz_questionnaire, quiz_questionnaire_id, and quiz_taken (true only when a submitted Response exists against the student's QuizResponseMap). The frontend AssignedReviews component uses these fields to gate each review row — if require_quiz && has_quiz_questionnaire && !quiz_taken, a yellow "Take Quiz" button is shown instead of the review button.

Four fields were added to StudentTask:

attr_accessor :assignment, :assignment_id, :current_stage, :participant,
              :stage_deadline, :topic, :permission_granted,
              :require_quiz, :quiz_taken, :has_quiz_questionnaire, :quiz_questionnaire_id

The from_participant factory method was updated to compute these fields:

def self.from_participant(participant)
  asgn = participant.assignment
  return nil unless asgn

  review_maps  = ReviewResponseMap.where(reviewer_id: participant.id)
  quiz_q_id    = review_maps.filter_map { |m| Team.find_by(id: m.reviewee_id)&.quiz_questionnaire_id }.first
  quiz_taken   = QuizResponseMap.where(reviewer_id: participant.id, reviewed_object_id: quiz_q_id)
                                .joins(:responses)
                                .where(responses: { is_submitted: true })
                                .exists?  if quiz_q_id

  new(
    assignment:             asgn.name,
    assignment_id:          asgn.id,
    require_quiz:           asgn.require_quiz || false,
    has_quiz_questionnaire: quiz_q_id.present?,
    quiz_questionnaire_id:  quiz_q_id,
    quiz_taken:             quiz_taken || false,
    # ... other fields
  )
end

4) QuizResponseMapsController

A QuizResponseMap is self-referential: reviewer_id == reviewee_id, both pointing to the reviewer's own AssignmentParticipant record. This invariant is what distinguishes quiz maps from peer-review maps throughout the system. The endpoint is idempotent — if a map already exists for this student/questionnaire pair it is returned as-is rather than creating a duplicate, making it safe for the frontend to call on every "Take Quiz" click.

A new controller handles quiz map creation:

# POST /quiz_response_maps
def create
  # ... validate params, find assignment, resolve quiz questionnaire from team ...
  map = QuizResponseMap.find_by(
    reviewed_object_id: quiz_questionnaire.id,
    reviewer_id: reviewer_participant.id,
    reviewee_id: reviewer_participant.id
  ) || QuizResponseMap.new(...)

  render json: {
    quiz_map_id:             map.id,
    quiz_questionnaire_id:   quiz_questionnaire.id,
    reviewer_participant_id: reviewer_participant.id
  }, status: :created
end

5) ResponseMapsController — Quiz Map Filtering

The index action must return only peer-review maps; quiz maps must not appear in the reviewer table. The primary guard next if map.reviewer_id == map.reviewee_id is reliable regardless of accidental id coincidences between questionnaire ids and assignment ids. A secondary next unless assignment check acts as belt-and-suspenders. Each returned row is augmented with quiz_taken and quiz_questionnaire_id so the frontend can render the "Take Quiz" button without a second API call.

ResponseMapsController#index was updated to exclude quiz maps and include per-row quiz state for the frontend:

result = maps.filter_map do |map|
  # E2619: skip quiz maps (reviewer_id == reviewee_id)
  next if map.reviewer_id == map.reviewee_id
  # belt-and-suspenders: reviewed_object_id must reference an assignment
  assignment = Assignment.find_by(id: map.reviewed_object_id)
  next unless assignment

  team = Team.find_by(id: map.reviewee_id)
  quiz_questionnaire_id = team&.quiz_questionnaire_id
  quiz_taken = quiz_questionnaire_id.present? &&
    QuizResponseMap.where(reviewer_id: map.reviewer_id, reviewed_object_id: quiz_questionnaire_id)
                   .joins("INNER JOIN responses ON responses.map_id = response_maps.id")
                   .where(responses: { is_submitted: true }).exists?

  { id: map.id, quiz_questionnaire_id:, quiz_taken:, ... }
end

6) Instructor "Create Quiz" Button (AssignReviewer)

When clicked, the button navigates to the questionnaire editor with URL parameters (type=Quiz, team_id, return_to) that cause the editor to link the created questionnaire back to the team automatically via PATCH /teams/:team_id/quiz_questionnaire. After saving, the editor redirects back to the reviewer page.

Note: This is a temporary solution. In the full Expertiza workflow, quiz creation is initiated by a submitting team member from their own task page — not by an instructor from the reviewer-assignment page. The button was placed here because the student-facing entry point was not yet implemented in this codebase and should be removed or relocated once the proper student team submission flow is in place.

An onCreateQuiz handler was added to the AssignReviewer page:

const onCreateQuiz = useCallback((teamId: number) => {
  navigate(
    `/questionnaires/new?type=Quiz` +
    `&team_id=${teamId}` +
    `&return_to=${encodeURIComponent(location.pathname + location.search)}`
  );
}, [navigate, location]);

After the questionnaire is saved, QuestionnaireEditor reads team_id and return_to from the URL, calls PATCH /teams/:team_id/quiz_questionnaire, and redirects back to the reviewer page.

7) Student "Take Quiz" Button (AssignedReviews)

The per-row quiz state (quiz_taken, has_quiz_questionnaire, quiz_questionnaire_id) is returned directly in the GET /response_maps response so no extra API call is needed to decide which button to show. When the student clicks "Take Quiz", the frontend first calls POST /quiz_response_maps (idempotent) to obtain or reuse a quiz map id, then navigates to the existing TeammateReview page with a redirect_after parameter encoding the peer-review URL. After the quiz is submitted the page auto-redirects to the actual review.

The AssignedReviews component now reads per-row quiz state from the response maps API response. When require_quiz && hasQuizQuestionnaire && !quizCompleted, a yellow "Take Quiz" button is shown:

const handleTakeQuiz = async (review: AssignedReviewRow, reviewUrl: string) => {
  const res = await axiosClient.post('/quiz_response_maps', {
    assignment_id:    review.assignmentId,
    reviewer_user_id: currentUser.id,
    reviewee_team_id: review.revieweeTeamId,
  });
  const { quiz_map_id, quiz_questionnaire_id } = res.data;
  navigate(
    `/student_teams/teammate_review?` +
    `map_id=${quiz_map_id}` +
    `&questionnaire_id=${quiz_questionnaire_id}` +
    `&questionnaire_type=Quiz` +
    `&redirect_after=${encodeURIComponent(reviewUrl)}`
  );
};

8) Quiz Mode in TeammateReview

Reusing the existing TeammateReview page for quiz-taking avoids duplicating the form rendering logic. Quiz mode is activated by questionnaire_type=Quiz in the URL, which causes the page to fetch quiz questionnaire items, disable the "Save Draft" button (quizzes are single-submission), display the score after submission, and show a "Proceed to Review" button that navigates to the redirect_after URL.

The existing TeammateReview page was extended with a quiz mode:

if (isQuizMode) {
    setQuizScore(submitRes.data?.total_score ?? null);
}

9) Correct Answer Fields in Questionnaire Editor

When the questionnaire type is "Quiz", each item in QuestionnaireItemsFieldArray needs a "Correct answer" input whose control type depends on the question type. A dropdown makes sense for choice questions (the options are already defined), a bounded number input for Scale, a checkbox for Checkbox, and a free-text input for TextField (scored case-insensitively). The correct_answer value is persisted through QuestionnairesController and serialised via Item#as_json only for quiz items.

QuestionnaireItemsFieldArray renders a "Correct answer" row for each quiz item. The input type varies by question type:

Question type Correct answer input
Text field Free-text <input type="text">; scored case-insensitively at submission
Multiple choice / Multiple choice checkbox <select> pre-populated from the item's alternatives
Scale <input type="number"> bounded by the item's weight range
Checkbox <input type="checkbox"> (correct = checked)

10) GET /questions/quiz_types Endpoint

Hard-coding the list of allowed quiz item types in the frontend would create a maintenance hazard every time a new type is added to the backend. The GET /questions/quiz_types endpoint lets the frontend fetch the authoritative list at runtime. The same constant is used in the create action to validate incoming item types, returning 422 if an unsupported type is submitted for a quiz questionnaire.

A new endpoint returns the allowed item types for quiz questionnaires so the frontend does not hard-code the list:

QUIZ_ITEM_TYPES = %w[TextField MultipleChoiceRadio MultipleChoiceCheckbox Scale Checkbox].freeze

# GET /questions/quiz_types
def quiz_types
  render json: QUIZ_ITEM_TYPES, status: :ok
end

The create action on QuestionsController also validates that quiz questionnaires only accept items of these types, returning 422 with an error message otherwise.

Test Plan

Tests were added for all new and modified backend components. The full suite passes with 0 failures.

Pre-Test Setup

All request specs use the shared create_roles_hierarchy helper and JWT-based auth:

before(:all) { @roles = create_roles_hierarchy }
let(:token)         { JsonWebToken.encode({ id: instructor.id }) }
let(:Authorization) { "Bearer #{token}" }

Model: Response — Quiz Scoring

11 new examples in spec/models/response_spec.rb validate the quiz scoring path, covering both spaced and CamelCase question type names, correct/incorrect answers, blank answers, multi-item accumulation, and maximum score calculation.

describe '#aggregate_questionnaire_score (quiz)' do
  it 'scores a correct "Text field" answer' do
    item   = make_quiz_item(question_type: 'Text field', correct_answer: 'Paris', weight: 2)
    answer = make_quiz_answer(item: item, comments: 'paris') # case-insensitive
    r      = quiz_response([answer])
    expect(r.aggregate_questionnaire_score).to eq(2)
  end

  it 'scores an incorrect "Text field" answer as 0' do
    item   = make_quiz_item(question_type: 'Text field', correct_answer: 'Paris', weight: 2)
    answer = make_quiz_answer(item: item, comments: 'London')
    r      = quiz_response([answer])
    expect(r.aggregate_questionnaire_score).to eq(0)
  end

  it 'gives 0 when the comments column is blank' do
    item   = make_quiz_item(question_type: 'Text field', correct_answer: 'Paris', weight: 2)
    answer = make_quiz_answer(item: item, comments: '')
    r      = quiz_response([answer])
    expect(r.aggregate_questionnaire_score).to eq(0)
  end
end

Model: StudentTask — Quiz Gateway Fields

7 examples in spec/models/student_task_spec.rb verify the quiz gateway fields are populated by from_participant and serialised by as_json.

Request: QuizResponseMaps

6 examples in spec/requests/api/v1/quiz_response_maps_controller_spec.rb:

Test Expected status
Happy path — creates map and returns IDs 201
Idempotent — reuses existing map on repeat call 201
Missing assignment_id 400
Missing reviewer_user_id 400
Assignment not found (id = 999999999) 404
Team has no quiz questionnaire 422

Request: ResponseMaps

7 examples in spec/requests/api/v1/response_maps_controller_spec.rb:

Test Expected status
GET — returns peer-review maps, excludes quiz maps 200
GET — each entry includes quiz_taken and quiz_questionnaire_id 200
GET — returns empty array when reviewer has no maps 200
GET — missing reviewer_user_id 400
POST — creates ReviewResponseMap and returns IDs 201
POST — idempotent, returns same map on repeat call 201
POST — missing required param 400

Request: Questions — Quiz Types

3 new examples in spec/requests/api/v1/questions_spec.rb:

describe 'GET /questions/quiz_types' do
  it 'returns 200 with the allowed quiz item type strings' do
    get '/questions/quiz_types', headers: { 'Authorization' => auth_header }
    types = JSON.parse(response.body)
    expect(types).to include('TextField', 'MultipleChoiceRadio', 'MultipleChoiceCheckbox')
  end
end

Test Coverage Summary

Spec file Examples before Examples after
spec/models/student_task_spec.rb 3 (failing) 7 ✅
spec/models/response_spec.rb 11 22 ✅
spec/requests/api/v1/questions_spec.rb 19 22 ✅
spec/requests/api/v1/quiz_response_maps_controller_spec.rb 0 6 ✅
spec/requests/api/v1/response_maps_controller_spec.rb 0 7 ✅
Total new examples 27

Files Modified

Backend (Ruby on Rails)

File Change
app/models/response.rb Added quiz-aware scoring to aggregate_questionnaire_score and maximum_score
app/models/student_task.rb Added four quiz gateway fields; updated from_participant and as_json
app/models/Item.rb Added QUIZ_ITEM_TYPES, is_quiz_item?, correct_answer_only_for_quiz validation, and conditional as_json
app/models/team.rb Added quiz_questionnaire_id attribute and quiz_questionnaire association
app/models/quiz_response_map.rb Minor documentation updates
app/controllers/questions_controller.rb Added GET /questions/quiz_types; added quiz item type validation in create
app/controllers/quiz_response_maps_controller.rb New controller: POST /quiz_response_maps
app/controllers/response_maps_controller.rb Updated index to filter quiz maps and return per-row quiz state; added create and destroy
app/controllers/teams_controller.rb Added PATCH /teams/:id/quiz_questionnaire
app/controllers/questionnaires_controller.rb Exposed correct_answer in strong parameters
config/routes.rb Added routes for quiz_response_maps, quiz_types, and quiz_questionnaire on teams
db/migrate/20260423000001_add_correct_answer_to_items.rb New migration: adds correct_answer string column to items
db/migrate/20260424000001_add_quiz_questionnaire_id_to_teams.rb New migration: adds quiz_questionnaire_id integer column + index to teams
spec/models/student_task_spec.rb Fixed existing test failures; added quiz gateway field assertions
spec/models/response_spec.rb Added 11 new quiz scoring tests
spec/requests/api/v1/questions_spec.rb Added tests for quiz_types endpoint and quiz item creation/rejection
spec/requests/api/v1/quiz_response_maps_controller_spec.rb New spec: 6 tests covering happy path, idempotency, and error cases
spec/requests/api/v1/response_maps_controller_spec.rb New spec: 7 tests covering quiz-map exclusion, quiz state fields, and POST CRUD

Frontend (React/TypeScript)

File Change
src/pages/StudentTasks/AssignedReviews.tsx Added "Take Quiz" button; per-row quiz state; handleTakeQuiz calling POST /quiz_response_maps
src/pages/Assignments/AssignReviewer.tsx Added "Create Quiz" button that navigates to the questionnaire editor pre-linked to the team
src/pages/Questionnaires/QuestionnaireItemsFieldArray.tsx Added correct-answer row for each quiz item type
src/pages/Questionnaires/QuestionnaireEditor.tsx Added team-quiz creation flow: links new questionnaire to team via PATCH /teams/:id/quiz_questionnaire
src/pages/Student Teams/TeammateReview.tsx Added quiz mode (isQuizMode), quiz score display, and redirect_after param support
src/pages/Assignments/AssignmentEditor.tsx Ensured require_quiz checkbox is wired to the form state
src/pages/Assignments/AssignmentUtil.ts Added require_quiz to IAssignmentFormValues
src/pages/Questionnaires/QuestionnaireUtils.tsx Added correct_answer to IItem; updated transformQuestionnaireRequest
src/components/Form/FormSelect.tsx Documentation only (JSDoc)

Github Pull Request

Video

https://youtu.be/BWh_6bavtbE

Mentor

Ed Gehringer (efg@ncsu.edu)

Team Member

  • An Mai (atmai@ncsu.edu)
  • Mekhi Parker (mrparke4@ncsu.edu)
  • Tejas Manoj Desai (tdesai@ncsu.edu)