Merge pull request #611 from openHPI/imporve_deadline_handling

Improve deadline handling
This commit is contained in:
Sebastian Serth
2020-05-12 19:01:55 +02:00
committed by GitHub
19 changed files with 97 additions and 66 deletions

View File

@ -8,7 +8,6 @@ gem 'factory_bot_rails'
gem 'forgery'
gem 'highline'
gem 'jbuilder'
gem 'jquery-rails'
gem 'ims-lti', '< 2.0.0'
gem 'kramdown'
gem 'pg'

View File

@ -155,13 +155,8 @@ GEM
ims-lti (1.2.4)
builder (>= 1.0, < 4.0)
oauth (>= 0.4.5, < 0.6)
jaro_winkler (1.5.4)
jbuilder (2.10.0)
activesupport (>= 5.0.0)
jquery-rails (4.4.0)
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
json (2.3.0)
jwt (2.2.1)
kramdown (2.2.1)
@ -228,7 +223,7 @@ GEM
pry (~> 0.13.0)
pry-rails (0.3.9)
pry (>= 0.10.4)
public_suffix (4.0.4)
public_suffix (4.0.5)
puma (4.3.3)
nio4r (~> 2.0)
pundit (2.1.0)
@ -301,7 +296,7 @@ GEM
rspec-expectations (>= 2.99.0.beta1)
rspec-core (3.9.2)
rspec-support (~> 3.9.3)
rspec-expectations (3.9.1)
rspec-expectations (3.9.2)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.9.0)
rspec-mocks (3.9.1)
@ -316,8 +311,7 @@ GEM
rspec-mocks (~> 3.9)
rspec-support (~> 3.9)
rspec-support (3.9.3)
rubocop (0.82.0)
jaro_winkler (~> 1.5.1)
rubocop (0.83.0)
parallel (~> 1.10)
parser (>= 2.7.0.1)
rainbow (>= 2.2.2, < 4.0)
@ -356,7 +350,7 @@ GEM
docile (~> 1.1)
simplecov-html (~> 0.11)
simplecov-html (0.12.2)
slim (4.0.1)
slim (4.1.0)
temple (>= 0.7.6, < 0.9)
tilt (>= 2.0.6, < 2.1)
slim-rails (3.2.0)
@ -443,7 +437,6 @@ DEPENDENCIES
i18n-js
ims-lti (< 2.0.0)
jbuilder
jquery-rails
kramdown
listen
mnemosyne-ruby

View File

@ -10,7 +10,6 @@
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require jquery_ujs
//= require turbolinks
//= require pagedown_bootstrap
//= require rails-timeago

View File

@ -39,7 +39,6 @@ CodeOceanEditorEvaluation = {
$('#submit').get(0).lastChild.nodeValue = I18n.t('exercises.editor.submit_within_grace_period');
} else if (this.late_submission_deadline && now > this.late_submission_deadline || now > this.submission_deadline) {
// after_late_deadline
debugger;
$('#submit').removeClass("btn-success btn-warning btn-danger").addClass("btn-danger");
$('#submit').get(0).lastChild.nodeValue = I18n.t('exercises.editor.submit_after_late_deadline');
}

View File

@ -138,14 +138,14 @@ module Lti
private :return_to_consumer
def send_score(exercise_id, score, user_id)
::NewRelic::Agent.add_custom_attributes({score: score, session: session})
fail(Error, "Score #{score} must be between 0 and #{MAXIMUM_SCORE}!") unless (0..MAXIMUM_SCORE).include?(score)
def send_score(submission)
::NewRelic::Agent.add_custom_attributes({score: submission.normalized_score, session: session})
fail(Error, "Score #{submission.normalized_score} must be between 0 and #{MAXIMUM_SCORE}!") unless (0..MAXIMUM_SCORE).include?(submission.normalized_score)
if session[:consumer_id]
lti_parameter = LtiParameter.where(consumers_id: session[:consumer_id],
external_users_id: user_id,
exercises_id: exercise_id).last
external_users_id: submission.user_id,
exercises_id: submission.exercise_id).last
consumer = Consumer.find_by(id: session[:consumer_id])
provider = build_tool_provider(consumer: consumer, parameters: lti_parameter.lti_parameters)
@ -153,15 +153,17 @@ module Lti
if provider.nil?
{status: 'error'}
elsif submission.after_late_deadline?
{status: 'too late'}
elsif provider.outcome_service?
Raven.extra_context({
provider: provider.inspect,
score: score,
score: submission.normalized_score,
lti_parameter: lti_parameter.inspect,
session: session.to_hash,
exercise_id: exercise_id
exercise_id: submission.exercise_id
})
response = provider.post_replace_result!(score)
response = provider.post_replace_result!(submission.normalized_score)
{code: response.response_code, message: response.post_response.body, status: response.code_major}
else
{status: 'unsupported'}

