canvas-lms/lib/api/v1/gradebook_history.rb

177 lines
6.8 KiB
Ruby

module Api::V1
module GradebookHistory
include Api
include Api::V1::Assignment
include Api::V1::Submission
def days_json(course, api_context)
day_hash = Hash.new{|hash, date| hash[date] = {:graders => {}} }
submissions_set(course, api_context).
each_with_object(day_hash) { |submission, day| update_graders_hash(day[day_string_for(submission)][:graders], submission, api_context) }.
each do |date, date_hash|
compress(date_hash, :graders)
date_hash[:graders].each { |grader| compress(grader, :assignments) }
end
day_hash.inject([]) do |memo, (date, hash)|
memo << hash.merge(:date => date)
end.sort_by { |a| a[:date] }.reverse
end
def json_for_date(date, course, api_context)
submissions_set(course, api_context, :date => date).
each_with_object(Hash.new) { |sub, memo| update_graders_hash(memo, sub, api_context) }.values.
each { |grader| compress(grader, :assignments) }
end
def version_json(course, version, api_context, opts={})
submission = opts[:submission] || version.versionable
assignment = opts[:assignment] || submission.assignment
student = opts[:student] || submission.user
current_grader = submission.grader || default_grader
model = version.model
json = model.without_versioned_attachments do
submission_attempt_json(model, assignment, api_context.user, api_context.session, nil, course).with_indifferent_access
end
grader = (json[:grader_id] && json[:grader_id] > 0 && user_cache[json[:grader_id]]) || default_grader
json = json.merge(
:grader => grader.name,
:assignment_name => assignment.title,
:user_name => student.name,
:current_grade => submission.grade,
:current_graded_at => submission.graded_at,
:current_grader => current_grader.name
)
json
end
def versions_json(course, versions, api_context, opts={})
# preload for efficiency
unless opts[:submission]
::Version.send(:preload_associations, versions, :versionable)
submissions = versions.map(&:versionable)
::Submission.send(:preload_associations, submissions, :assignment) unless opts[:assignment]
::Submission.send(:preload_associations, submissions, :user) unless opts[:student]
::Submission.send(:preload_associations, submissions, :grader)
end
versions.map do |version|
submission = opts[:submission] || version.versionable
assignment = opts[:assignment] || submission.assignment
student = opts[:student] || submission.user
version_json(course, version, api_context, :submission => submission, :assignment => assignment, :student => student)
end
end
def submissions_for(course, api_context, date, grader_id, assignment_id)
assignment = ::Assignment.find(assignment_id)
options = {:date => date, :assignment_id => assignment_id, :grader_id => grader_id}
submissions = submissions_set(course, api_context, options)
# load all versions for the given submissions and back-populate their
# versionable associations
submission_index = submissions.index_by(&:id)
versions = Version.where(:versionable_type => 'Submission', :versionable_id => submissions).order('number DESC')
versions.each{ |version| version.versionable = submission_index[version.versionable_id] }
# convert them all to json and then group by submission
versions = versions_json(course, versions, api_context, :assignment => assignment)
versions_hash = versions.group_by{ |version| version[:id] }
# populate previous_* and new_* keys and convert hash to array of objects
versions_hash.inject([]) do |memo, (submission_id, versions)|
prior = HashWithIndifferentAccess.new
filtered_versions = versions.sort_by{|v| v[:graded_at].to_i || 0 }.each_with_object([]) do |version, new_array|
if version[:score]
if prior[:id].nil? || prior[:score] != version[:score]
if prior[:id].nil? || prior[:graded_at].nil? || version[:graded_at].nil?
PREVIOUS_VERSION_ATTRS.each { |attr| version["previous_#{attr}".to_sym] = nil }
elsif prior[:score] != version[:score]
PREVIOUS_VERSION_ATTRS.each { |attr| version["previous_#{attr}".to_sym] = prior[attr] }
end
NEW_ATTRS.each { |attr| version["new_#{attr}".to_sym] = version[attr] }
new_array << version
end
end
prior.merge!(version.slice(:grade, :score, :graded_at, :grader, :id))
end.reverse
memo << { :submission_id => submission_id, :versions => filtered_versions }
end
end
def day_string_for(submission)
graded_at = submission.graded_at
return '' if graded_at.nil?
graded_at.in_time_zone.to_date.as_json
end
def submissions_set(course, api_context, options = {})
collection = ::Submission.for_course(course).order("graded_at DESC")
if options[:date]
date = options[:date]
collection = collection.where("graded_at<? AND graded_at>?", date.end_of_day, date.beginning_of_day)
else
collection = collection.where("graded_at IS NOT NULL")
end
if assignment_id = options[:assignment_id]
collection = collection.scoped_by_assignment_id(assignment_id)
end
if grader_id = options[:grader_id]
if grader_id.to_s == '0'
# yes, this is crazy. autograded submissions have the grader_id of (quiz_id x -1)
collection = collection.where("submissions.grader_id<=0")
else
collection = collection.scoped_by_grader_id(grader_id)
end
end
api_context.paginate(collection)
end
private
PREVIOUS_VERSION_ATTRS = [:grade, :graded_at, :grader]
NEW_ATTRS = [:grade, :graded_at, :grader, :score]
DEFAULT_GRADER = Struct.new(:name, :id)
def default_grader
@default_grader ||= DEFAULT_GRADER.new(I18n.t('gradebooks.history.graded_on_submission', 'Graded on submission'), 0)
end
def user_cache
@user_cache ||= Hash.new{ |hash, user_id| hash[user_id] = ::User.find(user_id) }
end
def assignment_cache
@assignment_cache ||= Hash.new{ |hash, assignment_id| hash[assignment_id] = ::Assignment.find(assignment_id) }
end
def update_graders_hash(hash, submission, api_context)
grader = submission.grader || default_grader
hash[grader.id] ||= {
:name => grader.name,
:id => grader.id,
:assignments => {}
}
hash[grader.id][:assignments][submission.assignment_id] ||= begin
assignment = assignment_cache[submission.assignment_id]
assignment_json(assignment, api_context.user, api_context.session)
end
end
def compress(hash, key)
hash[key] = hash[key].values
end
end
end