expertiza/expertiza

View on GitHub
app/controllers/response_controller.rb

Summary

Maintainability
D
1 day
Test Coverage
F
15%
class ResponseController < ApplicationController
  include AuthorizationHelper
  include ResponseHelper

  helper :submitted_content
  helper :file

  before_action :authorize_show_calibration_results, only: %i[show_calibration_results_for_student]
  before_action :set_response, only: %i[update delete view]

  # E2218: Method to check if that action is allowed for the user.
  def action_allowed?
    response = user_id = nil
    action = params[:action]
    # Initialize response and user id if action is edit or delete or update or view.
    if %w[edit delete update view].include?(action)
      response = Response.find(params[:id])
      user_id = response.map.reviewer.user_id if response.map.reviewer
    end
    case action
    when 'edit'
      # If response has been submitted, no further editing allowed.
      return false if response.is_submitted

      # Else, return true if the user is a reviewer for that response.
      current_user_is_reviewer?(response.map, user_id)

    # Deny access to anyone except reviewer & author's team
    when 'delete', 'update'
      current_user_is_reviewer?(response.map, user_id)
    when 'view'
      response_edit_allowed?(response.map, user_id)
    else
      user_logged_in?
    end
  end

  # E2218: Method to authorize if the reviewer can view the calibration results
  # When user manipulates the URL, the user should be authorized
  def authorize_show_calibration_results
    response_map = ResponseMap.find(params[:review_response_map_id])
    user_id = response_map.reviewer.user_id if response_map.reviewer
    # Deny access to the calibration result page if the current user is not a reviewer.
    unless current_user_is_reviewer?(response_map, user_id)
      flash[:error] = 'You are not allowed to view this calibration result'
      redirect_to controller: 'student_review', action: 'list', id: user_id
    end
  end

  # GET /response/json?response_id=xx
  def json
    response_id = params[:response_id] if params.key?(:response_id)
    response = Response.find(response_id)
    render json: response
  end

  # E2218: Method to delete a response.
  def delete
    # The locking was added for E1973, team-based reviewing. See lock.rb for details
    if @map.team_reviewing_enabled
      @response = Lock.get_lock(@response, current_user, Lock::DEFAULT_TIMEOUT)
      if @response.nil?
        response_lock_action
        return
      end
    end

    # user cannot delete other people's responses. Needs to be authenticated.
    map_id = @response.map.id
    # The lock will be automatically destroyed when the response is destroyed
    @response.delete
    redirect_to action: 'redirect', id: map_id, return: params[:return], msg: 'The response was deleted.'
  end

  # Determining the current phase and check if a review is already existing for this stage.
  # If so, edit that version otherwise create a new version.

  # Prepare the parameters when student clicks "Edit"
  # response questions with answers and scores are rendered in the edit page based on the version number
  def edit
    assign_action_parameters
    @prev = Response.where(map_id: @map.id)
    @review_scores = @prev.to_a
    if @prev.present?
      @sorted = @review_scores.sort do |m1, m2|
        if m1.version_num.to_i && m2.version_num.to_i
          m2.version_num.to_i <=> m1.version_num.to_i
        else
          m1.version_num ? -1 : 1
        end
      end
      @largest_version_num = @sorted[0]
    end
    # Added for E1973, team-based reviewing
    @map = @response.map
    if @map.team_reviewing_enabled
      @response = Lock.get_lock(@response, current_user, Lock::DEFAULT_TIMEOUT)
      if @response.nil?
        response_lock_action
        return
      end
    end

    @modified_object = @response.response_id
    # set more handy variables for the view
    set_content
    @review_scores = []
    @review_questions.each do |question|
      @review_scores << Answer.where(response_id: @response.response_id, question_id: question.id).first
    end
    @questionnaire = questionnaire_from_response
    render action: 'response'
  end

  # Update the response and answers when student "edit" existing response
  def update
    render nothing: true unless action_allowed?
    msg = ''
    begin
      # the response to be updated
      # Locking functionality added for E1973, team-based reviewing
      if @map.team_reviewing_enabled && !Lock.lock_between?(@response, current_user)
        response_lock_action
        return
      end

      @response.update_attribute('additional_comment', params[:review][:comments])
      @questionnaire = questionnaire_from_response
      questions = sort_questions(@questionnaire.questions)

      # for some rubrics, there might be no questions but only file submission (Dr. Ayala's rubric)
      create_answers(params, questions) unless params[:responses].nil?
      if params['isSubmit'] && params['isSubmit'] == 'Yes'
        @response.update_attribute('is_submitted', true)
      end

      if (@map.is_a? ReviewResponseMap) && @response.is_submitted && @response.significant_difference?
        @response.notify_instructor_on_difference
      end
    rescue StandardError
      msg = "Your response was not saved. Cause:189 #{$ERROR_INFO}"
    end
    ExpertizaLogger.info LoggerMessage.new(controller_name, session[:user].name, "Your response was submitted: #{@response.is_submitted}", request)
    redirect_to controller: 'response', action: 'save', id: @map.map_id,
                return: params.permit(:return)[:return], msg: msg, review: params.permit(:review)[:review],
                save_options: params.permit(:save_options)[:save_options]
  end

  def new
    assign_action_parameters
    set_content(true)
    if @assignment
      @stage = @assignment.current_stage(SignedUpTeam.topic_id(@participant.parent_id, @participant.user_id))
    end
    # Because of the autosave feature and the javascript that sync if two reviewing windows are opened
    # The response must be created when the review begin.
    # So do the answers, otherwise the response object can't find the questionnaire when the user hasn't saved his new review and closed the window.
    # A new response has to be created when there hasn't been any reviews done for the current round,
    # or when there has been a submission after the most recent review in this round.
    @response = @response.create_or_get_response(@map, @current_round.to_i)
    questions = sort_questions(@questionnaire.questions)
    store_total_cake_score
    init_answers(questions)
    render action: 'response'
  end

  def author; end

  # This method is used to send email from a Reviewer to an Author.
  # Email body and subject are inputted from Reviewer and passed to send_mail_to_author_reviewers method in MailerHelper.
  def send_email
    subject = params['send_email']['subject']
    body = params['send_email']['email_body']
    response = params['response']
    email = params['email']

    respond_to do |format|
      if subject.blank? || body.blank?
        flash[:error] = 'Please fill in the subject and the email content.'
        format.html { redirect_to controller: 'response', action: 'author', response: response, email: email }
        format.json { head :no_content }
      else
        # make a call to method invoking the email process
        MailerHelper.send_mail_to_author_reviewers(subject, body, email)
        flash[:success] = 'Email sent to the author.'
        format.html { redirect_to controller: 'student_task', action: 'list' }
        format.json { head :no_content }
      end
    end
  end

  def new_feedback
    review = Response.find(params[:id]) unless params[:id].nil?
    if review
      reviewer = AssignmentParticipant.where(user_id: session[:user].id, parent_id: review.map.assignment.id).first
      map = FeedbackResponseMap.where(reviewed_object_id: review.id, reviewer_id: reviewer.id).first
      if map.nil?
        # if no feedback exists by dat user den only create for dat particular response/review
        map = FeedbackResponseMap.create(reviewed_object_id: review.id, reviewer_id: reviewer.id, reviewee_id: review.map.reviewer.id)
      end
      redirect_to action: 'new', id: map.id, return: 'feedback'
    else
      redirect_back fallback_location: root_path
    end
  end

  # view response
  def view
    set_content
  end

  def create
    map_id = params[:id]
    unless params[:map_id].nil?
      map_id = params[:map_id]
    end # pass map_id as a hidden field in the review form
    @map = ResponseMap.find(map_id)
    if params[:review][:questionnaire_id]
      @questionnaire = Questionnaire.find(params[:review][:questionnaire_id])
      @round = params[:review][:round]
    else
      @round = nil
    end
    is_submitted = (params[:isSubmit] == 'Yes')
    # There could be multiple responses per round, when re-submission is enabled for that round.
    # Hence we need to pick the latest response.
    @response = Response.where(map_id: @map.id, round: @round.to_i).order(created_at: :desc).first
    if @response.nil?
      @response = Response.create(map_id: @map.id, additional_comment: params[:review][:comments],
                                  round: @round.to_i, is_submitted: is_submitted)
    end
    was_submitted = @response.is_submitted

    # ignore if autoupdate try to save when the response object is not yet created.s
    @response.update(additional_comment: params[:review][:comments], is_submitted: is_submitted)

    # :version_num=>@version)
    # Change the order for displaying questions for editing response views.
    questions = sort_questions(@questionnaire.questions)
    create_answers(params, questions) if params[:responses]
    msg = 'Your response was successfully saved.'
    error_msg = ''

    # only notify if is_submitted changes from false to true
    if (@map.is_a? ReviewResponseMap) && (!was_submitted && @response.is_submitted) && @response.significant_difference?
      @response.notify_instructor_on_difference
      @response.email
    end
    redirect_to controller: 'response', action: 'save', id: @map.map_id,
                return: params.permit(:return)[:return], msg: msg, error_msg: error_msg, review: params.permit(:review)[:review], save_options: params.permit(:save_options)[:save_options]
  end

  def save
    @map = ResponseMap.find(params[:id])
    @return = params[:return]
    @map.save
    ExpertizaLogger.info LoggerMessage.new(controller_name, session[:user].name, 'Response was successfully saved')
    redirect_to action: 'redirect', id: @map.map_id, return: params.permit(:return)[:return], msg: params.permit(:msg)[:msg], error_msg: params.permit(:error_msg)[:error_msg]
  end

  def redirect
    error_id = params[:error_msg]
    message_id = params[:msg]
    flash[:error] = error_id unless error_id&.empty?
    flash[:note] = message_id unless message_id&.empty?
    @map = Response.find_by(map_id: params[:id])
    case params[:return]
    when 'feedback'
      redirect_to controller: 'grades', action: 'view_my_scores', id: @map.reviewer.id
    when 'teammate'
      redirect_to view_student_teams_path student_id: @map.reviewer.id
    when 'instructor'
      redirect_to controller: 'grades', action: 'view', id: @map.response_map.assignment.id
    when 'assignment_edit'
      redirect_to controller: 'assignments', action: 'edit', id: @map.response_map.assignment.id
    when 'selfreview'
      redirect_to controller: 'submitted_content', action: 'edit', id: @map.response_map.reviewer_id
    when 'survey'
      redirect_to controller: 'survey_deployment', action: 'pending_surveys'
    when 'bookmark'
      bookmark = Bookmark.find(@map.response_map.reviewee_id)
      redirect_to controller: 'bookmarks', action: 'list', id: bookmark.topic_id
    when 'ta_review' # Page should be directed to list_submissions if TA/instructor performs the review
      redirect_to controller: 'assignments', action: 'list_submissions', id: @map.response_map.assignment.id
    else
      # if reviewer is team, then we have to get the id of the participant from the team
      # the id in reviewer_id is of an AssignmentTeam
      reviewer_id = @map.response_map.reviewer.get_logged_in_reviewer_id(current_user.try(:id))
      redirect_to controller: 'student_review', action: 'list', id: reviewer_id
    end
  end

  # This method set the appropriate values to the instance variables used in the 'show_calibration_results_for_student' page
  # Responses are fetched using calibration_response_map_id and review_response_map_id params passed in the URL
  # Questions are fetched by querying AssignmentQuestionnaire table to get the valid questions
  def show_calibration_results_for_student
    @assignment = Assignment.find(params[:assignment_id])
    @calibration_response = ReviewResponseMap.find(params[:calibration_response_map_id]).response[0]
    @review_response = ReviewResponseMap.find(params[:review_response_map_id]).response[0]
    @review_questions = AssignmentQuestionnaire.get_questions_by_assignment_id(params[:assignment_id])
  end

  def toggle_permission
    render nothing: true unless action_allowed?

    # the response to be updated
    @response = Response.find(params[:id])

    # Error message placeholder
    error_msg = ''

    begin
      @map = @response.map

      # Updating visibility for the response object, by E2022 @SujalAhrodia -->
      visibility = params[:visibility]
      unless visibility.nil?
        @response.update_attribute('visibility', visibility)
      end
    rescue StandardError
      error_msg = "Your response was not saved. Cause:189 #{$ERROR_INFO}"
    end
    redirect_to action: 'redirect', id: @map.map_id, return: params[:return], msg: params[:msg], error_msg: error_msg
  end

  private

  # E2218: Method to initialize response and response map for update, delete and view methods
  def set_response
    @response = Response.find(params[:id])
    @map = @response.map
  end

  # Added for E1973, team-based reviewing:
  # http://wiki.expertiza.ncsu.edu/index.php/CSC/ECE_517_Fall_2019_-_Project_E1973._Team_Based_Reviewing
  # Taken if the response is locked and cannot be edited right now
  def response_lock_action
    redirect_to action: 'redirect', id: @map.map_id, return: 'locked', error_msg: 'Another user is modifying this response or has modified this response. Try again later.'
  end

  # This method is called within the Edit or New actions
  # It will create references to the objects that the controller will need when a user creates a new response or edits an existing one.
  def assign_action_parameters
    case params[:action]
    when 'edit'
      @header = 'Edit'
      @next_action = 'update'
      @response = Response.find(params[:id])
      @map = @response.map
      @contributor = @map.contributor
    when 'new'
      @header = 'New'
      @next_action = 'create'
      @feedback = params[:feedback]
      @map = ResponseMap.find(params[:id])
      @modified_object = @map.id
    end
    @return = params[:return]
  end

  # This method is called within set_content and when the new_response flag is set to true
  # Depending on what type of response map corresponds to this response, the method gets the reference to the proper questionnaire
  # This is called after assign_instance_vars in the new method
  def questionnaire_from_response_map
    case @map.type
    when 'ReviewResponseMap', 'SelfReviewResponseMap'
      reviewees_topic = SignedUpTeam.topic_id_by_team_id(@contributor.id)
      @current_round = @assignment.number_of_current_round(reviewees_topic)
      @questionnaire = @map.questionnaire(@current_round, reviewees_topic)
    when
      'MetareviewResponseMap',
      'TeammateReviewResponseMap',
      'FeedbackResponseMap',
      'CourseSurveyResponseMap',
      'AssignmentSurveyResponseMap',
      'GlobalSurveyResponseMap',
      'BookmarkRatingResponseMap'
      if @assignment.duty_based_assignment?
        # E2147 : gets questionnaire of a particular duty in that assignment rather than generic questionnaire
        @questionnaire = @map.questionnaire_by_duty(@map.reviewee.duty_id)
      else
        @questionnaire = @map.questionnaire
      end
    end
  end

  # This method is called within set_content when the new_response flag is set to False
  # This method gets the questionnaire directly from the response object since it is available.
  def questionnaire_from_response
    # if user is not filling a new rubric, the @response object should be available.
    # we can find the questionnaire from the question_id in answers
    answer = @response.scores.first
    @questionnaire = @response.questionnaire_by_answer(answer)
  end

  # checks if the questionnaire is nil and opens drop down or rating accordingly
  def set_dropdown_or_scale
    use_dropdown = AssignmentQuestionnaire.where(assignment_id: @assignment.try(:id),
                                                 questionnaire_id: @questionnaire.try(:id))
                                          .first.try(:dropdown)
    @dropdown_or_scale = (use_dropdown ? 'dropdown' : 'scale')
  end

  # For each question in the list, starting with the first one, you update the comment and score
  def create_answers(params, questions)
    params[:responses].each_pair do |k, v|
      score = Answer.where(response_id: @response.id, question_id: questions[k.to_i].id).first
      score ||= Answer.create(response_id: @response.id, question_id: questions[k.to_i].id, answer: v[:score], comments: v[:comment])
      score.update_attribute('answer', v[:score])
      score.update_attribute('comments', v[:comment])
    end
  end

  # This method initialize answers for the questions in the response
  # Iterates over each questions and create corresponding answer for that
  def init_answers(questions)
    questions.each do |q|
      # it's unlikely that these answers exist, but in case the user refresh the browser some might have been inserted.
      answer = Answer.where(response_id: @response.id, question_id: q.id).first
      if answer.nil?
        Answer.create(response_id: @response.id, question_id: q.id, answer: nil, comments: '')
      end
    end
  end
end