View File

@ -56,6 +56,10 @@ module SubmissionScoring
unless output.nil?
score += output[:score] * output[:weight]
end
if output.present? && output[:status] == :timeout
output[:stderr] += "\n\n#{t('exercises.editor.timeout', permitted_execution_time: submission.exercise.execution_environment.permitted_execution_time.to_s)}"
end
end
end
submission.update(score: score)

View File

@ -459,10 +459,14 @@ class ExercisesController < ApplicationController
def transmit_lti_score
::NewRelic::Agent.add_custom_attributes({submission: @submission.id, normalized_score: @submission.normalized_score})
response = send_score(@submission.exercise_id, @submission.normalized_score, @submission.user_id)
response = send_score(@submission)
if response[:status] == 'success'
redirect_after_submit
elsif response[:status] == 'too late'
flash[:warning] = I18n.t('exercises.submit.too_late')
flash.keep(:warning)
redirect_after_submit
else
respond_to do |format|
format.html { redirect_to(implement_exercise_path(@submission.exercise)) }

View File

@ -9,6 +9,7 @@
// JS
import 'jquery';
import 'jquery-ujs'
import 'bootstrap/dist/js/bootstrap.bundle.min';
import 'chosen-js/chosen.jquery';
import 'jstree';

View File

@ -26,16 +26,17 @@ class ApplicationPolicy
private :no_one
def everyone_in_study_group
# !! Order is important !!
if @record.respond_to? :study_group # e.g. submission
study_group = @record.study_group
return false if study_group.blank?
users_in_same_study_group = study_group.users
elsif @record.respond_to? :users # e.g. study_group
users_in_same_study_group = @record.users
elsif @record.respond_to? :user # e.g. exercise
study_groups = @record.user.study_groups
users_in_same_study_group = study_groups.collect(&:users).flatten
elsif @record.respond_to? :users # e.g. study_group
users_in_same_study_group = @record.users
elsif @record.respond_to? :study_groups # e.g. user
study_groups = @record.study_groups
users_in_same_study_group = study_groups.collect(&:users).flatten

View File

@ -24,12 +24,14 @@ h1 = link_to_if(policy(@exercise).show?, @exercise, exercise_path(@exercise))
span.date = feedback.created_at
.card-collapse role="tabpanel"
.card-body.feedback
.text = feedback.feedback_text
.text = render_markdown(feedback.feedback_text)
.difficulty = "#{t('user_exercise_feedback.difficulty')} #{comment_presets[feedback.difficulty].join(' - ')}" if feedback.difficulty
.worktime = "#{t('user_exercise_feedback.working_time')} #{time_presets[feedback.user_estimated_worktime].join(' - ')}" if feedback.user_estimated_worktime
.card-footer
span.points = "#{t('exercises.statistics.score')}: #{@submissions[index].score}"
span.working_time.pull-right = "#{t('exercises.statistics.worktime')}: #{@exercise.average_working_time_for(feedback.user.id) or 0}"
- if policy(@exercise).detailed_statistics?
.card-footer
div.clearfix.feedback-header
span.points.flex-grow-1 = "#{t('exercises.statistics.score')}: #{@submissions[index].score}"
span.working_time.pull-right = "#{t('exercises.statistics.worktime')}: #{@exercise.average_working_time_for(feedback.user.id) or 0}"
= render('shared/pagination', collection: @feedbacks)

View File

