Guard modules page

closes LF-1051
flag=differentiated_modules

Test plan
1. Create several Discussions
2. Assign them different overrides
3. Place discussions in modules
4. Verify that only assigned students can see them on
the modules page

Change-Id: Idd77c28bd160ff0c6a4569606cea83ac83af99d0
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/348057
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Product-Review: Jason Gillett <jason.gillett@instructure.com>
Reviewed-by: Jackson Howe <jackson.howe@instructure.com>
QA-Review: Jackson Howe <jackson.howe@instructure.com>
This commit is contained in:
Jason Gillett 2024-05-22 11:35:47 -06:00
parent da46ff6a9f
commit baee9a6b72
2 changed files with 128 additions and 28 deletions

View File

@ -759,6 +759,7 @@ class DiscussionTopic < ActiveRecord::Base
scope :joins_ungraded_discussion_student_visibilities, lambda { |user_ids, course_ids|
joins(:ungraded_discussion_student_visibilities)
.where(ungraded_discussion_student_visibilities: { user_id: user_ids, course_id: course_ids })
.where(assignment_id: nil)
}
scope :visible_to_students_in_course_with_da, lambda { |user_ids, course_ids|
@ -766,8 +767,7 @@ class DiscussionTopic < ActiveRecord::Base
where.not(assignment_id: nil)
.joins_assignment_student_visibilities(user_ids, course_ids)
.union(
where(assignment_id: nil)
.joins_ungraded_discussion_student_visibilities(user_ids, course_ids)
joins_ungraded_discussion_student_visibilities(user_ids, course_ids)
)
else
without_assignment_in_course(course_ids)
@ -1955,13 +1955,12 @@ class DiscussionTopic < ActiveRecord::Base
.pluck("discussion_topics.id", "discussion_topics.assignment_id", "assignment_student_visibilities.user_id")
.group_by { |_, _, user_id| user_id }
# Ungraded discussions are *normally* visible to all -- the exception is
# section-specific discussions, so here get the ones visible to everyone in the
# course, and below get the ones that are visible to the right section.
ids_visible_to_all = without_assignment_in_course(opts[:course_id]).where(is_section_specific: false).pluck(:id)
# Initialize dictionaries for different visibility scopes
ungraded_differentiated_topic_ids_per_user = {}
ids_visible_to_sections = {}
ids_visible_to_all = []
# Now get the section-specific discussions that are in the proper sections.
# build hash of user_ids to array of section ids
# Get Section specific discussions:
sections_per_user = {}
Enrollment.active.where(course_id: opts[:course_id], user_id: opts[:user_id])
.pluck(:user_id, :course_section_id)
@ -1980,11 +1979,21 @@ class DiscussionTopic < ActiveRecord::Base
opts[:user_id].each { |user_id| topic_ids_per_user[user_id] = sections_per_user[user_id]&.map { |section_id| topic_ids_per_section[section_id] }&.flatten&.uniq&.compact }
ids_visible_to_sections = topic_ids_per_user
if Account.site_admin.feature_enabled?(:differentiated_modules)
ungraded_differentiated_topic_ids_per_user = joins_ungraded_discussion_student_visibilities(opts[:user_id], opts[:course_id]).where.not(is_section_specific: true).pluck("ungraded_discussion_student_visibilities.user_id", "discussion_topics.id").group_by(&:first).transform_values { |pairs| pairs.map(&:last).uniq }
else
# Ungraded discussions are *normally* visible to all -- the exception is
# section-specific discussions, so here get the ones visible to everyone in the
# course, and below get the ones that are visible to the right section.
ids_visible_to_all = without_assignment_in_course(opts[:course_id]).where(is_section_specific: false).pluck(:id)
end
# build map of user_ids to array of item ids {1 => [2,3,4], 2 => [2,4]}
opts[:user_id].index_with do |student_id|
assignment_item_ids = (plucked_visibilities[student_id] || []).map { |id, _, _| id }
section_specific_ids = ids_visible_to_sections[student_id] || []
assignment_item_ids.concat(ids_visible_to_all).concat(section_specific_ids)
ungraded_differentiated_specific_ids = ungraded_differentiated_topic_ids_per_user[student_id] || []
assignment_item_ids.concat(ids_visible_to_all).concat(section_specific_ids).concat(ungraded_differentiated_specific_ids)
end
end

View File

@ -3253,15 +3253,39 @@ describe DiscussionTopic do
end
describe "visible_ids_by_user" do
describe "differentiated topics" do
def discussion_and_assignment(opts = {})
assignment = @course.assignments.create!({
title: "some discussion assignment",
submission_types: "discussion_topic"
}.merge(opts))
[assignment.discussion_topic, assignment]
end
def add_section_to_topic(topic, section)
topic.is_section_specific = true
topic.discussion_topic_section_visibilities <<
DiscussionTopicSectionVisibility.new(
discussion_topic: topic,
course_section: section,
workflow_state: "active"
)
topic.save!
end
def add_section_differentiation_to_topic(topic, section)
topic.update!(only_visible_to_overrides: true)
topic.assignment_overrides.create!(set: section)
topic.save!
end
def add_student_differentiation_to_topic(topic, student)
topic.update!(only_visible_to_overrides: true)
override = topic.assignment_overrides.create!
override.assignment_override_students.create!(user: student)
topic.save!
end
def discussion_and_assignment(opts = {})
assignment = @course.assignments.create!({
title: "some discussion assignment",
submission_types: "discussion_topic"
}.merge(opts))
[assignment.discussion_topic, assignment]
end
describe "differentiated topics" do
before :once do
@course = course_factory(active_course: true)
@ -3300,17 +3324,6 @@ describe DiscussionTopic do
end
describe "section specific topic" do
def add_section_to_topic(topic, section)
topic.is_section_specific = true
topic.discussion_topic_section_visibilities <<
DiscussionTopicSectionVisibility.new(
discussion_topic: topic,
course_section: section,
workflow_state: "active"
)
topic.save!
end
it "filters section specific topics properly" do
course = course_factory(active_course: true)
section1 = course.course_sections.create!(name: "test section")
@ -3388,6 +3401,84 @@ describe DiscussionTopic do
expect(vis_hash[student.id].length).to be(0)
end
end
describe "differentiated modules" do
before do
Account.site_admin.enable_feature! :differentiated_modules
end
it "filters based on adhoc overrides" do
course = course_factory(active_course: true)
student_specific_topic = course.discussion_topics.create!(title: "student specific topic 1")
student_specific_topic2 = course.discussion_topics.create!(title: "student specific topic 2")
student1 = create_users(1, return_type: :record).first
course.enroll_student(student1)
student2 = create_users(1, return_type: :record).first
course.enroll_student(student2)
course.reload
add_student_differentiation_to_topic(student_specific_topic, student1)
add_student_differentiation_to_topic(student_specific_topic2, student2)
vis_hash = DiscussionTopic.visible_ids_by_user(course_id: course.id, user_id: [student1.id], item_type: :discussion)
expect(vis_hash[student1.id].length).to eq(1)
expect(vis_hash[student1.id].first).to eq(student_specific_topic.id)
end
it "filters based on section overrides" do
course = course_factory(active_course: true)
section1 = course.course_sections.create!(name: "test section")
section2 = course.course_sections.create!(name: "second test section")
section_specific_topic1 = course.discussion_topics.create!(title: "section specific topic 1")
section_specific_topic2 = course.discussion_topics.create!(title: "section specific topic 2")
add_section_differentiation_to_topic(section_specific_topic1, section1)
add_section_differentiation_to_topic(section_specific_topic2, section2)
student = create_users(1, return_type: :record).first
course.enroll_student(student, section: section1)
course.reload
vis_hash = DiscussionTopic.visible_ids_by_user(course_id: course.id, user_id: [student.id], item_type: :discussion)
expect(vis_hash[student.id].length).to eq(1)
expect(vis_hash[student.id].first).to eq(section_specific_topic1.id)
end
it "filters legacy section specific topics properly" do
course = course_factory(active_course: true)
section1 = course.course_sections.create!(name: "test section")
section2 = course.course_sections.create!(name: "second test section")
section_specific_topic1 = course.discussion_topics.create!(title: "section specific topic 1")
section_specific_topic2 = course.discussion_topics.create!(title: "section specific topic 2")
add_section_to_topic(section_specific_topic1, section1)
add_section_to_topic(section_specific_topic2, section2)
student = create_users(1, return_type: :record).first
course.enroll_student(student, section: section1)
course.reload
vis_hash = DiscussionTopic.visible_ids_by_user(course_id: course.id, user_id: [student.id], item_type: :discussion)
expect(vis_hash[student.id].length).to eq(1)
expect(vis_hash[student.id].first).to eq(section_specific_topic1.id)
end
it "filters graded discussions correctly" do
@course = course_factory(active_course: true)
section1 = @course.course_sections.create!(name: "Section 1")
section2 = @course.course_sections.create!(name: "Section 2")
student1 = user_factory(active_all: true)
student2 = user_factory(active_all: true)
@course.enroll_student(student1, section: section1)
@course.enroll_student(student2, section: section2)
# Create graded discussions with differentiation
discussion1 = discussion_and_assignment(only_visible_to_overrides: true).first
discussion2 = discussion_and_assignment(only_visible_to_overrides: true).first
create_section_override_for_assignment(discussion1.assignment, { course_section: section1 })
create_section_override_for_assignment(discussion2.assignment, { course_section: section2 })
vis_hash = DiscussionTopic.visible_ids_by_user(course_id: @course.id, user_id: [student1.id, student2.id])
expect(vis_hash[student1.id]).to contain_exactly(discussion1.id)
expect(vis_hash[student2.id]).to contain_exactly(discussion2.id)
end
end
end
describe "user_can_summarize" do