exclude grading schemes/assignment groups in selective assignment export

test plan:
 0. have a course containing an assignment with a custom grading scheme
    and named assignment group. also a graded discussion topic in a
    different assignment group with a different grading scheme, and
    a published quiz in a third assignment group.
 1. copy the course. the destination course should copy the assignment
    groups and grading schemes and associate them with the copied objects
    correctly.
 2. repeat step 1, but do a selective course copy, and select the
    assignment, quiz, and topic specifically. you should get the
    same result as in step 1.
 3. use the content exports API to selectively export the three objects
    e.g.,
      select[assignments][]=X&select[quizzes][]=Y&select[discussion_topics][]=Z
    import this package into a new course. the three objects should be
    there, but should not have custom assignment groups or grading
    schemes.

fixes CNVS-14820

Change-Id: I871771284b4a3bbd3695a99a6f2af613d76a8ebf
Reviewed-on: https://gerrit.instructure.com/39914
Tested-by: Jenkins <jenkins@instructure.com>
QA-Review: Clare Strong <clare@instructure.com>
Reviewed-by: James Williams  <jamesw@instructure.com>
Product-Review: Jeremy Stanley <jeremy@instructure.com>
This commit is contained in:
Jeremy Stanley 2014-08-25 10:49:53 -06:00
parent 4792a97aff
commit d518b6f131
9 changed files with 175 additions and 33 deletions

View File

@ -36,7 +36,12 @@ module CC
"xsi:schemaLocation"=> "#{CCHelper::CANVAS_NAMESPACE} #{CCHelper::XSD_URI}" "xsi:schemaLocation"=> "#{CCHelper::CANVAS_NAMESPACE} #{CCHelper::XSD_URI}"
) do |groups_node| ) do |groups_node|
@course.assignment_groups.active.each do |group| @course.assignment_groups.active.each do |group|
next unless export_object?(group) || group.assignments.any?{|a| export_object?(a)} add_item_to_export(group) if for_course_copy && !export_object?(group) && group.assignments.any? do |a|
export_object?(a) ||
a.quiz && export_object?(a.quiz) ||
a.discussion_topic && export_object?(a.discussion_topic)
end
next unless export_object?(group)
migration_id = CCHelper.create_key(group) migration_id = CCHelper.create_key(group)
groups_node.assignmentGroup(:identifier=>migration_id) do |group_node| groups_node.assignmentGroup(:identifier=>migration_id) do |group_node|
group_node.title group.name group_node.title group.name

View File