@ -42,8 +42,7 @@ h1 = @exercise
- submissions = Submission.where(user: @exercise.send(symbol).distinct, exercise: @exercise).in_study_group_of(current_user)
- if !policy(@exercise).detailed_statistics?
- submissions = submissions.final
- latest_submission = submissions.latest
- if latest_submission
- if submissions.any?
.table-responsive
table.table.table-striped.sortable
thead
@ -61,12 +60,13 @@ h1 = @exercise
tr
td = link_to_if symbol==:external_users && policy(user).statistics?, label, {controller: "exercises", action: "statistics", external_user_id: user.id, id: @exercise.id}
td = us['maximum_score'] or 0
td.align-middle
- if latest_submission.before_deadline?
td.align-middle
- latest_user_submission = submissions.where(user: user).latest
- if latest_user_submission.before_deadline?
.unit-test-result.positive-result.before_deadline
- elsif latest_submission.within_grace_period?
- elsif latest_user_submission.within_grace_period?
.unit-test-result.unknown-result.within_grace_period
- elsif latest_submission.after_late_deadline?
- elsif latest_user_submission.after_late_deadline?
.unit-test-result.negative-result.after_late_deadline
td = us['runs'] if policy(@exercise).detailed_statistics?
td = @exercise.average_working_time_for(user.id) or 0 if policy(@exercise).detailed_statistics?

View File

@ -13,10 +13,16 @@
.form-group
= f.text_area(:feedback_text, class: 'form-control', required: true, :rows => "10")
h4.mt-4 = t('user_exercise_feedback.difficulty')
= f.collection_radio_buttons :difficulty, @texts, :first, :last, html_options: {class: "form-check-inline"} do |b|
= b.label(:class => 'form-check') { b.radio_button + b.text }
= f.collection_radio_buttons :difficulty, @texts, :first, :last do |b|
.form-check
label.form-check-label
= b.radio_button(class: 'form-check-input')
= b.text
h4.mt-4 = t('user_exercise_feedback.working_time')
= f.collection_radio_buttons :user_estimated_worktime, @times, :first, :last, html_options: {class: "form-check-inline"} do |b|
= b.label(:class => 'form-check') { b.radio_button + b.text }
= f.collection_radio_buttons :user_estimated_worktime, @times, :first, :last do |b|
.form-check
label.form-check-label
= b.radio_button(class: 'form-check-input')
= b.text
= f.hidden_field(:exercise_id, :value => @exercise.id)
.actions = render('shared/submit_button', f: f, object: @uef)

View File

@ -431,6 +431,7 @@ de:
finishing_rate: Abschlussrate
submit:
failure: Beim Übermitteln Ihrer Punktzahl ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut.
too_late: Ihre Abgabe wurde erfolgreich gespeichert, ging jedoch nach der Abgabefrist ein.
full_score_redirect_to_rfc: Herzlichen Glückwunsch! Sie haben die maximale Punktzahl für diese Aufgabe an den Kurs übertragen. Ein anderer Teilnehmer hat eine Frage zu der von Ihnen gelösten Aufgabe. Er würde sich sicherlich sehr über ihre Hilfe und Kommentare freuen.
full_score_redirect_to_own_rfc: Herzlichen Glückwunsch! Sie haben die maximale Punktzahl für diese Aufgabe an den Kurs übertragen. Ihre Frage ist damit wahrscheinlich gelöst? Falls ja, fügen Sie doch den entscheidenden Kniff als Antwort hinzu und markieren die Frage als gelöst, bevor sie das Fenster schließen.
study_group_dashboard:

View File

@ -431,6 +431,7 @@ en:
finishing_rate: Finishing Rate
submit:
failure: An error occurred while transmitting your score. Please try again later.
too_late: Your submission was saved successfully but was received after the deadline passed.
full_score_redirect_to_rfc: Congratulations! You achieved and submitted the highest possible score for this exercise. Another participant has a question concerning the exercise you just solved. Your help and comments will be greatly appreciated!
full_score_redirect_to_own_rfc: Congratulations! You achieved and submitted the highest possible score for this exercise. Your question concerning the exercise is solved? If so, please share the essential insight with your fellows and mark the question as solved, before you close this window!
study_group_dashboard:

View File

