88 lines
3.3 KiB
Ruby
88 lines
3.3 KiB
Ruby
#
|
|
# Copyright (C) 2018 - present Instructure, Inc.
|
|
#
|
|
# This file is part of Canvas.
|
|
#
|
|
# Canvas is free software: you can redistribute it and/or modify it under
|
|
# the terms of the GNU Affero General Public License as published by the Free
|
|
# Software Foundation, version 3 of the License.
|
|
#
|
|
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
# details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License along
|
|
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
|
|
class AssignmentScoreStatisticsGenerator
|
|
def self.update_score_statistics_in_singleton(course_id)
|
|
# The 60s delay below is in case lots of little grade calculator
|
|
# updates come close together. Since we're a singleton, they won't
|
|
# queue up additional jobs if one exists. Our goal is to try to
|
|
# not run this potentially expensive query constantly.
|
|
send_later_if_production_enqueue_args(
|
|
:update_score_statistics,
|
|
{
|
|
singleton: "AssignmentScoreStatisticsGenerator:#{course_id}",
|
|
run_at: 60.seconds.from_now
|
|
},
|
|
course_id
|
|
)
|
|
end
|
|
|
|
def self.update_score_statistics(course_id)
|
|
course = Course.find(course_id)
|
|
|
|
# performance note: There is an overlap between
|
|
# Submission.not_placeholder and the submission where clause.
|
|
#
|
|
# note: because a score is needed for max/min/ave we are not filtering
|
|
# by assignment_student_visibilities, if a stat is added that doesn't
|
|
# require score then add a filter when the DA feature is on
|
|
statistics = Shackles.activate(:slave) do
|
|
course.assignments.published.preload(score_statistic: :assignment).
|
|
joins(:submissions).
|
|
joins("INNER JOIN #{Enrollment.quoted_table_name} enrollments ON submissions.user_id = enrollments.user_id").
|
|
merge(course.all_enrollments.of_student_type.active_or_pending).
|
|
merge(Submission.not_placeholder.where("submissions.excused IS NOT TRUE")).
|
|
where.not(submissions: { score: nil }).
|
|
where(submissions: { workflow_state: 'graded' }).
|
|
group("assignments.id").
|
|
select("assignments.id, max(score) max, min(score) min, avg(score) avg, count(submissions.id) count").to_a
|
|
end
|
|
|
|
connection = ScoreStatistic.connection
|
|
now = connection.quote(Time.now.utc)
|
|
bulk_values = statistics.map do |assignment|
|
|
values =
|
|
[
|
|
connection.quote(assignment.id),
|
|
connection.quote(assignment.max),
|
|
connection.quote(assignment.min),
|
|
connection.quote(assignment.avg),
|
|
connection.quote(assignment.count),
|
|
now,
|
|
now
|
|
].join(',')
|
|
"(#{values})"
|
|
end
|
|
|
|
bulk_values.each_slice(100) do |bulk_slice|
|
|
connection.execute(<<~SQL)
|
|
INSERT INTO #{ScoreStatistic.quoted_table_name}
|
|
(assignment_id, maximum, minimum, mean, count, created_at, updated_at)
|
|
VALUES #{bulk_slice.join(',')}
|
|
ON CONFLICT (assignment_id)
|
|
DO UPDATE SET
|
|
minimum = excluded.minimum,
|
|
maximum = excluded.maximum,
|
|
mean = excluded.mean,
|
|
count = excluded.count,
|
|
updated_at = excluded.updated_at
|
|
SQL
|
|
end
|
|
end
|
|
end
|