undelete outcome alignments that were erroneously deleted

when the rubric data was an array of HashWithIndifferentAccess instead of
just Hash (which happens when the rubric is copied to another course), it
didn't have the comma in front of learning_outcome_id that was expected in the
FixOutOfSyncOutcomeAlignments data fixup.

fixes CNVS-9021

test plan:

*** before checking out this ***
- create a new learning outcome
- create rubric with the learning outcome
- import the rubric into another course using "Import Content" in course
  settings
- add the rubric to an assignment
- note the id of the outcome you added to the rubric
- in the console, run the following command:
  `ContentTag.where(:learning_outcome_id => <LO_ID_HERE>).
    update_all(:workflow_state => 'deleted')'
- go to the outcome page and it will not list any alignments
- set up some other outcome alignments that are active, and others that are
  deleted, that you would not expect to change

*** checkout the commit ***
- make sure delayed jobs ar running, and the UndeleteSomeOutcomeAlignments job
  is not in the failed jobs list
- go to the first outcome page again, it should correctly list the alignments
  (meaning they were restored) that you manually deleted from the second
  course (the one you imported into) but not the original. This is okay, and
  it's because we deleted all the outcome's alignments in the console.  In
  real data, only the alignments from the copied course would have been deleted.
- other alignments should not be touched

Change-Id: I524cc6a86483ea8beb6809fcc14653ef3ca711df
Reviewed-on: https://gerrit.instructure.com/25648
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: Cameron Matheson <cameron@instructure.com>
QA-Review: Amber Taniuchi <amber@instructure.com>
Reviewed-by: Cody Cutrer <cody@instructure.com>
Product-Review: Simon Williams <simon@instructure.com>
This commit is contained in:
Simon Williams 2013-10-24 15:35:22 -06:00
parent 2ce439e40c
commit 5953c22992
5 changed files with 159 additions and 2 deletions

View File

@ -0,0 +1,11 @@
class UndeleteSomeOutcomeAlignments < ActiveRecord::Migration
tag :postdeploy
def self.up
DataFixup::UndeleteSomeOutcomeAlignments.send_later_if_production_enqueue_args(:run,
:priority => Delayed::LOW_PRIORITY, :max_attempts => 1)
end
def self.down
end
end

View File

@ -41,7 +41,7 @@ module DataFixup::FixOutOfSyncOutcomeAlignments
content_tags.tag_type = 'learning_outcome'
AND content_tags.workflow_state = 'active'
AND r.workflow_state = 'active'
AND NOT r.data LIKE '%:learning_outcome_id: ' || content_tags.learning_outcome_id || '%'
AND NOT r.data LIKE '%learning_outcome_id: ' || content_tags.learning_outcome_id || '%'
")
scope.find_each do |ct|
ct.destroy

View File

@ -0,0 +1,88 @@
module DataFixup::UndeleteSomeOutcomeAlignments
def self.run
occurred = Time.zone.parse("2013-09-14 00:00:00 UTC")
rubric_ids = []
# See lib/data_fixup/fix_out_of_sync_outcome_alignments, the second block
# with the comment "Active alignments to rubrics that should no longer be
# aligned". When the data was a HashWithIndifferentAccess instead of just
# a Hash, it didn't have the comma in front of learning_outcome_id. This
# brings those content tags back.
if CANVAS_RAILS2
scope = ContentTag.scoped(joins:
"INNER JOIN rubrics r
ON content_tags.content_id = r.id
AND content_tags.content_type = 'Rubric'
", select: "content_tags.*")
else
scope = ContentTag.joins("
INNER JOIN rubrics r
ON content_tags.content_id = r.id
AND content_tags.content_type = 'Rubric'
").select("content_tags.*")
end
scope = scope.where("
content_tags.tag_type = 'learning_outcome'
AND content_tags.workflow_state = 'deleted'
AND content_tags.updated_at > ?
AND r.workflow_state = 'active'
AND NOT r.data LIKE '%:learning_outcome_id: ' || content_tags.learning_outcome_id || '%'
AND r.data LIKE '%learning_outcome_id: ' || content_tags.learning_outcome_id || '%'
", occurred)
scope.find_each do |ct|
ct.workflow_state = 'active'
ct.save!
rubric_ids << ct.content_id
end
# The fourth block in that same fixup then found outcomes that should no
# longer be aligned to assignments, so we need to bring those back as well.
rubric_ids.each_slice(1000) do |rids|
if CANVAS_RAILS2
scope = ContentTag.scoped(joins:
"INNER JOIN assignments a
ON content_tags.content_id = a.id
AND content_tags.content_type = 'Assignment'
INNER JOIN rubric_associations ra
ON ra.association_id = a.id
AND ra.association_type = 'Assignment'
INNER JOIN rubrics r
ON ra.rubric_id = r.id
INNER JOIN content_tags ct2
ON ct2.content_id = r.id
AND ct2.content_type = 'Rubric'
AND ct2.tag_type = 'learning_outcome'
AND ct2.workflow_state = 'active'
AND ct2.learning_outcome_id = content_tags.learning_outcome_id
", select: "content_tags.*")
else
scope = ContentTag.joins("
INNER JOIN assignments a
ON content_tags.content_id = a.id
AND content_tags.content_type = 'Assignment'
INNER JOIN rubric_associations ra
ON ra.association_id = a.id
AND ra.association_type = 'Assignment'
INNER JOIN rubrics r
ON ra.rubric_id = r.id
INNER JOIN content_tags ct2
ON ct2.content_id = r.id
AND ct2.content_type = 'Rubric'
AND ct2.tag_type = 'learning_outcome'
AND ct2.workflow_state = 'active'
AND ct2.learning_outcome_id = content_tags.learning_outcome_id
").select("content_tags.*")
end
scope = scope.where("
content_tags.tag_type = 'learning_outcome'
AND content_tags.workflow_state = 'deleted'
AND ct2.content_id IN (?)
AND content_tags.updated_at > ?
", rids, occurred)
scope.find_each do |ct|
ct.workflow_state = 'active'
ct.save!
end
end
end
end

View File

@ -1,5 +1,5 @@
#
# Copyright (C) 2012 Instructure, Inc.
# Copyright (C) 2013 Instructure, Inc.
#
# This file is part of Canvas.
#

View File

@ -0,0 +1,58 @@
#
# Copyright (C) 2013 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb')
describe DataFixup::UndeleteSomeOutcomeAlignments do
before do
course_with_teacher_logged_in(:active_all => true)
outcome_with_rubric
@rubric_association_object = @course.assignments.create!(:title => 'blah')
@rubric_association = @rubric.rubric_associations.create!({
:association => @rubric_association_object,
:context => @course,
:purpose => 'grading'
})
end
it "should undelete tags on rubrics that should still exist, and their corresponding assignments" do
align = @rubric.learning_outcome_alignments.first
align2 = @rubric_association_object.learning_outcome_alignments.first
data = @rubric.data.map{|h| h.with_indifferent_access}
Rubric.where(:id => @rubric.id).update_all(:data => data.to_yaml)
ContentTag.where(:id => [align, align2]).update_all(:workflow_state => 'deleted')
align.reload.should be_deleted
align2.reload.should be_deleted
DataFixup::UndeleteSomeOutcomeAlignments.run
align.reload.should_not be_deleted
align2.reload.should_not be_deleted
end
it "should not undelete assignments tags that aren't linked to rubrics already undeleted" do
align = @rubric_association_object.learning_outcome_alignments.first
ContentTag.where(:id => align).update_all(:workflow_state => 'deleted')
align.reload.should be_deleted
DataFixup::UndeleteSomeOutcomeAlignments.run
align.reload.should be_deleted
end
end