Merge branch 'linter_toggle'

# Conflicts:
#	config/locales/de.yml
This commit is contained in:
Sebastian Serth
2020-10-15 18:33:19 +02:00
20 changed files with 94 additions and 44 deletions

View File

@@ -94,7 +94,9 @@ var CodeOceanEditor = {
},
getCardClass: function (result) {
if (result.stderr && !result.score) {
if (result.file_role === 'teacher_defined_linter') {
return 'card bg-info text-white'
} else if (result.stderr && !result.score) {
return 'card bg-danger text-white';
} else if (result.score < 1) {
return 'card bg-warning text-white';
@@ -428,7 +430,7 @@ var CodeOceanEditor = {
},
isActiveFileTestable: function () {
return this.isActiveFileExecutable() && ['teacher_defined_test', 'user_defined_test'].includes(this.active_frame.data('role'));
return this.isActiveFileExecutable() && ['teacher_defined_test', 'user_defined_test', 'teacher_defined_linter'].includes(this.active_frame.data('role'));
},
isBrowserSupported: function () {
@@ -443,8 +445,13 @@ var CodeOceanEditor = {
card.find('.card-title .number').text(index + 1);
card.find('.row .col-sm-9').eq(0).find('.number').eq(0).text(result.passed);
card.find('.row .col-sm-9').eq(0).find('.number').eq(1).text(result.count);
card.find('.row .col-sm-9').eq(1).find('.number').eq(0).text(parseFloat((result.score * result.weight).toFixed(2)));
card.find('.row .col-sm-9').eq(1).find('.number').eq(1).text(result.weight);
if (result.weight !== 0) {
card.find('.row .col-sm-9').eq(1).find('.number').eq(0).text(parseFloat((result.score * result.weight).toFixed(2)));
card.find('.row .col-sm-9').eq(1).find('.number').eq(1).text(result.weight);
} else {
// Hide score row if no score could be achieved
card.find('.attribute-row.row').eq(1).addClass('d-none');
}
card.find('.row .col-sm-9').eq(2).html(result.message);
// Add error message from code to card

View File

@@ -48,7 +48,12 @@ CodeOceanEditorEvaluation = {
printScoringResult: function (result, index) {
$('#results').show();
var card = $('#dummies').children().first().clone();
let card;
if (result.file_role === 'teacher_defined_linter') {
card = $('#linter-dummies').children().first().clone();
} else {
card = $('#test-dummies').children().first().clone();
}
if (card.isPresent()) {
// the card won't be present if @embed_options[:hide_test_results] == true
this.populateCard(card, result, index);
@@ -58,7 +63,7 @@ CodeOceanEditorEvaluation = {
printScoringResults: function (response) {
$('#results ul').first().html('');
$('.test-count .number').html(response.length);
$('.test-count .number').html(response.filter(function(x) { return x.file_role === 'teacher_defined_test'; }).length);
this.clearOutput();
_.each(response, function (result, index) {

View File

@@ -280,7 +280,7 @@ $(document).on('turbolinks:load', function () {
var observeFileRoleChanges = function () {
$(document).on('change', 'select[name$="[role]"]', function () {
var is_test_file = $(this).val() === 'teacher_defined_test';
var is_test_file = $(this).val() === 'teacher_defined_test' || $(this).val() === 'teacher_defined_linter';
var parent = $(this).parents('.card');
var fields = parent.find('.test-related-fields');
if (is_test_file) {

View File

@@ -3,7 +3,7 @@ require 'concurrent/future'
module SubmissionScoring
def collect_test_results(submission)
# Mnemosyne.trace 'custom.codeocean.collect_test_results', meta: { submission: submission.id } do
submission.collect_files.select(&:teacher_defined_test?).map do |file|
submission.collect_files.select(&:teacher_defined_assessment?).map do |file|
future = Concurrent::Future.execute do
# Mnemosyne.trace 'custom.codeocean.collect_test_results_block', meta: { file: file.id, submission: submission.id } do
assessor = Assessor.new(execution_environment: submission.execution_environment)
@@ -29,7 +29,7 @@ module SubmissionScoring
waiting_for_container_time: output[:waiting_for_container_time]
).save
output.merge!(assessment)
output.merge!(filename: file.name_with_extension, message: feedback_message(file, output[:score]), weight: file.weight)
output.merge!(filename: file.name_with_extension, message: feedback_message(file, output), weight: file.weight)
# end
end
future.value
@@ -45,9 +45,15 @@ module SubmissionScoring
private :execute_test_file
def feedback_message(file, score)
def feedback_message(file, output)
set_locale
score == Assessor::MAXIMUM_SCORE ? I18n.t('exercises.implement.default_feedback') : render_markdown(file.feedback_message)
if output[:score] == Assessor::MAXIMUM_SCORE && output[:file_role] == 'teacher_defined_test'
I18n.t('exercises.implement.default_test_feedback')
elsif output[:score] == Assessor::MAXIMUM_SCORE && output[:file_role] == 'teacher_defined_linter'
I18n.t('exercises.implement.default_linter_feedback')
else
render_markdown(file.feedback_message)
end
end
def score_submission(submission)

View File

@@ -19,11 +19,11 @@ module CodeOcean
include DefaultValues
DEFAULT_WEIGHT = 1.0
ROLES = %w(main_file reference_implementation regular_file executable_file teacher_defined_test user_defined_file user_defined_test)
TEACHER_DEFINED_ROLES = ROLES - %w(user_defined_file)
ROLES = %w[main_file reference_implementation regular_file executable_file teacher_defined_test user_defined_file user_defined_test teacher_defined_linter].freeze
TEACHER_DEFINED_ROLES = ROLES - %w[user_defined_file]
after_initialize :set_default_values
before_validation :clear_weight, unless: :teacher_defined_test?
before_validation :clear_weight, unless: :teacher_defined_assessment?
before_validation :hash_content, if: :content_present?
before_validation :set_ancestor_values, if: :incomplete_descendent?
@@ -45,19 +45,20 @@ module CodeOcean
ROLES.each do |role|
scope :"#{role}s", -> { where(role: role) }
end
scope :teacher_defined_assessments, -> { where(role: %w[teacher_defined_test teacher_defined_linter]) }
default_scope { order(name: :asc) }
validates :feedback_message, if: :teacher_defined_test?, presence: true
validates :feedback_message, absence: true, unless: :teacher_defined_test?
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
validates :read_only, boolean_presence: true
validates :role, inclusion: {in: ROLES}
validates :weight, if: :teacher_defined_test?, numericality: true, presence: true
validates :weight, absence: true, unless: :teacher_defined_test?
validates :weight, if: :teacher_defined_assessment?, numericality: true, presence: true
validates :weight, absence: true, unless: :teacher_defined_assessment?
validates :file, presence: true if :context.is_a?(Submission)
validates_with FileNameValidator, fields: [:name, :path, :file_type_id]
@@ -75,6 +76,10 @@ module CodeOcean
end
private :clear_weight
def teacher_defined_assessment?
teacher_defined_test? || teacher_defined_linter?
end
def content_present?
content? || native_file?
end
@@ -111,7 +116,7 @@ module CodeOcean
def set_default_values
set_default_values_if_present(content: '', hidden: false, read_only: false)
set_default_values_if_present(weight: DEFAULT_WEIGHT) if teacher_defined_test?
set_default_values_if_present(weight: DEFAULT_WEIGHT) if teacher_defined_assessment?
end
private :set_default_values

View File

@@ -265,7 +265,7 @@ class Exercise < ApplicationRecord
FROM files
WHERE context_type = 'Exercise'
AND context_id = #{id}
AND role = 'teacher_defined_test'
AND role IN ('teacher_defined_test', 'teacher_defined_linter')
GROUP BY context_id),
-- filter for rows containing max points
time_max_score AS
@@ -394,7 +394,7 @@ class Exercise < ApplicationRecord
WHERE exercise_id = #{id} AND user_id = #{user.id} AND user_type = '#{user_type}'
GROUP BY user_id, id, exercise_id),
MAX_POINTS AS
(SELECT context_id AS ex_id, sum(weight) AS max_points FROM files WHERE context_type = 'Exercise' AND context_id = #{id} AND role = 'teacher_defined_test' GROUP BY context_id),
(SELECT context_id AS ex_id, sum(weight) AS max_points FROM files WHERE context_type = 'Exercise' AND context_id = #{id} AND role IN ('teacher_defined_test', 'teacher_defined_linter') GROUP BY context_id),
-- filter for rows containing max points
TIME_MAX_SCORE AS
@@ -508,7 +508,7 @@ class Exercise < ApplicationRecord
0
end
else
files.teacher_defined_tests.sum(:weight)
files.teacher_defined_assessments.sum(:weight)
end
end

View File

@@ -55,7 +55,7 @@ module ProformaService
end
def tests
@exercise.files.filter { |file| file.role == 'teacher_defined_test' }.map do |file|
@exercise.files.filter { |file| file.role == 'teacher_defined_test' || file.role == 'teacher_defined_linter' }.map do |file|
Proforma::Test.new(
id: file.id,
title: file.name,
@@ -78,7 +78,7 @@ module ProformaService
def task_files
@exercise.files
.filter { |file| !file.role.in? %w[reference_implementation teacher_defined_test] }.map do |file|
.filter { |file| !file.role.in? %w[reference_implementation teacher_defined_test teacher_defined_linter] }.map do |file|
task_file(file)
end
end

View File

@@ -12,16 +12,25 @@ div.h-100 id='output_sidebar_uncollapsed' class='d-none col-sm-12 enforce-bottom
p.test-count == t('exercises.implement.test_count', count: 0)
- unless @embed_options[:hide_test_results]
ul.list-unstyled
ul#dummies.d-none.list-unstyled
ul#test-dummies.d-none.list-unstyled
li.card.mt-2
.card-header.py-2
h5.card-title.m-0 == t('exercises.implement.file', filename: '', number: 0)
h5.card-title.m-0 == t('exercises.implement.test_file', filename: '', number: 0)
.card-body.bg-white.text-dark
= row(label: 'exercises.implement.passed_tests', value: t('shared.out_of', maximum_value: 0, value: 0).html_safe)
= row(label: 'activerecord.attributes.submission.score', value: t('shared.out_of', maximum_value: 0, value: 0).html_safe)
= row(label: 'exercises.implement.feedback')
= row(label: 'exercises.implement.error_messages')
/= row(label: 'exercises.implement.output', value: link_to(t('shared.show'), '#'))
ul#linter-dummies.d-none.list-unstyled
li.card.mt-2
.card-header.py-2
h5.card-title.m-0 == t('exercises.implement.linter_file', filename: '', number: 0)
.card-body.bg-white.text-dark
= row(label: 'exercises.implement.code_rating', value: t('shared.out_of', maximum_value: 0, value: 0).html_safe)
= row(label: 'activerecord.attributes.submission.score', value: t('shared.out_of', maximum_value: 0, value: 0).html_safe)
= row(label: 'exercises.implement.feedback')
= row(label: 'exercises.implement.messages')
#score data-maximum-score=@exercise.maximum_score data-score=@exercise.final_submission(@current_user).try(:score)
h4
span == "#{t('activerecord.attributes.submission.score')}:&nbsp;"

View File

@@ -37,12 +37,12 @@ li.card.mt-2
label.form-check-label
= f.check_box(:read_only, class: 'form-check-input')
= t('activerecord.attributes.file.read_only')
.test-related-fields style="display: #{f.object.teacher_defined_test? ? 'initial' : 'none'};"
.test-related-fields style="display: #{f.object.teacher_defined_assessment? ? 'initial' : 'none'};"
.form-group
= f.label(:name, t('activerecord.attributes.file.feedback_message'))
= f.text_area(:feedback_message, class: 'form-control', maxlength: 255)
.help-block.form-text = t('.hints.feedback_message')
.form-group
= f.label(:role, t('activerecord.attributes.file.weight'))
= f.number_field(:weight, class: 'form-control', min: 1, step: 'any')
= f.number_field(:weight, class: 'form-control', min: 0, step: 'any')
= render('code_field', attribute: :content, form: f, label: t('activerecord.attributes.file.content'))

View File

@@ -30,7 +30,7 @@ h1 = Exercise.model_name.human(count: 2)
tr data-id=exercise.id
td.p-1.pt-2 = link_to_if(policy(exercise).show?, exercise.title, exercise, 'data-turbolinks' => "false")
td.p-1.pt-2 = link_to_if(exercise.execution_environment && policy(exercise.execution_environment).show?, exercise.execution_environment, exercise.execution_environment)
td.p-1.pt-2 = exercise.files.teacher_defined_tests.count
td.p-1.pt-2 = exercise.files.teacher_defined_assessments.count
td.p-1.pt-2 = exercise.maximum_score
td.p-1.pt-2 = exercise.exercise_tags.count
td.p-1.pt-2 = exercise.expected_difficulty

View File

@@ -53,7 +53,9 @@
- rescue Timeout::Error
pre= output or t('request_for_comments.no_output')
- assess_runs = testruns.select {|run| run.cause == 'assess'}
- assess_runs = testruns.select {|run| run.cause == 'assess' }
- unless @current_user.admin?
- assess_runs = assess_runs.select {|run| run.file.teacher_defined_test? }
- if assess_runs.size > 0
h5.mt-4= t('request_for_comments.test_results')
.testrun-assess-results

View File

@@ -4,7 +4,7 @@
= row(label: 'file.role', value: file.role? ? t("files.roles.#{file.role}") : '')
= row(label: 'file.hidden', value: file.hidden)
= row(label: 'file.read_only', value: file.read_only)
- if file.teacher_defined_test?
- 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))