Skip to content

Commit e7f4d78

Browse files
committed
Refactor statistic rendering to improve performance
- With these changes, we mostly pass the desired objects to the view, so that the results can be re-used there. - Furthermore, we improve the statistics shown for teachers, so that these align more with the expectations (and the actual submissions belonging to the teacher's study groups).
1 parent 9181092 commit e7f4d78

File tree

7 files changed

+54
-61
lines changed

7 files changed

+54
-61
lines changed

app/controllers/exercises_controller.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -494,14 +494,14 @@ def reload
494494

495495
def statistics
496496
# Show general statistic page for specific exercise
497-
contributor_statistics = {'InternalUser' => {}, 'ExternalUser' => {}, 'ProgrammingGroup' => {}}
497+
contributor_statistics = {InternalUser => {}, ExternalUser => {}, ProgrammingGroup => {}}
498498

499499
query = policy_scope(Submission).select('contributor_id, contributor_type, MAX(score) AS maximum_score, COUNT(id) AS runs')
500500
.where(exercise_id: @exercise.id)
501501
.group('contributor_id, contributor_type')
502502

503503
query.each do |tuple|
504-
contributor_statistics[tuple['contributor_type']][tuple['contributor_id'].to_i] = tuple
504+
contributor_statistics[tuple.contributor_type.constantize][tuple.contributor] = tuple
505505
end
506506

507507
render locals: {

app/controllers/external_users_controller.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,12 @@ def statistics
6969

7070
statistics = {}
7171

72-
ApplicationRecord.connection.exec_query(working_time_query(tag&.id)).each do |tuple|
72+
working_time_statistics = ApplicationRecord.connection.exec_query(working_time_query(tag&.id))
73+
attempted_exercises = Exercise.where(id: working_time_statistics.pluck('exercise_id'))
74+
working_time_statistics.each do |tuple|
7375
tuple = tuple.merge('working_time' => format_time_difference(tuple['working_time']))
74-
statistics[tuple['exercise_id'].to_i] = tuple
76+
exercise = attempted_exercises.find {|attempted_exercise| attempted_exercise.id == tuple['exercise_id'] }
77+
statistics[exercise] = tuple
7578
end
7679

7780
render locals: {

app/models/exercise.rb

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@ class Exercise < ApplicationRecord
5151

5252
MAX_GROUP_EXERCISE_FEEDBACKS = 20
5353

54-
def average_percentage
55-
if average_score && (maximum_score.to_d != BigDecimal('0.0')) && submissions.exists?(cause: 'submit')
56-
(average_score / maximum_score * 100).round(2)
54+
def average_percentage(base = submissions)
55+
if average_score(base) && (maximum_score.to_d != BigDecimal('0.0')) && base.exists?(cause: 'submit')
56+
(average_score(base) / maximum_score * 100).round(2)
5757
else
5858
0
5959
end
@@ -67,9 +67,9 @@ def finishers_percentage
6767
end
6868
end
6969

70-
def average_score
70+
def average_score(base = submissions)
7171
Submission.from(
72-
submissions.group(:contributor_id, :contributor_type)
72+
base.group(:contributor_id, :contributor_type)
7373
.select('MAX(score) as max_score')
7474
).average(:max_score).to_f
7575
end
@@ -549,8 +549,8 @@ def solved_by?(contributor)
549549
maximum_score(contributor).to_i == maximum_score.to_i
550550
end
551551

552-
def finishers_count
553-
Submission.from(submissions.where(score: maximum_score, cause: %w[submit assess remoteSubmit remoteAssess]).group(:contributor_id, :contributor_type).select(:contributor_id, :contributor_type), 'submissions').count
552+
def finishers_count(base = submissions)
553+
Submission.from(base.where(score: maximum_score, cause: %w[submit assess remoteSubmit remoteAssess]).group(:contributor_id, :contributor_type).select(:contributor_id, :contributor_type), 'submissions').count
554554
end
555555

556556
def set_default_values

app/models/submission.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class Submission < ApplicationRecord
4444
joins(:exercise).where('submissions.updated_at > exercises.late_submission_deadline')
4545
}
4646

47-
scope :latest, -> { order(updated_at: :desc).first }
47+
scope :latest, -> { order(submissions: {updated_at: :desc}).first }
4848

4949
validates :cause, inclusion: {in: CAUSES}
5050

app/views/exercises/statistics.html.slim

Lines changed: 27 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,53 +5,50 @@
55
- append_javascript_pack_tag('d3-tip')
66
h1 = @exercise
77

8-
= row(label: '.participants', value: @exercise.contributors.size)
8+
- statistic_base = policy_scope(Submission).where(exercise: @exercise).unscope(where: :cause)
9+
- participants = statistic_base.group(:contributor_id, :contributor_type).count.size
10+
- finishers_count = @exercise.finishers_count(statistic_base)
11+
- average_score = @exercise.average_score(statistic_base)
12+
- average_percentage = @exercise.average_percentage(statistic_base)
13+
14+
= row(label: '.participants', value: participants)
915

1016
- %i[intermediate final].each do |scope|
1117
/ i18n-tasks-use t('.intermediate_submissions') t('.final_submissions')
1218
= row(label: ".#{scope}_submissions") do
1319
/TODO: Refactor next line
14-
= "#{@exercise.submissions.send(scope).count} (#{t('.users_and_programming_groups', count: Submission.from(@exercise.submissions.send(scope).group(:contributor_id, :contributor_type).select(:contributor_id, :contributor_type)).count)})"
20+
= "#{statistic_base.send(scope).count} (#{t('.users_and_programming_groups', count: Submission.from(statistic_base.send(scope).group(:contributor_id, :contributor_type).select(:contributor_id, :contributor_type)).count)})"
1521

1622
= row(label: '.finishing_rate') do
1723
p
18-
- if @exercise.finishers_count
19-
span.number
20-
= @exercise.finishers_count
21-
=<> t('shared.out_of')
22-
span.number
23-
= @exercise.contributors.size
24-
=< t('exercises.statistics.external_users')
24+
- if finishers_count
25+
= t('.users_and_programming_groups', count: "#{finishers_count} #{t('shared.out_of')} #{participants}")
2526
- else
2627
= empty
27-
- finishers_count = @exercise.contributors.size
28-
- finishers_percentage = finishers_count.zero? ? 0 : (100.0 / finishers_count * @exercise.finishers_count).round(2)
28+
- finishers_percentage = participants.zero? ? 0 : (100.0 / participants * finishers_count).round(2)
2929
p = progress_bar(finishers_percentage)
3030

3131
= row(label: '.average_score') do
3232
p
33-
- if @exercise.average_score
34-
span.number
35-
= @exercise.average_score.round(2)
33+
- if average_score
34+
= average_score.round(2)
3635
=<> t('shared.out_of')
37-
span.number
38-
= @exercise.maximum_score
36+
= format('%.1f', @exercise.maximum_score)
3937
- else
4038
= empty
41-
p = progress_bar(@exercise.average_percentage)
39+
p = progress_bar(average_percentage)
4240

4341
- if policy(@exercise).detailed_statistics?
4442
= row(label: '.average_worktime') do
4543
p = @exercise.average_working_time
4644

47-
- {internal_users: t('.internal_users'), external_users: t('.external_users'), programming_groups: t('.programming_groups')}.each_pair do |symbol, label|
48-
- submissions = policy_scope(Submission).where(contributor: @exercise.send(symbol), exercise: @exercise)
49-
- if submissions.any?
50-
strong = label
51-
- if symbol == :external_users
45+
- contributor_statistics.each_pair do |user_type, user_with_submission_stats|
46+
- if user_with_submission_stats.any?
47+
h5 = t(".#{user_type.model_name.collection}")
48+
- if user_type == ExternalUser
5249
- working_time_array = []
53-
- @exercise.send(symbol).distinct.each do |user|
54-
- working_time = @exercise.average_working_time_for(user) || 0
50+
- user_with_submission_stats.each_key do |contributor|
51+
- working_time = @exercise.average_working_time_for(contributor) || 0
5552
- working_time_array.push working_time
5653
hr
5754
.d-none#data data-working-time=ActiveSupport::JSON.encode(working_time_array)
@@ -60,9 +57,8 @@ h1 = @exercise
6057
hr
6158
#chart_2
6259
hr
63-
- contributors = symbol.to_s.classify.constantize.where(id: submissions.joins(symbol).group(:contributor_id).select(:contributor_id).distinct)
6460
.table-responsive.mb-4
65-
table.table.table-striped class=(contributors.present? ? 'sortable' : '')
61+
table.table.table-striped class=(user_with_submission_stats.present? ? 'sortable' : '')
6662
thead
6763
tr
6864
th.header = t('.user')
@@ -71,12 +67,10 @@ h1 = @exercise
7167
th.header = t('.runs') if policy(@exercise).detailed_statistics?
7268
th.header = t('.worktime') if policy(@exercise).detailed_statistics?
7369
tbody
74-
- contributors.each do |contributor|
75-
- us = contributor_statistics[contributor.class.name][contributor.id] || {'maximum_score' => nil, 'runs' => nil}
76-
- label = contributor.displayname.to_s
70+
- user_with_submission_stats.each do |contributor, submission_stat|
7771
tr
78-
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}
79-
td = us['maximum_score'] || 0
72+
td = link_to_if user_type == ExternalUser && policy(contributor).statistics?, contributor.displayname, statistics_external_user_exercise_path(contributor, @exercise)
73+
td = submission_stat['maximum_score'] || '0.0'
8074
td.align-middle
8175
- latest_user_submission = @exercise.final_submission(contributor)
8276
- if latest_user_submission.present?
@@ -86,5 +80,5 @@ h1 = @exercise
8680
.deadline-result.unknown-result
8781
- elsif latest_user_submission.after_late_deadline?
8882
.deadline-result.negative-result
89-
td = us['runs'] if policy(@exercise).detailed_statistics?
90-
td = @exercise.average_working_time_for(contributor) || 0 if policy(@exercise).detailed_statistics?
83+
td = submission_stat['runs'] if policy(@exercise).detailed_statistics?
84+
td = @exercise.average_working_time_for(contributor) || '00:00:00' if policy(@exercise).detailed_statistics?
Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,23 @@
11
h1 = t('.title', user: @user.displayname)
22

3-
- submissions = policy_scope(Submission).where(contributor: @user)
4-
- exercises = Exercise.where(id: submissions.joins(:exercise).group(:exercise_id).select(:exercise_id).distinct).compact
5-
6-
- if submissions.any?
3+
- if statistics.present?
74
.table-responsive
8-
table.table.table-striped class=(exercises.present? ? 'sortable' : '')
5+
table.table.table-striped class=(statistics.present? ? 'sortable' : '')
96
thead
107
tr
118
th.header = t('.exercise')
129
th.header = t('.score')
1310
th.header = t('.deadline')
14-
th.header = t('.runs') if policy(exercises.first).detailed_statistics?
15-
th.header = t('.worktime') if policy(exercises.first).detailed_statistics?
11+
th.header = t('.runs') if policy(statistics.keys.first).detailed_statistics?
12+
th.header = t('.worktime') if policy(statistics.keys.first).detailed_statistics?
1613
tbody
17-
- exercises.each do |exercise|
14+
- statistics.each do |exercise, stats|
1815
// Grab any submission in context of study group (or all if admin). Then check for permission
19-
- any_submission = submissions.where(exercise:).first
20-
- if any_submission && policy(any_submission.study_group).show? && statistics[exercise.id]
21-
- stats = statistics[exercise.id]
16+
- any_submission = @user.submissions.where(exercise:).first
17+
- if any_submission && policy(any_submission.study_group).show?
2218
tr
23-
td = link_to exercise, controller: 'exercises', action: 'external_user_statistics', external_user_id: @user.id, id: exercise.id
24-
td = stats['maximum_score'] || 0
19+
td = link_to exercise, statistics_external_user_exercise_path(@user, exercise)
20+
td = stats['maximum_score'] || 0.0
2521
td.align-middle
2622
- latest_viewable_submission = exercise.final_submission(@user)
2723
- if latest_viewable_submission.present?
@@ -31,7 +27,7 @@ h1 = t('.title', user: @user.displayname)
3127
.deadline-result.unknown-result.within_grace_period
3228
- elsif latest_viewable_submission.after_late_deadline?
3329
.deadline-result.negative-result.after_late_deadline
34-
td = stats['runs'] || 0 if policy(exercises.first).detailed_statistics?
35-
td = stats['working_time'] || 0 if policy(exercises.first).detailed_statistics?
30+
td = stats['runs'] || 0 if policy(exercise).detailed_statistics?
31+
td = stats['working_time'] || '00:00:00' if policy(exercise).detailed_statistics?
3632
- else
3733
= t('exercises.external_users.statistics.no_data_available')

config/locales/en/exercise.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ en:
215215
finishing_rate: Finishing Rate
216216
intermediate_submissions: Intermediate Submissions
217217
internal_users: Internal Users
218-
participants: Participating Users
218+
participants: Participants
219219
programming_groups: Programming Groups
220220
runs: Runs
221221
score: Maximum Score

0 commit comments

Comments
 (0)