-
Notifications
You must be signed in to change notification settings - Fork 115
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reimplemented StudentQuizzesController #112
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
class Api::V1::StudentQuizzesController < ApplicationController | ||
include AuthorizationHelper | ||
|
||
# Checks if the current user is authorized to perform the requested action. | ||
# For students, it verifies if they have the necessary roles for the 'index' action. | ||
# For TAs, it checks if they have TA privileges and are assigned to the current course. | ||
def action_allowed? | ||
if current_user_is_a? 'Student' | ||
if action_name.eql? 'index' | ||
are_needed_authorizations_present?(params[:id], 'reviewer', 'submitter') | ||
else | ||
true | ||
end | ||
else | ||
current_user_has_ta_privileges? && user_ta_for_current_course? | ||
end | ||
end | ||
|
||
# Fetches and lists all quiz response mappings for a specific participant in an assignment. | ||
def index | ||
@participant = AssignmentParticipant.find(params[:id]) | ||
# Ensures the current user is authorized to view the participant's quiz mappings. | ||
return unless current_user_id?(@participant.user_id) | ||
|
||
@assignment = Assignment.find(@participant.parent_id) | ||
@quiz_mappings = ResponseMap.mappings_for_reviewer(@participant.id) | ||
end | ||
|
||
# Displays the questions and participant responses for a completed quiz | ||
def show_quiz_responses | ||
@response = Response.find(params[:response_id]) | ||
@response_map = ResponseMap.find(params[:map_id]) | ||
@questions = Question.where(questionnaire_id: @response_map.reviewed_object_id) | ||
@participant = AssignmentTeam.find(@response_map.reviewee_id).participants.first | ||
@quiz_score = @response.aggregate_questionnaire_score # Use the score calculated by Response model | ||
end | ||
|
||
# Fetches quizzes for a given assignment that a reviewer has not yet started. | ||
def fetch_available_quizzes_for_reviewer(assignment_id, reviewer_id) | ||
reviewer = Participant.find_by(user_id: reviewer_id, parent_id: assignment_id) | ||
return [] unless reviewer | ||
|
||
# Find quiz questionnaires created by teams in the assignment | ||
quiz_questionnaires = Questionnaire.where(instructor_id: Team.where(parent_id: assignment_id).pluck(:id)) | ||
|
||
# Filter out quizzes already started by the reviewer | ||
available_quizzes = quiz_questionnaires.reject do |quiz| | ||
quiz.started_by?(reviewer) | ||
end | ||
|
||
available_quizzes | ||
end | ||
|
||
# Submits the quiz response and calculates the score. | ||
def submit_quiz | ||
map = ResponseMap.find(params[:map_id]) | ||
# Check if there is any response for this map_id. This is to prevent student from taking the same quiz twice. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are embedding a POLICY into this mechanism. Bad design! Let the instructor decide whether someone can take a quiz over, e.g., to improve their score. |
||
if map.response.empty? | ||
response = Response.create(map_id: params[:map_id], created_at: DateTime.current, updated_at: DateTime.current) | ||
if response.calculate_score(params) | ||
redirect_to controller: 'student_quizzes', action: 'show_finished_quiz', map_id: map.id | ||
else | ||
flash[:error] = 'Please answer every question.' | ||
redirect_to action: :fetch_available_quizzes_for_reviewer, assignment_id: params[:assignment_id], questionnaire_id: response.response_map.reviewed_object_id, map_id: map.id | ||
end | ||
else | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is another POLICY that belongs elsewhere. Did you ever skip a question on a test? Why don't you want to let other students do that? |
||
flash[:error] = 'You have already taken this quiz, below is the record of your responses.' | ||
redirect_to controller: 'student_quizzes', action: 'show_finished_quiz', map_id: map.id | ||
end | ||
end | ||
|
||
# Provides a list of quiz questionnaires for a given assignment. | ||
# This method is called when instructors click "view quiz questions" on the pop-up panel. | ||
def view_questions | ||
@assignment_id = params[:id] | ||
@quiz_questionnaires = [] | ||
Team.where(parent_id: params[:id]).each do |quiz_creator| | ||
Questionnaire.where(instructor_id: quiz_creator.id).each do |questionnaire| | ||
@quiz_questionnaires.push questionnaire | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,12 @@ | ||
class ApplicationController < ActionController::API | ||
include JwtToken | ||
|
||
# Check if a participant has given authorizations | ||
def are_needed_authorizations_present?(id, *authorizations) | ||
participant = Participant.find_by(id: id) | ||
return false if participant.nil? | ||
|
||
authorization = participant.authorization | ||
!authorizations.include?(authorization) | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
module AuthorizationHelper | ||
|
||
# Determine if the currently logged-in user has the privileges of a TA (or higher) | ||
def current_user_has_ta_privileges? | ||
current_user_has_privileges_of?('Teaching Assistant') | ||
end | ||
|
||
# Determine if the currently logged-in user has the privileges of a Student (or higher) | ||
def current_user_has_student_privileges? | ||
current_user_has_privileges_of?('Student') | ||
end | ||
|
||
private | ||
|
||
# Determine if the currently logged-in user has the privileges of the given role name (or higher privileges) | ||
# If there is no currently logged-in user return false | ||
def current_user_has_privileges_of?(role_name) | ||
current_user_and_role_exist? && session[:user].role.has_all_privileges_of?(Role.find_by(name: role_name)) | ||
end | ||
|
||
# Check whether user is logged-in and user role exists | ||
SahithiAmmana marked this conversation as resolved.
Show resolved
Hide resolved
|
||
def current_user_and_role_exist? | ||
user_logged_in? && !session[:user].role.nil? | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
module ResponseHelper | ||
|
||
# Assigns total contribution for cake question across all reviewers to a hash map | ||
# Key : question_id, Value : total score for cake question | ||
def store_total_cake_score | ||
reviewee = ResponseMap.select(:reviewee_id, :type).where(id: @response.map_id.to_s).first | ||
@total_score = scores_per_question(reviewee.type, | ||
@review_questions, | ||
@participant.id, | ||
@assignment.id, | ||
reviewee.reviewee_id) | ||
end | ||
|
||
# Calculates the total score for each of the questions for a given participant and assignment. | ||
# Total scores per question, with question_id as the key and total score as the value. | ||
def scores_per_question(review_type, questions, participant_id, assignment_id, reviewee_id) | ||
questions.each_with_object({}) do |question, scores| | ||
next unless question.is_a?(Cake) | ||
|
||
score = question.running_total(review_type, question.id, participant_id, assignment_id, reviewee_id) | ||
scores[question.id] = score || 0 | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
class Cake < ScoredQuestion | ||
include ActionView::Helpers | ||
validates :size, presence: true | ||
|
||
# Retrieves and calculates the total score for a specific question. | ||
def running_total(review_type, question_id, participant_id, assignment_id, reviewee_id) | ||
team_id = Team.joins(teams_users: :participant) | ||
.where('participants.id = ? AND teams.parent_id = ?', participant_id, assignment_id) | ||
.pluck(:id) | ||
.first | ||
return 0 unless team_id | ||
|
||
if review_type == 'TeammateReviewResponseMap' | ||
answers = Participant.joins(user: :teams_users) | ||
.where('teams_users.team_id = ? AND participants.parent_id = ?', team_id, assignment_id) | ||
.pluck(:id) | ||
.flat_map do |team_member_id| | ||
Answer.joins(response: :response_map) | ||
.where("response_maps.reviewee_id = ? AND response_maps.reviewed_object_id = ? | ||
AND response_maps.reviewer_id = ? AND answers.question_id = ? | ||
AND response_maps.reviewee_id != ? AND answers.answer IS NOT NULL", | ||
team_member_id, assignment_id, participant_id, question_id, reviewee_id) | ||
end | ||
answers.compact.sum(&:answer) | ||
else | ||
0 | ||
end | ||
end | ||
|
||
# The score a user gave to a specific question. | ||
def score_given_by_user(participant_id, question_id, assignment_id, reviewee_id) | ||
Answer.joins(response: :response_map) | ||
.where("response_maps.reviewer_id = ? AND response_maps.reviewee_id = ? AND response_maps.reviewed_object_id = ? AND answers.question_id = ?", | ||
participant_id, reviewee_id, assignment_id, question_id) | ||
.pluck(:answer) | ||
.first || 0 | ||
end | ||
|
||
# The score a user has left to give for a question. | ||
def score_remaining_for_user(review_type, question_id, participant_id, assignment_id, reviewee_id) | ||
total_score = running_total(review_type, question_id, participant_id, assignment_id, reviewee_id) | ||
remaining_score = 100 - total_score | ||
remaining_score.negative? ? 0 : remaining_score | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
class QuizQuestionChoice < ApplicationRecord | ||
belongs_to :question, dependent: :destroy | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A better name for this method would be "unattempted_quizzes".