@ -73,7 +73,7 @@ module CC
"xmlns:xsi"=>"http://www.w3.org/2001/XMLSchema-instance", "xmlns:xsi"=>"http://www.w3.org/2001/XMLSchema-instance",
"xsi:schemaLocation"=> "#{CCHelper::ASSIGNMENT_NAMESPACE} #{CCHelper::ASSIGNMENT_XSD_URI}" "xsi:schemaLocation"=> "#{CCHelper::ASSIGNMENT_NAMESPACE} #{CCHelper::ASSIGNMENT_XSD_URI}"
) do |a| ) do |a|
AssignmentResources.create_cc_assignment(a, assignment, migration_id) AssignmentResources.create_cc_assignment(a, assignment, migration_id, @manifest)
end end
end end
@ -108,7 +108,7 @@ module CC
"xmlns:xsi"=>"http://www.w3.org/2001/XMLSchema-instance", "xmlns:xsi"=>"http://www.w3.org/2001/XMLSchema-instance",
"xsi:schemaLocation"=> "#{CCHelper::CANVAS_NAMESPACE} #{CCHelper::XSD_URI}" "xsi:schemaLocation"=> "#{CCHelper::CANVAS_NAMESPACE} #{CCHelper::XSD_URI}"
) do |a| ) do |a|
AssignmentResources.create_canvas_assignment(a, assignment) AssignmentResources.create_canvas_assignment(a, assignment, @manifest)
end end
assignment_file.close assignment_file.close
@ -128,7 +128,7 @@ module CC
"online_upload" => "file" "online_upload" => "file"
}.freeze }.freeze
def self.create_cc_assignment(node, assignment, migration_id) def self.create_cc_assignment(node, assignment, migration_id, manifest = nil)
node.title(assignment.title) node.title(assignment.title)
node.text(assignment.description, texttype: 'text/html') node.text(assignment.description, texttype: 'text/html')
if assignment.points_possible if assignment.points_possible
@ -149,20 +149,20 @@ module CC
"xmlns:xsi"=>"http://www.w3.org/2001/XMLSchema-instance", "xmlns:xsi"=>"http://www.w3.org/2001/XMLSchema-instance",
"xsi:schemaLocation"=> "#{CCHelper::CANVAS_NAMESPACE} #{CCHelper::XSD_URI}" "xsi:schemaLocation"=> "#{CCHelper::CANVAS_NAMESPACE} #{CCHelper::XSD_URI}"
) do |a| ) do |a|
AssignmentResources.create_canvas_assignment(a, assignment) AssignmentResources.create_canvas_assignment(a, assignment, manifest)
end end
end end
end end
def self.create_canvas_assignment(node, assignment) def self.create_canvas_assignment(node, assignment, manifest = nil)
node.title assignment.title node.title assignment.title
node.due_at CCHelper::ims_datetime(assignment.due_at) if assignment.due_at node.due_at CCHelper::ims_datetime(assignment.due_at) if assignment.due_at
node.lock_at CCHelper::ims_datetime(assignment.lock_at) if assignment.lock_at node.lock_at CCHelper::ims_datetime(assignment.lock_at) if assignment.lock_at
node.unlock_at CCHelper::ims_datetime(assignment.unlock_at) if assignment.unlock_at node.unlock_at CCHelper::ims_datetime(assignment.unlock_at) if assignment.unlock_at
node.all_day_date CCHelper::ims_date(assignment.all_day_date) if assignment.all_day_date node.all_day_date CCHelper::ims_date(assignment.all_day_date) if assignment.all_day_date
node.peer_reviews_due_at CCHelper::ims_datetime(assignment.peer_reviews_due_at) if assignment.peer_reviews_due_at node.peer_reviews_due_at CCHelper::ims_datetime(assignment.peer_reviews_due_at) if assignment.peer_reviews_due_at
node.assignment_group_identifierref CCHelper.create_key(assignment.assignment_group) node.assignment_group_identifierref CCHelper.create_key(assignment.assignment_group) if assignment.assignment_group && (!manifest || manifest.export_object?(assignment.assignment_group))
node.grading_standard_identifierref CCHelper.create_key(assignment.grading_standard) if assignment.grading_standard node.grading_standard_identifierref CCHelper.create_key(assignment.grading_standard) if assignment.grading_standard && (!manifest || manifest.export_object?(assignment.grading_standard))
node.workflow_state assignment.workflow_state node.workflow_state assignment.workflow_state
if assignment.rubric if assignment.rubric
assoc = assignment.rubric_association assoc = assignment.rubric_association

View File

@ -19,14 +19,16 @@ module CC
module GradingStandards module GradingStandards
def add_referenced_grading_standards def add_referenced_grading_standards
@course.assignments.active.where('grading_standard_id IS NOT NULL').each do |assignment| @course.assignments.active.where('grading_standard_id IS NOT NULL').each do |assignment|
next unless export_object?(assignment) next unless export_object?(assignment) ||
assignment.quiz && export_object?(assignment.quiz) ||
assignment.discussion_topic && export_object?(assignment.discussion_topic)
gs = assignment.grading_standard gs = assignment.grading_standard
add_item_to_export(gs) if gs && gs.context_type == 'Course' && gs.context_id == @course.id add_item_to_export(gs) if gs && gs.context_type == 'Course' && gs.context_id == @course.id
end end
end end
def create_grading_standards(document=nil) def create_grading_standards(document=nil)
add_referenced_grading_standards add_referenced_grading_standards if for_course_copy
standards_to_copy = (@course.grading_standards.to_a + [@course.grading_standard]).compact.uniq(&:id).select{|s| export_object?(s)} standards_to_copy = (@course.grading_standards.to_a + [@course.grading_standard]).compact.uniq(&:id).select{|s| export_object?(s)}
return nil unless standards_to_copy.size > 0 return nil unless standards_to_copy.size > 0

View File

