Merge branch 'master' into refactor_proforma_import_export

# Conflicts:
#	spec/controllers/exercises_controller_spec.rb
This commit is contained in:
Karol
2022-01-11 22:20:18 +01:00
122 changed files with 1558 additions and 1424 deletions

View File

@ -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);
});
};

View File

@ -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) {

View File

@ -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();

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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}")

View File

@ -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

View File

@ -1,7 +1,6 @@
# frozen_string_literal: true
class ExternalUser < User
validates :consumer_id, presence: true
validates :external_id, presence: true
def displayname

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View 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')

View File

@ -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')
| &nbsp;
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)

View File

@ -1,3 +1,3 @@
# frozen_string_literal: true
json.extract! @file, :id, :name_with_extension
json.extract! @file, :id, :filepath

View File

@ -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))

View File

@ -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?

View File

@ -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?

View File

@ -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")

View File

@ -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?

View File

@ -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

View File

@ -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))