Eliminate assignment_context_modules_without_overrides n+1

refs LX-1871
flag = selective_release_backend
[skip-crystalball]

Test plan:
 - Open the assignments index page on a course with a lot of
   assignments
 - Search request logs for:
   `SELECT "assignment_overrides".* FROM "public"."assignment_overrides"
    WHERE "assignment_overrides"."workflow_state" = 'active'
    AND "assignment_overrides"."context_module_id" =`
 - Expect there to be significantly fewer of these than without this
   change
 - Open the assignments index API and expect the visible_to_everyone
   value to still be correct (particularly for assignments in multiple
   modules where some modules have overrides)

Change-Id: I3791315786e94ac064b1f45475bb22b2ce44e3b5
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/352497
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Sarah Gerard <sarah.gerard@instructure.com>
QA-Review: Sarah Gerard <sarah.gerard@instructure.com>
Product-Review: Jackson Howe <jackson.howe@instructure.com>
This commit is contained in:
Jackson Howe 2024-07-11 11:31:37 -06:00
parent 3fb40be4ff
commit 13694bfa85
2 changed files with 76 additions and 23 deletions

View File

@ -25,7 +25,8 @@ module DatesOverridable
:has_too_many_overrides,
:preloaded_override_students,
:has_course_overrides,
:preloaded_module_ids
:preloaded_module_ids,
:preloaded_module_overrides
attr_writer :without_overrides
include DifferentiableAssignment
@ -83,24 +84,20 @@ module DatesOverridable
def all_assignment_overrides
if Account.site_admin.feature_enabled? :selective_release_backend
assignment_overrides.or(context_module_overrides)
assignment_overrides.or(AssignmentOverride.active.where(context_module_id: module_ids))
else
assignment_overrides.where.not(set_type: "Course")
end
end
def context_module_overrides
AssignmentOverride.active.where(context_module_id: module_ids)
end
def visible_to_everyone
if Account.site_admin.feature_enabled? :selective_release_backend
if is_a?(DiscussionTopic)
# need to check if is_section_specific for ungraded discussions
# this column will eventually be deprecated and then this can be removed
course_overrides? || ((!only_visible_to_overrides && !is_section_specific) && (module_ids.empty? || (module_ids.any? && assignment_context_modules_without_overrides.any?)))
course_overrides? || ((!only_visible_to_overrides && !is_section_specific) && (module_ids.empty? || (module_ids.any? && modules_without_overrides?)))
else
course_overrides? || (!only_visible_to_overrides && (module_ids.empty? || (module_ids.any? && assignment_context_modules_without_overrides.any?)))
course_overrides? || (!only_visible_to_overrides && (module_ids.empty? || (module_ids.any? && modules_without_overrides?)))
end
else
!only_visible_to_overrides
@ -120,9 +117,10 @@ module DatesOverridable
end
end
def assignment_context_modules_without_overrides
context_modules_with_overrides = context_module_overrides.select(:context_module_id)
assignment_context_modules.where.not(id: context_modules_with_overrides)
def modules_without_overrides?
module_ids_with_overrides = context_module_overrides.map(&:context_module_id)
module_ids_without_overrides = module_ids.reject { |module_id| module_ids_with_overrides.include?(module_id) }
module_ids_without_overrides.any?
end
def self.preload_override_data_for_objects(learning_objects)
@ -131,6 +129,7 @@ module DatesOverridable
preload_has_course_overrides(learning_objects)
preload_module_ids(learning_objects)
preload_module_overrides(learning_objects)
end
def self.preload_has_course_overrides(learning_objects)
@ -182,6 +181,14 @@ module DatesOverridable
end
end
def self.preload_module_overrides(learning_objects)
all_module_ids = learning_objects.map(&:module_ids).flatten.uniq
all_module_overrides = AssignmentOverride.active.where(context_module_id: all_module_ids)
learning_objects.each do |lo|
lo.preloaded_module_overrides = all_module_overrides.select { |ao| lo.module_ids.include?(ao.context_module_id) }
end
end
def self.overrides_for_objects(learning_objects)
AssignmentOverride.where(assignment_id: learning_objects.select { |lo| lo.is_a?(Assignment) }.map(&:id))
.or(AssignmentOverride.where(quiz_id: learning_objects.select { |lo| lo.is_a?(Quizzes::Quiz) }.map(&:id)))
@ -201,6 +208,12 @@ module DatesOverridable
@preloaded_module_ids
end
def context_module_overrides
return AssignmentOverride.active.where(context_module_id: module_ids) if @preloaded_module_overrides.nil?
@preloaded_module_overrides
end
def multiple_due_dates?
if overridden
!!multiple_due_dates_apply_to?(overridden_for_user)

View File

