Files
codeocean/app/controllers/request_for_comments_controller.rb
Sebastian Serth 8fc5123bae Exclusively lock Runners during code executions
Previously, the same runner could be used multiple times with different submissions simultaneously. This, however, yielded errors, for example when one submission time oud (causing the running to be deleted) while another submission was still executed.

Admin actions, such as the shell, can be still executed regardless of any other code execution.

Fixes CODEOCEAN-HG
Fixes openHPI/poseidon#423
2023-10-31 12:35:24 +01:00

219 lines
8.9 KiB
Ruby

# frozen_string_literal: true
class RequestForCommentsController < ApplicationController
include CommonBehavior
before_action :require_user!
before_action :set_request_for_comment, only: %i[show mark_as_solved set_thank_you_note clear_question]
before_action :set_study_group_grouping,
only: %i[index my_comment_requests rfcs_with_my_comments rfcs_for_exercise]
def authorize!
authorize(@request_for_comments || @request_for_comment)
end
private :authorize!
# GET /request_for_comments
# GET /request_for_comments.json
def index
@search = policy_scope(RequestForComment)
.last_per_user(2)
.joins(:exercise)
.where(exercises: {unpublished: false})
.order(created_at: :desc) # Order for the LIMIT part of the query
.ransack(params[:q])
# This total is used later to calculate the total number of entries
request_for_comments = @search.result
# All conditions are included in the query, so get the number of requested records
.paginate(page: params[:page], per_page: per_page_param)
@request_for_comments = RequestForComment.where(id: request_for_comments)
.with_last_activity # expensive query, so we only do it for the current page
.includes(submission: %i[study_group exercise contributor])
.includes(:file, :comments, user: [:consumer])
.order(created_at: :desc) # Order for the view
# We need to manually enable the pagination links.
.extending(WillPaginate::ActiveRecord::RelationMethods)
@request_for_comments.current_page = WillPaginate::PageNumber(params[:page] || 1)
@request_for_comments.limit_value = per_page_param
@request_for_comments.total_entries = @search.result.length
authorize!
end
# GET /my_request_for_comments
def my_comment_requests
@search = policy_scope(RequestForComment)
.joins(:submission)
.where(user: current_user)
.or(policy_scope(RequestForComment)
.joins(:submission)
.where(submission: {contributor: current_user.programming_groups}))
.order(created_at: :desc) # Order for the LIMIT part of the query
.ransack(params[:q])
# This total is used later to calculate the total number of entries
request_for_comments = @search.result
# All conditions are included in the query, so get the number of requested records
.paginate(page: params[:page], per_page: per_page_param)
@request_for_comments = RequestForComment.where(id: request_for_comments)
.with_last_activity
.includes(submission: %i[study_group exercise])
.includes(:file, :comments, :user)
.order(created_at: :desc) # Order for the view
# We need to manually enable the pagination links.
.extending(WillPaginate::ActiveRecord::RelationMethods)
@request_for_comments.current_page = WillPaginate::PageNumber(params[:page] || 1)
@request_for_comments.limit_value = per_page_param
@request_for_comments.total_entries = @search.result.length
authorize!
render 'index'
end
# GET /my_rfc_activity
def rfcs_with_my_comments
# As we order by `last_comment`, we need to include `with_last_activity` in the original query.
# Therefore, the optimization chosen above doesn't work here.
@search = policy_scope(RequestForComment)
.joins(:comments) # we don't need to outer join here, because we know the user has commented on these
.where(comments: {user: current_user})
.ransack(params[:q])
# This total is used later to calculate the total number of entries
request_for_comments = @search.result
.paginate(page: params[:page], per_page: per_page_param)
@request_for_comments = RequestForComment.where(id: request_for_comments)
.with_last_activity
.includes(submission: %i[study_group exercise contributor])
.includes(:file, :comments, user: [:consumer])
.order(last_comment: :desc)
# We need to manually enable the pagination links.
.extending(WillPaginate::ActiveRecord::RelationMethods)
@request_for_comments.current_page = WillPaginate::PageNumber(params[:page] || 1)
@request_for_comments.limit_value = per_page_param
@request_for_comments.total_entries = @search.result.length
authorize!
render 'index'
end
# GET /exercises/:id/request_for_comments
def rfcs_for_exercise
exercise = Exercise.find(params[:exercise_id])
@search = policy_scope(RequestForComment)
.with_last_activity
.where(exercise_id: exercise.id)
.ransack(params[:q])
@request_for_comments = @search.result
.joins(:exercise)
.order(last_comment: :desc)
.paginate(page: params[:page], per_page: per_page_param)
# let the exercise decide, whether its rfcs should be visible
authorize(exercise)
render 'index'
end
# GET /request_for_comments/1/mark_as_solved
def mark_as_solved
authorize!
@request_for_comment.solved = true
respond_to do |format|
if @request_for_comment.save
format.json { render :show, status: :ok, location: @request_for_comment }
else
format.json { render json: @request_for_comment.errors, status: :unprocessable_entity }
end
end
end
# POST /request_for_comments/1/set_thank_you_note
def set_thank_you_note
authorize!
@request_for_comment.thank_you_note = params[:note]
commenters = @request_for_comment.commenters
commenters.each {|commenter| UserMailer.send_thank_you_note(@request_for_comment, commenter).deliver_now }
respond_to do |format|
if @request_for_comment.save
format.json { render :show, status: :ok, location: @request_for_comment }
else
format.json { render json: @request_for_comment.errors, status: :unprocessable_entity }
end
end
end
# POST /request_for_comments/1/clear_question
def clear_question
authorize!
update_and_respond(object: @request_for_comment, params: {question: nil})
end
# GET /request_for_comments/1
# GET /request_for_comments/1.json
def show
authorize!
end
# POST /request_for_comments.json
def create
# Consider all requests as JSON
request.format = 'json'
raise Pundit::NotAuthorizedError if @embed_options[:disable_rfc]
@request_for_comment = RequestForComment.new(request_for_comment_params)
respond_to do |format|
if @request_for_comment.save
begin
# execute the tests here and wait until they finished.
# As the same runner is used for the score and test run, no parallelization is possible
# A run is triggered from the frontend and does not need to be handled here.
@request_for_comment.submission.calculate_score(current_user)
rescue Runner::Error::RunnerInUse => e
Rails.logger.debug { "Scoring a submission failed because the runner was already in use: #{e.message}" }
format.json { render json: {error: t('exercises.editor.runner_in_use'), status: :runner_in_use}, status: :conflict }
rescue Runner::Error => e
Rails.logger.debug { "Runner error while requesting comments: #{e.message}" }
format.json { render json: {danger: t('exercises.editor.depleted'), status: :container_depleted}, status: :service_unavailable }
else
format.json { render :show, status: :created, location: @request_for_comment }
end
else
format.html { render :new }
format.json { render json: @request_for_comment.errors, status: :unprocessable_entity }
end
end
authorize!
end
private
# Use callbacks to share common setup or constraints between actions.
def set_request_for_comment
@request_for_comment = RequestForComment.find(params[:id])
end
def request_for_comment_params
# The study_group_id might not be present in the session (e.g. for internal users), resulting in session[:study_group_id] = nil which is intended.
params.require(:request_for_comment).permit(:exercise_id, :file_id, :question, :requested_at, :solved, :submission_id).merge(user: current_user)
end
# The index page requires the grouping of the study groups
# The study groups are grouped by the current study group and other study groups of the user
def set_study_group_grouping
current_study_group = StudyGroup.find_by(id: session[:study_group_id])
my_study_groups = case current_user.consumer.rfc_visibility
when 'all' then current_user.study_groups.order(name: :desc)
when 'consumer' then current_user.study_groups.where(consumer: current_user.consumer).order(name: :desc)
when 'study_group' then current_study_group.present? ? Array(current_study_group) : []
else raise "Unknown RfC Visibility #{current_user.consumer.rfc_visibility}"
end
@study_groups_grouping = [[t('request_for_comments.index.study_groups.current'), Array(current_study_group)],
[t('request_for_comments.index.study_groups.my'), my_study_groups.reject {|group| group == current_study_group }]]
end
end