Send score for all members of a programming group

This commit is contained in:
kiragrammel
2023-08-10 17:11:15 +02:00
committed by Sebastian Serth
parent 2fb8def1d0
commit e2baa2ee55
12 changed files with 77 additions and 47 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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}%. " \

View File

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

View File

@ -1,6 +1,6 @@
h1 = t('.headline')
- consumer = @submission.user.consumer
- consumer = current_user.consumer
p
= t(".success_#{consumer ? 'with' : 'without'}_outcome", consumer: consumer)