@ -452,7 +452,8 @@ class DockerClient
output = nil
Timeout.timeout(@execution_environment.permitted_execution_time.to_i) do
# TODO: check phusion doku again if we need -i -t options here
output = container.exec(['bash', '-c', command])
# https://stackoverflow.com/questions/363223/how-do-i-get-both-stdout-and-stderr-to-go-to-the-terminal-and-a-log-file
output = container.exec(['bash', '-c', "#{command} 1> >(tee -a /tmp/stdout.log) 2> >(tee -a /tmp/stderr.log >&2); rm -f /tmp/std*.log"], tty: false)
end
Rails.logger.debug 'output from container.exec'
Rails.logger.debug output
@ -467,8 +468,10 @@ class DockerClient
result
rescue Timeout::Error
Rails.logger.info('got timeout error for container ' + container.to_s)
stdout = container.exec(['cat', '/tmp/stdout.log'])[0].join.force_encoding('utf-8')
stderr = container.exec(['cat', '/tmp/stderr.log'])[0].join.force_encoding('utf-8')
kill_container(container)
{status: :timeout}
{status: :timeout, stdout: stdout, stderr: stderr}
end
private :send_command

View File

@ -11,6 +11,7 @@
"highlight.js": "^10.0.3",
"jquery": "^3.5.1",
"jquery-ui": "^1.12.1",
"jquery-ujs": "^1.2.2",
"jstree": "^3.3.9",
"opensans-webkit": "^1.1.0",
"popper.js": "^1.16.1",

View File

@ -105,7 +105,8 @@ describe Lti do
context 'with an invalid score' do
it 'raises an exception' do
expect { controller.send(:send_score, submission.exercise_id, Lti::MAXIMUM_SCORE * 2, submission.user_id) }.to raise_error(Lti::Error)
allow(submission).to receive(:normalized_score).and_return Lti::MAXIMUM_SCORE * 2
expect { controller.send(:send_score, submission) }.to raise_error(Lti::Error)
end
end
@ -118,7 +119,8 @@ describe Lti do
context 'when grading is not supported' do
it 'returns a corresponding status' do
expect_any_instance_of(IMS::LTI::ToolProvider).to receive(:outcome_service?).and_return(false)
expect(controller.send(:send_score, submission.exercise_id, score, submission.user_id)[:status]).to eq('unsupported')
allow(submission).to receive(:normalized_score).and_return score
expect(controller.send(:send_score, submission)[:status]).to eq('unsupported')
end
end
@ -135,11 +137,13 @@ describe Lti do
end
it 'sends the score' do
controller.send(:send_score, submission.exercise_id, score, submission.user_id)
allow(submission).to receive(:normalized_score).and_return score
controller.send(:send_score, submission)
end
it 'returns code, message, and status' do
result = controller.send(:send_score, submission.exercise_id, score, submission.user_id)
allow(submission).to receive(:normalized_score).and_return score
result = controller.send(:send_score, submission)
expect(result[:code]).to eq(response.response_code)
expect(result[:message]).to eq(response.body)
expect(result[:status]).to eq(response.code_major)
@ -149,7 +153,8 @@ describe Lti do
context 'without a tool consumer' do
it 'returns a corresponding status' do
expect(controller.send(:send_score, submission.exercise_id, score, submission.user_id)[:status]).to eq('error')
allow(submission).to receive(:normalized_score).and_return score
expect(controller.send(:send_score, submission)[:status]).to eq('error')
end
end
end

View File

@ -369,7 +369,10 @@ describe DockerClient, docker: true do
end
context 'when a timeout occurs' do
before(:each) { expect(container).to receive(:exec).and_raise(Timeout::Error) }
before(:each) do
expect(container).to receive(:exec).once.and_raise(Timeout::Error)
expect(container).to receive(:exec).twice.and_return([ [], [] ])
end
it 'destroys the container asynchronously' do
pending("Container is destroyed, but not as expected in this test. ToDo update this test.")

View File

