release mastery paths content after disabling feature on course

also don't restrict content if imported into a course
with mastery paths disabled

test plan:
* create a course with mastery paths enabled
 and content set to be released on completion of
 a trigger assignment (including other assignments,
 ungraded quizzes, and wiki pages)
* copy the course into another one that does not
 have mastery paths enabled
 * all content should be visible by students
 in the copied course

* in the original course, disable the mastery paths
 feature
* it should give a warning (that actually works to not
 turn the feature flag off if you cancel the dialog)
 that says that the content will have to reconfigured
 if re-enabled
* all content previously hidden from students should
 now be available

closes #LS-1459

Change-Id: I47c8dc773ecdc60b5921853c266959f3e8335f0b
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/248869
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Jeremy Stanley <jeremy@instructure.com>
QA-Review: Jeremy Stanley <jeremy@instructure.com>
Product-Review: Jeremy Stanley <jeremy@instructure.com>
This commit is contained in:
James Williams 2020-09-29 12:11:19 -06:00
parent 8bef98717c
commit 7a80ec7e92
13 changed files with 162 additions and 14 deletions

View File

@ -71,7 +71,7 @@ export default class WikiPage extends Backbone.Model
parse: (response, options) ->
if response.wiki_page
response = _.extend _.omit(response, 'wiki_page'), response.wiki_page
response.set_assignment = response.assignment?
response.set_assignment = response.assignment? && response.assignment.only_visible_to_overrides
assign_attributes = response.assignment || {}
response.assignment = @createAssignment(assign_attributes)
response

View File