@ -196,7 +196,7 @@ module CC
if quiz.assignment && !quiz.assignment.deleted? if quiz.assignment && !quiz.assignment.deleted?
assignment_migration_id = CCHelper.create_key(quiz.assignment) assignment_migration_id = CCHelper.create_key(quiz.assignment)
doc.assignment(:identifier=>assignment_migration_id) do |a| doc.assignment(:identifier=>assignment_migration_id) do |a|
AssignmentResources.create_canvas_assignment(a, quiz.assignment) AssignmentResources.create_canvas_assignment(a, quiz.assignment, @manifest)
end end
end end
if quiz.assignment_group_id if quiz.assignment_group_id

View File

@ -123,7 +123,7 @@ module CC
if topic.assignment && !topic.assignment.deleted? if topic.assignment && !topic.assignment.deleted?
assignment_migration_id = CCHelper.create_key(topic.assignment) assignment_migration_id = CCHelper.create_key(topic.assignment)
doc.assignment(:identifier=>assignment_migration_id) do |a| doc.assignment(:identifier=>assignment_migration_id) do |a|
AssignmentResources.create_canvas_assignment(a, topic.assignment) AssignmentResources.create_canvas_assignment(a, topic.assignment, @manifest)
end end
end end
end end

View File

@ -54,6 +54,29 @@ describe ContentMigration do
to_assign.assignment_group.should == @copy_to.assignment_groups.find_by_migration_id(mig_id(g)) to_assign.assignment_group.should == @copy_to.assignment_groups.find_by_migration_id(mig_id(g))
end end
it "should link assignments to assignment groups on complete export" do
g = @copy_from.assignment_groups.create!(:name => "group")
from_assign = @copy_from.assignments.create!(:title => "some assignment", :assignment_group_id => g.id)
run_export_and_import
to_assign = @copy_to.assignments.find_by_migration_id(mig_id(from_assign))
to_assign.assignment_group.should == @copy_to.assignment_groups.find_by_migration_id(mig_id(g))
end
it "should not link assignments to assignment groups on selective export" do
g = @copy_from.assignment_groups.create!(:name => "group")
from_assign = @copy_from.assignments.create!(:title => "some assignment", :assignment_group_id => g.id)
# test that we neither export nor reference the assignment group
unrelated_group = @copy_to.assignment_groups.create! name: 'unrelated group with coincidentally matching migration id'
unrelated_group.update_attribute :migration_id, mig_id(g)
run_export_and_import do |export|
export.selected_content = { 'assignments' => { mig_id(from_assign) => "1" } }
end
to_assign = @copy_to.assignments.find_by_migration_id(mig_id(from_assign))
to_assign.assignment_group.should_not == unrelated_group
unrelated_group.reload.name.should_not eql g.name
end
it "should copy assignment attributes" do it "should copy assignment attributes" do
assignment_model(:course => @copy_from, :points_possible => 40, :submission_types => 'file_upload', :grading_type => 'points') assignment_model(:course => @copy_from, :points_possible => 40, :submission_types => 'file_upload', :grading_type => 'points')
@assignment.turnitin_enabled = true @assignment.turnitin_enabled = true
@ -313,6 +336,31 @@ describe ContentMigration do
@copy_to.grading_standards.map(&:title).should eql %w(Two) @copy_to.grading_standards.map(&:title).should eql %w(Two)
@copy_to.assignments.first.grading_standard.title.should eql 'Two' @copy_to.assignments.first.grading_standard.title.should eql 'Two'
end end
it "should copy referenced grading standards in complete export" do
gs = make_grading_standard(@copy_from, title: 'GS')
assign = @copy_from.assignments.build
assign.grading_standard = gs
assign.save!
run_export_and_import
@copy_to.assignments.first.grading_standard.title.should eql gs.title
end
it "should not copy referenced grading standards in selective export" do
gs = make_grading_standard(@copy_from, title: 'One')
assign = @copy_from.assignments.build
assign.grading_standard = gs
assign.save!
# test that we neither export nor reference the grading standard
unrelated_grading_standard = make_grading_standard(@copy_to, title: 'unrelated grading standard with coincidentally matching migration id')
unrelated_grading_standard.update_attribute :migration_id, mig_id(gs)
run_export_and_import do |export|
export.selected_content = { 'assignments' => { mig_id(assign) => "1" } }
end
@copy_to.assignments.count.should eql 1
@copy_to.assignments.first.grading_standard.should be_nil
unrelated_grading_standard.reload.title.should_not eql gs.title
end
end end
end end
end end

