Merge branch 'master' into refactor_proforma_import_export
# Conflicts: # spec/controllers/exercises_controller_spec.rb
This commit is contained in:
@ -68,7 +68,7 @@ $(document).on('turbolinks:load', function() {
|
||||
_.each(response.docker, function(data) {
|
||||
groups.update({
|
||||
id: data.id,
|
||||
visible: data.pool_size > 0
|
||||
visible: data.prewarmingPoolSize > 0
|
||||
});
|
||||
});
|
||||
};
|
||||
@ -78,26 +78,27 @@ $(document).on('turbolinks:load', function() {
|
||||
dataset.add({
|
||||
group: data.id,
|
||||
x: vis.moment(),
|
||||
y: data.quantity
|
||||
y: data.usedRunners
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var updateProgressBar = function(progress_bar, data) {
|
||||
var percentage = Math.min(Math.round(data.quantity / data.pool_size * 100), 100);
|
||||
var percentage = Math.min(Math.round(data.idleRunners / data.prewarmingPoolSize * 100), 100);
|
||||
progress_bar.attr({
|
||||
'aria-valuemax': data.pool_size,
|
||||
'aria-valuenow': data.quantity,
|
||||
'aria-valuemax': data.prewarmingPoolSize,
|
||||
'aria-valuenow': data.idleRunners,
|
||||
style: 'width: ' + percentage + '%'
|
||||
});
|
||||
progress_bar.html(data.quantity);
|
||||
progress_bar.html(data.idleRunners);
|
||||
};
|
||||
|
||||
var updateTable = function(response) {
|
||||
_.each(response.docker, function(data) {
|
||||
var row = $('tbody tr[data-id=' + data.id + ']');
|
||||
$('.pool-size', row).html(data.pool_size);
|
||||
var progress_bar = $('.quantity .progress .progress-bar', row);
|
||||
$('.prewarming-pool-size', row).html(data.prewarmingPoolSize);
|
||||
$('.used-runners', row).html(`+ ${data.usedRunners}`);
|
||||
var progress_bar = $('.idle-runners .progress .progress-bar', row);
|
||||
updateProgressBar(progress_bar, data);
|
||||
});
|
||||
};
|
||||
|
@ -17,6 +17,7 @@ var CodeOceanEditor = {
|
||||
//Request-For-Comments-Configuration
|
||||
REQUEST_FOR_COMMENTS_DELAY: 0,
|
||||
REQUEST_TOOLTIP_TIME: 5000,
|
||||
REQUEST_TOOLTIP_DELAY: 10 * 60 * 1000,
|
||||
|
||||
editors: [],
|
||||
editor_for_file: new Map(),
|
||||
@ -170,7 +171,7 @@ var CodeOceanEditor = {
|
||||
|
||||
this.active_frame = frame;
|
||||
frame.show();
|
||||
this.resizeParentOfAceEditor(frame.find('.ace_editor.ace-tm'));
|
||||
this.resizeParentOfAceEditor(frame.find('.ace_editor'));
|
||||
},
|
||||
|
||||
getProgressBarClass: function (percentage) {
|
||||
@ -406,7 +407,7 @@ var CodeOceanEditor = {
|
||||
initializeRegexes: function () {
|
||||
// These RegEx are run on the HTML escaped output!
|
||||
this.regex_for_language.set("ace/mode/python", /File "(.+?)", line (\d+)/g);
|
||||
this.regex_for_language.set("ace/mode/java", /(.*\.java):(\d+):/g);
|
||||
this.regex_for_language.set("ace/mode/java", /(?:\.\/)?(.*\.java):(\d+):/g);
|
||||
},
|
||||
|
||||
initializeTooltips: function () {
|
||||
@ -444,10 +445,12 @@ var CodeOceanEditor = {
|
||||
|
||||
setTimeout(function () {
|
||||
button.prop('disabled', false);
|
||||
button.tooltip('show');
|
||||
setTimeout(function () {
|
||||
button.tooltip('hide');
|
||||
}, this.REQUEST_TOOLTIP_TIME);
|
||||
button.tooltip('show');
|
||||
setTimeout(function () {
|
||||
button.tooltip('hide');
|
||||
}, this.REQUEST_TOOLTIP_TIME);
|
||||
}, this.REQUEST_TOOLTIP_DELAY)
|
||||
}.bind(this), this.REQUEST_FOR_COMMENTS_DELAY);
|
||||
},
|
||||
|
||||
@ -623,15 +626,18 @@ var CodeOceanEditor = {
|
||||
},
|
||||
|
||||
jumpToSourceLine: function (event) {
|
||||
var file = $(event.target).data('file');
|
||||
var line = $(event.target).data('line');
|
||||
const file = $(event.target).data('file');
|
||||
const line = $(event.target).data('line');
|
||||
|
||||
// set active file, only needed for codepilot, so skipped for now
|
||||
|
||||
var frame = $('div.frame[data-filename="' + file + '"]');
|
||||
const frame = $('div.frame[data-filename="' + file + '"]');
|
||||
this.showFrame(frame);
|
||||
this.toggleButtonStates();
|
||||
|
||||
var editor = this.editor_for_file.get(file);
|
||||
const file_id = frame.find('.editor').data('file-id');
|
||||
this.setActiveFile(frame.data('filename'), file_id);
|
||||
this.selectFileInJsTree($('#files'), file_id);
|
||||
|
||||
const editor = this.editor_for_file.get(file);
|
||||
editor.gotoLine(line, 0);
|
||||
event.preventDefault();
|
||||
},
|
||||
@ -829,7 +835,11 @@ var CodeOceanEditor = {
|
||||
const percentile75 = data['working_time_75_percentile'];
|
||||
const accumulatedWorkTimeUser = data['working_time_accumulated'];
|
||||
|
||||
const minTimeIntervention = 10 * 60 * 1000;
|
||||
let minTimeIntervention = 10 * 60 * 1000;
|
||||
if ($('#editor').data('exercise-id') === 909) {
|
||||
// 30 minutes for our large Map exercise
|
||||
minTimeIntervention = 30 * 60 * 1000;
|
||||
}
|
||||
|
||||
let timeUntilIntervention;
|
||||
if ((accumulatedWorkTimeUser - percentile75) > 0) {
|
||||
|
@ -115,7 +115,7 @@ CodeOceanEditorSubmissions = {
|
||||
this.showSpinner(this);
|
||||
this.ajax({
|
||||
method: 'GET',
|
||||
url: $('#start-over').data('url')
|
||||
url: $('#start-over').data('url') || $('#start-over-active-file').data('url')
|
||||
}).done(function(response) {
|
||||
this.hideSpinner();
|
||||
_.each(this.editors, function(editor) {
|
||||
@ -196,9 +196,9 @@ CodeOceanEditorSubmissions = {
|
||||
const button = $(event.target) || $('#submit');
|
||||
this.createSubmission(button, null, function (response) {
|
||||
if (response.redirect) {
|
||||
this.unloadAutoSave();
|
||||
this.editors = [];
|
||||
Turbolinks.clearCache();
|
||||
clearTimeout(this.autosaveTimer);
|
||||
Turbolinks.visit(response.redirect);
|
||||
} else if (response.status === 'container_depleted') {
|
||||
this.showContainerDepletedMessage();
|
||||
|
@ -29,16 +29,17 @@ class CommunitySolutionsController < ApplicationController
|
||||
if last_contribution.blank?
|
||||
last_contribution = @community_solution.exercise
|
||||
new_readme_file = {content: '', file_type: FileType.find_by(file_extension: '.txt'), hidden: false, read_only: false, name: 'ReadMe', role: 'regular_file', context: @community_solution}
|
||||
@files << CodeOcean::File.create!(new_readme_file)
|
||||
# If the first user did not save, the ReadMe file already exists
|
||||
@files << CodeOcean::File.find_or_create_by!(new_readme_file)
|
||||
end
|
||||
all_visible_files = last_contribution.files.select(&:visible)
|
||||
# Add the ReadMe file first
|
||||
@files += all_visible_files.select {|f| CodeOcean::File.find_by(id: f.file_id).context_type == 'CommunitySolution' }
|
||||
@files += all_visible_files.select {|f| CodeOcean::File.find_by(id: f.file_id)&.context_type == 'CommunitySolution' }
|
||||
# Then, add all remaining files and sort them by name with extension
|
||||
@files += (all_visible_files - @files).sort_by(&:name_with_extension)
|
||||
@files += (all_visible_files - @files).sort_by(&:filepath)
|
||||
|
||||
# Own Submission as a reference
|
||||
@own_files = @submission.collect_files.select(&:visible).sort_by(&:name_with_extension)
|
||||
@own_files = @submission.collect_files.select(&:visible).sort_by(&:filepath)
|
||||
# Remove the file_id from the second graph. Otherwise, the comparison and file-tree selection does not work as expected
|
||||
@own_files.map do |file|
|
||||
file.file_id = nil
|
||||
|
@ -5,7 +5,7 @@ module RedirectBehavior
|
||||
|
||||
def redirect_after_submit
|
||||
Rails.logger.debug { "Redirecting user with score:s #{@submission.normalized_score}" }
|
||||
if @submission.normalized_score.to_d == 1.0.to_d
|
||||
if @submission.normalized_score.to_d == BigDecimal('1.0')
|
||||
if redirect_to_community_solution?
|
||||
redirect_to_community_solution
|
||||
return
|
||||
|
@ -4,7 +4,7 @@ class ExecutionEnvironmentsController < ApplicationController
|
||||
include CommonBehavior
|
||||
|
||||
before_action :set_docker_images, only: %i[create edit new update]
|
||||
before_action :set_execution_environment, only: MEMBER_ACTIONS + %i[execute_command shell statistics]
|
||||
before_action :set_execution_environment, only: MEMBER_ACTIONS + %i[execute_command shell statistics sync_to_runner_management]
|
||||
before_action :set_testing_framework_adapters, only: %i[create edit new update]
|
||||
|
||||
def authorize!
|
||||
@ -166,6 +166,20 @@ class ExecutionEnvironmentsController < ApplicationController
|
||||
update_and_respond(object: @execution_environment, params: execution_environment_params)
|
||||
end
|
||||
|
||||
def sync_to_runner_management
|
||||
return unless Runner.management_active?
|
||||
|
||||
begin
|
||||
Runner.strategy_class.sync_environment(@execution_environment)
|
||||
rescue Runner::Error => e
|
||||
Rails.logger.debug { "Runner error while synchronizing execution environment with id #{@execution_environment.id}: #{e.message}" }
|
||||
Sentry.capture_exception(e)
|
||||
redirect_to @execution_environment, alert: t('execution_environments.index.synchronize.failure', error: e.message)
|
||||
else
|
||||
redirect_to @execution_environment, notice: t('execution_environments.index.synchronize.success')
|
||||
end
|
||||
end
|
||||
|
||||
def sync_all_to_runner_management
|
||||
authorize ExecutionEnvironment
|
||||
|
||||
@ -179,6 +193,7 @@ class ExecutionEnvironmentsController < ApplicationController
|
||||
success << true
|
||||
rescue Runner::Error => e
|
||||
Rails.logger.debug { "Runner error while getting all execution environments: #{e.message}" }
|
||||
Sentry.capture_exception(e)
|
||||
environments_to_remove = []
|
||||
success << false
|
||||
end
|
||||
@ -189,6 +204,7 @@ class ExecutionEnvironmentsController < ApplicationController
|
||||
Runner.strategy_class.sync_environment(execution_environment)
|
||||
rescue Runner::Error => e
|
||||
Rails.logger.debug { "Runner error while synchronizing execution environment with id #{execution_environment.id}: #{e.message}" }
|
||||
Sentry.capture_exception(e)
|
||||
false
|
||||
end
|
||||
|
||||
@ -198,6 +214,7 @@ class ExecutionEnvironmentsController < ApplicationController
|
||||
Runner.strategy_class.remove_environment(execution_environment)
|
||||
rescue Runner::Error => e
|
||||
Rails.logger.debug { "Runner error while deleting execution environment with id #{execution_environment.id}: #{e.message}" }
|
||||
Sentry.capture_exception(e)
|
||||
false
|
||||
end
|
||||
|
||||
|
@ -59,7 +59,7 @@ raise: false
|
||||
end
|
||||
|
||||
def collect_paths(files)
|
||||
unique_paths = files.map(&:path).reject(&:blank?).uniq
|
||||
unique_paths = files.map(&:path).compact_blank.uniq
|
||||
subpaths = unique_paths.map do |path|
|
||||
Array.new((path.split('/').length + 1)) do |n|
|
||||
path.split('/').shift(n).join('/')
|
||||
@ -321,7 +321,7 @@ raise: false
|
||||
@search = Search.new
|
||||
@search.exercise = @exercise
|
||||
@submission = current_user.submissions.where(exercise_id: @exercise.id).order('created_at DESC').first
|
||||
@files = (@submission ? @submission.collect_files : @exercise.files).select(&:visible).sort_by(&:name_with_extension)
|
||||
@files = (@submission ? @submission.collect_files : @exercise.files).select(&:visible).sort_by(&:filepath)
|
||||
@paths = collect_paths(@files)
|
||||
|
||||
@user_id = if current_user.respond_to? :external_id
|
||||
|
@ -15,8 +15,9 @@ class PingController < ApplicationController
|
||||
private
|
||||
|
||||
def postgres_connected!
|
||||
# any unhandled exception leads to a HTTP 500 response.
|
||||
ApplicationRecord.establish_connection
|
||||
ApplicationRecord.connection
|
||||
ApplicationRecord.connected?
|
||||
raise ActiveRecord::ConnectionNotEstablished unless ApplicationRecord.connected?
|
||||
end
|
||||
end
|
||||
|
@ -24,7 +24,7 @@ class ProxyExercisesController < ApplicationController
|
||||
|
||||
def create
|
||||
myparams = proxy_exercise_params
|
||||
myparams[:exercises] = Exercise.find(myparams[:exercise_ids].reject(&:empty?))
|
||||
myparams[:exercises] = Exercise.find(myparams[:exercise_ids].compact_blank)
|
||||
@proxy_exercise = ProxyExercise.new(myparams)
|
||||
authorize!
|
||||
|
||||
@ -78,7 +78,7 @@ class ProxyExercisesController < ApplicationController
|
||||
|
||||
def update
|
||||
myparams = proxy_exercise_params
|
||||
myparams[:exercises] = Exercise.find(myparams[:exercise_ids].reject(&:blank?))
|
||||
myparams[:exercises] = Exercise.find(myparams[:exercise_ids].compact_blank)
|
||||
update_and_respond(object: @proxy_exercise, params: myparams)
|
||||
end
|
||||
end
|
||||
|
@ -23,7 +23,7 @@ class StudyGroupsController < ApplicationController
|
||||
def update
|
||||
myparams = study_group_params
|
||||
myparams[:external_users] =
|
||||
StudyGroupMembership.find(myparams[:study_group_membership_ids].reject(&:empty?)).map(&:user)
|
||||
StudyGroupMembership.find(myparams[:study_group_membership_ids].compact_blank).map(&:user)
|
||||
myparams.delete(:study_group_membership_ids)
|
||||
update_and_respond(object: @study_group, params: myparams)
|
||||
end
|
||||
|
@ -27,12 +27,7 @@ class SubmissionsController < ApplicationController
|
||||
|
||||
stringio = Zip::OutputStream.write_buffer do |zio|
|
||||
@files.each do |file|
|
||||
zio.put_next_entry(if file.path.to_s == ''
|
||||
file.name_with_extension
|
||||
else
|
||||
File.join(file.path,
|
||||
file.name_with_extension)
|
||||
end)
|
||||
zio.put_next_entry(file.filepath)
|
||||
zio.write(file.content.presence || file.native_file.read)
|
||||
end
|
||||
|
||||
@ -247,12 +242,9 @@ class SubmissionsController < ApplicationController
|
||||
# parse remote request url
|
||||
content += "#{request.base_url}/evaluate\n"
|
||||
@submission.files.each do |file|
|
||||
file_path = file.path.to_s == '' ? file.name_with_extension : File.join(file.path, file.name_with_extension)
|
||||
content += "#{file_path}=#{file.file_id}\n"
|
||||
end
|
||||
File.open(path, 'w+') do |f|
|
||||
f.write(content)
|
||||
content += "#{file.filepath}=#{file.file_id}\n"
|
||||
end
|
||||
File.write(path, content)
|
||||
path
|
||||
end
|
||||
|
||||
@ -317,7 +309,7 @@ class SubmissionsController < ApplicationController
|
||||
# @files contains all visible files for the user
|
||||
# @file contains the specific file requested for run / test / render / ...
|
||||
set_files
|
||||
@file = @files.detect {|file| file.name_with_extension == sanitize_filename }
|
||||
@file = @files.detect {|file| file.filepath == sanitize_filename }
|
||||
head :not_found unless @file
|
||||
end
|
||||
|
||||
|
@ -39,7 +39,7 @@ class UserExerciseFeedbacksController < ApplicationController
|
||||
authorize!
|
||||
if validate_inputs(uef_params)
|
||||
path =
|
||||
if rfc && submission && submission.normalized_score.to_d == 1.0.to_d
|
||||
if rfc && submission && submission.normalized_score.to_d == BigDecimal('1.0')
|
||||
request_for_comment_path(rfc)
|
||||
else
|
||||
implement_exercise_path(@exercise)
|
||||
@ -82,7 +82,7 @@ class UserExerciseFeedbacksController < ApplicationController
|
||||
authorize!
|
||||
if @exercise && validate_inputs(uef_params)
|
||||
path =
|
||||
if rfc && submission && submission.normalized_score.to_d == 1.0.to_d
|
||||
if rfc && submission && submission.normalized_score.to_d == BigDecimal('1.0')
|
||||
request_for_comment_path(rfc)
|
||||
else
|
||||
implement_exercise_path(@exercise)
|
||||
|
@ -11,12 +11,29 @@ module Admin
|
||||
Runner.strategy_class.pool_size
|
||||
rescue Runner::Error => e
|
||||
Rails.logger.debug { "Runner error while fetching current pool size: #{e.message}" }
|
||||
[]
|
||||
{}
|
||||
end
|
||||
|
||||
ExecutionEnvironment.order(:id).select(:id, :pool_size).map do |execution_environment|
|
||||
execution_environment.attributes.merge(quantity: pool_size[execution_environment.id])
|
||||
# Fetch the actual values (ID is stored as a symbol) or get an empty hash for merge
|
||||
actual = pool_size[execution_environment.id.to_s.to_sym] || {}
|
||||
|
||||
template = {
|
||||
id: execution_environment.id,
|
||||
prewarmingPoolSize: execution_environment.pool_size,
|
||||
idleRunners: 0,
|
||||
usedRunners: 0,
|
||||
}
|
||||
|
||||
# Existing values in the template get replaced with actual values
|
||||
template.merge(actual)
|
||||
end
|
||||
end
|
||||
|
||||
def self.runner_management_release
|
||||
Runner.strategy_class.release
|
||||
rescue Runner::Error => e
|
||||
e.inspect
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -41,7 +41,6 @@ module CodeOcean
|
||||
|
||||
validates :feedback_message, if: :teacher_defined_assessment?, presence: true
|
||||
validates :feedback_message, absence: true, unless: :teacher_defined_assessment?
|
||||
validates :file_type_id, presence: true
|
||||
validates :hashed_content, if: :content_present?, presence: true
|
||||
validates :hidden, boolean_presence: true
|
||||
validates :name, presence: true
|
||||
@ -99,7 +98,7 @@ module CodeOcean
|
||||
private :incomplete_descendent?
|
||||
|
||||
def name_with_extension
|
||||
name + (file_type.file_extension || '')
|
||||
name.to_s + (file_type&.file_extension || '')
|
||||
end
|
||||
|
||||
def set_ancestor_values
|
||||
|
@ -7,8 +7,5 @@ module Creation
|
||||
belongs_to :user, polymorphic: true
|
||||
alias_method :author, :user
|
||||
alias_method :creator, :user
|
||||
|
||||
validates :user_id, presence: true
|
||||
validates :user_type, presence: true
|
||||
end
|
||||
end
|
||||
|
@ -36,6 +36,7 @@ class ExecutionEnvironment < ApplicationRecord
|
||||
after_destroy :delete_runner_environment
|
||||
after_save :working_docker_image?, if: :validate_docker_image?
|
||||
|
||||
after_update_commit :sync_runner_environment, unless: proc {|_| Rails.env.test? }
|
||||
after_rollback :delete_runner_environment, on: :create
|
||||
after_rollback :sync_runner_environment, on: %i[update destroy]
|
||||
|
||||
@ -79,7 +80,7 @@ class ExecutionEnvironment < ApplicationRecord
|
||||
|
||||
def validate_docker_image?
|
||||
# We only validate the code execution with the provided image if there is at least one container to test with.
|
||||
pool_size.positive? && docker_image.present? && !Rails.env.test?
|
||||
pool_size.positive? && docker_image.present? && !Rails.env.test? && Runner.management_active?
|
||||
end
|
||||
|
||||
def working_docker_image?
|
||||
@ -92,9 +93,9 @@ class ExecutionEnvironment < ApplicationRecord
|
||||
rescue Runner::Error => e
|
||||
# In case of an Runner::Error, we retry multiple times before giving up.
|
||||
# The time between each retry increases to allow the runner management to catch up.
|
||||
if retries < 5 && !Rails.env.test?
|
||||
if retries < 30 && !Rails.env.test?
|
||||
retries += 1
|
||||
sleep retries
|
||||
sleep 1.second.to_i
|
||||
retry
|
||||
elsif errors.exclude?(:docker_image)
|
||||
errors.add(:docker_image, "error: #{e}")
|
||||
@ -104,7 +105,7 @@ class ExecutionEnvironment < ApplicationRecord
|
||||
end
|
||||
|
||||
def delete_runner_environment
|
||||
Runner.strategy_class.remove_environment(self)
|
||||
Runner.strategy_class.remove_environment(self) if Runner.management_active?
|
||||
rescue Runner::Error => e
|
||||
unless errors.include?(:docker_image)
|
||||
errors.add(:docker_image, "error: #{e}")
|
||||
@ -114,7 +115,7 @@ class ExecutionEnvironment < ApplicationRecord
|
||||
|
||||
def sync_runner_environment
|
||||
previous_saved_environment = self.class.find(id)
|
||||
Runner.strategy_class.sync_environment(previous_saved_environment)
|
||||
Runner.strategy_class.sync_environment(previous_saved_environment) if Runner.management_active?
|
||||
rescue Runner::Error => e
|
||||
unless errors.include?(:docker_image)
|
||||
errors.add(:docker_image, "error: #{e}")
|
||||
|
@ -49,7 +49,7 @@ class Exercise < ApplicationRecord
|
||||
MAX_GROUP_EXERCISE_FEEDBACKS = 20
|
||||
|
||||
def average_percentage
|
||||
if average_score && (maximum_score.to_d != 0.0.to_d) && submissions.exists?(cause: 'submit')
|
||||
if average_score && (maximum_score.to_d != BigDecimal('0.0')) && submissions.exists?(cause: 'submit')
|
||||
(average_score / maximum_score * 100).round(2)
|
||||
else
|
||||
0
|
||||
@ -580,7 +580,7 @@ cause: %w[submit assess remoteSubmit remoteAssess]}).distinct
|
||||
private :valid_submission_deadlines?
|
||||
|
||||
def needs_more_feedback?(submission)
|
||||
if submission.normalized_score.to_d == 1.0.to_d
|
||||
if submission.normalized_score.to_d == BigDecimal('1.0')
|
||||
user_exercise_feedbacks.final.size <= MAX_GROUP_EXERCISE_FEEDBACKS
|
||||
else
|
||||
user_exercise_feedbacks.intermediate.size <= MAX_GROUP_EXERCISE_FEEDBACKS
|
||||
|
@ -1,7 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ExternalUser < User
|
||||
validates :consumer_id, presence: true
|
||||
validates :external_id, presence: true
|
||||
|
||||
def displayname
|
||||
|
@ -175,7 +175,7 @@ class ProxyExercise < ApplicationRecord
|
||||
return 0.0
|
||||
end
|
||||
points_ratio = exercise.maximum_score(user) / max_score
|
||||
if points_ratio.to_d == 0.0.to_d
|
||||
if points_ratio.to_d == BigDecimal('0.0')
|
||||
Rails.logger.debug { "scoring user #{user.id} for exercise #{exercise.id}: points_ratio=#{points_ratio} score: 0" }
|
||||
return 0.0
|
||||
elsif points_ratio > 1.0
|
||||
|
@ -6,7 +6,7 @@ class Runner < ApplicationRecord
|
||||
|
||||
before_validation :request_id
|
||||
|
||||
validates :execution_environment, :user, :runner_id, presence: true
|
||||
validates :runner_id, presence: true
|
||||
|
||||
attr_accessor :strategy
|
||||
|
||||
|
@ -45,7 +45,6 @@ class Submission < ApplicationRecord
|
||||
scope :in_study_group_of, ->(user) { where(study_group_id: user.study_groups) unless user.admin? }
|
||||
|
||||
validates :cause, inclusion: {in: CAUSES}
|
||||
validates :exercise_id, presence: true
|
||||
|
||||
# after_save :trigger_working_times_action_cable
|
||||
|
||||
@ -160,7 +159,7 @@ class Submission < ApplicationRecord
|
||||
# @raise [Runner::Error] if the code could not be run due to a failure with the runner.
|
||||
# See the specific type and message for more details.
|
||||
def run(file, &block)
|
||||
run_command = command_for execution_environment.run_command, file.name_with_extension
|
||||
run_command = command_for execution_environment.run_command, file.filepath
|
||||
durations = {}
|
||||
prepared_runner do |runner, waiting_duration|
|
||||
durations[:execution_duration] = runner.attach_to_execution(run_command, &block)
|
||||
@ -185,7 +184,7 @@ class Submission < ApplicationRecord
|
||||
end
|
||||
|
||||
def run_test_file(file, runner, waiting_duration)
|
||||
test_command = command_for execution_environment.test_command, file.name_with_extension
|
||||
test_command = command_for execution_environment.test_command, file.filepath
|
||||
result = {file_role: file.role, waiting_for_container_time: waiting_duration}
|
||||
output = runner.execute_command(test_command, raise_exception: false)
|
||||
result.merge(output)
|
||||
@ -222,7 +221,7 @@ class Submission < ApplicationRecord
|
||||
end
|
||||
|
||||
def command_for(template, file)
|
||||
filepath = collect_files.find {|f| f.name_with_extension == file }.filepath
|
||||
filepath = collect_files.find {|f| f.filepath == file }.filepath
|
||||
template % command_substitutions(filepath)
|
||||
end
|
||||
|
||||
@ -255,7 +254,7 @@ class Submission < ApplicationRecord
|
||||
waiting_for_container_time: output[:waiting_for_container_time]
|
||||
)
|
||||
|
||||
filename = file.name_with_extension
|
||||
filename = file.filepath
|
||||
|
||||
if file.teacher_defined_linter?
|
||||
LinterCheckRun.create_from(testrun, assessment)
|
||||
@ -291,8 +290,9 @@ class Submission < ApplicationRecord
|
||||
score += output[:score] * output[:weight] unless output.nil?
|
||||
end
|
||||
end
|
||||
update(score: score)
|
||||
if normalized_score.to_d == 1.0.to_d
|
||||
# Prevent floating point precision issues by converting to BigDecimal, e.g., for `0.28 * 25`
|
||||
update(score: score.to_d)
|
||||
if normalized_score.to_d == BigDecimal('1.0')
|
||||
Thread.new do
|
||||
RequestForComment.where(exercise_id: exercise_id, user_id: user_id, user_type: user_type).find_each do |rfc|
|
||||
rfc.full_score_reached = true
|
||||
|
@ -4,8 +4,4 @@ class UserExerciseIntervention < ApplicationRecord
|
||||
belongs_to :user, polymorphic: true
|
||||
belongs_to :intervention
|
||||
belongs_to :exercise
|
||||
|
||||
validates :user, presence: true
|
||||
validates :exercise, presence: true
|
||||
validates :intervention, presence: true
|
||||
end
|
||||
|
@ -5,10 +5,5 @@ class UserProxyExerciseExercise < ApplicationRecord
|
||||
belongs_to :exercise
|
||||
belongs_to :proxy_exercise
|
||||
|
||||
validates :user_id, presence: true
|
||||
validates :user_type, presence: true
|
||||
validates :exercise_id, presence: true
|
||||
validates :proxy_exercise_id, presence: true
|
||||
|
||||
validates :user_id, uniqueness: {scope: %i[proxy_exercise_id user_type]}
|
||||
end
|
||||
|
@ -1,7 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ExecutionEnvironmentPolicy < AdminOnlyPolicy
|
||||
%i[execute_command? shell? statistics? show?].each do |action|
|
||||
%i[execute_command? shell? statistics? show? sync_to_runner_management?].each do |action|
|
||||
define_method(action) { admin? || author? }
|
||||
end
|
||||
|
||||
|
@ -40,9 +40,9 @@ module ProformaService
|
||||
end
|
||||
|
||||
def task_files
|
||||
@task_files ||= @task.all_files.reject {|file| file.id == 'ms-placeholder-file' }.map do |task_file|
|
||||
@task_files ||= @task.all_files.reject {|file| file.id == 'ms-placeholder-file' }.to_h do |task_file|
|
||||
[task_file.id, codeocean_file_from_task_file(task_file)]
|
||||
end.to_h
|
||||
end
|
||||
end
|
||||
|
||||
def codeocean_file_from_task_file(file)
|
||||
|
@ -10,33 +10,38 @@ h1 = t('breadcrumbs.dashboard.show')
|
||||
h2 Version
|
||||
|
||||
div.mb-4
|
||||
= "CodeOcean Release:"
|
||||
= application_name
|
||||
=< t("admin.dashboard.show.release")
|
||||
| :
|
||||
pre = Sentry.configuration.release
|
||||
|
||||
- if Runner.management_active?
|
||||
div.mb-4
|
||||
= Runner.strategy_class.name.demodulize
|
||||
=< "Release:"
|
||||
pre = Runner.strategy_class.release
|
||||
=< t("admin.dashboard.show.release")
|
||||
| :
|
||||
pre = Admin::DashboardHelper.runner_management_release
|
||||
|
||||
h2 Docker
|
||||
|
||||
- if Runner.management_active?
|
||||
h3 = t('.current')
|
||||
h3 = t('admin.dashboard.show.current')
|
||||
.table-responsive
|
||||
table.table
|
||||
thead
|
||||
tr
|
||||
th = t('activerecord.models.execution_environment.one')
|
||||
th = t('activerecord.attributes.execution_environment.pool_size')
|
||||
th = t('.quantity')
|
||||
th = t('admin.dashboard.show.idleRunners')
|
||||
th = t('admin.dashboard.show.usedRunners')
|
||||
tbody
|
||||
- ExecutionEnvironment.order(:name).each do |execution_environment|
|
||||
tr data-id=execution_environment.id
|
||||
td.name = link_to_if(policy(execution_environment).show?, execution_environment, execution_environment)
|
||||
td.pool-size
|
||||
td.quantity = progress_bar(0)
|
||||
h3 = t('.history')
|
||||
td.prewarming-pool-size
|
||||
td.idle-runners = progress_bar(0)
|
||||
td.used-runners
|
||||
h3 = t('admin.dashboard.show.history')
|
||||
#graph
|
||||
- else
|
||||
p = t('.inactive')
|
||||
p = t('admin.dashboard.show.inactive')
|
||||
|
@ -4,13 +4,17 @@
|
||||
= f.text_field(:name, class: 'form-control', required: true)
|
||||
.form-group
|
||||
= f.label(:path, t('activerecord.attributes.file.path'))
|
||||
= f.select(:path, @paths, {}, class: 'form-control')
|
||||
|
|
||||
a.toggle-input data={text_initial: t('shared.new'), text_toggled: t('shared.back')} href='#' = t('shared.new')
|
||||
.original-input = f.select(:path, @paths, {}, class: 'form-control')
|
||||
= f.text_field(:path, class: 'alternative-input form-control', disabled: true)
|
||||
.form-group
|
||||
= f.label(:file_type_id, t('activerecord.attributes.file.file_type_id'))
|
||||
= f.collection_select(:file_type_id, FileType.where(binary: false).order(:name), :id, :name, {selected: @exercise.execution_environment.file_type.try(:id)}, class: 'form-control')
|
||||
.form-group
|
||||
= f.label(:file_template_id, t('activerecord.attributes.file.file_template_id'))
|
||||
= f.collection_select(:file_template_id, FileTemplate.all.order(:name), :id, :name, {:include_blank => true}, class: 'form-control')
|
||||
- if FileTemplate.any?
|
||||
.form-group
|
||||
= f.label(:file_template_id, t('activerecord.attributes.file.file_template_id'))
|
||||
= f.collection_select(:file_template_id, FileTemplate.all.order(:name), :id, :name, {:include_blank => true}, class: 'form-control')
|
||||
= f.hidden_field(:context_id)
|
||||
.d-none#noTemplateLabel data-text=t('file_template.no_template_label')
|
||||
.actions = render('shared/submit_button', f: f, object: CodeOcean::File.new)
|
||||
|
@ -1,3 +1,3 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
json.extract! @file, :id, :name_with_extension
|
||||
json.extract! @file, :id, :filepath
|
||||
|
@ -1,6 +1,12 @@
|
||||
h1
|
||||
= @execution_environment
|
||||
h1.d-inline-block = @execution_environment
|
||||
.btn-group.float-right
|
||||
= render('shared/edit_button', object: @execution_environment)
|
||||
button.btn.btn-secondary.float-right.dropdown-toggle data-toggle='dropdown' type='button'
|
||||
ul.dropdown-menu.dropdown-menu-right role='menu'
|
||||
li = link_to(t('execution_environments.index.synchronize.button'), sync_to_runner_management_execution_environment_path(@execution_environment), method: :post, class: 'dropdown-item text-dark') if policy(@execution_environment).sync_to_runner_management?
|
||||
li = link_to(t('execution_environments.index.shell'), shell_execution_environment_path(@execution_environment), class: 'dropdown-item text-dark') if policy(@execution_environment).shell?
|
||||
li = link_to(t('shared.statistics'), statistics_execution_environment_path(@execution_environment), 'data-turbolinks' => "false", class: 'dropdown-item text-dark') if policy(@execution_environment).statistics?
|
||||
li = link_to(t('shared.destroy'), @execution_environment, data: {confirm: t('shared.confirm_destroy')}, method: :delete, class: 'dropdown-item text-dark') if policy(@execution_environment).destroy?
|
||||
|
||||
= row(label: 'execution_environment.name', value: @execution_environment.name)
|
||||
= row(label: 'execution_environment.user', value: link_to_if(policy(@execution_environment.author).show?, @execution_environment.author, @execution_environment.author))
|
||||
|
@ -1,4 +1,4 @@
|
||||
div class=(defined?(own_solution) ? "own-frame" : "frame") data-executable=file.file_type.executable? data-filename=file.name_with_extension data-renderable=file.file_type.renderable? data-role=file.role data-binary=file.file_type.binary? data-context-type=file.context_type data-read-only=file.read_only
|
||||
div class=(defined?(own_solution) ? "own-frame" : "frame") data-executable=file.file_type.executable? data-filename=file.filepath data-renderable=file.file_type.renderable? data-role=file.role data-binary=file.file_type.binary? data-context-type=file.context_type data-read-only=file.read_only
|
||||
- if file.file_type.binary?
|
||||
.binary-file data-file-id=file.ancestor_id
|
||||
- if file.file_type.renderable?
|
||||
|
@ -7,10 +7,7 @@ li.card.mt-2
|
||||
a class=['file-heading', collapsed_class] data-toggle="collapse" href="#collapse#{f.index}" aria-expanded="#{aria_expanded}"
|
||||
div.clearfix role="button"
|
||||
i class="fa" aria-hidden="true"
|
||||
- if f.object.name.present? && f.object.file_type.present?
|
||||
span = f.object.name_with_extension
|
||||
- else
|
||||
span = f.object.name
|
||||
span = f.object.filepath
|
||||
.card-collapse.collapse class=('in' if f.object.name.nil?) id="collapse#{f.index}" role="tabpanel"
|
||||
.card-body
|
||||
- if policy(f.object).destroy? && id.present?
|
||||
|
@ -65,9 +65,9 @@ h1
|
||||
td.align-middle
|
||||
-this.testruns.includes(:file).order("files.name").each do |run|
|
||||
- if run.passed
|
||||
.unit-test-result.positive-result title=[run.file&.name_with_extension, run.output].join("\n").strip
|
||||
.unit-test-result.positive-result title=[run.file&.filepath, run.output].join("\n").strip
|
||||
- else
|
||||
.unit-test-result.unknown-result title=[run.file&.name_with_extension, run.output].join("\n").strip
|
||||
.unit-test-result.unknown-result title=[run.file&.filepath, run.output].join("\n").strip
|
||||
td = @working_times_until[index] if index > 0 if policy(@exercise).detailed_statistics?
|
||||
- elsif this.is_a? UserExerciseIntervention
|
||||
td = this.created_at.strftime("%F %T")
|
||||
|
@ -49,7 +49,7 @@ ul.list-unstyled#files
|
||||
a.file-heading.collapsed data-toggle="collapse" data-parent="#files" href=".collapse#{file.id}"
|
||||
div.clearfix role="button"
|
||||
i class="fa" aria-hidden="true"
|
||||
span = file.name_with_extension
|
||||
span = file.filepath
|
||||
.card-collapse.collapse class="collapse#{file.id}" role="tabpanel"
|
||||
.card-body
|
||||
- if policy(file).destroy?
|
||||
|
@ -254,7 +254,7 @@ javascript:
|
||||
function deleteComment(commentId, editor, file_id, callback) {
|
||||
var jqxhr = $.ajax({
|
||||
type: 'DELETE',
|
||||
url: Routes.comments_path(commentId)
|
||||
url: Routes.comment_path(commentId)
|
||||
});
|
||||
jqxhr.done(function () {
|
||||
setAnnotations(editor, file_id);
|
||||
@ -266,7 +266,7 @@ javascript:
|
||||
function updateComment(commentId, text, editor, file_id, callback) {
|
||||
var jqxhr = $.ajax({
|
||||
type: 'PATCH',
|
||||
url: Routes.comments_path(commentId),
|
||||
url: Routes.comment_path(commentId),
|
||||
data: {
|
||||
comment: {
|
||||
text: text
|
||||
|
@ -7,4 +7,4 @@
|
||||
- if file.teacher_defined_assessment?
|
||||
= row(label: 'file.feedback_message', value: render_markdown(file.feedback_message), class: 'm-0')
|
||||
= row(label: 'file.weight', value: file.weight)
|
||||
= row(label: 'file.content', value: file.native_file? ? link_to_if(policy(file).show?, file.native_file.file.filename, file.native_file.url) : code_tag(file.content))
|
||||
= row(label: 'file.content', value: file.native_file? ? link_to_if(policy(file).show?, file.native_file.file.filename, file.native_file.url) : code_tag(file.content, file.file_type.programming_language))
|
||||
|
Reference in New Issue
Block a user