@ -65,10 +65,12 @@ export default class FeatureFlagView extends Backbone.View {
applyAction(action) {
$.when(this.canUpdate(action)).then(
$.when(this.checkSiteAdmin()).then(
() => this.model.updateState(action),
() => this.render() // undo UI change if user cancels
)
() =>
$.when(this.checkSiteAdmin()).then(
() => this.model.updateState(action),
() => this.render() // undo UI change if user cancels
),
() => this.render() // ditto
)
}

View File

@ -88,6 +88,28 @@ module ConditionalRelease
rules_data
end
def self.release_mastery_paths_content_in_course(course)
overrides_scope = AssignmentOverride.where(:set_type => AssignmentOverride::SET_TYPE_NOOP, :set_id => AssignmentOverride::NOOP_MASTERY_PATHS).active
assignment_ids = overrides_scope.where.not(:assignment_id => nil).pluck(:assignment_id)
assignment_ids.sort.each_slice(100) do |sliced_ids|
course.assignments.active.where(:id => sliced_ids).where(:only_visible_to_overrides => true).where.not(submission_types: 'wiki_page').to_a.each do |assignment|
assignment.update_attribute(:only_visible_to_overrides, false)
end
end
wp_assignment_ids = course.wiki_pages.not_deleted.where.not(:assignment_id => nil).pluck(:assignment_id)
wp_assignment_ids.sort.each_slice(100) do |sliced_ids|
course.assignments.active.where(:id => sliced_ids).where(:only_visible_to_overrides => true, :submission_types => 'wiki_page').each do |wp_assignment|
wp_assignment.update_attribute(:only_visible_to_overrides, false)
end
end
quiz_ids = overrides_scope.where(:assignment_id => nil).where.not(:quiz_id => nil).pluck(:quiz_id)
quiz_ids.sort.each_slice(100) do |sliced_ids|
course.quizzes.active.where(:id => sliced_ids).where(:only_visible_to_overrides => true).to_a.each do |quiz|
quiz.update_attribute(:only_visible_to_overrides, false)
end
end
end
class << self
private

View File

@ -158,11 +158,13 @@ module Importers
elsif ['external_tool'].include?(hash[:submission_format])
item.submission_types = "external_tool"
end
if item.submission_types == "online_quiz"
case item.submission_types
when "online_quiz"
item.saved_by = :quiz
end
if item.submission_types == "discussion_topic"
when "discussion_topic"
item.saved_by = :discussion_topic
when "wiki_page"
item.saved_by = :wiki_page
end
if hash[:grading_type]
@ -230,7 +232,11 @@ module Importers
end
if hash[:assignment_overrides]
added_overrides = false
hash[:assignment_overrides].each do |o|
next if o[:set_id].to_i == AssignmentOverride::NOOP_MASTERY_PATHS &&
o[:set_type] == AssignmentOverride::SET_TYPE_NOOP &&
!context.feature_enabled?(:conditional_release)
override = item.assignment_overrides.where(o.slice(:set_type, :set_id)).first
override ||= item.assignment_overrides.build
override.set_type = o[:set_type]
@ -241,10 +247,12 @@ module Importers
override.send "override_#{field}", Canvas::Migration::MigratorHelper.get_utc_time_from_timestamp(o[field])
end
override.save!
added_overrides = true
migration.add_imported_item(override,
key: [item.migration_id, override.set_type, override.set_id].join('/'))
end
if hash.has_key?(:only_visible_to_overrides)
can_restrict = added_overrides || (item.submission_types == "wiki_page" && context.feature_enabled?(:conditional_release))
if hash.has_key?(:only_visible_to_overrides) && can_restrict
item.only_visible_to_overrides = hash[:only_visible_to_overrides]
end
end

View File

@ -245,7 +245,11 @@ module Importers
end
if hash[:assignment_overrides]
added_overrides = false
hash[:assignment_overrides].each do |o|
next if o[:set_id].to_i == AssignmentOverride::NOOP_MASTERY_PATHS &&
o[:set_type] == AssignmentOverride::SET_TYPE_NOOP &&
!context.feature_enabled?(:conditional_release)
override = item.assignment_overrides.where(o.slice(:set_type, :set_id)).first
override ||= item.assignment_overrides.build
override.set_type = o[:set_type]
@ -256,10 +260,11 @@ module Importers
override.send "override_#{field}", Canvas::Migration::MigratorHelper.get_utc_time_from_timestamp(o[field])
end
override.save!
added_overrides = true
migration.add_imported_item(override,
key: [item.migration_id, override.set_type, override.set_id].join('/'))
end
if hash.has_key?(:only_visible_to_overrides)
if hash.has_key?(:only_visible_to_overrides) && added_overrides
item.only_visible_to_overrides = hash[:only_visible_to_overrides]
end
end

View File

@ -240,7 +240,7 @@ module Importers
allow_save = false
end
if allow_save && hash[:migration_id]
if hash[:assignment].present?
if hash[:assignment].present? && context.feature_enabled?(:conditional_release)
hash[:assignment][:title] ||= item.title
item.assignment = Importers::AssignmentImporter.import_from_migration(
hash[:assignment], context, migration)

View File

@ -252,6 +252,8 @@ conditional_release:
results.
applies_to: Course
root_opt_in: true
custom_transition_proc: conditional_release_transition_hook
after_state_change_proc: conditional_release_after_change_hook
wrap_calendar_event_titles:
state: allowed
display_name: Wrap event titles in Calendar month view

View File

@ -52,6 +52,7 @@ module CC::Importer::Canvas
wiki[:assignment] = {
migration_id: asg_id,
assignment_overrides: [],
submission_types: 'wiki_page',
only_visible_to_overrides: meta['only_visible_to_overrides'] == 'true'
}
end

View File

@ -80,5 +80,24 @@ module FeatureFlags
I18n.t("Disabling the Elementary Theme will change the font in the Canvas interface for all users in your course.")
transitions['off']['reload_page'] = true
end
def self.conditional_release_transition_hook(_user, context, _from_state, transitions)
if context.is_a?(Course)
transitions['off'] ||= {}
transitions['off']['message'] =
I18n.t("Disabling the Mastery Paths feature will release configured assignments and content to all students.
If the feature is re-enabled, these assignments will need to be configured for Mastery Paths again.")
end
end
def self.conditional_release_after_change_hook(_user, context, _old_state, new_state)
if context.is_a?(Course) && new_state == "off"
ConditionalRelease::Service.send_later_if_production_enqueue_args(
:release_mastery_paths_content_in_course,
{:priority => Delayed::LOW_PRIORITY, :n_strand => ["conditional_release_unassignment", context.global_root_account_id]},
context
)
end
end
end
end

View File

@ -216,5 +216,51 @@ describe ConditionalRelease::Service do
end
end
end
context "releasing content after disabling feature flag" do
before :once do
Account.default.allow_feature!(:conditional_release)
course_with_student(:active_all => true)
@course.enable_feature!(:conditional_release)
@module = @course.context_modules.create!(:workflow_state => "active")
end
def release_content
Feature.definitions['conditional_release'].after_state_change_proc.call(@teacher, @course, 'on', 'off')
end
it "should release mastery paths assigned assignments" do
assmt = assignment_model(course: @course, workflow_state: 'published', only_visible_to_overrides: true)
assignment_override_model(assignment: assmt,
set_type: AssignmentOverride::SET_TYPE_NOOP,
set_id: AssignmentOverride::NOOP_MASTERY_PATHS)
tag = @module.add_item(:id => assmt.id, :type => "assignment")
expect(@course.module_items_visible_to(@student).to_a).to eq []
release_content
expect(@course.module_items_visible_to(@student).to_a).to eq [tag]
end
it "should release mastery paths assigned ungraded quizzes" do
quiz = quiz_model(course: @course, quiz_type: "survey", only_visible_to_overrides: true)
assignment_override_model(quiz: quiz,
set_type: AssignmentOverride::SET_TYPE_NOOP,
set_id: AssignmentOverride::NOOP_MASTERY_PATHS)
tag = @module.add_item(:id => quiz.id, :type => "quiz")
expect(@course.module_items_visible_to(@student).to_a).to eq []
release_content
expect(@course.module_items_visible_to(@student).to_a).to eq [tag]
end
it "should release mastery paths assigned wiki pages" do
wiki_page_assignment_model(course: @course, only_visible_to_overrides: true)
tag = @module.add_item(:id => @page.id, :type => "wiki_page")
expect(@course.module_items_visible_to(@student).to_a).to eq []
release_content
expect(@course.module_items_visible_to(@student).to_a).to eq [tag]
end
end
end
end

View File

@ -685,10 +685,11 @@ describe ContentMigration do
end
it "should copy only noop overrides" do
Account.default.enable_feature!(:conditional_release)
assignment_override_model(assignment: @assignment, set_type: 'ADHOC')
assignment_override_model(assignment: @assignment, set_type: 'Noop',
set_id: 1, title: 'Tag 1')
assignment_override_model(assignment: @assignment, set_type: 'Noop',
assignment_override_model(assignment: @assignment, set_type: AssignmentOverride::SET_TYPE_NOOP,
set_id: AssignmentOverride::NOOP_MASTERY_PATHS, title: 'Tag 1')
assignment_override_model(assignment: @assignment, set_type: AssignmentOverride::SET_TYPE_NOOP,
set_id: nil, title: 'Tag 2')
@assignment.only_visible_to_overrides = true
@assignment.save!
@ -700,7 +701,21 @@ describe ContentMigration do
expect(to_assignment.assignment_overrides.detect{ |o| o.set_id.nil? }.title).to eq 'Tag 2'
end
it "should ignore conditional release noop overrides if feature is not enabled in destination" do
assignment_override_model(assignment: @assignment,
set_type: AssignmentOverride::SET_TYPE_NOOP,
set_id: AssignmentOverride::NOOP_MASTERY_PATHS)
@assignment.only_visible_to_overrides = true
@assignment.save!
run_course_copy
to_assignment = @copy_to.assignments.first
expect(to_assignment.only_visible_to_overrides).to be_falsey
expect(to_assignment.assignment_overrides.length).to eq 0
end
it "should copy dates" do
Account.default.enable_feature!(:conditional_release)
due_at = 1.hour.from_now.round
assignment_override_model(assignment: @assignment, set_type: 'Noop',
set_id: 1, title: 'Tag 1', due_at: due_at)
@ -713,6 +728,7 @@ describe ContentMigration do
end
it "preserves only_visible_to_overrides for page assignments" do
Account.default.enable_feature!(:conditional_release)
a1 = assignment_model(context: @copy_from, title: 'a1', submission_types: 'wiki_page', only_visible_to_overrides: true)
a1.build_wiki_page(title: a1.title, context: a1.context).save!
a2 = assignment_model(context: @copy_from, title: 'a2', submission_types: 'wiki_page', only_visible_to_overrides: false)
@ -723,6 +739,15 @@ describe ContentMigration do
a2_to = @copy_to.assignments.where(migration_id: mig_id(a2)).take
expect(a2_to.only_visible_to_overrides).to eq false
end
it "ignores page assignments if mastery paths is not enabled in destination" do
a1 = assignment_model(context: @copy_from, title: 'a1', submission_types: 'wiki_page', only_visible_to_overrides: true)
a1.build_wiki_page(title: a1.title, context: a1.context).save!
run_course_copy
page_to = @copy_to.wiki_pages.where(migration_id: mig_id(a1.wiki_page)).take
expect(page_to.assignment).to eq nil
expect(@copy_to.assignments.where(migration_id: mig_id(a1)).exists?).to eq false
end
end
it "should copy the thing" do

View File

@ -1129,6 +1129,7 @@ equation: <img class="equation_image" title="Log_216" src="/equation_images/Log_
end
it "should copy only noop overrides" do
Account.default.enable_feature!(:conditional_release)
due_at = 1.hour.from_now.round
assignment_override_model(quiz: @quiz_plain, set_type: 'Noop', set_id: 1, title: 'Tag 3')
assignment_override_model(quiz: @quiz_assigned, set_type: 'Noop', set_id: 1, title: 'Tag 4', due_at: due_at)
@ -1139,6 +1140,22 @@ equation: <img class="equation_image" title="Log_216" src="/equation_images/Log_
expect(to_quiz_assigned.assignment_overrides.pluck(:title)).to eq ['Tag 4']
expect(to_quiz_assigned.assignment_overrides.first.due_at).to eq due_at
end
it "should ignore conditional release noop overrides if feature is not enabled in destination" do
assignment_override_model(quiz: @quiz_assigned, set_type: 'Noop', set_id: 1, title: 'ignore me')
@quiz_assigned.update_attribute(:only_visible_to_overrides, true)
assignment_override_model(quiz: @quiz_plain, set_type: 'Noop', set_id: 9001, title: 'should keep this')
@quiz_plain.update_attribute(:only_visible_to_overrides, true)
run_course_copy
to_quiz_plain = @copy_to.quizzes.where(migration_id: mig_id(@quiz_plain)).first
to_quiz_assigned = @copy_to.quizzes.where(migration_id: mig_id(@quiz_assigned)).first
expect(to_quiz_assigned.assignment_overrides.count).to eq 0
expect(to_quiz_assigned.only_visible_to_overrides).to eq false
expect(to_quiz_plain.assignment_overrides.count).to eq 1
expect(to_quiz_plain.only_visible_to_overrides).to eq true
end
end
it "should not destroy assessment questions when copying twice" do

View File

@ -81,6 +81,7 @@ describe ContentMigration do
end
it "should keep assignment relationship" do
@copy_to.enable_feature!(:conditional_release)
vanilla_page_from = @copy_from.wiki_pages.create!(title: "Everyone Sees This Page")
title = "conditional page"
wiki_page_assignment_model(course: @copy_from, title: title)