@ -692,12 +692,13 @@ describe "preload_override_data_for_objects" do
@discussion2 = discussion_topic_model(context: @course)
@page1 = wiki_page_model(course: @course)
@page2 = wiki_page_model(course: @course)
@all_objects = [@assignment1, @assignment2, @quiz1, @quiz2, @discussion1, @discussion2, @page1, @page2]
end
let(:all_objects) { [@assignment1, @assignment2, @quiz1, @quiz2, @discussion1, @discussion2, @page1, @page2] }
describe "preload_has_course_overrides" do
it "sets has_course_overrides to nil by default" do
@all_objects.each do |lo|
all_objects.each do |lo|
expect(lo.has_course_overrides).to be_nil
end
end
@ -706,22 +707,22 @@ describe "preload_override_data_for_objects" do
[@assignment1, @quiz2, @discussion1, @page2].each do |lo|
lo.assignment_overrides.create!(set: @course)
end
DatesOverridable.preload_has_course_overrides(@all_objects)
DatesOverridable.preload_has_course_overrides(all_objects)
expected_values = [true, false, false, true, true, false, false, true]
expect(@all_objects.map(&:has_course_overrides)).to eq expected_values
expect(@all_objects.map(&:course_overrides?)).to eq expected_values
expect(all_objects.map(&:has_course_overrides)).to eq expected_values
expect(all_objects.map(&:course_overrides?)).to eq expected_values
end
it "ignores deleted overrides" do
@assignment1.assignment_overrides.create!(set: @course, workflow_state: "deleted")
DatesOverridable.preload_has_course_overrides(@all_objects)
DatesOverridable.preload_has_course_overrides(all_objects)
expect(@assignment1.has_course_overrides).to be false
expect(@assignment1.course_overrides?).to be false
end
it "ignores non-course overrides" do
@assignment1.assignment_overrides.create!(set: @course.default_section)
DatesOverridable.preload_has_course_overrides(@all_objects)
DatesOverridable.preload_has_course_overrides(all_objects)
expect(@assignment1.has_course_overrides).to be false
expect(@assignment1.course_overrides?).to be false
end
@ -735,7 +736,7 @@ describe "preload_override_data_for_objects" do
describe "preload_module_ids" do
it "sets preloaded_module_ids to nil by default" do
@all_objects.each do |lo|
all_objects.each do |lo|
expect(lo.preloaded_module_ids).to be_nil
end
end
@ -746,10 +747,10 @@ describe "preload_override_data_for_objects" do
@quiz1.context_module_tags.create!(context_module: @module1, context: @course, tag_type: "context_module")
@discussion2.context_module_tags.create!(context_module: @module2, context: @course, tag_type: "context_module")
DatesOverridable.preload_module_ids(@all_objects)
DatesOverridable.preload_module_ids(all_objects)
expected_values = [[@module1.id, @module2.id], [], [@module1.id], [], [], [@module2.id], [], []]
expect(@all_objects.map(&:preloaded_module_ids)).to eq expected_values
expect(@all_objects.map(&:module_ids)).to eq expected_values
expect(all_objects.map(&:preloaded_module_ids)).to eq expected_values
expect(all_objects.map(&:module_ids)).to eq expected_values
end
it "works for assignments that are part of a quiz" do
@ -781,7 +782,7 @@ describe "preload_override_data_for_objects" do
it "ignores deleted ContentTags" do
@assignment1.context_module_tags.create!(context_module: @module1, context: @course, tag_type: "context_module", workflow_state: "deleted")
@assignment1.context_module_tags.create!(context_module: @module2, context: @course, tag_type: "context_module")
DatesOverridable.preload_module_ids(@all_objects)
DatesOverridable.preload_module_ids(all_objects)
expect(@assignment1.preloaded_module_ids).to eq [@module2.id]
expect(@assignment1.module_ids).to eq [@module2.id]
end
@ -792,4 +793,43 @@ describe "preload_override_data_for_objects" do
expect(@assignment1.module_ids).to eq [@module1.id]
end
end
describe "preload_module_overrides" do
before :once do
@assignment1.context_module_tags.create!(context_module: @module1, context: @course, tag_type: "context_module")
@assignment1.context_module_tags.create!(context_module: @module2, context: @course, tag_type: "context_module")
@quiz1.context_module_tags.create!(context_module: @module1, context: @course, tag_type: "context_module")
@discussion2.context_module_tags.create!(context_module: @module2, context: @course, tag_type: "context_module")
@ao1 = @module1.assignment_overrides.create!
@ao2 = @module1.assignment_overrides.create!
@ao3 = @module2.assignment_overrides.create!
end
it "sets preloaded_module_overrides to nil by default" do
all_objects.each do |lo|
expect(lo.preloaded_module_overrides).to be_nil
end
end
it "sets the preloaded_module_overrides attribute correctly" do
DatesOverridable.preload_module_ids(all_objects)
DatesOverridable.preload_module_overrides(all_objects)
expected_values = [[@ao1, @ao2, @ao3], [], [@ao1, @ao2], [], [], [@ao3], [], []]
expect(all_objects.map(&:preloaded_module_overrides)).to eq expected_values
expect(all_objects.map(&:context_module_overrides)).to eq expected_values
end
it "ignores deleted overrides" do
@ao1.destroy
DatesOverridable.preload_module_ids(all_objects)
DatesOverridable.preload_module_overrides(all_objects)
expect(@assignment1.preloaded_module_overrides).to eq [@ao2, @ao3]
expect(@assignment1.context_module_overrides).to eq [@ao2, @ao3]
end
it "falls back to one-off calculation if not preloaded" do
expect(@assignment1.preloaded_module_overrides).to be_nil
expect(@assignment1.context_module_overrides).to eq [@ao1, @ao2, @ao3]
end
end
end