View File

@ -4,6 +4,15 @@ describe ContentMigration do
context "course copy discussions" do context "course copy discussions" do
include_examples "course copy" include_examples "course copy"
def graded_discussion_topic
@topic = @copy_from.discussion_topics.build(:title => "topic")
@assignment = @copy_from.assignments.build(:submission_types => 'discussion_topic', :title => @topic.title)
@assignment.infer_times
@assignment.saved_by = :discussion_topic
@topic.assignment = @assignment
@topic.save
end
it "should copy discussion topic attributes" do it "should copy discussion topic attributes" do
topic = @copy_from.discussion_topics.create!(:title => "topic", :message => "<p>bloop</p>", topic = @copy_from.discussion_topics.create!(:title => "topic", :message => "<p>bloop</p>",
:pinned => true, :discussion_type => "threaded", :pinned => true, :discussion_type => "threaded",
@ -25,25 +34,20 @@ describe ContentMigration do
end end
it "should copy a discussion topic when assignment is selected" do it "should copy a discussion topic when assignment is selected" do
topic = @copy_from.discussion_topics.build(:title => "topic") graded_discussion_topic
assignment = @copy_from.assignments.build(:submission_types => 'discussion_topic', :title => topic.title)
assignment.infer_times
assignment.saved_by = :discussion_topic
topic.assignment = assignment
topic.save
# Should not fail if the destination has a group # Should not fail if the destination has a group
@copy_to.groups.create!(:name => 'some random group of people') @copy_to.groups.create!(:name => 'some random group of people')
@cm.copy_options = { @cm.copy_options = {
:assignments => {mig_id(assignment) => "1"}, :assignments => {mig_id(@assignment) => "1"},
:discussion_topics => {mig_id(topic) => "0"}, :discussion_topics => {mig_id(@topic) => "0"},
} }
@cm.save! @cm.save!
run_course_copy run_course_copy
@copy_to.discussion_topics.find_by_migration_id(mig_id(topic)).should_not be_nil @copy_to.discussion_topics.find_by_migration_id(mig_id(@topic)).should_not be_nil
end end
it "should properly copy selected delayed announcements" do it "should properly copy selected delayed announcements" do
@ -74,22 +78,56 @@ describe ContentMigration do
end end
it "should not copy deleted assignment attached to topic" do it "should not copy deleted assignment attached to topic" do
topic = @copy_from.discussion_topics.build(:title => "topic") graded_discussion_topic
assignment = @copy_from.assignments.build(:submission_types => 'discussion_topic', :title => topic.title) @assignment.workflow_state = 'deleted'
assignment.infer_times @assignment.save!
assignment.saved_by = :discussion_topic
topic.assignment = assignment
topic.save!
assignment.workflow_state = 'deleted'
assignment.save!
topic.reload @topic.reload
topic.active?.should == true @topic.active?.should == true
run_course_copy run_course_copy
@copy_to.discussion_topics.find_by_migration_id(mig_id(topic)).should_not be_nil @copy_to.discussion_topics.find_by_migration_id(mig_id(@topic)).should_not be_nil
@copy_to.assignments.find_by_migration_id(mig_id(assignment)).should be_nil @copy_to.assignments.find_by_migration_id(mig_id(@assignment)).should be_nil
end end
it "should copy the assignment group and grading standard in selective copy" do
graded_discussion_topic
gs = make_grading_standard(@copy_from, title: 'One')
group = @copy_from.assignment_groups.create!(:name => "new group")
@assignment.assignment_group = group
@assignment.grading_standard = gs
@assignment.save!
@cm.copy_options = { 'everything' => '0', 'discussion_topics' => { mig_id(@topic) => "1" } }
run_course_copy
new_topic = @copy_to.discussion_topics.find_by_migration_id(mig_id(@topic))
new_topic.assignment.should be_present
new_topic.assignment.assignment_group.migration_id.should eql mig_id(group)
new_topic.assignment.grading_standard.migration_id.should eql mig_id(gs)
end
it "should not copy the assignment group and grading standard in selective export" do
graded_discussion_topic
gs = make_grading_standard(@copy_from, title: 'One')
group = @copy_from.assignment_groups.create!(:name => "new group")
@assignment.assignment_group = group
@assignment.grading_standard = gs
@assignment.save!
# test that we neither export nor reference the grading standard and assignment group
decoy_gs = make_grading_standard(@copy_to, title: 'decoy')
decoy_gs.update_attribute :migration_id, mig_id(gs)
decoy_ag = @copy_to.assignment_groups.create! name: 'decoy'
decoy_ag.update_attribute :migration_id, mig_id(group)
run_export_and_import do |export|
export.selected_content = { 'discussion_topics' => { mig_id(@topic) => "1" } }
end
new_topic = @copy_to.discussion_topics.find_by_migration_id(mig_id(@topic))
new_topic.assignment.should be_present
new_topic.assignment.grading_standard.should be_nil
new_topic.assignment.assignment_group.migration_id.should_not eql mig_id(@group)
decoy_gs.reload.title.should_not eql gs.title
decoy_ag.reload.name.should_not eql group.name
end
end end
end end

View File

@ -33,6 +33,26 @@ shared_examples_for "course copy" do
@copy_to.reload @copy_to.reload
end end
def run_export_and_import
export = @copy_from.content_exports.build
export.export_type = ContentExport::COMMON_CARTRIDGE
export.user = @teacher
yield(export) if block_given?
export.save
export.export_course
export.workflow_state.should == 'exported'
export.attachment_id.should_not be_nil
@cm.set_default_settings
@cm.migration_type = 'canvas_cartridge_importer'
worker = Canvas::Migration::Worker::CCWorker.new
@cm.attachment_id = export.attachment_id
@cm.skip_job_progress = true
worker.perform(@cm)
@cm.workflow_state.should == 'imported'
@copy_to.reload
end
def make_grading_standard(context, opts = {}) def make_grading_standard(context, opts = {})
gs = context.grading_standards.new gs = context.grading_standards.new
gs.title = opts[:title] || "Standard eh" gs.title = opts[:title] || "Standard eh"

View File

@ -580,5 +580,34 @@ equation: <img class="equation_image" title="Log_216" src="/equation_images/Log_
qq2.assessment_question_id.should == aqb2.assessment_questions.first.id qq2.assessment_question_id.should == aqb2.assessment_questions.first.id
qq2.question_data['points_possible'].should == qq.question_data['points_possible'] qq2.question_data['points_possible'].should == qq.question_data['points_possible']
end end
it "should copy the assignment group in selective copy" do
pending unless Qti.qti_enabled?
group = @copy_from.assignment_groups.create!(:name => "new group")
quiz = @copy_from.quizzes.create(:title => "asmnt", :quiz_type => "assignment", :assignment_group_id => group.id)
quiz.publish!
@cm.copy_options = { 'everything' => '0', 'quizzes' => { mig_id(quiz) => "1" } }
run_course_copy
dest_quiz = @copy_to.quizzes.find_by_migration_id mig_id(quiz)
dest_quiz.assignment_group.migration_id.should eql mig_id(group)
end
it "should not copy the assignment group in selective export" do
pending unless Qti.qti_enabled?
group = @copy_from.assignment_groups.create!(:name => "new group")
quiz = @copy_from.quizzes.create(:title => "asmnt", :quiz_type => "assignment", :assignment_group_id => group.id)
quiz.publish!
# test that we neither export nor reference the assignment group
decoy_assignment_group = @copy_to.assignment_groups.create!(:name => "decoy")
decoy_assignment_group.update_attribute(:migration_id, mig_id(group))
run_export_and_import do |export|
export.selected_content = { 'quizzes' => { mig_id(quiz) => "1" } }
end
dest_quiz = @copy_to.quizzes.find_by_migration_id mig_id(quiz)
dest_quiz.assignment_group.migration_id.should_not eql decoy_assignment_group
decoy_assignment_group.reload.name.should_not eql group.name
end
end end
end end