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

committed by
Sebastian Serth

parent
97138288f4
commit
0234414bae
@ -27,8 +27,8 @@ class Exercise < ApplicationRecord
|
||||
has_many :exercise_tips
|
||||
has_many :tips, through: :exercise_tips
|
||||
|
||||
has_many :external_users, source: :user, source_type: 'ExternalUser', through: :submissions
|
||||
has_many :internal_users, source: :user, source_type: 'InternalUser', through: :submissions
|
||||
has_many :external_users, source: :contributor, source_type: 'ExternalUser', through: :submissions
|
||||
has_many :internal_users, source: :contributor, source_type: 'InternalUser', through: :submissions
|
||||
alias users external_users
|
||||
|
||||
scope :with_submissions, -> { where('id IN (SELECT exercise_id FROM submissions)') }
|
||||
@ -65,12 +65,10 @@ class Exercise < ApplicationRecord
|
||||
end
|
||||
|
||||
def average_score
|
||||
if submissions.exists?(cause: 'submit')
|
||||
maximum_scores_query = submissions.select('MAX(score) AS maximum_score').group(:user_id).to_sql.sub('$1', id.to_s)
|
||||
self.class.connection.exec_query("SELECT AVG(maximum_score) AS average_score FROM (#{maximum_scores_query}) AS maximum_scores").first['average_score'].to_f
|
||||
else
|
||||
0
|
||||
end
|
||||
Submission.from(
|
||||
submissions.group(:contributor_id, :contributor_type)
|
||||
.select('MAX(score) as max_score')
|
||||
).average(:max_score).to_f
|
||||
end
|
||||
|
||||
def average_number_of_submissions
|
||||
@ -78,64 +76,66 @@ class Exercise < ApplicationRecord
|
||||
user_count.zero? ? 0 : submissions.count / user_count.to_f
|
||||
end
|
||||
|
||||
def time_maximum_score(user)
|
||||
submissions.where(user:).where("cause IN ('submit','assess')").where.not(score: nil).order('score DESC, created_at ASC').first.created_at
|
||||
rescue StandardError
|
||||
Time.zone.at(0)
|
||||
def time_maximum_score(contributor)
|
||||
submissions
|
||||
.where(contributor:, cause: %w[submit assess])
|
||||
.where.not(score: nil)
|
||||
.order(score: :desc, created_at: :asc)
|
||||
.first&.created_at || Time.zone.at(0)
|
||||
end
|
||||
|
||||
def user_working_time_query
|
||||
"
|
||||
SELECT user_id,
|
||||
user_type,
|
||||
SELECT contributor_id,
|
||||
contributor_type,
|
||||
SUM(working_time_new) AS working_time,
|
||||
MAX(score) AS score
|
||||
FROM
|
||||
(SELECT user_id,
|
||||
user_type,
|
||||
(SELECT contributor_id,
|
||||
contributor_type,
|
||||
score,
|
||||
CASE WHEN #{StatisticsHelper.working_time_larger_delta} THEN '0' ELSE working_time END AS working_time_new
|
||||
FROM
|
||||
(SELECT user_id,
|
||||
user_type,
|
||||
(SELECT contributor_id,
|
||||
contributor_type,
|
||||
score,
|
||||
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
|
||||
FROM submissions
|
||||
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
|
||||
|
||||
def study_group_working_time_query(exercise_id, study_group_id, additional_filter)
|
||||
"
|
||||
WITH working_time_between_submissions AS (
|
||||
SELECT submissions.user_id,
|
||||
submissions.user_type,
|
||||
SELECT submissions.contributor_id,
|
||||
submissions.contributor_type,
|
||||
score,
|
||||
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
|
||||
FROM submissions
|
||||
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 (
|
||||
SELECT user_id,
|
||||
user_type,
|
||||
SELECT contributor_id,
|
||||
contributor_type,
|
||||
score,
|
||||
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,
|
||||
CASE WHEN #{StatisticsHelper.working_time_larger_delta} THEN '0' ELSE working_time END AS working_time_filtered
|
||||
FROM working_time_between_submissions
|
||||
),
|
||||
working_times_with_score_expanded AS (
|
||||
SELECT user_id,
|
||||
user_type,
|
||||
SELECT contributor_id,
|
||||
contributor_type,
|
||||
created_at,
|
||||
working_time_filtered,
|
||||
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
|
||||
),
|
||||
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.
|
||||
-- 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.
|
||||
SELECT DISTINCT ON (user_type, user_id, corrected_score) user_id,
|
||||
user_type,
|
||||
SELECT DISTINCT ON (contributor_type, contributor_id, corrected_score) contributor_id,
|
||||
contributor_type,
|
||||
created_at + INTERVAL '1us',
|
||||
'00:00:00' as working_time_filtered,
|
||||
corrected_score
|
||||
FROM working_times_with_score_expanded
|
||||
),
|
||||
working_times_with_score_not_null_and_shifted AS (
|
||||
SELECT user_id,
|
||||
user_type,
|
||||
coalesce(lag(corrected_score) over (PARTITION BY user_type, user_id ORDER BY created_at ASC),
|
||||
SELECT contributor_id,
|
||||
contributor_type,
|
||||
coalesce(lag(corrected_score) over (PARTITION BY contributor_type, contributor_id ORDER BY created_at ASC),
|
||||
0) AS shifted_score,
|
||||
created_at,
|
||||
working_time_filtered
|
||||
FROM working_times_with_duplicated_last_row_per_score
|
||||
),
|
||||
working_times_to_be_sorted AS (
|
||||
SELECT user_id,
|
||||
user_type,
|
||||
SELECT contributor_id,
|
||||
contributor_type,
|
||||
shifted_score AS score,
|
||||
MIN(created_at) AS start_time,
|
||||
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
|
||||
GROUP BY user_id, user_type, score
|
||||
GROUP BY contributor_id, contributor_type, score
|
||||
),
|
||||
working_times_with_index AS (
|
||||
SELECT (dense_rank() over (ORDER BY total_working_time, user_type, user_id ASC) - 1) AS index,
|
||||
user_id,
|
||||
user_type,
|
||||
SELECT (dense_rank() over (ORDER BY total_working_time, contributor_type, contributor_id ASC) - 1) AS index,
|
||||
contributor_id,
|
||||
contributor_type,
|
||||
score,
|
||||
start_time,
|
||||
working_time_per_score,
|
||||
total_working_time
|
||||
FROM working_times_to_be_sorted)
|
||||
SELECT index,
|
||||
user_id,
|
||||
user_type,
|
||||
contributor_id,
|
||||
contributor_type,
|
||||
name,
|
||||
score,
|
||||
start_time,
|
||||
working_time_per_score,
|
||||
total_working_time
|
||||
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
|
||||
SELECT index,
|
||||
user_id,
|
||||
user_type,
|
||||
contributor_id,
|
||||
contributor_type,
|
||||
name,
|
||||
score,
|
||||
start_time,
|
||||
working_time_per_score,
|
||||
total_working_time
|
||||
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;
|
||||
"
|
||||
end
|
||||
@ -218,7 +218,7 @@ class Exercise < ApplicationRecord
|
||||
additional_filter = if user.blank?
|
||||
''
|
||||
else
|
||||
"AND user_id = #{user.id} AND user_type = '#{user.class.name}'"
|
||||
"AND contributor_id = #{user.id} AND contributor_type = '#{user.class.name}'"
|
||||
end
|
||||
|
||||
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'])
|
||||
additional_user_data[bucket][tuple['index']] = {start_time: tuple['start_time'], score: tuple['score']}
|
||||
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
|
||||
|
||||
if results.ntuples.positive?
|
||||
if results.size.positive?
|
||||
first_index = results[0]['index']
|
||||
last_index = results[results.ntuples - 1]['index']
|
||||
last_index = results[results.size - 1]['index']
|
||||
buckets = last_index - first_index
|
||||
user_progress.each do |timings_array|
|
||||
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("
|
||||
WITH working_time AS
|
||||
(
|
||||
SELECT user_id,
|
||||
SELECT contributor_id,
|
||||
id,
|
||||
exercise_id,
|
||||
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
|
||||
WHERE #{self.class.sanitize_sql(['exercise_id = ?', id])}
|
||||
AND user_type = 'ExternalUser'
|
||||
GROUP BY user_id,
|
||||
AND contributor_type = 'ExternalUser'
|
||||
GROUP BY contributor_id,
|
||||
id,
|
||||
exercise_id), max_points AS
|
||||
(
|
||||
@ -286,37 +286,37 @@ class Exercise < ApplicationRecord
|
||||
first_time_max_score AS
|
||||
(
|
||||
SELECT id,
|
||||
user_id,
|
||||
contributor_id,
|
||||
exercise_id,
|
||||
max_score,
|
||||
working_time,
|
||||
rn
|
||||
FROM (
|
||||
SELECT id,
|
||||
user_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
|
||||
WHERE rn = 1), times_until_max_points AS
|
||||
(
|
||||
SELECT w.id,
|
||||
w.user_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
|
||||
WHERE w.user_id = m.user_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
|
||||
all_working_times_until_max AS (
|
||||
(
|
||||
SELECT id,
|
||||
user_id,
|
||||
contributor_id,
|
||||
exercise_id,
|
||||
max_score,
|
||||
working_time
|
||||
@ -324,7 +324,7 @@ class Exercise < ApplicationRecord
|
||||
UNION ALL
|
||||
(
|
||||
SELECT id,
|
||||
user_id,
|
||||
contributor_id,
|
||||
exercise_id,
|
||||
max_score,
|
||||
working_time
|
||||
@ -333,10 +333,10 @@ class Exercise < ApplicationRecord
|
||||
(
|
||||
SELECT 1
|
||||
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
|
||||
(
|
||||
SELECT user_id,
|
||||
SELECT contributor_id,
|
||||
exercise_id,
|
||||
max_score,
|
||||
CASE
|
||||
@ -345,16 +345,16 @@ class Exercise < ApplicationRecord
|
||||
END AS working_time_new
|
||||
FROM all_working_times_until_max ), result AS
|
||||
(
|
||||
SELECT e.external_id AS external_user_id,
|
||||
f.user_id,
|
||||
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
|
||||
WHERE f.user_id = e.id
|
||||
WHERE f.contributor_id = e.id
|
||||
GROUP BY e.external_id,
|
||||
f.user_id,
|
||||
f.contributor_id,
|
||||
exercise_id )
|
||||
SELECT unnest(percentile_cont(#{self.class.sanitize_sql(['array[?]', quantiles])}) within GROUP (ORDER BY working_time))
|
||||
FROM result
|
||||
@ -370,7 +370,7 @@ class Exercise < ApplicationRecord
|
||||
@working_time_statistics = {'InternalUser' => {}, 'ExternalUser' => {}}
|
||||
self.class.connection.exec_query(user_working_time_query).each do |tuple|
|
||||
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
|
||||
|
||||
@ -388,20 +388,20 @@ class Exercise < ApplicationRecord
|
||||
@working_time_statistics[user.class.name][user.id]['working_time']
|
||||
end
|
||||
|
||||
def accumulated_working_time_for_only(user)
|
||||
user_type = user.external_user? ? 'ExternalUser' : 'InternalUser'
|
||||
def accumulated_working_time_for_only(contributor)
|
||||
contributor_type = contributor.class.name
|
||||
begin
|
||||
result = self.class.connection.exec_query("
|
||||
WITH WORKING_TIME AS
|
||||
(SELECT user_id,
|
||||
(SELECT contributor_id,
|
||||
id,
|
||||
exercise_id,
|
||||
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
|
||||
FROM submissions
|
||||
WHERE exercise_id = #{id} AND user_id = #{user.id} AND user_type = '#{user_type}'
|
||||
GROUP BY user_id, id, exercise_id),
|
||||
WHERE exercise_id = #{id} AND contributor_id = #{contributor.id} AND contributor_type = '#{contributor_type}'
|
||||
GROUP BY contributor_id, id, exercise_id),
|
||||
MAX_POINTS AS
|
||||
(SELECT context_id AS ex_id, sum(weight) AS max_points FROM files WHERE context_type = 'Exercise' AND context_id = #{id} AND role 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
|
||||
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 (
|
||||
SELECT id,USER_id,exercise_id,max_score,working_time,
|
||||
ROW_NUMBER() OVER(PARTITION BY user_id, exercise_id ORDER BY id ASC) AS rn
|
||||
SELECT id,contributor_id,exercise_id,max_score,working_time,
|
||||
ROW_NUMBER() OVER(PARTITION BY contributor_id, exercise_id ORDER BY id ASC) AS rn
|
||||
FROM TIME_MAX_SCORE) T
|
||||
WHERE rn = 1),
|
||||
|
||||
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
|
||||
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
|
||||
((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
|
||||
(SELECT id, user_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))),
|
||||
(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.contributor_id = W1.contributor_id AND F.exercise_id = W1.exercise_id))),
|
||||
|
||||
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
|
||||
)
|
||||
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
|
||||
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
|
||||
rescue StandardError
|
||||
@ -508,14 +508,13 @@ class Exercise < ApplicationRecord
|
||||
end
|
||||
private :generate_token
|
||||
|
||||
def maximum_score(user = nil)
|
||||
if user
|
||||
# FIXME: where(user: user) will not work here!
|
||||
begin
|
||||
submissions.where(user:).where("cause IN ('submit','assess')").where.not(score: nil).order('score DESC').first.score || 0
|
||||
rescue StandardError
|
||||
0
|
||||
end
|
||||
def maximum_score(contributor = nil)
|
||||
if contributor
|
||||
submissions
|
||||
.where(contributor:, cause: %w[submit assess])
|
||||
.where.not(score: nil)
|
||||
.order(score: :desc)
|
||||
.first&.score || 0
|
||||
else
|
||||
@maximum_score ||= if files.loaded?
|
||||
files.filter(&:teacher_defined_assessment?).pluck(:weight).sum
|
||||
@ -525,12 +524,12 @@ class Exercise < ApplicationRecord
|
||||
end
|
||||
end
|
||||
|
||||
def final_submission(user)
|
||||
submissions.final.where(user_id: user.id, user_type: user.class.name).order(created_at: :desc).first
|
||||
def final_submission(contributor)
|
||||
submissions.final.order(created_at: :desc).find_by(contributor:)
|
||||
end
|
||||
|
||||
def solved_by?(user)
|
||||
maximum_score(user).to_i == maximum_score.to_i
|
||||
def solved_by?(contributor)
|
||||
maximum_score(contributor).to_i == maximum_score.to_i
|
||||
end
|
||||
|
||||
def finishers
|
||||
@ -587,9 +586,9 @@ cause: %w[submit assess remoteSubmit remoteAssess]}).distinct
|
||||
def last_submission_per_user
|
||||
Submission.joins("JOIN (
|
||||
SELECT
|
||||
user_id,
|
||||
user_type,
|
||||
first_value(id) OVER (PARTITION BY user_id ORDER BY created_at DESC) AS fv
|
||||
contributor_id,
|
||||
contributor_type,
|
||||
first_value(id) OVER (PARTITION BY contributor_id ORDER BY created_at DESC) AS fv
|
||||
FROM submissions
|
||||
WHERE exercise_id = #{id}
|
||||
) AS t ON t.fv = submissions.id").distinct
|
||||
|
Reference in New Issue
Block a user