Skip to content

Commit 9fc7e3e

Browse files
committed
Further improve performance of exercise statistics
This commit mainly improves the handling and determination of the deadline status (by preloading the records, using a dedicated SQL query).
1 parent 1dd16f1 commit 9fc7e3e

File tree

4 files changed

+34
-32
lines changed

4 files changed

+34
-32
lines changed

app/controllers/exercises_controller.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -497,9 +497,10 @@ def statistics
497497
contributor_statistics = {InternalUser => {}, ExternalUser => {}, ProgrammingGroup => {}}
498498

499499
query = SubmissionPolicy::DeadlineScope.new(current_user, Submission).resolve
500-
.select('contributor_id, contributor_type, MAX(score) AS maximum_score, COUNT(id) AS runs')
500+
.select("contributor_id, contributor_type, MAX(score) AS maximum_score, COUNT(id) AS runs, MAX(updated_at) FILTER (WHERE cause IN ('submit', 'assess', 'remoteSubmit', 'remoteAssess')) AS updated_at, exercise_id")
501501
.where(exercise_id: @exercise.id)
502-
.group('contributor_id, contributor_type')
502+
.group('contributor_id, contributor_type, exercise_id')
503+
.includes(:contributor, :exercise)
503504

504505
query.each do |tuple|
505506
contributor_statistics[tuple.contributor_type.constantize][tuple.contributor] = tuple

app/controllers/external_users_controller.rb

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,14 @@ def working_time_query(tag = nil)
3232
bar.exercise_id,
3333
max(score) as maximum_score,
3434
count(bar.id) as runs,
35-
sum(working_time_new) AS working_time
35+
sum(working_time_new) AS working_time,
36+
max_updated_at as updated_at
3637
FROM
3738
(SELECT contributor_id,
3839
exercise_id,
3940
score,
4041
id,
42+
max_updated_at,
4143
CASE
4244
WHEN #{StatisticsHelper.working_time_larger_delta} THEN '0'
4345
ELSE working_time
@@ -46,7 +48,9 @@ def working_time_query(tag = nil)
4648
(SELECT contributor_id,
4749
exercise_id,
4850
max(score) AS score,
51+
max(filtered_submissions.updated_at) FILTER (WHERE cause IN ('submit', 'assess', 'remoteSubmit', 'remoteAssess')) AS max_updated_at,
4952
filtered_submissions.id,
53+
filtered_submissions.updated_at,
5054
(filtered_submissions.updated_at - lag(filtered_submissions.updated_at) over (PARTITION BY contributor_id, exercise_id
5155
ORDER BY filtered_submissions.updated_at)) AS working_time
5256
FROM filtered_submissions
@@ -61,7 +65,8 @@ def working_time_query(tag = nil)
6165
) AS bar
6266
#{tag.nil? ? '' : " JOIN exercise_tags et ON et.exercise_id = bar.exercise_id AND #{ExternalUser.sanitize_sql(['et.tag_id = ?', tag])}"}
6367
GROUP BY contributor_id,
64-
bar.exercise_id;
68+
bar.exercise_id,
69+
max_updated_at;
6570
"
6671
end
6772

@@ -75,12 +80,13 @@ def statistics
7580

7681
statistics = {}
7782

78-
working_time_statistics = ApplicationRecord.connection.exec_query(working_time_query(tag&.id))
79-
attempted_exercises = Exercise.where(id: working_time_statistics.pluck('exercise_id'))
83+
# We fake the statistics hash to be "submissions"
84+
# Available are: contributor_id, exercise_id, maximum_score, runs, working_time, updated_at
85+
working_time_statistics = Submission.find_by_sql(working_time_query(tag&.id))
86+
ActiveRecord::Associations::Preloader.new(records: working_time_statistics, associations: [:exercise]).call
8087
working_time_statistics.each do |tuple|
81-
tuple = tuple.merge('working_time' => format_time_difference(tuple['working_time']))
82-
exercise = attempted_exercises.find {|attempted_exercise| attempted_exercise.id == tuple['exercise_id'] }
83-
statistics[exercise] = tuple
88+
tuple['working_time'] = format_time_difference(tuple['working_time'])
89+
statistics[tuple.exercise] = tuple
8490
end
8591

8692
render locals: {

app/views/exercises/statistics.html.slim

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,12 @@ h1 = @exercise
8080
td = link_to_if user_type == ExternalUser && policy(contributor).statistics?, contributor.displayname, statistics_external_user_exercise_path(contributor, @exercise)
8181
td = submission_stat['maximum_score'] || '0.0'
8282
td.align-middle
83-
- latest_user_submission = @exercise.final_submission(contributor)
84-
- if latest_user_submission.present?
85-
- if latest_user_submission.before_deadline?
83+
- if submission_stat.updated_at.present?
84+
- if submission_stat.before_deadline?
8685
.deadline-result.positive-result
87-
- elsif latest_user_submission.within_grace_period?
86+
- elsif submission_stat.within_grace_period?
8887
.deadline-result.unknown-result
89-
- elsif latest_user_submission.after_late_deadline?
88+
- elsif submission_stat.after_late_deadline?
9089
.deadline-result.negative-result
9190
td = submission_stat['runs'] if policy(@exercise).detailed_statistics?
9291
td = @exercise.average_working_time_for(contributor) || '00:00:00' if policy(@exercise).detailed_statistics?

app/views/external_users/statistics.html.slim

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,19 @@ h1 = t('.title', user: @user.displayname)
1111
th.header = t('.runs') if policy(statistics.keys.first).detailed_statistics?
1212
th.header = t('.worktime') if policy(statistics.keys.first).detailed_statistics?
1313
tbody
14-
- statistics.each do |exercise, stats|
15-
// Grab any submission in context of study group (or all if admin). Then check for permission
16-
- any_submission = @user.submissions.where(exercise:).first
17-
- if any_submission && policy(any_submission.study_group).show?
18-
tr
19-
td = link_to exercise, statistics_external_user_exercise_path(@user, exercise)
20-
td = stats['maximum_score'] || 0.0
21-
td.align-middle
22-
- latest_viewable_submission = exercise.final_submission(@user)
23-
- if latest_viewable_submission.present?
24-
- if latest_viewable_submission.before_deadline?
25-
.deadline-result.positive-result.before_deadline
26-
- elsif latest_viewable_submission.within_grace_period?
27-
.deadline-result.unknown-result.within_grace_period
28-
- elsif latest_viewable_submission.after_late_deadline?
29-
.deadline-result.negative-result.after_late_deadline
30-
td = stats['runs'] || 0 if policy(exercise).detailed_statistics?
31-
td = stats['working_time'] || '00:00:00' if policy(exercise).detailed_statistics?
14+
- statistics.each do |exercise, submission_stat|
15+
tr
16+
td = link_to exercise, statistics_external_user_exercise_path(@user, exercise)
17+
td = submission_stat['maximum_score'] || 0.0
18+
td.align-middle
19+
- if submission_stat.updated_at.present?
20+
- if submission_stat.before_deadline?
21+
.deadline-result.positive-result
22+
- elsif submission_stat.within_grace_period?
23+
.deadline-result.unknown-result
24+
- elsif submission_stat.after_late_deadline?
25+
.deadline-result.negative-result
26+
td = submission_stat['runs'] || 0 if policy(exercise).detailed_statistics?
27+
td = submission_stat['working_time'] || '00:00:00' if policy(exercise).detailed_statistics?
3228
- else
3329
= t('exercises.external_users.statistics.no_data_available')

0 commit comments

Comments
 (0)