make grade export report per enrollment

fixes CORE-1276

test plan
 - have a user with two pseudonyms
 - it should have one line in grade export report

Change-Id: I7e3dbb45905371f18f89ee1546830d41b56937fa
Reviewed-on: https://gerrit.instructure.com/146259
Tested-by: Jenkins
Reviewed-by: James Williams  <jamesw@instructure.com>
QA-Review: Jeremy Putnam <jeremyp@instructure.com>
Product-Review: Rob Orton <rob@instructure.com>
This commit is contained in:
Rob Orton 2018-04-07 22:53:32 -06:00
parent 9ac6326ff6
commit 13a0f66420
4 changed files with 92 additions and 69 deletions

View File

@ -78,12 +78,21 @@ module AccountReports
headers << I18n.t('unposted final score')
write_report headers do |csv|
students.find_each do |student|
students.preload(:root_account, :sis_pseudonym).find_in_batches do |student_chunk|
users = student_chunk.map {|e| User.new(id: e.user_id)}.compact
users.uniq!
users_by_id = users.index_by(&:id)
pseudonyms = load_cross_shard_logins(users, include_deleted: @include_deleted)
student_chunk.each do |student|
p = loaded_pseudonym(pseudonyms,
users_by_id[student.user_id],
include_deleted: @include_deleted,
enrollment: student)
next unless p
arr = []
arr << student["user_name"]
arr << student["user_id"]
arr << student["sis_user_id"]
arr << p.sis_user_id
arr << student["course_name"]
arr << student["course_id"]
arr << student["course_sis_id"]
@ -102,6 +111,7 @@ module AccountReports
end
end
end
end
def mgp_grade_export
terms = @account_report.parameters[:enrollment_term_id].blank? ?
@ -158,15 +168,24 @@ module AccountReports
headers << I18n.t('unposted final score')
generate_and_run_report headers do |csv|
students.find_in_batches do |student_chunk|
students.preload(:root_account, :sis_pseudonym).find_in_batches do |student_chunk|
users = student_chunk.map {|e| User.new(id: e.user_id)}.compact
users.uniq!
users_by_id = users.index_by(&:id)
pseudonyms = load_cross_shard_logins(users, include_deleted: @include_deleted)
students_by_course = student_chunk.group_by { |x| x.course_id }
students_by_course.each do |course_id, course_students|
scores = indexed_scores(course_students, grading_periods)
course_students.each do |student|
p = loaded_pseudonym(pseudonyms,
users_by_id[student.user_id],
include_deleted: @include_deleted,
enrollment: student)
next unless p
arr = []
arr << student["user_name"]
arr << student["user_id"]
arr << student["sis_user_id"]
arr << p.sis_user_id
arr << student["course_name"]
arr << student["course_id"]
arr << student["course_sis_id"]
@ -219,46 +238,45 @@ module AccountReports
end
def student_grade_scope
students = root_account.pseudonyms.except(:preload).
select("pseudonyms.id, u.name AS user_name, e.user_id, e.course_id,
pseudonyms.sis_user_id, c.name AS course_name,
students = root_account.enrollments.
select("u.name AS user_name, enrollments.user_id, enrollments.course_id,
c.name AS course_name,
enrollments.root_account_id, enrollments.sis_pseudonym_id,
c.sis_source_id AS course_sis_id, s.name AS section_name,
e.course_section_id, s.sis_source_id AS section_sis_id,
e.id as enrollment_id,
enrollments.course_section_id, s.sis_source_id AS section_sis_id,
enrollments.id AS enrollment_id,
t.name AS term_name, t.id AS term_id,
t.sis_source_id AS term_sis_id,
sc.current_score,
sc.final_score,
sc.unposted_current_score,
sc.unposted_final_score,
CASE WHEN e.workflow_state = 'active' THEN 'active'
WHEN e.workflow_state = 'completed' THEN 'concluded'
WHEN e.workflow_state = 'inactive' THEN 'inactive'
WHEN e.workflow_state = 'deleted' THEN 'deleted' END AS enroll_state").
order("t.id, c.id, e.id").
joins("INNER JOIN #{User.quoted_table_name} u ON pseudonyms.user_id = u.id
INNER JOIN #{Enrollment.quoted_table_name} e ON pseudonyms.user_id = e.user_id
AND e.type = 'StudentEnrollment'
INNER JOIN #{Course.quoted_table_name} c ON c.id = e.course_id
CASE WHEN enrollments.workflow_state = 'active' THEN 'active'
WHEN enrollments.workflow_state = 'completed' THEN 'concluded'
WHEN enrollments.workflow_state = 'inactive' THEN 'inactive'
WHEN enrollments.workflow_state = 'deleted' THEN 'deleted' END AS enroll_state").
order("t.id, c.id, enrollments.id").
joins("INNER JOIN #{User.quoted_table_name} u ON enrollments.user_id = u.id
INNER JOIN #{Course.quoted_table_name} c ON c.id = enrollments.course_id
INNER JOIN #{EnrollmentTerm.quoted_table_name} t ON c.enrollment_term_id = t.id
INNER JOIN #{CourseSection.quoted_table_name} s ON e.course_section_id = s.id
LEFT JOIN #{Score.quoted_table_name} sc ON sc.enrollment_id = e.id AND sc.course_score IS TRUE")
INNER JOIN #{CourseSection.quoted_table_name} s ON enrollments.course_section_id = s.id
LEFT JOIN #{Score.quoted_table_name} sc ON sc.enrollment_id = enrollments.id AND sc.course_score IS TRUE").
where("enrollments.type='StudentEnrollment'")
if @include_deleted
students = students.where("e.workflow_state IN ('active', 'completed', 'inactive', 'deleted')")
students = students.where("enrollments.workflow_state IN ('active', 'completed', 'inactive', 'deleted')")
if @account_report.parameters.has_key? 'limiting_period'
limiting_period = @account_report.parameters['limiting_period'].to_i
students = students.where("e.workflow_state = 'active'
students = students.where("enrollments.workflow_state = 'active'
OR c.conclude_at >= ?
OR (e.workflow_state IN ('inactive', 'deleted')
AND e.updated_at >= ?)",
OR (enrollments.workflow_state IN ('inactive', 'deleted')
AND enrollments.updated_at >= ?)",
limiting_period.days.ago, limiting_period.days.ago)
end
else
students = students.where(
"pseudonyms.workflow_state<>'deleted'
AND c.workflow_state='available'
AND e.workflow_state IN ('active', 'completed')
"c.workflow_state='available'
AND enrollments.workflow_state IN ('active', 'completed')
AND sc.workflow_state <> 'deleted'")
end

View File

@ -195,6 +195,28 @@ module AccountReports::ReportHelper
end
end
def loaded_pseudonym(pseudonyms, u, include_deleted: false, enrollment: nil)
context = enrollment || root_account
user_pseudonyms = pseudonyms[u.id] || []
u.instance_variable_set(include_deleted ? :@all_pseudonyms : :@all_active_pseudonyms, user_pseudonyms)
SisPseudonym.for(u, context, {type: :trusted, require_sis: false, include_deleted: include_deleted})
end
def load_cross_shard_logins(users, include_deleted: false)
shards = root_account.trusted_account_ids.map {|id| Shard.shard_for(id)}
shards << root_account.shard
User.preload_shard_associations(users)
shards = shards & users.map(&:associated_shards).flatten
pseudonyms = Pseudonym.shard(shards.uniq).where(user_id: users)
pseudonyms = pseudonyms.active unless include_deleted
pseudonyms.each do |p|
p.account = root_account if p.account_id == root_account.id
end
preloads = Account.reflections['role_links'] ? {account: :role_links} : :account
ActiveRecord::Associations::Preloader.new.preload(pseudonyms, preloads)
pseudonyms.group_by(&:user_id)
end
def include_deleted_objects
if @account_report.has_parameter? "include_deleted"
@include_deleted = value_to_boolean(@account_report.parameters["include_deleted"])

View File

@ -529,28 +529,6 @@ module AccountReports
headers
end
def loaded_pseudonym(pseudonyms, u, include_deleted: false, enrollment: nil)
context = enrollment || root_account
user_pseudonyms = pseudonyms[u.id] || []
u.instance_variable_set(include_deleted ? :@all_pseudonyms : :@all_active_pseudonyms, user_pseudonyms)
SisPseudonym.for(u, context, {type: :trusted, require_sis: false, include_deleted: include_deleted})
end
def load_cross_shard_logins(users, include_deleted: false)
shards = root_account.trusted_account_ids.map {|id| Shard.shard_for(id)}
shards << root_account.shard
User.preload_shard_associations(users)
shards = shards & users.map(&:associated_shards).flatten
pseudonyms = Pseudonym.shard(shards.uniq).where(user_id: users)
pseudonyms = pseudonyms.active unless include_deleted
pseudonyms.each do |p|
p.account = root_account if p.account_id == root_account.id
end
preloads = Account.reflections['role_links'] ? { account: :role_links } : :account
ActiveRecord::Associations::Preloader.new.preload(pseudonyms, preloads)
pseudonyms.group_by(&:user_id)
end
def groups
if @sis_format
# headers are not translated on sis_export to maintain import compatibility

View File

@ -88,7 +88,12 @@ describe "Default Account Reports" do
@enrollment1.find_score.update_attribute(:unposted_final_score, 92)
end
it "should run grade export for a term" do
it "should run grade export for a term and return one line per enrollment" do
user_with_managed_pseudonym(user: @user1, account: @account)
p = @account.pseudonyms.where(sis_user_id: 'user_sis_id_01').take
@enrollment1.sis_pseudonym = p
@enrollment1.save!
parameters = {}
parameters["enrollment_term"] = @term1.id
parsed = read_report('grade_export_csv', {order: 13, params: parameters})