@ -1720,9 +1720,9 @@ caniuse-api@^3.0.0:
lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001039, caniuse-lite@^1.0.30001043:
version "1.0.30001053"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001053.tgz#b7ae027567ce2665b965b0437e4512b296ccd20d"
integrity sha512-HtV4wwIZl6GA4Oznse8aR274XUOYGZnQLcf/P8vHgmlfqSNelwD+id8CyHOceqLqt9yfKmo7DUZTh1EuS9pukg==
version "1.0.30001055"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001055.tgz#7b52c3537f7a8c0408aca867e83d2b04268b54cd"
integrity sha512-MbwsBmKrBSKIWldfdIagO5OJWZclpJtS4h0Jrk/4HFrXJxTdVdH23Fd+xCiHriVGvYcWyW8mR/CPsYajlH8Iuw==
case-sensitive-paths-webpack-plugin@^2.3.0:
version "2.3.0"
@ -2804,9 +2804,9 @@ ee-first@1.1.1:
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
electron-to-chromium@^1.3.413:
version "1.3.430"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.430.tgz#33914f7c2db771bdcf30977bd4fd6258ee8a2f37"
integrity sha512-HMDYkANGhx6vfbqpOf/hc6hWEmiOipOHGDeRDeUb3HLD3XIWpvKQxFgWf0tgHcr3aNv6I/8VPecplqmQsXoZSw==
version "1.3.434"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.434.tgz#a67dcb268e93768e2169399999ccffa4783f048e"
integrity sha512-WjzGrE6appXvMyc2kH9Ide7OxsgTuRzag9sjQ5AcbOnbS9ut7P1HzOeEbJFLhr81IR7n2Hlr6qTTSGTXLIX5Pg==
elliptic@^6.0.0, elliptic@^6.5.2:
version "6.5.2"
@ -2872,9 +2872,9 @@ enhanced-resolve@^4.1.0:
tapable "^1.0.0"
entities@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4"
integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==
version "2.0.2"
resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.2.tgz#ac74db0bba8d33808bbf36809c3a5c3683531436"
integrity sha512-dmD3AvJQBUjKpcNkoqr+x+IF0SdRtPz9Vk0uTy4yWqga9ibB6s4v++QFWNohjiUGoMlF552ZvNyXDxz5iW0qmw==
errno@^0.1.3, errno@~0.1.7:
version "0.1.7"
@ -2962,9 +2962,9 @@ etag@~1.8.1:
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
eventemitter3@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.0.tgz#d65176163887ee59f386d64c82610b696a4a74eb"
integrity sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==
version "4.0.4"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384"
integrity sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==
events@^3.0.0:
version "3.1.0"
@ -4172,7 +4172,14 @@ jquery-ui@^1.12.1:
resolved "https://registry.yarnpkg.com/jquery-ui/-/jquery-ui-1.12.1.tgz#bcb4045c8dd0539c134bc1488cdd3e768a7a9e51"
integrity sha1-vLQEXI3QU5wTS8FIjN0+dop6nlE=
jquery@>=1.9.1, jquery@^3.5.1:
jquery-ujs@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/jquery-ujs/-/jquery-ujs-1.2.2.tgz#6a8ef1020e6b6dda385b90a4bddc128c21c56397"
integrity sha1-ao7xAg5rbdo4W5CkvdwSjCHFY5c=
dependencies:
jquery ">=1.8.0"
jquery@>=1.8.0, jquery@>=1.9.1, jquery@^3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.5.1.tgz#d7b4d08e1bfdb86ad2f1a3d039ea17304717abb5"
integrity sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg==
@ -6093,9 +6100,9 @@ postcss-values-parser@^2.0.0, postcss-values-parser@^2.0.1:
uniq "^1.0.1"
postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.5, postcss@^7.0.6:
version "7.0.29"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.29.tgz#d3a903872bd52280b83bce38cdc83ce55c06129e"
integrity sha512-ba0ApvR3LxGvRMMiUa9n0WR4HjzcYm7tS+ht4/2Nd0NLtHpPIH77fuB9Xh1/yJVz9O/E/95Y/dn8ygWsyffXtw==
version "7.0.30"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.30.tgz#cc9378beffe46a02cbc4506a0477d05fcea9a8e2"
integrity sha512-nu/0m+NtIzoubO+xdAlwZl/u5S5vi/y6BCsoL8D+8IxsD3XvBS8X4YEADNIVXKVuQvduiucnRv+vPIqj56EGMQ==
dependencies:
chalk "^2.4.2"
source-map "^0.6.1"
@ -6557,9 +6564,9 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
safe-regex@^1.1.0:
version "1.1.0"