Rename user to contributor in submission
This commit is contained in:

committed by
Sebastian Serth

parent
97138288f4
commit
0234414bae
@ -919,10 +919,6 @@ var CodeOceanEditor = {
|
|||||||
const delta = 100; // time in ms to wait for window event before time gets stopped
|
const delta = 100; // time in ms to wait for window event before time gets stopped
|
||||||
let tid;
|
let tid;
|
||||||
$.ajax({
|
$.ajax({
|
||||||
data: {
|
|
||||||
exercise_id: editor.data('exercise-id'),
|
|
||||||
user_id: editor.data('user-id')
|
|
||||||
},
|
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
// get working times for this exercise
|
// get working times for this exercise
|
||||||
|
@ -17,7 +17,7 @@ module FileParameters
|
|||||||
# avoid that public files from other contexts can be created
|
# avoid that public files from other contexts can be created
|
||||||
# `next` is similar to an early return and will proceed with the next iteration of the loop
|
# `next` is similar to an early return and will proceed with the next iteration of the loop
|
||||||
next true if file.context_type == 'Exercise' && file.context_id != exercise.id
|
next true if file.context_type == 'Exercise' && file.context_id != exercise.id
|
||||||
next true if file.context_type == 'Submission' && (file.context.user_id != current_user.id || file.context.user_type != current_user.class.name)
|
next true if file.context_type == 'Submission' && (file.context.contributor_id != current_user.id || file.context.contributor_type != current_user.class.name)
|
||||||
next true if file.context_type == 'CommunitySolution' && controller_name != 'community_solutions'
|
next true if file.context_type == 'CommunitySolution' && controller_name != 'community_solutions'
|
||||||
|
|
||||||
# Optimization: We already queried the ancestor file, let's reuse the object.
|
# Optimization: We already queried the ancestor file, let's reuse the object.
|
||||||
|
@ -141,12 +141,12 @@ module Lti
|
|||||||
raise Error.new("Score #{submission.normalized_score} must be between 0 and #{MAXIMUM_SCORE}!")
|
raise Error.new("Score #{submission.normalized_score} must be between 0 and #{MAXIMUM_SCORE}!")
|
||||||
end
|
end
|
||||||
|
|
||||||
if submission.user.consumer
|
if submission.contributor.consumer
|
||||||
lti_parameter = LtiParameter.where(consumers_id: submission.user.consumer.id,
|
lti_parameter = LtiParameter.where(consumers_id: submission.contributor.consumer.id,
|
||||||
external_users_id: submission.user_id,
|
external_users_id: submission.contributor_id,
|
||||||
exercises_id: submission.exercise_id).last
|
exercises_id: submission.exercise_id).last
|
||||||
|
|
||||||
provider = build_tool_provider(consumer: submission.user.consumer, parameters: lti_parameter.lti_parameters)
|
provider = build_tool_provider(consumer: submission.contributor.consumer, parameters: lti_parameter.lti_parameters)
|
||||||
end
|
end
|
||||||
|
|
||||||
if provider.nil?
|
if provider.nil?
|
||||||
|
@ -16,7 +16,7 @@ module RedirectBehavior
|
|||||||
# redirect 10 percent pseudorandomly to the feedback page
|
# redirect 10 percent pseudorandomly to the feedback page
|
||||||
if current_user.respond_to? :external_id
|
if current_user.respond_to? :external_id
|
||||||
if @submission.redirect_to_feedback? && !@embed_options[:disable_redirect_to_feedback]
|
if @submission.redirect_to_feedback? && !@embed_options[:disable_redirect_to_feedback]
|
||||||
clear_lti_session_data(@submission.exercise_id, @submission.user_id)
|
clear_lti_session_data(@submission.exercise_id)
|
||||||
redirect_to_user_feedback
|
redirect_to_user_feedback
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@ -27,7 +27,7 @@ module RedirectBehavior
|
|||||||
flash[:notice] = I18n.t('exercises.submit.full_score_redirect_to_own_rfc')
|
flash[:notice] = I18n.t('exercises.submit.full_score_redirect_to_own_rfc')
|
||||||
flash.keep(:notice)
|
flash.keep(:notice)
|
||||||
|
|
||||||
clear_lti_session_data(@submission.exercise_id, @submission.user_id)
|
clear_lti_session_data(@submission.exercise_id)
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html { redirect_to(rfc) }
|
format.html { redirect_to(rfc) }
|
||||||
format.json { render(json: {redirect: url_for(rfc)}) }
|
format.json { render(json: {redirect: url_for(rfc)}) }
|
||||||
@ -45,7 +45,7 @@ module RedirectBehavior
|
|||||||
# increase counter 'times_featured' in rfc
|
# increase counter 'times_featured' in rfc
|
||||||
rfc.increment(:times_featured)
|
rfc.increment(:times_featured)
|
||||||
|
|
||||||
clear_lti_session_data(@submission.exercise_id, @submission.user_id)
|
clear_lti_session_data(@submission.exercise_id)
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html { redirect_to(rfc) }
|
format.html { redirect_to(rfc) }
|
||||||
format.json { render(json: {redirect: url_for(rfc)}) }
|
format.json { render(json: {redirect: url_for(rfc)}) }
|
||||||
@ -56,7 +56,7 @@ module RedirectBehavior
|
|||||||
else
|
else
|
||||||
# redirect to feedback page if score is less than 100 percent
|
# redirect to feedback page if score is less than 100 percent
|
||||||
if @exercise.needs_more_feedback?(@submission) && !@embed_options[:disable_redirect_to_feedback]
|
if @exercise.needs_more_feedback?(@submission) && !@embed_options[:disable_redirect_to_feedback]
|
||||||
clear_lti_session_data(@submission.exercise_id, @submission.user_id)
|
clear_lti_session_data(@submission.exercise_id)
|
||||||
redirect_to_user_feedback
|
redirect_to_user_feedback
|
||||||
else
|
else
|
||||||
redirect_to_lti_return_path
|
redirect_to_lti_return_path
|
||||||
@ -118,8 +118,8 @@ module RedirectBehavior
|
|||||||
|
|
||||||
def redirect_to_lti_return_path
|
def redirect_to_lti_return_path
|
||||||
Sentry.set_extras(
|
Sentry.set_extras(
|
||||||
consumers_id: @submission.user&.consumer,
|
consumers_id: current_user.consumer_id,
|
||||||
external_users_id: @submission.user_id,
|
external_users_id: current_user.id,
|
||||||
exercises_id: @submission.exercise_id,
|
exercises_id: @submission.exercise_id,
|
||||||
session: session.to_hash,
|
session: session.to_hash,
|
||||||
submission: @submission.inspect,
|
submission: @submission.inspect,
|
||||||
@ -128,7 +128,7 @@ module RedirectBehavior
|
|||||||
)
|
)
|
||||||
|
|
||||||
path = lti_return_path(submission_id: @submission.id)
|
path = lti_return_path(submission_id: @submission.id)
|
||||||
clear_lti_session_data(@submission.exercise_id, @submission.user_id)
|
clear_lti_session_data(@submission.exercise_id)
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html { redirect_to(path) }
|
format.html { redirect_to(path) }
|
||||||
format.json { render(json: {redirect: path}) }
|
format.json { render(json: {redirect: path}) }
|
||||||
|
@ -22,7 +22,8 @@ module SubmissionParameters
|
|||||||
def merge_user(params)
|
def merge_user(params)
|
||||||
# The study_group_id might not be present in the session (e.g. for internal users), resulting in session[:study_group_id] = nil which is intended.
|
# The study_group_id might not be present in the session (e.g. for internal users), resulting in session[:study_group_id] = nil which is intended.
|
||||||
params.merge(
|
params.merge(
|
||||||
user: current_user,
|
contributor_id: current_user.id,
|
||||||
|
contributor_type: current_user.class.name,
|
||||||
study_group_id: current_user.current_study_group_id
|
study_group_id: current_user.current_study_group_id
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
@ -55,32 +55,32 @@ class ExecutionEnvironmentsController < ApplicationController
|
|||||||
SELECT exercise_id, avg(working_time) as average_time, stddev_samp(extract('epoch' from working_time)) * interval '1 second' as stddev_time
|
SELECT exercise_id, avg(working_time) as average_time, stddev_samp(extract('epoch' from working_time)) * interval '1 second' as stddev_time
|
||||||
FROM
|
FROM
|
||||||
(
|
(
|
||||||
SELECT user_id,
|
SELECT contributor_id,
|
||||||
exercise_id,
|
exercise_id,
|
||||||
sum(working_time_new) AS working_time
|
sum(working_time_new) AS working_time
|
||||||
FROM
|
FROM
|
||||||
(SELECT user_id,
|
(SELECT contributor_id,
|
||||||
exercise_id,
|
exercise_id,
|
||||||
CASE WHEN #{StatisticsHelper.working_time_larger_delta} THEN '0' ELSE working_time END AS working_time_new
|
CASE WHEN #{StatisticsHelper.working_time_larger_delta} THEN '0' ELSE working_time END AS working_time_new
|
||||||
FROM
|
FROM
|
||||||
(SELECT user_id,
|
(SELECT contributor_id,
|
||||||
exercise_id,
|
exercise_id,
|
||||||
id,
|
id,
|
||||||
(created_at - lag(created_at) over (PARTITION BY user_id, exercise_id
|
(created_at - lag(created_at) over (PARTITION BY contributor_id, exercise_id
|
||||||
ORDER BY created_at)) AS working_time
|
ORDER BY created_at)) AS working_time
|
||||||
FROM submissions
|
FROM submissions
|
||||||
WHERE exercise_id IN (SELECT ID FROM exercises WHERE #{ExecutionEnvironment.sanitize_sql(['execution_environment_id = ?', @execution_environment.id])})
|
WHERE exercise_id IN (SELECT ID FROM exercises WHERE #{ExecutionEnvironment.sanitize_sql(['execution_environment_id = ?', @execution_environment.id])})
|
||||||
GROUP BY exercise_id, user_id, id) AS foo) AS bar
|
GROUP BY exercise_id, contributor_id, id) AS foo) AS bar
|
||||||
GROUP BY user_id, exercise_id
|
GROUP BY contributor_id, exercise_id
|
||||||
) AS baz GROUP BY exercise_id;
|
) AS baz GROUP BY exercise_id;
|
||||||
"
|
"
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_query
|
def contributor_query
|
||||||
"
|
"
|
||||||
SELECT
|
SELECT
|
||||||
id AS exercise_id,
|
id AS exercise_id,
|
||||||
COUNT(DISTINCT user_id) AS users,
|
COUNT(DISTINCT contributor_id) AS contributors,
|
||||||
AVG(score) AS average_score,
|
AVG(score) AS average_score,
|
||||||
MAX(score) AS maximum_score,
|
MAX(score) AS maximum_score,
|
||||||
stddev_samp(score) as stddev_score,
|
stddev_samp(score) as stddev_score,
|
||||||
@ -88,24 +88,24 @@ class ExecutionEnvironmentsController < ApplicationController
|
|||||||
WHEN MAX(score)=0 THEN 0
|
WHEN MAX(score)=0 THEN 0
|
||||||
ELSE 100 / MAX(score) * AVG(score)
|
ELSE 100 / MAX(score) * AVG(score)
|
||||||
END AS percent_correct,
|
END AS percent_correct,
|
||||||
SUM(submission_count) / COUNT(DISTINCT user_id) AS average_submission_count
|
SUM(submission_count) / COUNT(DISTINCT contributor_id) AS average_submission_count
|
||||||
FROM
|
FROM
|
||||||
(SELECT e.id,
|
(SELECT e.id,
|
||||||
s.user_id,
|
s.contributor_id,
|
||||||
MAX(s.score) AS score,
|
MAX(s.score) AS score,
|
||||||
COUNT(s.id) AS submission_count
|
COUNT(s.id) AS submission_count
|
||||||
FROM submissions s
|
FROM submissions s
|
||||||
JOIN exercises e ON e.id = s.exercise_id
|
JOIN exercises e ON e.id = s.exercise_id
|
||||||
WHERE #{ExecutionEnvironment.sanitize_sql(['e.execution_environment_id = ?', @execution_environment.id])}
|
WHERE #{ExecutionEnvironment.sanitize_sql(['e.execution_environment_id = ?', @execution_environment.id])}
|
||||||
GROUP BY e.id,
|
GROUP BY e.id,
|
||||||
s.user_id) AS inner_query
|
s.contributor_id) AS inner_query
|
||||||
GROUP BY id;
|
GROUP BY id;
|
||||||
"
|
"
|
||||||
end
|
end
|
||||||
|
|
||||||
def statistics
|
def statistics
|
||||||
working_time_statistics = {}
|
working_time_statistics = {}
|
||||||
user_statistics = {}
|
contributor_statistics = {}
|
||||||
|
|
||||||
ApplicationRecord.connection.exec_query(working_time_query).each do |tuple|
|
ApplicationRecord.connection.exec_query(working_time_query).each do |tuple|
|
||||||
tuple = tuple.merge({
|
tuple = tuple.merge({
|
||||||
@ -115,13 +115,13 @@ class ExecutionEnvironmentsController < ApplicationController
|
|||||||
working_time_statistics[tuple['exercise_id'].to_i] = tuple
|
working_time_statistics[tuple['exercise_id'].to_i] = tuple
|
||||||
end
|
end
|
||||||
|
|
||||||
ApplicationRecord.connection.exec_query(user_query).each do |tuple|
|
ApplicationRecord.connection.exec_query(contributor_query).each do |tuple|
|
||||||
user_statistics[tuple['exercise_id'].to_i] = tuple
|
contributor_statistics[tuple['exercise_id'].to_i] = tuple
|
||||||
end
|
end
|
||||||
|
|
||||||
render locals: {
|
render locals: {
|
||||||
working_time_statistics:,
|
working_time_statistics:,
|
||||||
user_statistics:,
|
contributor_statistics:,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -467,9 +467,9 @@ class ExercisesController < ApplicationController
|
|||||||
# Show general statistic page for specific exercise
|
# Show general statistic page for specific exercise
|
||||||
user_statistics = {'InternalUser' => {}, 'ExternalUser' => {}}
|
user_statistics = {'InternalUser' => {}, 'ExternalUser' => {}}
|
||||||
|
|
||||||
query = Submission.select('user_id, user_type, MAX(score) AS maximum_score, COUNT(id) AS runs')
|
query = Submission.select('contributor_id, contributor_type, MAX(score) AS maximum_score, COUNT(id) AS runs')
|
||||||
.where(exercise_id: @exercise.id)
|
.where(exercise_id: @exercise.id)
|
||||||
.group('user_id, user_type')
|
.group('contributor_id, contributor_type')
|
||||||
|
|
||||||
query = if policy(@exercise).detailed_statistics?
|
query = if policy(@exercise).detailed_statistics?
|
||||||
query
|
query
|
||||||
@ -481,7 +481,7 @@ class ExercisesController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
query.each do |tuple|
|
query.each do |tuple|
|
||||||
user_statistics[tuple['user_type']][tuple['user_id'].to_i] = tuple
|
user_statistics[tuple['contributor_type']][tuple['contributor_id'].to_i] = tuple
|
||||||
end
|
end
|
||||||
|
|
||||||
render locals: {
|
render locals: {
|
||||||
@ -493,7 +493,7 @@ class ExercisesController < ApplicationController
|
|||||||
# Render statistics page for one specific external user
|
# Render statistics page for one specific external user
|
||||||
|
|
||||||
if policy(@exercise).detailed_statistics?
|
if policy(@exercise).detailed_statistics?
|
||||||
submissions = Submission.where(user: @external_user, exercise: @exercise)
|
submissions = Submission.where(contributor: @external_user, exercise: @exercise)
|
||||||
.in_study_group_of(current_user)
|
.in_study_group_of(current_user)
|
||||||
.order('created_at')
|
.order('created_at')
|
||||||
@show_autosaves = params[:show_autosaves] == 'true' || submissions.none? {|s| s.cause != 'autosave' }
|
@show_autosaves = params[:show_autosaves] == 'true' || submissions.none? {|s| s.cause != 'autosave' }
|
||||||
@ -510,7 +510,7 @@ class ExercisesController < ApplicationController
|
|||||||
@working_times_until.push((format_time_difference(@deltas[0..index].sum) if index.positive?))
|
@working_times_until.push((format_time_difference(@deltas[0..index].sum) if index.positive?))
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
final_submissions = Submission.where(user: @external_user,
|
final_submissions = Submission.where(contributor: @external_user,
|
||||||
exercise_id: @exercise.id).in_study_group_of(current_user).final
|
exercise_id: @exercise.id).in_study_group_of(current_user).final
|
||||||
submissions = []
|
submissions = []
|
||||||
%i[before_deadline within_grace_period after_late_deadline].each do |filter|
|
%i[before_deadline within_grace_period after_late_deadline].each do |filter|
|
||||||
|
@ -5,7 +5,7 @@ class FlowrController < ApplicationController
|
|||||||
require_user!
|
require_user!
|
||||||
# get the latest submission for this user that also has a test run (i.e. structured_errors if applicable)
|
# get the latest submission for this user that also has a test run (i.e. structured_errors if applicable)
|
||||||
submission = Submission.joins(:testruns)
|
submission = Submission.joins(:testruns)
|
||||||
.where(submissions: {user: current_user})
|
.where(submissions: {contributor: current_user})
|
||||||
.includes(structured_errors: [structured_error_attributes: [:error_template_attribute]])
|
.includes(structured_errors: [structured_error_attributes: [:error_template_attribute]])
|
||||||
.merge(Testrun.order(created_at: :desc)).first
|
.merge(Testrun.order(created_at: :desc)).first
|
||||||
|
|
||||||
|
@ -42,10 +42,10 @@ class SessionsController < ApplicationController
|
|||||||
def destroy_through_lti
|
def destroy_through_lti
|
||||||
@submission = Submission.find(params[:submission_id])
|
@submission = Submission.find(params[:submission_id])
|
||||||
authorize(@submission, :show?)
|
authorize(@submission, :show?)
|
||||||
lti_parameter = LtiParameter.where(external_users_id: @submission.user_id, exercises_id: @submission.exercise_id).last
|
lti_parameter = LtiParameter.where(external_users_id: current_user.id, exercises_id: @submission.exercise_id).last
|
||||||
@url = consumer_return_url(build_tool_provider(consumer: @submission.user.consumer, parameters: lti_parameter&.lti_parameters))
|
@url = consumer_return_url(build_tool_provider(consumer: current_user.consumer, parameters: lti_parameter&.lti_parameters))
|
||||||
|
|
||||||
clear_lti_session_data(@submission.exercise_id, @submission.user_id)
|
clear_lti_session_data(@submission.exercise_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
|
@ -22,7 +22,7 @@ class SubmissionsController < ApplicationController
|
|||||||
|
|
||||||
def index
|
def index
|
||||||
@search = Submission.ransack(params[:q])
|
@search = Submission.ransack(params[:q])
|
||||||
@submissions = @search.result.includes(:exercise, :user).paginate(page: params[:page], per_page: per_page_param)
|
@submissions = @search.result.includes(:exercise, :contributor).paginate(page: params[:page], per_page: per_page_param)
|
||||||
authorize!
|
authorize!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -126,7 +126,7 @@ class UserExerciseFeedbacksController < ApplicationController
|
|||||||
user_id = current_user.id
|
user_id = current_user.id
|
||||||
user_type = current_user.class.name
|
user_type = current_user.class.name
|
||||||
latest_submission = Submission
|
latest_submission = Submission
|
||||||
.where(user_id:, user_type:, exercise_id:)
|
.where(contributor_id: user_id, contributor_type: user_type, exercise_id:)
|
||||||
.order(created_at: :desc).final.first
|
.order(created_at: :desc).final.first
|
||||||
|
|
||||||
authorize(latest_submission, :show?)
|
authorize(latest_submission, :show?)
|
||||||
|
@ -43,7 +43,7 @@ module StatisticsHelper
|
|||||||
{
|
{
|
||||||
key: 'currently_active',
|
key: 'currently_active',
|
||||||
name: t('statistics.entries.users.currently_active'),
|
name: t('statistics.entries.users.currently_active'),
|
||||||
data: Submission.where(created_at: 5.minutes.ago.., user_type: ExternalUser.name).distinct.count(:user_id),
|
data: Submission.where(created_at: 5.minutes.ago.., contributor_type: ExternalUser.name).distinct.count(:contributor_id),
|
||||||
url: statistics_graphs_path,
|
url: statistics_graphs_path,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
9
app/models/concerns/contributor.rb
Normal file
9
app/models/concerns/contributor.rb
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Contributor
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
has_many :submissions, as: :contributor
|
||||||
|
end
|
||||||
|
end
|
14
app/models/concerns/contributor_creation.rb
Normal file
14
app/models/concerns/contributor_creation.rb
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module ContributorCreation
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
include Contributor
|
||||||
|
|
||||||
|
included do
|
||||||
|
belongs_to :contributor, polymorphic: true
|
||||||
|
alias_method :user, :contributor
|
||||||
|
alias_method :user=, :contributor=
|
||||||
|
alias_method :author, :user
|
||||||
|
alias_method :creator, :user
|
||||||
|
end
|
||||||
|
end
|
@ -27,8 +27,8 @@ class Exercise < ApplicationRecord
|
|||||||
has_many :exercise_tips
|
has_many :exercise_tips
|
||||||
has_many :tips, through: :exercise_tips
|
has_many :tips, through: :exercise_tips
|
||||||
|
|
||||||
has_many :external_users, source: :user, source_type: 'ExternalUser', through: :submissions
|
has_many :external_users, source: :contributor, source_type: 'ExternalUser', through: :submissions
|
||||||
has_many :internal_users, source: :user, source_type: 'InternalUser', through: :submissions
|
has_many :internal_users, source: :contributor, source_type: 'InternalUser', through: :submissions
|
||||||
alias users external_users
|
alias users external_users
|
||||||
|
|
||||||
scope :with_submissions, -> { where('id IN (SELECT exercise_id FROM submissions)') }
|
scope :with_submissions, -> { where('id IN (SELECT exercise_id FROM submissions)') }
|
||||||
@ -65,12 +65,10 @@ class Exercise < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
def average_score
|
def average_score
|
||||||
if submissions.exists?(cause: 'submit')
|
Submission.from(
|
||||||
maximum_scores_query = submissions.select('MAX(score) AS maximum_score').group(:user_id).to_sql.sub('$1', id.to_s)
|
submissions.group(:contributor_id, :contributor_type)
|
||||||
self.class.connection.exec_query("SELECT AVG(maximum_score) AS average_score FROM (#{maximum_scores_query}) AS maximum_scores").first['average_score'].to_f
|
.select('MAX(score) as max_score')
|
||||||
else
|
).average(:max_score).to_f
|
||||||
0
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def average_number_of_submissions
|
def average_number_of_submissions
|
||||||
@ -78,64 +76,66 @@ class Exercise < ApplicationRecord
|
|||||||
user_count.zero? ? 0 : submissions.count / user_count.to_f
|
user_count.zero? ? 0 : submissions.count / user_count.to_f
|
||||||
end
|
end
|
||||||
|
|
||||||
def time_maximum_score(user)
|
def time_maximum_score(contributor)
|
||||||
submissions.where(user:).where("cause IN ('submit','assess')").where.not(score: nil).order('score DESC, created_at ASC').first.created_at
|
submissions
|
||||||
rescue StandardError
|
.where(contributor:, cause: %w[submit assess])
|
||||||
Time.zone.at(0)
|
.where.not(score: nil)
|
||||||
|
.order(score: :desc, created_at: :asc)
|
||||||
|
.first&.created_at || Time.zone.at(0)
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_working_time_query
|
def user_working_time_query
|
||||||
"
|
"
|
||||||
SELECT user_id,
|
SELECT contributor_id,
|
||||||
user_type,
|
contributor_type,
|
||||||
SUM(working_time_new) AS working_time,
|
SUM(working_time_new) AS working_time,
|
||||||
MAX(score) AS score
|
MAX(score) AS score
|
||||||
FROM
|
FROM
|
||||||
(SELECT user_id,
|
(SELECT contributor_id,
|
||||||
user_type,
|
contributor_type,
|
||||||
score,
|
score,
|
||||||
CASE WHEN #{StatisticsHelper.working_time_larger_delta} THEN '0' ELSE working_time END AS working_time_new
|
CASE WHEN #{StatisticsHelper.working_time_larger_delta} THEN '0' ELSE working_time END AS working_time_new
|
||||||
FROM
|
FROM
|
||||||
(SELECT user_id,
|
(SELECT contributor_id,
|
||||||
user_type,
|
contributor_type,
|
||||||
score,
|
score,
|
||||||
id,
|
id,
|
||||||
(created_at - lag(created_at) over (PARTITION BY user_id, exercise_id
|
(created_at - lag(created_at) over (PARTITION BY contributor_id, exercise_id
|
||||||
ORDER BY created_at)) AS working_time
|
ORDER BY created_at)) AS working_time
|
||||||
FROM submissions
|
FROM submissions
|
||||||
WHERE #{self.class.sanitize_sql(['exercise_id = ?', id])}) AS foo) AS bar
|
WHERE #{self.class.sanitize_sql(['exercise_id = ?', id])}) AS foo) AS bar
|
||||||
GROUP BY user_id, user_type
|
GROUP BY contributor_id, contributor_type
|
||||||
"
|
"
|
||||||
end
|
end
|
||||||
|
|
||||||
def study_group_working_time_query(exercise_id, study_group_id, additional_filter)
|
def study_group_working_time_query(exercise_id, study_group_id, additional_filter)
|
||||||
"
|
"
|
||||||
WITH working_time_between_submissions AS (
|
WITH working_time_between_submissions AS (
|
||||||
SELECT submissions.user_id,
|
SELECT submissions.contributor_id,
|
||||||
submissions.user_type,
|
submissions.contributor_type,
|
||||||
score,
|
score,
|
||||||
created_at,
|
created_at,
|
||||||
(created_at - lag(created_at) over (PARTITION BY submissions.user_type, submissions.user_id, exercise_id
|
(created_at - lag(created_at) over (PARTITION BY submissions.contributor_type, submissions.contributor_id, exercise_id
|
||||||
ORDER BY created_at)) AS working_time
|
ORDER BY created_at)) AS working_time
|
||||||
FROM submissions
|
FROM submissions
|
||||||
WHERE #{self.class.sanitize_sql(['exercise_id = ? and study_group_id = ?', exercise_id, study_group_id])} #{self.class.sanitize_sql(additional_filter)}),
|
WHERE #{self.class.sanitize_sql(['exercise_id = ? and study_group_id = ?', exercise_id, study_group_id])} #{self.class.sanitize_sql(additional_filter)}),
|
||||||
working_time_with_deltas_ignored AS (
|
working_time_with_deltas_ignored AS (
|
||||||
SELECT user_id,
|
SELECT contributor_id,
|
||||||
user_type,
|
contributor_type,
|
||||||
score,
|
score,
|
||||||
sum(CASE WHEN score IS NOT NULL THEN 1 ELSE 0 END)
|
sum(CASE WHEN score IS NOT NULL THEN 1 ELSE 0 END)
|
||||||
over (ORDER BY user_type, user_id, created_at ASC) AS change_in_score,
|
over (ORDER BY contributor_type, contributor_id, created_at ASC) AS change_in_score,
|
||||||
created_at,
|
created_at,
|
||||||
CASE WHEN #{StatisticsHelper.working_time_larger_delta} THEN '0' ELSE working_time END AS working_time_filtered
|
CASE WHEN #{StatisticsHelper.working_time_larger_delta} THEN '0' ELSE working_time END AS working_time_filtered
|
||||||
FROM working_time_between_submissions
|
FROM working_time_between_submissions
|
||||||
),
|
),
|
||||||
working_times_with_score_expanded AS (
|
working_times_with_score_expanded AS (
|
||||||
SELECT user_id,
|
SELECT contributor_id,
|
||||||
user_type,
|
contributor_type,
|
||||||
created_at,
|
created_at,
|
||||||
working_time_filtered,
|
working_time_filtered,
|
||||||
first_value(score)
|
first_value(score)
|
||||||
over (PARTITION BY user_type, user_id, change_in_score ORDER BY created_at ASC) AS corrected_score
|
over (PARTITION BY contributor_type, contributor_id, change_in_score ORDER BY created_at ASC) AS corrected_score
|
||||||
FROM working_time_with_deltas_ignored
|
FROM working_time_with_deltas_ignored
|
||||||
),
|
),
|
||||||
working_times_with_duplicated_last_row_per_score AS (
|
working_times_with_duplicated_last_row_per_score AS (
|
||||||
@ -145,62 +145,62 @@ class Exercise < ApplicationRecord
|
|||||||
-- Duplicate last row per user and score and make it unique by setting another created_at timestamp.
|
-- Duplicate last row per user and score and make it unique by setting another created_at timestamp.
|
||||||
-- In addition, the working time is set to zero in order to prevent getting a wrong time.
|
-- In addition, the working time is set to zero in order to prevent getting a wrong time.
|
||||||
-- This duplication is needed, as we will shift the scores and working times by one and need to ensure not to loose any information.
|
-- This duplication is needed, as we will shift the scores and working times by one and need to ensure not to loose any information.
|
||||||
SELECT DISTINCT ON (user_type, user_id, corrected_score) user_id,
|
SELECT DISTINCT ON (contributor_type, contributor_id, corrected_score) contributor_id,
|
||||||
user_type,
|
contributor_type,
|
||||||
created_at + INTERVAL '1us',
|
created_at + INTERVAL '1us',
|
||||||
'00:00:00' as working_time_filtered,
|
'00:00:00' as working_time_filtered,
|
||||||
corrected_score
|
corrected_score
|
||||||
FROM working_times_with_score_expanded
|
FROM working_times_with_score_expanded
|
||||||
),
|
),
|
||||||
working_times_with_score_not_null_and_shifted AS (
|
working_times_with_score_not_null_and_shifted AS (
|
||||||
SELECT user_id,
|
SELECT contributor_id,
|
||||||
user_type,
|
contributor_type,
|
||||||
coalesce(lag(corrected_score) over (PARTITION BY user_type, user_id ORDER BY created_at ASC),
|
coalesce(lag(corrected_score) over (PARTITION BY contributor_type, contributor_id ORDER BY created_at ASC),
|
||||||
0) AS shifted_score,
|
0) AS shifted_score,
|
||||||
created_at,
|
created_at,
|
||||||
working_time_filtered
|
working_time_filtered
|
||||||
FROM working_times_with_duplicated_last_row_per_score
|
FROM working_times_with_duplicated_last_row_per_score
|
||||||
),
|
),
|
||||||
working_times_to_be_sorted AS (
|
working_times_to_be_sorted AS (
|
||||||
SELECT user_id,
|
SELECT contributor_id,
|
||||||
user_type,
|
contributor_type,
|
||||||
shifted_score AS score,
|
shifted_score AS score,
|
||||||
MIN(created_at) AS start_time,
|
MIN(created_at) AS start_time,
|
||||||
SUM(working_time_filtered) AS working_time_per_score,
|
SUM(working_time_filtered) AS working_time_per_score,
|
||||||
SUM(SUM(working_time_filtered)) over (PARTITION BY user_type, user_id) AS total_working_time
|
SUM(SUM(working_time_filtered)) over (PARTITION BY contributor_type, contributor_id) AS total_working_time
|
||||||
FROM working_times_with_score_not_null_and_shifted
|
FROM working_times_with_score_not_null_and_shifted
|
||||||
GROUP BY user_id, user_type, score
|
GROUP BY contributor_id, contributor_type, score
|
||||||
),
|
),
|
||||||
working_times_with_index AS (
|
working_times_with_index AS (
|
||||||
SELECT (dense_rank() over (ORDER BY total_working_time, user_type, user_id ASC) - 1) AS index,
|
SELECT (dense_rank() over (ORDER BY total_working_time, contributor_type, contributor_id ASC) - 1) AS index,
|
||||||
user_id,
|
contributor_id,
|
||||||
user_type,
|
contributor_type,
|
||||||
score,
|
score,
|
||||||
start_time,
|
start_time,
|
||||||
working_time_per_score,
|
working_time_per_score,
|
||||||
total_working_time
|
total_working_time
|
||||||
FROM working_times_to_be_sorted)
|
FROM working_times_to_be_sorted)
|
||||||
SELECT index,
|
SELECT index,
|
||||||
user_id,
|
contributor_id,
|
||||||
user_type,
|
contributor_type,
|
||||||
name,
|
name,
|
||||||
score,
|
score,
|
||||||
start_time,
|
start_time,
|
||||||
working_time_per_score,
|
working_time_per_score,
|
||||||
total_working_time
|
total_working_time
|
||||||
FROM working_times_with_index
|
FROM working_times_with_index
|
||||||
JOIN external_users ON user_type = 'ExternalUser' AND user_id = external_users.id
|
JOIN external_users ON contributor_type = 'ExternalUser' AND contributor_id = external_users.id
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT index,
|
SELECT index,
|
||||||
user_id,
|
contributor_id,
|
||||||
user_type,
|
contributor_type,
|
||||||
name,
|
name,
|
||||||
score,
|
score,
|
||||||
start_time,
|
start_time,
|
||||||
working_time_per_score,
|
working_time_per_score,
|
||||||
total_working_time
|
total_working_time
|
||||||
FROM working_times_with_index
|
FROM working_times_with_index
|
||||||
JOIN internal_users ON user_type = 'InternalUser' AND user_id = internal_users.id
|
JOIN internal_users ON contributor_type = 'InternalUser' AND contributor_id = internal_users.id
|
||||||
ORDER BY index, score ASC;
|
ORDER BY index, score ASC;
|
||||||
"
|
"
|
||||||
end
|
end
|
||||||
@ -218,7 +218,7 @@ class Exercise < ApplicationRecord
|
|||||||
additional_filter = if user.blank?
|
additional_filter = if user.blank?
|
||||||
''
|
''
|
||||||
else
|
else
|
||||||
"AND user_id = #{user.id} AND user_type = '#{user.class.name}'"
|
"AND contributor_id = #{user.id} AND contributor_type = '#{user.class.name}'"
|
||||||
end
|
end
|
||||||
|
|
||||||
results = self.class.connection.exec_query(study_group_working_time_query(id, study_group_id,
|
results = self.class.connection.exec_query(study_group_working_time_query(id, study_group_id,
|
||||||
@ -236,12 +236,12 @@ class Exercise < ApplicationRecord
|
|||||||
user_progress[bucket][tuple['index']] = format_time_difference(tuple['working_time_per_score'])
|
user_progress[bucket][tuple['index']] = format_time_difference(tuple['working_time_per_score'])
|
||||||
additional_user_data[bucket][tuple['index']] = {start_time: tuple['start_time'], score: tuple['score']}
|
additional_user_data[bucket][tuple['index']] = {start_time: tuple['start_time'], score: tuple['score']}
|
||||||
additional_user_data[max_bucket + 1][tuple['index']] =
|
additional_user_data[max_bucket + 1][tuple['index']] =
|
||||||
{id: tuple['user_id'], type: tuple['user_type'], name: ERB::Util.html_escape(tuple['name'])}
|
{id: tuple['contributor_id'], type: tuple['contributor_type'], name: ERB::Util.html_escape(tuple['name'])}
|
||||||
end
|
end
|
||||||
|
|
||||||
if results.ntuples.positive?
|
if results.size.positive?
|
||||||
first_index = results[0]['index']
|
first_index = results[0]['index']
|
||||||
last_index = results[results.ntuples - 1]['index']
|
last_index = results[results.size - 1]['index']
|
||||||
buckets = last_index - first_index
|
buckets = last_index - first_index
|
||||||
user_progress.each do |timings_array|
|
user_progress.each do |timings_array|
|
||||||
timings_array[buckets] = nil if timings_array.present? && timings_array.length != buckets + 1
|
timings_array[buckets] = nil if timings_array.present? && timings_array.length != buckets + 1
|
||||||
@ -255,15 +255,15 @@ class Exercise < ApplicationRecord
|
|||||||
result = self.class.connection.exec_query("
|
result = self.class.connection.exec_query("
|
||||||
WITH working_time AS
|
WITH working_time AS
|
||||||
(
|
(
|
||||||
SELECT user_id,
|
SELECT contributor_id,
|
||||||
id,
|
id,
|
||||||
exercise_id,
|
exercise_id,
|
||||||
Max(score) AS max_score,
|
Max(score) AS max_score,
|
||||||
(created_at - Lag(created_at) OVER (partition BY user_id, exercise_id ORDER BY created_at)) AS working_time
|
(created_at - Lag(created_at) OVER (partition BY contributor_id, exercise_id ORDER BY created_at)) AS working_time
|
||||||
FROM submissions
|
FROM submissions
|
||||||
WHERE #{self.class.sanitize_sql(['exercise_id = ?', id])}
|
WHERE #{self.class.sanitize_sql(['exercise_id = ?', id])}
|
||||||
AND user_type = 'ExternalUser'
|
AND contributor_type = 'ExternalUser'
|
||||||
GROUP BY user_id,
|
GROUP BY contributor_id,
|
||||||
id,
|
id,
|
||||||
exercise_id), max_points AS
|
exercise_id), max_points AS
|
||||||
(
|
(
|
||||||
@ -286,37 +286,37 @@ class Exercise < ApplicationRecord
|
|||||||
first_time_max_score AS
|
first_time_max_score AS
|
||||||
(
|
(
|
||||||
SELECT id,
|
SELECT id,
|
||||||
user_id,
|
contributor_id,
|
||||||
exercise_id,
|
exercise_id,
|
||||||
max_score,
|
max_score,
|
||||||
working_time,
|
working_time,
|
||||||
rn
|
rn
|
||||||
FROM (
|
FROM (
|
||||||
SELECT id,
|
SELECT id,
|
||||||
user_id,
|
contributor_id,
|
||||||
exercise_id,
|
exercise_id,
|
||||||
max_score,
|
max_score,
|
||||||
working_time,
|
working_time,
|
||||||
Row_number() OVER(partition BY user_id, exercise_id ORDER BY id ASC) AS rn
|
Row_number() OVER(partition BY contributor_id, exercise_id ORDER BY id ASC) AS rn
|
||||||
FROM time_max_score) T
|
FROM time_max_score) T
|
||||||
WHERE rn = 1), times_until_max_points AS
|
WHERE rn = 1), times_until_max_points AS
|
||||||
(
|
(
|
||||||
SELECT w.id,
|
SELECT w.id,
|
||||||
w.user_id,
|
w.contributor_id,
|
||||||
w.exercise_id,
|
w.exercise_id,
|
||||||
w.max_score,
|
w.max_score,
|
||||||
w.working_time,
|
w.working_time,
|
||||||
m.id AS reachedmax_at
|
m.id AS reachedmax_at
|
||||||
FROM working_time W,
|
FROM working_time W,
|
||||||
first_time_max_score M
|
first_time_max_score M
|
||||||
WHERE w.user_id = m.user_id
|
WHERE w.contributor_id = m.contributor_id
|
||||||
AND w.exercise_id = m.exercise_id
|
AND w.exercise_id = m.exercise_id
|
||||||
AND w.id <= m.id),
|
AND w.id <= m.id),
|
||||||
-- if user never makes it to max points, take all times
|
-- if user never makes it to max points, take all times
|
||||||
all_working_times_until_max AS (
|
all_working_times_until_max AS (
|
||||||
(
|
(
|
||||||
SELECT id,
|
SELECT id,
|
||||||
user_id,
|
contributor_id,
|
||||||
exercise_id,
|
exercise_id,
|
||||||
max_score,
|
max_score,
|
||||||
working_time
|
working_time
|
||||||
@ -324,7 +324,7 @@ class Exercise < ApplicationRecord
|
|||||||
UNION ALL
|
UNION ALL
|
||||||
(
|
(
|
||||||
SELECT id,
|
SELECT id,
|
||||||
user_id,
|
contributor_id,
|
||||||
exercise_id,
|
exercise_id,
|
||||||
max_score,
|
max_score,
|
||||||
working_time
|
working_time
|
||||||
@ -333,10 +333,10 @@ class Exercise < ApplicationRecord
|
|||||||
(
|
(
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM first_time_max_score F
|
FROM first_time_max_score F
|
||||||
WHERE f.user_id = w1.user_id
|
WHERE f.contributor_id = w1.contributor_id
|
||||||
AND f.exercise_id = w1.exercise_id))), filtered_times_until_max AS
|
AND f.exercise_id = w1.exercise_id))), filtered_times_until_max AS
|
||||||
(
|
(
|
||||||
SELECT user_id,
|
SELECT contributor_id,
|
||||||
exercise_id,
|
exercise_id,
|
||||||
max_score,
|
max_score,
|
||||||
CASE
|
CASE
|
||||||
@ -345,16 +345,16 @@ class Exercise < ApplicationRecord
|
|||||||
END AS working_time_new
|
END AS working_time_new
|
||||||
FROM all_working_times_until_max ), result AS
|
FROM all_working_times_until_max ), result AS
|
||||||
(
|
(
|
||||||
SELECT e.external_id AS external_user_id,
|
SELECT e.external_id AS external_contributor_id,
|
||||||
f.user_id,
|
f.contributor_id,
|
||||||
exercise_id,
|
exercise_id,
|
||||||
Max(max_score) AS max_score,
|
Max(max_score) AS max_score,
|
||||||
Sum(working_time_new) AS working_time
|
Sum(working_time_new) AS working_time
|
||||||
FROM filtered_times_until_max f,
|
FROM filtered_times_until_max f,
|
||||||
external_users e
|
external_users e
|
||||||
WHERE f.user_id = e.id
|
WHERE f.contributor_id = e.id
|
||||||
GROUP BY e.external_id,
|
GROUP BY e.external_id,
|
||||||
f.user_id,
|
f.contributor_id,
|
||||||
exercise_id )
|
exercise_id )
|
||||||
SELECT unnest(percentile_cont(#{self.class.sanitize_sql(['array[?]', quantiles])}) within GROUP (ORDER BY working_time))
|
SELECT unnest(percentile_cont(#{self.class.sanitize_sql(['array[?]', quantiles])}) within GROUP (ORDER BY working_time))
|
||||||
FROM result
|
FROM result
|
||||||
@ -370,7 +370,7 @@ class Exercise < ApplicationRecord
|
|||||||
@working_time_statistics = {'InternalUser' => {}, 'ExternalUser' => {}}
|
@working_time_statistics = {'InternalUser' => {}, 'ExternalUser' => {}}
|
||||||
self.class.connection.exec_query(user_working_time_query).each do |tuple|
|
self.class.connection.exec_query(user_working_time_query).each do |tuple|
|
||||||
tuple = tuple.merge('working_time' => format_time_difference(tuple['working_time']))
|
tuple = tuple.merge('working_time' => format_time_difference(tuple['working_time']))
|
||||||
@working_time_statistics[tuple['user_type']][tuple['user_id'].to_i] = tuple
|
@working_time_statistics[tuple['contributor_type']][tuple['contributor_id'].to_i] = tuple
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -388,20 +388,20 @@ class Exercise < ApplicationRecord
|
|||||||
@working_time_statistics[user.class.name][user.id]['working_time']
|
@working_time_statistics[user.class.name][user.id]['working_time']
|
||||||
end
|
end
|
||||||
|
|
||||||
def accumulated_working_time_for_only(user)
|
def accumulated_working_time_for_only(contributor)
|
||||||
user_type = user.external_user? ? 'ExternalUser' : 'InternalUser'
|
contributor_type = contributor.class.name
|
||||||
begin
|
begin
|
||||||
result = self.class.connection.exec_query("
|
result = self.class.connection.exec_query("
|
||||||
WITH WORKING_TIME AS
|
WITH WORKING_TIME AS
|
||||||
(SELECT user_id,
|
(SELECT contributor_id,
|
||||||
id,
|
id,
|
||||||
exercise_id,
|
exercise_id,
|
||||||
max(score) AS max_score,
|
max(score) AS max_score,
|
||||||
(created_at - lag(created_at) OVER (PARTITION BY user_id, exercise_id
|
(created_at - lag(created_at) OVER (PARTITION BY contributor_id, exercise_id
|
||||||
ORDER BY created_at)) AS working_time
|
ORDER BY created_at)) AS working_time
|
||||||
FROM submissions
|
FROM submissions
|
||||||
WHERE exercise_id = #{id} AND user_id = #{user.id} AND user_type = '#{user_type}'
|
WHERE exercise_id = #{id} AND contributor_id = #{contributor.id} AND contributor_type = '#{contributor_type}'
|
||||||
GROUP BY user_id, id, exercise_id),
|
GROUP BY contributor_id, id, exercise_id),
|
||||||
MAX_POINTS AS
|
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 IN ('teacher_defined_test', 'teacher_defined_linter') 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),
|
||||||
|
|
||||||
@ -413,33 +413,33 @@ class Exercise < ApplicationRecord
|
|||||||
|
|
||||||
-- find row containing the first time max points
|
-- find row containing the first time max points
|
||||||
FIRST_TIME_MAX_SCORE AS
|
FIRST_TIME_MAX_SCORE AS
|
||||||
( SELECT id,USER_id,exercise_id,max_score,working_time, rn
|
( SELECT id,contributor_id,exercise_id,max_score,working_time, rn
|
||||||
FROM (
|
FROM (
|
||||||
SELECT id,USER_id,exercise_id,max_score,working_time,
|
SELECT id,contributor_id,exercise_id,max_score,working_time,
|
||||||
ROW_NUMBER() OVER(PARTITION BY user_id, exercise_id ORDER BY id ASC) AS rn
|
ROW_NUMBER() OVER(PARTITION BY contributor_id, exercise_id ORDER BY id ASC) AS rn
|
||||||
FROM TIME_MAX_SCORE) T
|
FROM TIME_MAX_SCORE) T
|
||||||
WHERE rn = 1),
|
WHERE rn = 1),
|
||||||
|
|
||||||
TIMES_UNTIL_MAX_POINTS AS (
|
TIMES_UNTIL_MAX_POINTS AS (
|
||||||
SELECT W.id, W.user_id, W.exercise_id, W.max_score, W.working_time, M.id AS reachedmax_at
|
SELECT W.id, W.contributor_id, W.exercise_id, W.max_score, W.working_time, M.id AS reachedmax_at
|
||||||
FROM WORKING_TIME W, FIRST_TIME_MAX_SCORE M
|
FROM WORKING_TIME W, FIRST_TIME_MAX_SCORE M
|
||||||
WHERE W.user_id = M.user_id AND W.exercise_id = M.exercise_id AND W.id <= M.id),
|
WHERE W.contributor_id = M.contributor_id AND W.exercise_id = M.exercise_id AND W.id <= M.id),
|
||||||
|
|
||||||
-- if user never makes it to max points, take all times
|
-- if contributor never makes it to max points, take all times
|
||||||
ALL_WORKING_TIMES_UNTIL_MAX AS
|
ALL_WORKING_TIMES_UNTIL_MAX AS
|
||||||
((SELECT id, user_id, exercise_id, max_score, working_time FROM TIMES_UNTIL_MAX_POINTS)
|
((SELECT id, contributor_id, exercise_id, max_score, working_time FROM TIMES_UNTIL_MAX_POINTS)
|
||||||
UNION ALL
|
UNION ALL
|
||||||
(SELECT id, user_id, exercise_id, max_score, working_time FROM WORKING_TIME W1
|
(SELECT id, contributor_id, exercise_id, max_score, working_time FROM WORKING_TIME W1
|
||||||
WHERE NOT EXISTS (SELECT 1 FROM FIRST_TIME_MAX_SCORE F WHERE F.user_id = W1.user_id AND F.exercise_id = W1.exercise_id))),
|
WHERE NOT EXISTS (SELECT 1 FROM FIRST_TIME_MAX_SCORE F WHERE F.contributor_id = W1.contributor_id AND F.exercise_id = W1.exercise_id))),
|
||||||
|
|
||||||
FILTERED_TIMES_UNTIL_MAX AS
|
FILTERED_TIMES_UNTIL_MAX AS
|
||||||
(
|
(
|
||||||
SELECT user_id,exercise_id, max_score, CASE WHEN #{StatisticsHelper.working_time_larger_delta} THEN '0' ELSE working_time END AS working_time_new
|
SELECT contributor_id,exercise_id, max_score, CASE WHEN #{StatisticsHelper.working_time_larger_delta} THEN '0' ELSE working_time END AS working_time_new
|
||||||
FROM ALL_WORKING_TIMES_UNTIL_MAX
|
FROM ALL_WORKING_TIMES_UNTIL_MAX
|
||||||
)
|
)
|
||||||
SELECT e.external_id AS external_user_id, f.user_id, exercise_id, MAX(max_score) AS max_score, sum(working_time_new) AS working_time
|
SELECT e.external_id AS external_contributor_id, f.contributor_id, exercise_id, MAX(max_score) AS max_score, sum(working_time_new) AS working_time
|
||||||
FROM FILTERED_TIMES_UNTIL_MAX f, EXTERNAL_USERS e
|
FROM FILTERED_TIMES_UNTIL_MAX f, EXTERNAL_USERS e
|
||||||
WHERE f.user_id = e.id GROUP BY e.external_id, f.user_id, exercise_id
|
WHERE f.contributor_id = e.id GROUP BY e.external_id, f.contributor_id, exercise_id
|
||||||
")
|
")
|
||||||
parse_duration(result.first['working_time']).to_f
|
parse_duration(result.first['working_time']).to_f
|
||||||
rescue StandardError
|
rescue StandardError
|
||||||
@ -508,14 +508,13 @@ class Exercise < ApplicationRecord
|
|||||||
end
|
end
|
||||||
private :generate_token
|
private :generate_token
|
||||||
|
|
||||||
def maximum_score(user = nil)
|
def maximum_score(contributor = nil)
|
||||||
if user
|
if contributor
|
||||||
# FIXME: where(user: user) will not work here!
|
submissions
|
||||||
begin
|
.where(contributor:, cause: %w[submit assess])
|
||||||
submissions.where(user:).where("cause IN ('submit','assess')").where.not(score: nil).order('score DESC').first.score || 0
|
.where.not(score: nil)
|
||||||
rescue StandardError
|
.order(score: :desc)
|
||||||
0
|
.first&.score || 0
|
||||||
end
|
|
||||||
else
|
else
|
||||||
@maximum_score ||= if files.loaded?
|
@maximum_score ||= if files.loaded?
|
||||||
files.filter(&:teacher_defined_assessment?).pluck(:weight).sum
|
files.filter(&:teacher_defined_assessment?).pluck(:weight).sum
|
||||||
@ -525,12 +524,12 @@ class Exercise < ApplicationRecord
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def final_submission(user)
|
def final_submission(contributor)
|
||||||
submissions.final.where(user_id: user.id, user_type: user.class.name).order(created_at: :desc).first
|
submissions.final.order(created_at: :desc).find_by(contributor:)
|
||||||
end
|
end
|
||||||
|
|
||||||
def solved_by?(user)
|
def solved_by?(contributor)
|
||||||
maximum_score(user).to_i == maximum_score.to_i
|
maximum_score(contributor).to_i == maximum_score.to_i
|
||||||
end
|
end
|
||||||
|
|
||||||
def finishers
|
def finishers
|
||||||
@ -587,9 +586,9 @@ cause: %w[submit assess remoteSubmit remoteAssess]}).distinct
|
|||||||
def last_submission_per_user
|
def last_submission_per_user
|
||||||
Submission.joins("JOIN (
|
Submission.joins("JOIN (
|
||||||
SELECT
|
SELECT
|
||||||
user_id,
|
contributor_id,
|
||||||
user_type,
|
contributor_type,
|
||||||
first_value(id) OVER (PARTITION BY user_id ORDER BY created_at DESC) AS fv
|
first_value(id) OVER (PARTITION BY contributor_id ORDER BY created_at DESC) AS fv
|
||||||
FROM submissions
|
FROM submissions
|
||||||
WHERE exercise_id = #{id}
|
WHERE exercise_id = #{id}
|
||||||
) AS t ON t.fv = submissions.id").distinct
|
) AS t ON t.fv = submissions.id").distinct
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
class Submission < ApplicationRecord
|
class Submission < ApplicationRecord
|
||||||
include Context
|
include Context
|
||||||
include Creation
|
include ContributorCreation
|
||||||
include ActionCableHelper
|
include ActionCableHelper
|
||||||
|
|
||||||
CAUSES = %w[assess download file render run save submit test autosave requestComments remoteAssess
|
CAUSES = %w[assess download file render run save submit test autosave requestComments remoteAssess
|
||||||
@ -19,12 +19,11 @@ class Submission < ApplicationRecord
|
|||||||
has_many :comments, through: :files
|
has_many :comments, through: :files
|
||||||
|
|
||||||
belongs_to :external_users, lambda {
|
belongs_to :external_users, lambda {
|
||||||
where(submissions: {user_type: 'ExternalUser'}).includes(:submissions)
|
where(submissions: {contributor_type: 'ExternalUser'}).includes(:submissions)
|
||||||
}, foreign_key: :user_id, class_name: 'ExternalUser', optional: true
|
}, foreign_key: :contributor_id, class_name: 'ExternalUser', optional: true
|
||||||
belongs_to :internal_users, lambda {
|
belongs_to :internal_users, lambda {
|
||||||
where(submissions: {user_type: 'InternalUser'}).includes(:submissions)
|
where(submissions: {contributor_type: 'InternalUser'}).includes(:submissions)
|
||||||
}, foreign_key: :user_id, class_name: 'InternalUser', optional: true
|
}, foreign_key: :contributor_id, class_name: 'InternalUser', optional: true
|
||||||
|
|
||||||
delegate :execution_environment, to: :exercise
|
delegate :execution_environment, to: :exercise
|
||||||
|
|
||||||
scope :final, -> { where(cause: %w[submit remoteSubmit]) }
|
scope :final, -> { where(cause: %w[submit remoteSubmit]) }
|
||||||
@ -88,7 +87,7 @@ class Submission < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
def siblings
|
def siblings
|
||||||
user.submissions.where(exercise_id:)
|
contributor.submissions.where(exercise_id:)
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_s
|
def to_s
|
||||||
@ -125,11 +124,11 @@ class Submission < ApplicationRecord
|
|||||||
# Redirect 10% of users to the exercise feedback page. Ensure, that always the same
|
# Redirect 10% of users to the exercise feedback page. Ensure, that always the same
|
||||||
# users get redirected per exercise and different users for different exercises. If
|
# users get redirected per exercise and different users for different exercises. If
|
||||||
# desired, the number of feedbacks can be limited with exercise.needs_more_feedback?(submission)
|
# desired, the number of feedbacks can be limited with exercise.needs_more_feedback?(submission)
|
||||||
(user_id + exercise.created_at.to_i) % 10 == 1
|
(contributor_id + exercise.created_at.to_i) % 10 == 1
|
||||||
end
|
end
|
||||||
|
|
||||||
def own_unsolved_rfc(user = self.user)
|
def own_unsolved_rfc(user = self.user)
|
||||||
Pundit.policy_scope(user, RequestForComment).unsolved.find_by(exercise_id: exercise, user_id:)
|
Pundit.policy_scope(user, RequestForComment).unsolved.find_by(exercise:, user:)
|
||||||
end
|
end
|
||||||
|
|
||||||
def unsolved_rfc(user = self.user)
|
def unsolved_rfc(user = self.user)
|
||||||
@ -208,11 +207,11 @@ class Submission < ApplicationRecord
|
|||||||
def prepared_runner
|
def prepared_runner
|
||||||
request_time = Time.zone.now
|
request_time = Time.zone.now
|
||||||
begin
|
begin
|
||||||
runner = Runner.for(user, exercise.execution_environment)
|
runner = Runner.for(contributor, exercise.execution_environment)
|
||||||
files = collect_files
|
files = collect_files
|
||||||
files.reject!(&:reference_implementation?) if cause == 'run'
|
files.reject!(&:reference_implementation?) if cause == 'run'
|
||||||
files.reject!(&:teacher_defined_assessment?) if cause == 'run'
|
files.reject!(&:teacher_defined_assessment?) if cause == 'run'
|
||||||
Rails.logger.debug { "#{Time.zone.now.getutc.inspect}: Copying files to Runner #{runner.id} for #{user_type} #{user_id} and Submission #{id}." }
|
Rails.logger.debug { "#{Time.zone.now.getutc.inspect}: Copying files to Runner #{runner.id} for #{contributor_type} #{contributor_id} and Submission #{id}." }
|
||||||
runner.copy_files(files)
|
runner.copy_files(files)
|
||||||
rescue Runner::Error => e
|
rescue Runner::Error => e
|
||||||
e.waiting_duration = Time.zone.now - request_time
|
e.waiting_duration = Time.zone.now - request_time
|
||||||
@ -313,7 +312,7 @@ class Submission < ApplicationRecord
|
|||||||
update(score: score.to_d)
|
update(score: score.to_d)
|
||||||
if normalized_score.to_d == BigDecimal('1.0')
|
if normalized_score.to_d == BigDecimal('1.0')
|
||||||
Thread.new do
|
Thread.new do
|
||||||
RequestForComment.where(exercise_id:, user_id:, user_type:).find_each do |rfc|
|
RequestForComment.joins(:submission).where(submission: {contributor:}, exercise:).find_each do |rfc|
|
||||||
rfc.full_score_reached = true
|
rfc.full_score_reached = true
|
||||||
rfc.save
|
rfc.save
|
||||||
end
|
end
|
||||||
|
@ -11,7 +11,7 @@ class User < ApplicationRecord
|
|||||||
has_many :study_groups, through: :study_group_memberships, as: :user
|
has_many :study_groups, through: :study_group_memberships, as: :user
|
||||||
has_many :exercises, as: :user
|
has_many :exercises, as: :user
|
||||||
has_many :file_types, as: :user
|
has_many :file_types, as: :user
|
||||||
has_many :submissions, as: :user
|
has_many :submissions, as: :contributor
|
||||||
has_many :participations, through: :submissions, source: :exercise, as: :user
|
has_many :participations, through: :submissions, source: :exercise, as: :user
|
||||||
has_many :user_proxy_exercise_exercises, as: :user
|
has_many :user_proxy_exercise_exercises, as: :user
|
||||||
has_many :user_exercise_interventions, as: :user
|
has_many :user_exercise_interventions, as: :user
|
||||||
|
@ -8,14 +8,14 @@ h1 = @execution_environment
|
|||||||
th.header = t(title)
|
th.header = t(title)
|
||||||
tbody
|
tbody
|
||||||
- @execution_environment.exercises.each do |exercise|
|
- @execution_environment.exercises.each do |exercise|
|
||||||
- us = user_statistics[exercise.id]
|
- us = contributor_statistics[exercise.id]
|
||||||
- if not us then us = {"users" => 0, "average_score" => 0.0, "maximum_score" => 0, "stddev_score" => 0.0, "percent_correct" => nil, "average_submission_count" => 0}
|
- if not us then us = {"contributors" => 0, "average_score" => 0.0, "maximum_score" => 0, "stddev_score" => 0.0, "percent_correct" => nil, "average_submission_count" => 0}
|
||||||
- wts = working_time_statistics[exercise.id]
|
- wts = working_time_statistics[exercise.id]
|
||||||
- if wts then average_time = wts["average_time"] else 0
|
- if wts then average_time = wts["average_time"] else 0
|
||||||
- if wts then stddev_time = wts["stddev_time"] else 0
|
- if wts then stddev_time = wts["stddev_time"] else 0
|
||||||
tr
|
tr
|
||||||
td = link_to_if policy(exercise).statistics?, exercise.title, controller: "exercises", action: "statistics", id: exercise.id, 'data-turbolinks' => "false"
|
td = link_to_if policy(exercise).statistics?, exercise.title, controller: "exercises", action: "statistics", id: exercise.id, 'data-turbolinks' => "false"
|
||||||
td = us["users"]
|
td = us["contributors"]
|
||||||
td = us["average_score"].to_f.round(4)
|
td = us["average_score"].to_f.round(4)
|
||||||
td = us["maximum_score"].to_f.round(2)
|
td = us["maximum_score"].to_f.round(2)
|
||||||
td = us["stddev_score"].to_f.round(4)
|
td = us["stddev_score"].to_f.round(4)
|
||||||
|
@ -3,14 +3,14 @@ h1 = @exercise_collection
|
|||||||
= row(label: 'exercise_collections.name', value: @exercise_collection.name)
|
= row(label: 'exercise_collections.name', value: @exercise_collection.name)
|
||||||
= row(label: 'exercise_collections.updated_at', value: @exercise_collection.updated_at)
|
= row(label: 'exercise_collections.updated_at', value: @exercise_collection.updated_at)
|
||||||
= row(label: 'exercise_collections.exercises', value: @exercise_collection.exercises.count)
|
= row(label: 'exercise_collections.exercises', value: @exercise_collection.exercises.count)
|
||||||
= row(label: 'exercise_collections.users', value: @exercise_collection.exercises.joins(:submissions).group("submissions.user_id").count.count)
|
= row(label: 'exercise_collections.users_and_programming_groups', value: Submission.from(@exercise_collection.exercises.joins(:submissions).group(:contributor_id, :contributor_type).select(:contributor_id, :contributor_type)).count)
|
||||||
= row(label: 'exercise_collections.solutions', value: @exercise_collection.exercises.joins(:submissions).group("submissions.user_id").group("id").count.count)
|
= row(label: 'exercise_collections.solutions', value: Submission.from(@exercise_collection.exercises.joins(:submissions).group(:contributor_id, :contributor_type, :id).select(:contributor_id, :contributor_type)).count)
|
||||||
= row(label: 'exercise_collections.submissions', value: @exercise_collection.exercises.joins(:submissions).count)
|
= row(label: 'exercise_collections.submissions', value: @exercise_collection.exercises.joins(:submissions).count)
|
||||||
/ further metrics:
|
/ further metrics:
|
||||||
/ number of users that attempted at least one exercise @exercise_collection.exercises.joins(:submissions).group("submissions.user_id").count.count
|
/ number of contributors that attempted at least one exercise @exercise_collection.exercises.joins(:submissions).group("submissions.contributor_id", "submissions.contributor_type").count.count
|
||||||
/ number of solutions: @exercise_collection.exercises.joins(:submissions).group("submissions.user_id").group("id").count.count
|
/ number of solutions: @exercise_collection.exercises.joins(:submissions).group("submissions.contributor_id", "submissions.contributor_type").group("id").count.count
|
||||||
/ further filters:
|
/ further filters:
|
||||||
/ Only before specific date: date = DateTime.parse("2015-01-01 00:00:00.000000") ; @exercise_collection.exercises.joins(:submissions).where(["submissions.created_at > ?", date]).group("submissions.user_id").count.count
|
/ Only before specific date: date = DateTime.parse("2015-01-01 00:00:00.000000") ; @exercise_collection.exercises.joins(:submissions).where(["submissions.created_at > ?", date]).group("submissions.contributor_id", "submissions.contributor_type").count.count
|
||||||
/ Only with specific cause: @exercise_collection.exercises.joins(:submissions).where("submissions.cause" == 'assess').count
|
/ Only with specific cause: @exercise_collection.exercises.joins(:submissions).where("submissions.cause" == 'assess').count
|
||||||
|
|
||||||
= row(label: 'exercises.statistics.average_worktime', value: @exercise_collection.average_working_time.round(3).to_s + 's')
|
= row(label: 'exercises.statistics.average_worktime', value: @exercise_collection.average_working_time.round(3).to_s + 's')
|
||||||
|
@ -9,7 +9,7 @@ h1 = @exercise
|
|||||||
|
|
||||||
- [:intermediate, :final].each do |scope|
|
- [:intermediate, :final].each do |scope|
|
||||||
= row(label: ".#{scope}_submissions") do
|
= row(label: ".#{scope}_submissions") do
|
||||||
= "#{@exercise.submissions.send(scope).count} (#{t('.users', count: @exercise.submissions.send(scope).distinct.count(:user_id))})"
|
= "#{@exercise.submissions.send(scope).count} (#{t('.users', count: @exercise.submissions.send(scope).distinct.count(:contributor_id))})"
|
||||||
|
|
||||||
= row(label: '.finishing_rate') do
|
= row(label: '.finishing_rate') do
|
||||||
p
|
p
|
||||||
@ -43,7 +43,7 @@ h1 = @exercise
|
|||||||
p = @exercise.average_working_time
|
p = @exercise.average_working_time
|
||||||
|
|
||||||
- Hash[:internal_users => t('.internal_users'), :external_users => t('.external_users')].each_pair do |symbol, label|
|
- Hash[:internal_users => t('.internal_users'), :external_users => t('.external_users')].each_pair do |symbol, label|
|
||||||
- submissions = Submission.where(user: @exercise.send(symbol), exercise: @exercise).in_study_group_of(current_user)
|
- submissions = Submission.where(contributor: @exercise.send(symbol), exercise: @exercise).in_study_group_of(current_user)
|
||||||
- if !policy(@exercise).detailed_statistics?
|
- if !policy(@exercise).detailed_statistics?
|
||||||
- submissions = submissions.final
|
- submissions = submissions.final
|
||||||
- if submissions.any?
|
- if submissions.any?
|
||||||
@ -60,9 +60,9 @@ h1 = @exercise
|
|||||||
hr
|
hr
|
||||||
div#chart_2
|
div#chart_2
|
||||||
hr
|
hr
|
||||||
- users = symbol.to_s.classify.constantize.where(id: submissions.joins(symbol).group(:user_id).select(:user_id).distinct)
|
- contributors = symbol.to_s.classify.constantize.where(id: submissions.joins(symbol).group(:contributor_id).select(:contributor_id).distinct)
|
||||||
.table-responsive.mb-4
|
.table-responsive.mb-4
|
||||||
table.table.table-striped class="#{users.present? ? 'sortable' : ''}"
|
table.table.table-striped class="#{contributors.present? ? 'sortable' : ''}"
|
||||||
thead
|
thead
|
||||||
tr
|
tr
|
||||||
th.header = t('.user')
|
th.header = t('.user')
|
||||||
@ -71,14 +71,14 @@ h1 = @exercise
|
|||||||
th.header = t('.runs') if policy(@exercise).detailed_statistics?
|
th.header = t('.runs') if policy(@exercise).detailed_statistics?
|
||||||
th.header = t('.worktime') if policy(@exercise).detailed_statistics?
|
th.header = t('.worktime') if policy(@exercise).detailed_statistics?
|
||||||
tbody
|
tbody
|
||||||
- users.each do |user|
|
- contributors.each do |contributor|
|
||||||
- if user_statistics[user.class.name][user.id] then us = user_statistics[user.class.name][user.id] else us = {"maximum_score" => nil, "runs" => nil}
|
- if contributor_statistics[contributor.class.name][contributor.id] then us = contributor_statistics[contributor.class.name][contributor.id] else us = {"maximum_score" => nil, "runs" => nil}
|
||||||
- label = "#{user.displayname}"
|
- label = "#{contributor.displayname}"
|
||||||
tr
|
tr
|
||||||
td = link_to_if symbol==:external_users && policy(user).statistics?, label, {controller: "exercises", action: "external_user_statistics", external_user_id: user.id, id: @exercise.id}
|
td = link_to_if symbol==:external_users && policy(contributor).statistics?, label, {controller: "exercises", action: "external_user_statistics", external_user_id: contributor.id, id: @exercise.id}
|
||||||
td = us['maximum_score'] or 0
|
td = us['maximum_score'] or 0
|
||||||
td.align-middle
|
td.align-middle
|
||||||
- latest_user_submission = submissions.where(user: user).final.latest
|
- latest_user_submission = submissions.where(contributor:).final.latest
|
||||||
- if latest_user_submission.present?
|
- if latest_user_submission.present?
|
||||||
- if latest_user_submission.before_deadline?
|
- if latest_user_submission.before_deadline?
|
||||||
.unit-test-result.positive-result
|
.unit-test-result.positive-result
|
||||||
@ -87,4 +87,4 @@ h1 = @exercise
|
|||||||
- elsif latest_user_submission.after_late_deadline?
|
- elsif latest_user_submission.after_late_deadline?
|
||||||
.unit-test-result.negative-result
|
.unit-test-result.negative-result
|
||||||
td = us['runs'] if policy(@exercise).detailed_statistics?
|
td = us['runs'] if policy(@exercise).detailed_statistics?
|
||||||
td = @exercise.average_working_time_for(user) or 0 if policy(@exercise).detailed_statistics?
|
td = @exercise.average_working_time_for(contributor) or 0 if policy(@exercise).detailed_statistics?
|
||||||
|
@ -13,7 +13,7 @@ h1 = Submission.model_name.human(count: 2)
|
|||||||
thead
|
thead
|
||||||
tr
|
tr
|
||||||
th = sort_link(@search, :exercise_id, t('activerecord.attributes.submission.exercise'))
|
th = sort_link(@search, :exercise_id, t('activerecord.attributes.submission.exercise'))
|
||||||
th = sort_link(@search, :user_id, t('activerecord.attributes.submission.user'))
|
th = sort_link(@search, :user_id, t('activerecord.attributes.submission.contributor'))
|
||||||
th = sort_link(@search, :cause, t('activerecord.attributes.submission.cause'))
|
th = sort_link(@search, :cause, t('activerecord.attributes.submission.cause'))
|
||||||
th = sort_link(@search, :score, t('activerecord.attributes.submission.score'))
|
th = sort_link(@search, :score, t('activerecord.attributes.submission.score'))
|
||||||
th = sort_link(@search, :created_at, t('shared.created_at'))
|
th = sort_link(@search, :created_at, t('shared.created_at'))
|
||||||
@ -22,7 +22,7 @@ h1 = Submission.model_name.human(count: 2)
|
|||||||
- @submissions.each do |submission|
|
- @submissions.each do |submission|
|
||||||
tr
|
tr
|
||||||
td = link_to_if(submission.exercise && policy(submission.exercise).show?, submission.exercise, submission.exercise)
|
td = link_to_if(submission.exercise && policy(submission.exercise).show?, submission.exercise, submission.exercise)
|
||||||
td = link_to_if(policy(submission.user).show?, submission.user, submission.user)
|
td = link_to_if(policy(submission.contributor).show?, submission.contributor, submission.contributor)
|
||||||
td = t("submissions.causes.#{submission.cause}")
|
td = t("submissions.causes.#{submission.cause}")
|
||||||
td = submission.score
|
td = submission.score
|
||||||
td = l(submission.created_at, format: :short)
|
td = l(submission.created_at, format: :short)
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
h1 = @submission
|
h1 = @submission
|
||||||
|
|
||||||
= row(label: 'submission.exercise', value: link_to_if(policy(@submission.exercise).show?, @submission.exercise, @submission.exercise))
|
= row(label: 'submission.exercise', value: link_to_if(policy(@submission.exercise).show?, @submission.exercise, @submission.exercise))
|
||||||
= row(label: 'submission.user', value: link_to_if(policy(@submission.user).show?, @submission.user, @submission.user))
|
= row(label: 'submission.contributor', value: link_to_if(policy(@submission.contributor).show?, @submission.contributor, @submission.contributor))
|
||||||
= row(label: 'submission.study_group', value: link_to_if(@submission.study_group.present? && policy(@submission.study_group).show?, @submission.study_group, @submission.study_group))
|
= row(label: 'submission.study_group', value: link_to_if(@submission.study_group.present? && policy(@submission.study_group).show?, @submission.study_group, @submission.study_group))
|
||||||
= row(label: 'submission.cause', value: t("submissions.causes.#{@submission.cause}"))
|
= row(label: 'submission.cause', value: t("submissions.causes.#{@submission.cause}"))
|
||||||
= row(label: 'submission.score', value: @submission.score)
|
= row(label: 'submission.score', value: @submission.score)
|
||||||
|
@ -119,10 +119,10 @@ de:
|
|||||||
submission:
|
submission:
|
||||||
cause: Anlass
|
cause: Anlass
|
||||||
code: Code
|
code: Code
|
||||||
|
contributor: Mitwirkende:r
|
||||||
exercise: Aufgabe
|
exercise: Aufgabe
|
||||||
files: Dateien
|
files: Dateien
|
||||||
score: Punktzahl
|
score: Punktzahl
|
||||||
user: Autor
|
|
||||||
study_group: Lerngruppe
|
study_group: Lerngruppe
|
||||||
study_group:
|
study_group:
|
||||||
name: Name
|
name: Name
|
||||||
|
@ -119,10 +119,10 @@ en:
|
|||||||
submission:
|
submission:
|
||||||
cause: Cause
|
cause: Cause
|
||||||
code: Code
|
code: Code
|
||||||
|
contributor: Contributor
|
||||||
exercise: Exercise
|
exercise: Exercise
|
||||||
files: Files
|
files: Files
|
||||||
score: Score
|
score: Score
|
||||||
user: Author
|
|
||||||
study_group: Study Group
|
study_group: Study Group
|
||||||
study_group:
|
study_group:
|
||||||
name: Name
|
name: Name
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class RenameUserColumnsToContributorInSubmissions < ActiveRecord::Migration[7.0]
|
||||||
|
def change
|
||||||
|
change_table :submissions do |t|
|
||||||
|
t.rename :user_id, :contributor_id
|
||||||
|
t.rename :user_type, :contributor_type
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -454,15 +454,15 @@ ActiveRecord::Schema[7.0].define(version: 2023_07_27_080619) do
|
|||||||
create_table "submissions", id: :serial, force: :cascade do |t|
|
create_table "submissions", id: :serial, force: :cascade do |t|
|
||||||
t.integer "exercise_id"
|
t.integer "exercise_id"
|
||||||
t.float "score"
|
t.float "score"
|
||||||
t.integer "user_id"
|
t.integer "contributor_id"
|
||||||
t.datetime "created_at"
|
t.datetime "created_at"
|
||||||
t.datetime "updated_at"
|
t.datetime "updated_at"
|
||||||
t.string "cause"
|
t.string "cause"
|
||||||
t.string "user_type"
|
t.string "contributor_type"
|
||||||
t.bigint "study_group_id"
|
t.bigint "study_group_id"
|
||||||
|
t.index ["contributor_id"], name: "index_submissions_on_contributor_id"
|
||||||
t.index ["exercise_id"], name: "index_submissions_on_exercise_id"
|
t.index ["exercise_id"], name: "index_submissions_on_exercise_id"
|
||||||
t.index ["study_group_id"], name: "index_submissions_on_study_group_id"
|
t.index ["study_group_id"], name: "index_submissions_on_study_group_id"
|
||||||
t.index ["user_id"], name: "index_submissions_on_user_id"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "subscriptions", id: :serial, force: :cascade do |t|
|
create_table "subscriptions", id: :serial, force: :cascade do |t|
|
||||||
|
@ -28,7 +28,7 @@ ExecutionEnvironment.create_factories user: admin
|
|||||||
@exercises = find_factories_by_class(Exercise).map(&:name).index_with {|factory_name| FactoryBot.create(factory_name, user: teacher) }
|
@exercises = find_factories_by_class(Exercise).map(&:name).index_with {|factory_name| FactoryBot.create(factory_name, user: teacher) }
|
||||||
|
|
||||||
# submissions
|
# submissions
|
||||||
FactoryBot.create(:submission, exercise: @exercises[:fibonacci], user: external_user)
|
FactoryBot.create(:submission, exercise: @exercises[:fibonacci], contributor: external_user)
|
||||||
|
|
||||||
# The old images included in the seed data do not feature a dedicated `user` and therefore require a privileged execution.
|
# The old images included in the seed data do not feature a dedicated `user` and therefore require a privileged execution.
|
||||||
ExecutionEnvironment.update_all privileged_execution: true # rubocop:disable Rails/SkipsModelValidations
|
ExecutionEnvironment.update_all privileged_execution: true # rubocop:disable Rails/SkipsModelValidations
|
||||||
|
@ -25,7 +25,7 @@ describe FileParameters do
|
|||||||
|
|
||||||
it 'new file' do
|
it 'new file' do
|
||||||
submission = create(:submission, exercise: hello_world, id: 1337)
|
submission = create(:submission, exercise: hello_world, id: 1337)
|
||||||
controller.instance_variable_set(:@current_user, submission.user)
|
controller.instance_variable_set(:@current_user, submission.contributor)
|
||||||
|
|
||||||
new_file = create(:file, context: submission)
|
new_file = create(:file, context: submission)
|
||||||
expect(file_accepted?(new_file)).to be true
|
expect(file_accepted?(new_file)).to be true
|
||||||
@ -58,8 +58,8 @@ describe FileParameters do
|
|||||||
it 'file of another submission' do
|
it 'file of another submission' do
|
||||||
learner1 = create(:learner)
|
learner1 = create(:learner)
|
||||||
learner2 = create(:learner)
|
learner2 = create(:learner)
|
||||||
submission_learner1 = create(:submission, exercise: hello_world, user: learner1)
|
submission_learner1 = create(:submission, exercise: hello_world, contributor: learner1)
|
||||||
_submission_learner2 = create(:submission, exercise: hello_world, user: learner2)
|
_submission_learner2 = create(:submission, exercise: hello_world, contributor: learner2)
|
||||||
|
|
||||||
controller.instance_variable_set(:@current_user, learner2)
|
controller.instance_variable_set(:@current_user, learner2)
|
||||||
other_submissions_file = create(:file, context: submission_learner1)
|
other_submissions_file = create(:file, context: submission_learner1)
|
||||||
|
@ -107,7 +107,7 @@ describe Lti do
|
|||||||
let(:submission) { create(:submission) }
|
let(:submission) { create(:submission) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
create(:lti_parameter, consumers_id: consumer.id, external_users_id: submission.user_id, exercises_id: submission.exercise_id)
|
create(:lti_parameter, consumers_id: consumer.id, external_users_id: submission.contributor_id, exercises_id: submission.exercise_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with an invalid score' do
|
context 'with an invalid score' do
|
||||||
@ -156,7 +156,7 @@ describe Lti do
|
|||||||
|
|
||||||
context 'without a tool consumer' do
|
context 'without a tool consumer' do
|
||||||
it 'returns a corresponding status' do
|
it 'returns a corresponding status' do
|
||||||
submission.user.consumer = nil
|
submission.contributor.consumer = nil
|
||||||
|
|
||||||
allow(submission).to receive(:normalized_score).and_return score
|
allow(submission).to receive(:normalized_score).and_return score
|
||||||
expect(controller.send(:send_score, submission)[:status]).to eq('error')
|
expect(controller.send(:send_score, submission)[:status]).to eq('error')
|
||||||
|
@ -5,9 +5,9 @@ require 'rails_helper'
|
|||||||
describe CodeOcean::FilesController do
|
describe CodeOcean::FilesController do
|
||||||
render_views
|
render_views
|
||||||
|
|
||||||
let(:user) { create(:admin) }
|
let(:contributor) { create(:admin) }
|
||||||
|
|
||||||
before { allow(controller).to receive(:current_user).and_return(user) }
|
before { allow(controller).to receive(:current_user).and_return(contributor) }
|
||||||
|
|
||||||
describe 'GET #show_protected_upload' do
|
describe 'GET #show_protected_upload' do
|
||||||
context 'with a valid filename' do
|
context 'with a valid filename' do
|
||||||
@ -30,7 +30,7 @@ describe CodeOcean::FilesController do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe 'POST #create' do
|
describe 'POST #create' do
|
||||||
let(:submission) { create(:submission, user:) }
|
let(:submission) { create(:submission, contributor:) }
|
||||||
|
|
||||||
context 'with a valid file' do
|
context 'with a valid file' do
|
||||||
let(:perform_request) { proc { post :create, params: {code_ocean_file: build(:file, context: submission).attributes, format: :json} } }
|
let(:perform_request) { proc { post :create, params: {code_ocean_file: build(:file, context: submission).attributes, format: :json} } }
|
||||||
|
@ -164,7 +164,7 @@ describe ExercisesController do
|
|||||||
expect_assigns(exercise: :exercise)
|
expect_assigns(exercise: :exercise)
|
||||||
|
|
||||||
context 'with an existing submission' do
|
context 'with an existing submission' do
|
||||||
let!(:submission) { create(:submission, exercise_id: exercise.id, user_id: user.id, user_type: user.class.name) }
|
let!(:submission) { create(:submission, exercise:, contributor: user) }
|
||||||
|
|
||||||
it "populates the editors with the submission's files' content" do
|
it "populates the editors with the submission's files' content" do
|
||||||
perform_request.call
|
perform_request.call
|
||||||
@ -260,18 +260,18 @@ describe ExercisesController do
|
|||||||
let(:external_user) { create(:external_user) }
|
let(:external_user) { create(:external_user) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
2.times { create(:submission, cause: 'autosave', user: external_user, exercise:) }
|
create_list(:submission, 2, cause: 'autosave', contributor: external_user, exercise:)
|
||||||
2.times { create(:submission, cause: 'run', user: external_user, exercise:) }
|
create_list(:submission, 2, cause: 'run', contributor: external_user, exercise:)
|
||||||
create(:submission, cause: 'assess', user: external_user, exercise:)
|
create(:submission, cause: 'assess', contributor: external_user, exercise:)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when viewing the default submission statistics page without a parameter' do
|
context 'when viewing the default submission statistics page without a parameter' do
|
||||||
it 'does not list autosaved submissions' do
|
it 'does not list autosaved submissions' do
|
||||||
perform_request
|
perform_request
|
||||||
expect(assigns(:all_events).filter {|event| event.is_a? Submission }).to contain_exactly(
|
expect(assigns(:all_events).filter {|event| event.is_a? Submission }).to contain_exactly(
|
||||||
an_object_having_attributes(cause: 'run', user_id: external_user.id),
|
an_object_having_attributes(cause: 'run', contributor: external_user),
|
||||||
an_object_having_attributes(cause: 'assess', user_id: external_user.id),
|
an_object_having_attributes(cause: 'assess', contributor: external_user),
|
||||||
an_object_having_attributes(cause: 'run', user_id: external_user.id)
|
an_object_having_attributes(cause: 'run', contributor: external_user)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -283,7 +283,7 @@ describe ExercisesController do
|
|||||||
perform_request
|
perform_request
|
||||||
submissions = assigns(:all_events).filter {|event| event.is_a? Submission }
|
submissions = assigns(:all_events).filter {|event| event.is_a? Submission }
|
||||||
expect(submissions).to match_array Submission.all
|
expect(submissions).to match_array Submission.all
|
||||||
expect(submissions).to include an_object_having_attributes(cause: 'autosave', user_id: external_user.id)
|
expect(submissions).to include an_object_having_attributes(cause: 'autosave', contributor: external_user)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -291,7 +291,7 @@ describe ExercisesController do
|
|||||||
describe 'POST #submit' do
|
describe 'POST #submit' do
|
||||||
let(:output) { {} }
|
let(:output) { {} }
|
||||||
let(:perform_request) { post :submit, format: :json, params: {id: exercise.id, submission: {cause: 'submit', exercise_id: exercise.id}} }
|
let(:perform_request) { post :submit, format: :json, params: {id: exercise.id, submission: {cause: 'submit', exercise_id: exercise.id}} }
|
||||||
let(:user) { create(:external_user) }
|
let(:contributor) { create(:external_user) }
|
||||||
let(:scoring_response) do
|
let(:scoring_response) do
|
||||||
[{
|
[{
|
||||||
status: :ok,
|
status: :ok,
|
||||||
@ -312,8 +312,8 @@ describe ExercisesController do
|
|||||||
end
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
create(:lti_parameter, external_user: user, exercise:)
|
create(:lti_parameter, external_user: contributor, exercise:)
|
||||||
submission = build(:submission, exercise:, user:)
|
submission = build(:submission, exercise:, contributor:)
|
||||||
allow(submission).to receive_messages(normalized_score: 1, calculate_score: scoring_response, redirect_to_feedback?: false)
|
allow(submission).to receive_messages(normalized_score: 1, calculate_score: scoring_response, redirect_to_feedback?: false)
|
||||||
allow(Submission).to receive(:create).and_return(submission)
|
allow(Submission).to receive(:create).and_return(submission)
|
||||||
end
|
end
|
||||||
|
@ -210,7 +210,7 @@ describe SessionsController do
|
|||||||
# Todo replace session with lti_parameter
|
# Todo replace session with lti_parameter
|
||||||
# Todo create LtiParameter Object
|
# Todo create LtiParameter Object
|
||||||
# session[:lti_parameters] = {}
|
# session[:lti_parameters] = {}
|
||||||
allow(controller).to receive(:current_user).and_return(submission.user)
|
allow(controller).to receive(:current_user).and_return(submission.contributor)
|
||||||
perform_request.call
|
perform_request.call
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -6,9 +6,9 @@ describe SubmissionsController do
|
|||||||
render_views
|
render_views
|
||||||
|
|
||||||
let(:submission) { create(:submission) }
|
let(:submission) { create(:submission) }
|
||||||
let(:user) { create(:admin) }
|
let(:contributor) { create(:admin) }
|
||||||
|
|
||||||
before { allow(controller).to receive(:current_user).and_return(user) }
|
before { allow(controller).to receive(:current_user).and_return(contributor) }
|
||||||
|
|
||||||
describe 'POST #create' do
|
describe 'POST #create' do
|
||||||
before do
|
before do
|
||||||
|
@ -22,12 +22,12 @@ describe 'Editor', js: true do
|
|||||||
weight: 2.0,
|
weight: 2.0,
|
||||||
}]
|
}]
|
||||||
end
|
end
|
||||||
let(:user) { create(:teacher) }
|
let(:contributor) { create(:teacher) }
|
||||||
let(:exercise_without_test) { create(:tdd) }
|
let(:exercise_without_test) { create(:tdd) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
visit(sign_in_path)
|
visit(sign_in_path)
|
||||||
fill_in('email', with: user.email)
|
fill_in('email', with: contributor.email)
|
||||||
fill_in('password', with: attributes_for(:teacher)[:password])
|
fill_in('password', with: attributes_for(:teacher)[:password])
|
||||||
click_button(I18n.t('sessions.new.link'))
|
click_button(I18n.t('sessions.new.link'))
|
||||||
allow_any_instance_of(LtiHelper).to receive(:lti_outcome_service?).and_return(true)
|
allow_any_instance_of(LtiHelper).to receive(:lti_outcome_service?).and_return(true)
|
||||||
@ -111,7 +111,7 @@ describe 'Editor', js: true do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'contains a button for submitting the exercise' do
|
it 'contains a button for submitting the exercise' do
|
||||||
submission = build(:submission, user:, exercise:)
|
submission = build(:submission, contributor:, exercise:)
|
||||||
allow(submission).to receive(:calculate_score).and_return(scoring_response)
|
allow(submission).to receive(:calculate_score).and_return(scoring_response)
|
||||||
allow(Submission).to receive(:find).and_return(submission)
|
allow(Submission).to receive(:find).and_return(submission)
|
||||||
click_button(I18n.t('exercises.editor.score'))
|
click_button(I18n.t('exercises.editor.score'))
|
||||||
|
@ -9,10 +9,10 @@ describe 'ExternalUserStatistics', js: true do
|
|||||||
let(:password) { 'password123456' }
|
let(:password) { 'password123456' }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
2.times { create(:submission, cause: 'autosave', user: learner, exercise:, study_group:) }
|
2.times { create(:submission, cause: 'autosave', contributor: learner, exercise:, study_group:) }
|
||||||
2.times { create(:submission, cause: 'run', user: learner, exercise:, study_group:) }
|
2.times { create(:submission, cause: 'run', contributor: learner, exercise:, study_group:) }
|
||||||
create(:submission, cause: 'assess', user: learner, exercise:, study_group:)
|
create(:submission, cause: 'assess', contributor: learner, exercise:, study_group:)
|
||||||
create(:submission, cause: 'submit', user: learner, exercise:, study_group:)
|
create(:submission, cause: 'submit', contributor: learner, exercise:, study_group:)
|
||||||
|
|
||||||
study_group.external_users << learner
|
study_group.external_users << learner
|
||||||
study_group.internal_users << user
|
study_group.internal_users << user
|
||||||
|
@ -7,7 +7,7 @@ describe Exercise do
|
|||||||
let(:users) { create_list(:external_user, 10) }
|
let(:users) { create_list(:external_user, 10) }
|
||||||
|
|
||||||
def create_submissions
|
def create_submissions
|
||||||
create_list(:submission, 10, cause: 'submit', exercise:, score: Forgery(:basic).number, user: users.sample)
|
create_list(:submission, 10, cause: 'submit', exercise:, score: Forgery(:basic).number, contributor: users.sample)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'validates the number of main files' do
|
it 'validates the number of main files' do
|
||||||
@ -77,7 +77,10 @@ describe Exercise do
|
|||||||
before { create_submissions }
|
before { create_submissions }
|
||||||
|
|
||||||
it 'returns the average score expressed as a percentage' do
|
it 'returns the average score expressed as a percentage' do
|
||||||
maximum_percentages = exercise.submissions.group_by(&:user_id).values.map {|submission| submission.max_by(&:score).score / exercise.maximum_score * 100 }
|
maximum_percentages = exercise.submissions.group_by do |s|
|
||||||
|
[s.contributor_type,
|
||||||
|
s.contributor_id]
|
||||||
|
end.values.map {|submission| submission.max_by(&:score).score / exercise.maximum_score * 100 }
|
||||||
expect(exercise.average_percentage).to eq(maximum_percentages.average.round(2))
|
expect(exercise.average_percentage).to eq(maximum_percentages.average.round(2))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -96,7 +99,10 @@ describe Exercise do
|
|||||||
before { create_submissions }
|
before { create_submissions }
|
||||||
|
|
||||||
it "returns the average of all users' maximum scores" do
|
it "returns the average of all users' maximum scores" do
|
||||||
maximum_scores = exercise.submissions.group_by(&:user_id).values.map {|submission| submission.max_by(&:score).score }
|
maximum_scores = exercise.submissions.group_by do |s|
|
||||||
|
[s.contributor_type,
|
||||||
|
s.contributor_id]
|
||||||
|
end.values.map {|submission| submission.max_by(&:score).score }
|
||||||
expect(exercise.average_score).to be_within(0.1).of(maximum_scores.average)
|
expect(exercise.average_score).to be_within(0.1).of(maximum_scores.average)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -13,8 +13,8 @@ describe Submission do
|
|||||||
expect(described_class.create.errors[:exercise]).to be_present
|
expect(described_class.create.errors[:exercise]).to be_present
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'validates the presence of a user' do
|
it 'validates the presence of a contributor' do
|
||||||
expect(described_class.create.errors[:user]).to be_present
|
expect(described_class.create.errors[:contributor]).to be_present
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#main_file' do
|
describe '#main_file' do
|
||||||
@ -67,19 +67,19 @@ describe Submission do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe '#siblings' do
|
describe '#siblings' do
|
||||||
let(:siblings) { described_class.find_by(user:).siblings }
|
let(:siblings) { described_class.find_by(contributor:).siblings }
|
||||||
let(:user) { create(:external_user) }
|
let(:contributor) { create(:external_user) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
10.times.each_with_index do |_, index|
|
10.times.each_with_index do |_, index|
|
||||||
create(:submission, exercise: submission.exercise, user: (index.even? ? user : create(:external_user)))
|
create(:submission, exercise: submission.exercise, contributor: (index.even? ? contributor : create(:external_user)))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns all the creator's submissions for the same exercise" do
|
it "returns all the creator's submissions for the same exercise" do
|
||||||
expect(siblings).to be_an(ActiveRecord::Relation)
|
expect(siblings).to be_an(ActiveRecord::Relation)
|
||||||
expect(siblings.map(&:exercise).uniq).to eq([submission.exercise])
|
expect(siblings.map(&:exercise).uniq).to eq([submission.exercise])
|
||||||
expect(siblings.map(&:user).uniq).to eq([user])
|
expect(siblings.map(&:contributor).uniq).to eq([contributor])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -92,8 +92,8 @@ describe Submission do
|
|||||||
describe '#redirect_to_feedback?' do
|
describe '#redirect_to_feedback?' do
|
||||||
context 'with no exercise feedback' do
|
context 'with no exercise feedback' do
|
||||||
let(:exercise) { create(:dummy) }
|
let(:exercise) { create(:dummy) }
|
||||||
let(:user) { build(:external_user, id: (11 - (exercise.created_at.to_i % 10)) % 10) }
|
let(:contributor) { build(:external_user, id: (11 - (exercise.created_at.to_i % 10)) % 10) }
|
||||||
let(:submission) { build(:submission, exercise:, user:) }
|
let(:submission) { build(:submission, exercise:, contributor:) }
|
||||||
|
|
||||||
it 'sends 10% of users to feedback page' do
|
it 'sends 10% of users to feedback page' do
|
||||||
expect(submission.send(:redirect_to_feedback?)).to be_truthy
|
expect(submission.send(:redirect_to_feedback?)).to be_truthy
|
||||||
@ -101,7 +101,7 @@ describe Submission do
|
|||||||
|
|
||||||
it 'does not redirect other users' do
|
it 'does not redirect other users' do
|
||||||
9.times do |i|
|
9.times do |i|
|
||||||
submission = build(:submission, exercise:, user: build(:external_user, id: (11 - (exercise.created_at.to_i % 10)) - i - 1))
|
submission = build(:submission, exercise:, contributor: build(:external_user, id: (11 - (exercise.created_at.to_i % 10)) - i - 1))
|
||||||
expect(submission.send(:redirect_to_feedback?)).to be_falsey
|
expect(submission.send(:redirect_to_feedback?)).to be_falsey
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -109,8 +109,8 @@ describe Submission do
|
|||||||
|
|
||||||
context 'with little exercise feedback' do
|
context 'with little exercise feedback' do
|
||||||
let(:exercise) { create(:dummy_with_user_feedbacks) }
|
let(:exercise) { create(:dummy_with_user_feedbacks) }
|
||||||
let(:user) { build(:external_user, id: (11 - (exercise.created_at.to_i % 10)) % 10) }
|
let(:contributor) { build(:external_user, id: (11 - (exercise.created_at.to_i % 10)) % 10) }
|
||||||
let(:submission) { build(:submission, exercise:, user:) }
|
let(:submission) { build(:submission, exercise:, contributor:) }
|
||||||
|
|
||||||
it 'sends 10% of users to feedback page' do
|
it 'sends 10% of users to feedback page' do
|
||||||
expect(submission.send(:redirect_to_feedback?)).to be_truthy
|
expect(submission.send(:redirect_to_feedback?)).to be_truthy
|
||||||
@ -118,7 +118,7 @@ describe Submission do
|
|||||||
|
|
||||||
it 'does not redirect other users' do
|
it 'does not redirect other users' do
|
||||||
9.times do |i|
|
9.times do |i|
|
||||||
submission = build(:submission, exercise:, user: build(:external_user, id: (11 - (exercise.created_at.to_i % 10)) - i - 1))
|
submission = build(:submission, exercise:, contributor: build(:external_user, id: (11 - (exercise.created_at.to_i % 10)) - i - 1))
|
||||||
expect(submission.send(:redirect_to_feedback?)).to be_falsey
|
expect(submission.send(:redirect_to_feedback?)).to be_falsey
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -7,7 +7,7 @@ describe RequestForCommentPolicy do
|
|||||||
|
|
||||||
context 'when the RfC visibility is not considered' do
|
context 'when the RfC visibility is not considered' do
|
||||||
let(:submission) { create(:submission, study_group: create(:study_group)) }
|
let(:submission) { create(:submission, study_group: create(:study_group)) }
|
||||||
let(:rfc) { create(:rfc, submission:, user: submission.user) }
|
let(:rfc) { create(:rfc, submission:, user: submission.contributor) }
|
||||||
|
|
||||||
%i[destroy? edit?].each do |action|
|
%i[destroy? edit?].each do |action|
|
||||||
permissions(action) do
|
permissions(action) do
|
||||||
|
@ -20,8 +20,8 @@ describe SubmissionPolicy do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'grants access to authors' do
|
it 'grants access to authors' do
|
||||||
user = create(:external_user)
|
contributor = create(:external_user)
|
||||||
expect(policy).to permit(user, build(:submission, exercise: Exercise.new, user_id: user.id, user_type: user.class.name))
|
expect(policy).to permit(contributor, build(:submission, exercise: Exercise.new, contributor:))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
Reference in New Issue
Block a user