Send score for all members of a programming group
This commit is contained in:

committed by
Sebastian Serth

parent
2fb8def1d0
commit
e2baa2ee55
@ -211,10 +211,13 @@ CodeOceanEditorSubmissions = {
|
||||
Turbolinks.visit(response.redirect);
|
||||
} else if (response.status === 'container_depleted') {
|
||||
this.showContainerDepletedMessage();
|
||||
} else if (response.message) {
|
||||
$.flash.danger({
|
||||
text: response.message
|
||||
});
|
||||
} else {
|
||||
for (let [type, text] of Object.entries(response)) {
|
||||
$.flash[type]({
|
||||
text: text,
|
||||
showPermanent: true // We might display a very long text message!
|
||||
})
|
||||
}
|
||||
}
|
||||
this.initializeEventHandlers();
|
||||
})
|
||||
|
@ -18,6 +18,7 @@ class ApplicationController < ActionController::Base
|
||||
rescue_from Pundit::NotAuthorizedError, with: :render_not_authorized
|
||||
rescue_from ActiveRecord::RecordNotFound, with: :render_not_found
|
||||
rescue_from ActionController::InvalidAuthenticityToken, with: :render_csrf_error
|
||||
add_flash_types :danger, :warning, :info, :success
|
||||
|
||||
def current_user
|
||||
@current_user ||= find_or_login_current_user&.store_current_study_group_id(session[:study_group_id])
|
||||
|
@ -9,6 +9,7 @@ module Lti
|
||||
MAXIMUM_SCORE = 1
|
||||
MAXIMUM_SESSION_AGE = 60.minutes
|
||||
SESSION_PARAMETERS = %w[launch_presentation_return_url lis_outcome_service_url lis_result_sourcedid].freeze
|
||||
ERROR_STATUS = %w[error unsupported].freeze
|
||||
|
||||
def build_tool_provider(options = {})
|
||||
if options[:consumer] && options[:parameters]
|
||||
@ -135,21 +136,27 @@ module Lti
|
||||
|
||||
private :return_to_consumer
|
||||
|
||||
def send_score(submission)
|
||||
def send_scores(submission)
|
||||
unless (0..MAXIMUM_SCORE).cover?(submission.normalized_score)
|
||||
raise Error.new("Score #{submission.normalized_score} must be between 0 and #{MAXIMUM_SCORE}!")
|
||||
end
|
||||
|
||||
if submission.contributor.consumer
|
||||
lti_parameter = LtiParameter.where(consumers_id: submission.contributor.consumer.id,
|
||||
external_users_id: submission.contributor_id,
|
||||
submission.users.map {|user| send_score_for submission, user }
|
||||
end
|
||||
|
||||
private :send_scores
|
||||
|
||||
def send_score_for(submission, user)
|
||||
if user.consumer
|
||||
lti_parameter = LtiParameter.where(consumers_id: user.consumer.id,
|
||||
external_users_id: user.id,
|
||||
exercises_id: submission.exercise_id).last
|
||||
|
||||
provider = build_tool_provider(consumer: submission.contributor.consumer, parameters: lti_parameter.lti_parameters)
|
||||
provider = build_tool_provider(consumer: user.consumer, parameters: lti_parameter&.lti_parameters)
|
||||
end
|
||||
|
||||
if provider.nil?
|
||||
{status: 'error'}
|
||||
{status: 'error', user: user.displayname}
|
||||
elsif provider.outcome_service?
|
||||
Sentry.set_extras({
|
||||
provider: provider.inspect,
|
||||
@ -171,17 +178,17 @@ module Lti
|
||||
|
||||
begin
|
||||
response = provider.post_replace_result!(normalized_lti_score)
|
||||
{code: response.response_code, message: response.post_response.body, status: response.code_major, score_sent: normalized_lti_score}
|
||||
{code: response.response_code, message: response.post_response.body, status: response.code_major, score_sent: normalized_lti_score, user: user.displayname}
|
||||
rescue IMS::LTI::XMLParseError, Net::OpenTimeout, Net::ReadTimeout, Errno::ECONNRESET, SocketError
|
||||
# A parsing error might happen if the LTI provider is down and doesn't return a valid XML response
|
||||
{status: 'error'}
|
||||
{status: 'error', user: user.displayname}
|
||||
end
|
||||
else
|
||||
{status: 'unsupported'}
|
||||
{status: 'unsupported', user: user.displayname}
|
||||
end
|
||||
end
|
||||
|
||||
private :send_score
|
||||
private :send_score_for
|
||||
|
||||
def set_current_user
|
||||
@current_user = ExternalUser.find_or_create_by(consumer_id: @consumer.id, external_id: @provider.user_id)
|
||||
|
@ -536,25 +536,39 @@ class ExercisesController < ApplicationController
|
||||
Rails.logger.debug { "Runner error while submitting submission #{@submission.id}: #{e.message}" }
|
||||
respond_to do |format|
|
||||
format.html { redirect_to(implement_exercise_path(@submission.exercise)) }
|
||||
format.json { render(json: {message: I18n.t('exercises.editor.depleted'), status: :container_depleted}) }
|
||||
format.json { render(json: {danger: I18n.t('exercises.editor.depleted'), status: :container_depleted}) }
|
||||
end
|
||||
end
|
||||
|
||||
def transmit_lti_score
|
||||
response = send_score(@submission)
|
||||
responses = send_scores(@submission)
|
||||
messages = {}
|
||||
failed_users = []
|
||||
|
||||
if response[:status] == 'success'
|
||||
if response[:score_sent] != @submission.normalized_score
|
||||
# Score has been reduced due to the passed deadline
|
||||
flash.now[:warning] = I18n.t('exercises.submit.too_late')
|
||||
flash.keep(:warning)
|
||||
responses.each do |response|
|
||||
if Lti::ERROR_STATUS.include? response[:status]
|
||||
failed_users << response[:user]
|
||||
elsif response[:score_sent] != @submission.normalized_score # the score was sent successfully, but received too late
|
||||
messages[:warning] = I18n.t('exercises.submit.too_late')
|
||||
end
|
||||
redirect_after_submit
|
||||
end
|
||||
|
||||
if failed_users.size == responses.size # all submissions failed
|
||||
messages[:danger] = I18n.t('exercises.submit.failure')
|
||||
elsif failed_users.size.positive? # at least one submission failed
|
||||
messages[:warning] = [[messages[:warning]], I18n.t('exercises.submit.warning_not_for_all_users_submitted', user: failed_users.join(', '))].join('<br><br>')
|
||||
messages[:warning] = "#{messages[:warning]}\n\n#{I18n.t('exercises.submit.warning_not_for_all_users_submitted', user: failed_users.join(', '))}".strip
|
||||
else
|
||||
respond_to do |format|
|
||||
format.html { redirect_to(implement_exercise_path(@submission.exercise, alert: I18n.t('exercises.submit.failure'))) }
|
||||
format.json { render(json: {message: I18n.t('exercises.submit.failure')}, status: :service_unavailable) }
|
||||
messages.each do |type, message_text|
|
||||
flash.now[type] = message_text
|
||||
flash.keep(type)
|
||||
end
|
||||
return redirect_after_submit
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to(implement_exercise_path(@submission.exercise), **messages) }
|
||||
format.json { render(json: messages) } # We must not change the HTTP status code here, since otherwise the custom message is not displayed.
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -36,8 +36,8 @@ class RemoteEvaluationController < ApplicationController
|
||||
def try_lti
|
||||
# TODO: Need to consider and support programming groups
|
||||
if !@submission.user.nil? && lti_outcome_service?(@submission.exercise_id, @submission.user.id)
|
||||
lti_response = send_score(@submission)
|
||||
process_lti_response(lti_response)
|
||||
lti_responses = send_scores(@submission)
|
||||
process_lti_response(lti_responses.first)
|
||||
else
|
||||
{
|
||||
message: "Your submission was successfully scored with #{@submission.normalized_score * 100}%. " \
|
||||
|
@ -72,11 +72,11 @@ class Submission < ApplicationRecord
|
||||
end
|
||||
|
||||
def normalized_score
|
||||
if !score.nil? && !exercise.maximum_score.nil? && exercise.maximum_score.positive?
|
||||
score / exercise.maximum_score
|
||||
else
|
||||
0
|
||||
end
|
||||
@normalized_score ||= if !score.nil? && !exercise.maximum_score.nil? && exercise.maximum_score.positive?
|
||||
score / exercise.maximum_score
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
def percentage
|
||||
|
@ -1,6 +1,6 @@
|
||||
h1 = t('.headline')
|
||||
|
||||
- consumer = @submission.user.consumer
|
||||
- consumer = current_user.consumer
|
||||
|
||||
p
|
||||
= t(".success_#{consumer ? 'with' : 'without'}_outcome", consumer: consumer)
|
||||
|
@ -550,6 +550,7 @@ de:
|
||||
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.
|
||||
warning_not_for_all_users_submitted: "Die Punkteübertragung war nur teilweise erfolgreich. Die Punkte konnten nicht für %{user} übertragen werden. Diese Person(en) sollte die Aufgabe über die e-Learning Platform erneut öffnen und anschließend die Punkte selbst übermitteln."
|
||||
study_group_dashboard:
|
||||
live_dashboard: Live Dashboard
|
||||
time_spent_per_learner: Verwendete Zeit pro Lerner
|
||||
|
@ -550,6 +550,7 @@ en:
|
||||
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!
|
||||
warning_not_for_all_users_submitted: "The transmission of points was only partially successful. The score was not transmitted for %{user}. The user(s) should reopen the exercise via the e-learning platform and then try to submit the points themselves."
|
||||
study_group_dashboard:
|
||||
live_dashboard: Live Dashboard
|
||||
time_spent_per_learner: Time spent per Learner
|
||||
|
@ -102,7 +102,7 @@ describe Lti do
|
||||
end
|
||||
end
|
||||
|
||||
describe '#send_score' do
|
||||
describe '#send_scores' do
|
||||
let(:consumer) { create(:consumer) }
|
||||
let(:score) { 0.5 }
|
||||
let(:submission) { create(:submission) }
|
||||
@ -114,7 +114,7 @@ describe Lti do
|
||||
context 'with an invalid score' do
|
||||
it 'raises an exception' do
|
||||
allow(submission).to receive(:normalized_score).and_return Lti::MAXIMUM_SCORE * 2
|
||||
expect { controller.send(:send_score, submission) }.to raise_error(Lti::Error)
|
||||
expect { controller.send(:send_scores, submission) }.to raise_error(Lti::Error)
|
||||
end
|
||||
end
|
||||
|
||||
@ -124,13 +124,13 @@ describe Lti do
|
||||
it 'returns a corresponding status' do
|
||||
allow_any_instance_of(IMS::LTI::ToolProvider).to receive(:outcome_service?).and_return(false)
|
||||
allow(submission).to receive(:normalized_score).and_return score
|
||||
expect(controller.send(:send_score, submission)[:status]).to eq('unsupported')
|
||||
expect(controller.send(:send_scores, submission).first[:status]).to eq('unsupported')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when grading is supported' do
|
||||
let(:response) { double }
|
||||
let(:send_score) { controller.send(:send_score, submission) }
|
||||
let(:send_scores) { controller.send(:send_scores, submission).first }
|
||||
|
||||
before do
|
||||
allow_any_instance_of(IMS::LTI::ToolProvider).to receive(:outcome_service?).and_return(true)
|
||||
@ -144,13 +144,13 @@ describe Lti do
|
||||
|
||||
it 'sends the score' do
|
||||
expect_any_instance_of(IMS::LTI::ToolProvider).to receive(:post_replace_result!).with(score)
|
||||
send_score
|
||||
send_scores
|
||||
end
|
||||
|
||||
it 'returns code, message, and status' do
|
||||
expect(send_score[:code]).to eq(response.response_code)
|
||||
expect(send_score[:message]).to eq(response.body)
|
||||
expect(send_score[:status]).to eq(response.code_major)
|
||||
expect(send_scores[:code]).to eq(response.response_code)
|
||||
expect(send_scores[:message]).to eq(response.body)
|
||||
expect(send_scores[:status]).to eq(response.code_major)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -160,7 +160,7 @@ describe Lti do
|
||||
submission.contributor.consumer = nil
|
||||
|
||||
allow(submission).to receive(:normalized_score).and_return score
|
||||
expect(controller.send(:send_score, submission)[:status]).to eq('error')
|
||||
expect(controller.send(:send_scores, submission).first[:status]).to eq('error')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -325,7 +325,7 @@ describe ExercisesController do
|
||||
|
||||
context 'when the score transmission succeeds' do
|
||||
before do
|
||||
allow(controller).to receive(:send_score).and_return(status: 'success')
|
||||
allow(controller).to receive(:send_scores).and_return([{status: 'success'}])
|
||||
perform_request
|
||||
end
|
||||
|
||||
@ -341,7 +341,7 @@ describe ExercisesController do
|
||||
|
||||
context 'when the score transmission fails' do
|
||||
before do
|
||||
allow(controller).to receive(:send_score).and_return(status: 'unsupported')
|
||||
allow(controller).to receive(:send_scores).and_return([{status: 'unsupported'}])
|
||||
perform_request
|
||||
end
|
||||
|
||||
@ -351,8 +351,11 @@ describe ExercisesController do
|
||||
expect(assigns(:submission)).to be_a(Submission)
|
||||
end
|
||||
|
||||
it 'returns an error message' do
|
||||
expect(response.parsed_body).to eq('danger' => I18n.t('exercises.submit.failure'))
|
||||
end
|
||||
|
||||
expect_json
|
||||
expect_http_status(:service_unavailable)
|
||||
end
|
||||
end
|
||||
|
||||
@ -369,7 +372,7 @@ describe ExercisesController do
|
||||
end
|
||||
|
||||
it 'does not send scores' do
|
||||
expect(controller).not_to receive(:send_score)
|
||||
expect(controller).not_to receive(:send_scores)
|
||||
end
|
||||
|
||||
expect_json
|
||||
|
@ -91,7 +91,7 @@ describe Exercise do
|
||||
|
||||
context 'without submissions' do
|
||||
it 'returns nil' do
|
||||
expect(exercise.average_score).to be 0
|
||||
expect(exercise.average_score).to be 0.0
|
||||
end
|
||||
end
|
||||
|
||||
|
Reference in New Issue
Block a user