autolab/Autolab

View on GitHub
app/controllers/api/v1/assessments_controller.rb

Summary

Maintainability
B
5 hrs
Test Coverage
require "archive"

class Api::V1::AssessmentsController < Api::V1::BaseApiController

  include AssessmentHandinCore
  include AssessmentAutogradeCore

  before_action -> {require_privilege :user_courses}, only: [:index, :problems, :writeup, :handout]
  before_action -> {require_privilege :user_submit}, only: [:submit]
  before_action -> {require_privilege :instructor_all}, only: [:set_group_settings]

  before_action :set_assessment, except: [:index]

  def index
    asmts = @course.assessments.ordered
    allowed = [:name, :display_name, :start_at, :due_at, :end_at, :category_name]
    if @cud.student?
      asmts = asmts.released
    end

    respond_with asmts, only: allowed
  end

  def show
    allowed = [:name, :display_name, :description, :start_at, :due_at, :end_at, :updated_at, :max_grace_days,
               :max_submissions, :disable_handins, :category_name, :group_size, :writeup_format, :handout_format,
               :has_scoreboard, :has_autograder, :max_unpenalized_submissions, :max_total_score, :max_scores]

    result = @assessment.attributes.symbolize_keys
    result.merge!(:has_scoreboard => @assessment.has_scoreboard?)
    result.merge!(:has_autograder => @assessment.has_autograder?)
    result.merge!(:max_unpenalized_submissions => @assessment.effective_version_threshold)
    if @assessment.writeup_is_file?
      result.merge!(:writeup_format => "file")
    elsif @assessment.writeup_is_url?
      result.merge!(:writeup_format => "url")
    else
      result.merge!(:writeup_format => "none")
    end
    if @assessment.overwrites_method?(:handout) or @assessment.handout_is_file?
      result.merge!(:handout_format => "file")
    elsif @assessment.handout_is_url?
      result.merge!(:handout_format => "url")
    else
      result.merge!(:handout_format => "none")
    end

    result.merge!(:max_total_score => @assessment.default_total_score)

    result.filter! do |member|
      allowed.include?(member)
    end

    max_scores = {}
    @assessment.problems.each do |prob|
      max_scores[prob.name] = prob.max_score
    end
    result.merge!(:max_scores => max_scores)

    respond_with result
  end

  # endpoint for obtaining the writeup
  def writeup
    if @assessment.writeup_is_url?
      respond_with_hash({:url => @assessment.writeup}) and return
    end

    if @assessment.writeup_is_file?
      # Note: writeup_is_file? validates that the writeup lies within the assessment folder
      filename = @assessment.writeup_path
      send_file(filename,
                disposition: "inline",
                file: File.basename(filename))
      return
    end

    respond_with_hash({:writeup => "none"})
  end

  # endpoint for obtaining the handout
  def handout
    extend_config_module(@assessment, nil, @cud)

    if @assessment.overwrites_method?(:handout)
      hash = @assessment.config_module.handout
      # Ensure that handout lies within the assessment folder
      unless Archive.in_dir?(Pathname(hash["fullpath"]), @assessment.folder_path)
        respond_with_hash({:handout => "none"}) and return
      end

      send_file(hash["fullpath"],
                disposition: "inline",
                filename: hash["filename"])
      return
    end

    if @assessment.handout_is_url?
      respond_with_hash({:url => @assessment.handout}) and return
    end

    if @assessment.handout_is_file?
      # Note: handout_is_file? validates that the handout lies within the assessment folder
      filename = @assessment.handout_path
      send_file(filename,
                disposition: "inline",
                file: File.basename(filename))
      return
    end

    respond_with_hash({:handout => "none"})
  end

  # endpoint for submitting to assessments
  # Does not support embedded quizzes.
  # Potential Errors:
  #   Submission Rejected:
  #   - Assessment is an embedded quiz
  #   - Handins disabled
  #   - Submitting late without submit-late flag
  #   - Past assessment end time
  #   - Before assessment start time
  #   - Exceeded max submission count
  #   - Exceeded max handin file size
  #   - Submission was blank
  #   - Submission failed file type check
  #   - Cannot submit until all group members confirm their group membership
  #   - A member of your group has reached the submission limit for this assessment
  #   Autograding Failed:
  #   - No autograding properties
  #   - Error submitting job
  #   - Error uploading submission file
  #   - Submission rejected by autograder
  #   - One or more files in the Autograder module don't exist.
  #   - Encountered unexpected exception
  def submit
    if @assessment.embedded_quiz
      raise ApiError.new("Assessment is an embedded quiz", :bad_request)
    end

    if not params.has_key?(:submission)
      raise ApiError.new("Required parameter 'submission' not found", :bad_request)
    end

    if not params[:submission].has_key?("file")
      raise ApiError.new("Required parameter 'submission['file']' not found", :bad_request)
    end

    # validate Handin
    validity = validateHandin(params[:submission]["file"].size,
                              params[:submission]["file"].content_type,
                              params[:submission]["file"].original_filename)
    case validity
    when :valid
    when :handin_disabled
      raise ApiError.new("Handins for this assessment is disabled", :forbidden)
    when :submission_empty
      raise ApiError.new("Submission was blank", :forbidden)
    when :file_too_large
      msg = "Submission is larger than the max allowed " \
            "size (#{@assessment.max_size} MB) - please remove any " \
            "unnecessary logfiles and binaries."
      raise ApiError.new(msg, :forbidden)
    when :fail_type_check
      raise ApiError.new("Submission failed Filetype Check", :forbidden)
    else
      raise ApiError.new("Unexpected error during handin validation", :forbidden)
    end

    group_validity = validateHandinForGroups()
    case group_validity
    when :valid
    when :awaiting_member_confirmation
      raise ApiError.new("Submission not allowed until all group members confirm their group membership", :forbidden)
    when :group_submission_limit_exceeded
      raise ApiError.new("A member of your group has reached the submission limit for this assessment", :forbidden)
    else
      raise ApiError.new("Unexpected error during handin validation for groups", :forbidden)
    end

    # attempt to save the submission
    begin
      submissions = saveHandin(params[:submission], current_app.id)
    rescue StandardError => e
      # TODO: log error
      raise ApiError.new("Unexpected error during submission handin.\nDetails: #{e.message}", :internal_server_error)
    end

    # autograde the submission
    if @assessment.has_autograder?
      begin
        sendJob(@course, @assessment, submissions, @cud)
      rescue AssessmentAutogradeCore::AutogradeError => e
        raise ApiError.new("Submission accepted, but autograding failed: " + e.message, :internal_server_error)
      end
    end

    respond_with_hash({version: submissions[0].version, filename: submissions[0].filename})
  end

  def set_group_settings
    require_params([:group_size, :allow_student_assign_group])
    @assessment.group_size = params[:group_size].to_i
    @assessment.allow_student_assign_group = (params[:allow_student_assign_group].to_s == "true")
    @assessment.save!
    respond_with_hash({ group_size: @assessment.group_size, 
      allow_student_assign_group: @assessment.allow_student_assign_group })
  end

end