imported link refactoring

test plan:
* regression test links within copied content

* should also fix a pre-existing bug where
 links to content within assessment/quiz questions
 were not being copied

closes #CNVS-20890 #CNVS-20740

Change-Id: I1d1b89faba468690eaddaa1e29cd2936c807cc60
Reviewed-on: https://gerrit.instructure.com/55830
Tested-by: Jenkins
Reviewed-by: Jeremy Stanley <jeremy@instructure.com>
QA-Review: Michael Hargiss <mhargiss@instructure.com>
Product-Review: James Williams  <jamesw@instructure.com>
This commit is contained in:
James Williams 2015-06-04 07:51:57 -06:00
parent 3c44f4f6e7
commit 91646b65eb
47 changed files with 1012 additions and 656 deletions

View File

@ -79,14 +79,55 @@ class AssessmentQuestion < ActiveRecord::Base
end end
end end
def translate_link_regex
@regex ||= Regexp.new(%{/#{context_type.downcase.pluralize}/#{context_id}/(?:files/(\\d+)/(?:download|preview)|file_contents/(course%20files/[^'"?]*))(?:\\?([^'"]*))?})
end
def file_substitutions
@file_substitutions ||= {}
end
def translate_file_link(link, match_data=nil)
match_data ||= link.match(translate_link_regex)
return link unless match_data
id = match_data[1]
path = match_data[2]
id_or_path = id || path
if !file_substitutions[id_or_path]
if id
file = Attachment.where(context_type: context_type, context_id: context_id, id: id_or_path).first
elsif path
path = URI.unescape(id_or_path)
file = Folder.find_attachment_in_context_with_path(assessment_question_bank.context, path)
end
begin
new_file = file.clone_for(self)
rescue => e
new_file = nil
er_id = Canvas::Errors.capture_exception(:file_clone_during_translate_links, e)[:error_report]
logger.error("Error while cloning attachment during"\
" AssessmentQuestion#translate_links: "\
"id: #{self.id} error_report: #{er_id}")
end
new_file.save if new_file
file_substitutions[id_or_path] = new_file
end
if sub = file_substitutions[id_or_path]
query_rest = match_data[3] ? "&#{match_data[3]}" : ''
"/assessment_questions/#{self.id}/files/#{sub.id}/download?verifier=#{sub.uuid}#{query_rest}"
else
link
end
end
def translate_links def translate_links
# we can't translate links unless this question has a context (through a bank) # we can't translate links unless this question has a context (through a bank)
return unless assessment_question_bank && assessment_question_bank.context return unless assessment_question_bank && assessment_question_bank.context
# This either matches the id from a url like: /courses/15395/files/11454/download # This either matches the id from a url like: /courses/15395/files/11454/download
# or gets the relative path at the end of one like: /courses/15395/file_contents/course%20files/unfiled/test.jpg # or gets the relative path at the end of one like: /courses/15395/file_contents/course%20files/unfiled/test.jpg
regex = Regexp.new(%{/#{context_type.downcase.pluralize}/#{context_id}/(?:files/(\\d+)/(?:download|preview)|file_contents/(course%20files/[^'"?]*))(?:\\?([^'"]*))?})
file_substitutions = {}
deep_translate = lambda do |obj| deep_translate = lambda do |obj|
if obj.is_a?(Hash) if obj.is_a?(Hash)
@ -94,33 +135,8 @@ class AssessmentQuestion < ActiveRecord::Base
elsif obj.is_a?(Array) elsif obj.is_a?(Array)
obj.map {|v| deep_translate.call(v) } obj.map {|v| deep_translate.call(v) }
elsif obj.is_a?(String) elsif obj.is_a?(String)
obj.gsub(regex) do |match| obj.gsub(translate_link_regex) do |match|
id_or_path = $1 || $2 translate_file_link(match, $~)
if !file_substitutions[id_or_path]
if $1
file = Attachment.where(context_type: context_type, context_id: context_id, id: id_or_path).first
elsif $2
path = URI.unescape(id_or_path)
file = Folder.find_attachment_in_context_with_path(assessment_question_bank.context, path)
end
begin
new_file = file.clone_for(self)
rescue => e
new_file = nil
er_id = Canvas::Errors.capture_exception(:file_clone_during_translate_links, e)[:error_report]
logger.error("Error while cloning attachment during"\
" AssessmentQuestion#translate_links: "\
"id: #{self.id} error_report: #{er_id}")
end
new_file.save if new_file
file_substitutions[id_or_path] = new_file
end
if sub = file_substitutions[id_or_path]
query_rest = $3 ? "&#{$3}" : ''
"/assessment_questions/#{self.id}/files/#{sub.id}/download?verifier=#{sub.uuid}#{query_rest}"
else
match
end
end end
else else
obj obj

View File

@ -36,7 +36,7 @@ class ContentMigration < ActiveRecord::Base
DATE_FORMAT = "%m/%d/%Y" DATE_FORMAT = "%m/%d/%Y"
attr_accessible :context, :migration_settings, :user, :source_course, :copy_options, :migration_type, :initiated_source attr_accessible :context, :migration_settings, :user, :source_course, :copy_options, :migration_type, :initiated_source
attr_accessor :imported_migration_items, :outcome_to_id_map attr_accessor :imported_migration_items, :outcome_to_id_map, :attachment_path_id_lookup, :attachment_path_id_lookup_lower
workflow do workflow do
state :created state :created
@ -588,27 +588,27 @@ class ContentMigration < ActiveRecord::Base
ContentMigration.where(:id => self).update_all(:progress=>val) ContentMigration.where(:id => self).update_all(:progress=>val)
end end
def add_missing_content_links(item) def html_converter
@missing_content_links ||= {} @html_converter ||= ImportedHtmlConverter.new(self)
item[:field] ||= :text
key = "#{item[:class]}_#{item[:id]}_#{item[:field]}"
if item[:missing_links].present?
@missing_content_links[key] = item
else
@missing_content_links.delete(key)
end
end end
def add_warnings_for_missing_content_links def convert_html(*args)
return unless @missing_content_links html_converter.convert(*args)
@missing_content_links.each_value do |item| end
if item[:missing_links].any?
add_warning(t(:missing_content_links_title, "Missing links found in imported content") + " - #{item[:class]} #{item[:field]}", def convert_text(*args)
{:error_message => "#{item[:class]} #{item[:field]} - " + t(:missing_content_links_message, html_converter.convert_text(*args)
"The following references could not be resolved:") + " " + item[:missing_links].join(', '), end
:fix_issue_html_url => item[:url]})
end def resolve_content_links!
end html_converter.resolve_content_links!
end
def add_warning_for_missing_content_links(type, field, missing_links, fix_issue_url)
add_warning(t(:missing_content_links_title, "Missing links found in imported content") + " - #{type} #{field}",
{:error_message => "#{type} #{field} - " + t(:missing_content_links_message,
"The following references could not be resolved:") + " " + missing_links.join(', '),
:fix_issue_html_url => fix_issue_url})
end end
UPLOAD_TIMEOUT = 1.hour UPLOAD_TIMEOUT = 1.hour
@ -700,17 +700,31 @@ class ContentMigration < ActiveRecord::Base
def imported_migration_items def imported_migration_items
@imported_migration_items_hash ||= {} @imported_migration_items_hash ||= {}
@imported_migration_items_hash.values.flatten @imported_migration_items_hash.values.map(&:values).flatten
end
def imported_migration_items_hash(klass)
@imported_migration_items_hash ||= {}
@imported_migration_items_hash[klass.name] ||= {}
end end
def imported_migration_items_by_class(klass) def imported_migration_items_by_class(klass)
@imported_migration_items_hash ||= {} imported_migration_items_hash(klass).values
@imported_migration_items_hash[klass.name] ||= [] end
def find_imported_migration_item(klass, migration_id)
imported_migration_items_hash(klass)[migration_id]
end end
def add_imported_item(item) def add_imported_item(item)
arr = imported_migration_items_by_class(item.class) imported_migration_items_hash(item.class)[item.migration_id] = item
arr << item unless arr.include?(item) end
def add_attachment_path(path, migration_id)
self.attachment_path_id_lookup ||= {}
self.attachment_path_id_lookup_lower ||= {}
self.attachment_path_id_lookup[path] = migration_id
self.attachment_path_id_lookup_lower[path.downcase] = migration_id
end end
def add_external_tool_translation(migration_id, target_tool, custom_fields) def add_external_tool_translation(migration_id, target_tool, custom_fields)

View File

@ -184,6 +184,7 @@ class Course < ActiveRecord::Base
has_many :context_external_tools, :as => :context, :dependent => :destroy, :order => 'name' has_many :context_external_tools, :as => :context, :dependent => :destroy, :order => 'name'
belongs_to :wiki belongs_to :wiki
has_many :quizzes, :class_name => 'Quizzes::Quiz', :as => :context, :dependent => :destroy, :order => 'lock_at, title' has_many :quizzes, :class_name => 'Quizzes::Quiz', :as => :context, :dependent => :destroy, :order => 'lock_at, title'
has_many :quiz_questions, :class_name => 'Quizzes::QuizQuestion', :through => :quizzes
has_many :active_quizzes, :class_name => 'Quizzes::Quiz', :as => :context, :include => :assignment, :conditions => ['quizzes.workflow_state != ?', 'deleted'], :order => 'created_at' has_many :active_quizzes, :class_name => 'Quizzes::Quiz', :as => :context, :include => :assignment, :conditions => ['quizzes.workflow_state != ?', 'deleted'], :order => 'created_at'
has_many :assessment_questions, :through => :assessment_question_banks has_many :assessment_questions, :through => :assessment_question_banks
has_many :assessment_question_banks, :as => :context, :include => [:assessment_questions, :assessment_question_bank_users] has_many :assessment_question_banks, :as => :context, :include => [:assessment_questions, :assessment_question_bank_users]
@ -1967,8 +1968,7 @@ class Course < ActiveRecord::Base
end end
attr_accessor :full_migration_hash, :external_url_hash, attr_accessor :full_migration_hash, :external_url_hash,
:folder_name_lookups, :attachment_path_id_lookup, :attachment_path_id_lookup_lower, :folder_name_lookups, :assignment_group_no_drop_assignments, :migration_results
:assignment_group_no_drop_assignments, :migration_results
def backup_to_json def backup_to_json
@ -2009,7 +2009,6 @@ class Course < ActiveRecord::Base
end end
def copy_attachments_from_course(course, options={}) def copy_attachments_from_course(course, options={})
self.attachment_path_id_lookup = {}
root_folder = Folder.root_folders(self).first root_folder = Folder.root_folders(self).first
root_folder_name = root_folder.name + '/' root_folder_name = root_folder.name + '/'
ce = options[:content_export] ce = options[:content_export]
@ -2025,7 +2024,7 @@ class Course < ActiveRecord::Base
if !ce || ce.export_object?(file) if !ce || ce.export_object?(file)
begin begin
new_file = file.clone_for(self, nil, :overwrite => true) new_file = file.clone_for(self, nil, :overwrite => true)
self.attachment_path_id_lookup[file.full_display_path.gsub(/\A#{root_folder_name}/, '')] = new_file.migration_id cm.add_attachment_path(file.full_display_path.gsub(/\A#{root_folder_name}/, ''), new_file.migration_id)
new_folder_id = merge_mapped_id(file.folder) new_folder_id = merge_mapped_id(file.folder)
if file.folder && file.folder.parent_folder_id.nil? if file.folder && file.folder.parent_folder_id.nil?

View File

@ -10,6 +10,8 @@ module Importers
Importers::AssessmentQuestionImporter.process_migration(data, migration) Importers::AssessmentQuestionImporter.process_migration(data, migration)
Importers::LearningOutcomeImporter.process_migration(data, migration) Importers::LearningOutcomeImporter.process_migration(data, migration)
migration.resolve_content_links!
migration.progress = 100 migration.progress = 100
migration.workflow_state = :imported migration.workflow_state = :imported
migration.save migration.save

View File

@ -67,16 +67,6 @@ module Importers
begin begin
question = self.import_from_migration(question, migration.context, migration, question_bank) question = self.import_from_migration(question, migration.context, migration, question_bank)
# If the question appears to have links, we need to translate them so that file links point
# to the AssessmentQuestion. Ideally we would just do this before saving the question, but
# the link needs to include the id of the AQ, which we don't have until it's saved. This will
# be a problem as long as we use the question as a context for its attachments. (We're turning this
# hash into a string so we can quickly check if anywhere in the hash might have a URL.)
if question.to_s =~ %r{/files/\d+/(download|preview)}
AssessmentQuestion.find(question[:assessment_question_id]).translate_links
end
question_data[:aq_data][question['migration_id']] = question question_data[:aq_data][question['migration_id']] = question
rescue rescue
migration.add_import_warning(t('#migration.quiz_question_type', "Quiz Question"), question[:question_name], $!) migration.add_import_warning(t('#migration.quiz_question_type', "Quiz Question"), question[:question_name], $!)
@ -90,9 +80,8 @@ module Importers
hash = hash.with_indifferent_access hash = hash.with_indifferent_access
hash.delete(:question_bank_migration_id) if hash.has_key?(:question_bank_migration_id) hash.delete(:question_bank_migration_id) if hash.has_key?(:question_bank_migration_id)
self.prep_for_import(hash, context, migration) self.prep_for_import(hash, migration, :assessment_question)
missing_links = hash.delete(:missing_links) || {}
import_warnings = hash.delete(:import_warnings) || [] import_warnings = hash.delete(:import_warnings) || []
if error = hash.delete(:import_error) if error = hash.delete(:import_error)
import_warnings << error import_warnings << error
@ -115,54 +104,49 @@ module Importers
hash['assessment_question_id'] = id hash['assessment_question_id'] = id
end end
if migration if import_warnings
missing_links.each do |field, links| import_warnings.each do |warning|
migration.add_missing_content_links(:class => self.to_s, migration.add_warning(warning, {
:id => hash['assessment_question_id'], :field => field, :missing_links => links, :fix_issue_html_url => "/#{context.class.to_s.underscore.pluralize}/#{context.id}/question_banks/#{bank.id}#question_#{hash['assessment_question_id']}_question_text"
:url => "/#{context.class.to_s.underscore.pluralize}/#{context.id}/question_banks/#{bank.id}#question_#{hash['assessment_question_id']}_question_text") })
end
if import_warnings
import_warnings.each do |warning|
migration.add_warning(warning, {
:fix_issue_html_url => "/#{context.class.to_s.underscore.pluralize}/#{context.id}/question_banks/#{bank.id}#question_#{hash['assessment_question_id']}_question_text"
})
end
end end
end end
hash hash
end end
def self.prep_for_import(hash, context, migration=nil) def self.prep_for_import(hash, migration, item_type)
return hash if hash[:prepped_for_import] return hash if hash[:prepped_for_import]
hash[:missing_links] = {}
[:question_text, :correct_comments_html, :incorrect_comments_html, :neutral_comments_html, :more_comments_html].each do |field| [:question_text, :correct_comments_html, :incorrect_comments_html, :neutral_comments_html, :more_comments_html].each do |field|
hash[:missing_links][field] = []
if hash[field].present? if hash[field].present?
hash[field] = ImportedHtmlConverter.convert(hash[field], context, migration, {:remove_outer_nodes_if_one_child => true}) do |warn, link| hash[field] = migration.convert_html(
hash[:missing_links][field] << link if warn == :missing_link hash[field], item_type, hash[:migration_id], field, {:remove_outer_nodes_if_one_child => true}
end )
end end
end end
[:correct_comments, :incorrect_comments, :neutral_comments, :more_comments].each do |field| [:correct_comments, :incorrect_comments, :neutral_comments, :more_comments].each do |field|
html_field = "#{field}_html".to_sym html_field = "#{field}_html".to_sym
if hash[field].present? && hash[field] == hash[html_field] if hash[field].present? && hash[field] == hash[html_field]
hash.delete(html_field) hash.delete(html_field)
end end
end end
hash[:answers].each_with_index do |answer, i| hash[:answers].each_with_index do |answer, i|
[:html, :comments_html, :left_html].each do |field| [:html, :comments_html, :left_html].each do |field|
key = "answer #{i} #{field}" key = "answer #{i} #{field}"
hash[:missing_links][key] = []
if answer[field].present? if answer[field].present?
answer[field] = ImportedHtmlConverter.convert(answer[field], context, migration, {:remove_outer_nodes_if_one_child => true}) do |warn, link| answer[field] = migration.convert_html(
hash[:missing_links][key] << link if warn == :missing_link answer[field], item_type, hash[:migration_id], key, {:remove_outer_nodes_if_one_child => true}
end )
end end
end end
if answer[:comments].present? && answer[:comments] == answer[:comments_html] if answer[:comments].present? && answer[:comments] == answer[:comments_html]
answer.delete(:comments_html) answer.delete(:comments_html)
end end
end if hash[:answers] end if hash[:answers]
hash[:prepped_for_import] = true hash[:prepped_for_import] = true
hash hash
end end

View File

@ -33,14 +33,14 @@ module Importers
end end
end end
def self.import_from_migration(hash, context, migration=nil, item=nil) def self.import_from_migration(hash, context, migration, item=nil)
hash = hash.with_indifferent_access hash = hash.with_indifferent_access
return nil if hash[:migration_id] && hash[:assignment_groups_to_import] && !hash[:assignment_groups_to_import][hash[:migration_id]] return nil if hash[:migration_id] && hash[:assignment_groups_to_import] && !hash[:assignment_groups_to_import][hash[:migration_id]]
item ||= AssignmentGroup.where(context_id: context, context_type: context.class.to_s, id: hash[:id]).first item ||= AssignmentGroup.where(context_id: context, context_type: context.class.to_s, id: hash[:id]).first
item ||= AssignmentGroup.where(context_id: context, context_type: context.class.to_s, migration_id: hash[:migration_id]).first if hash[:migration_id] item ||= AssignmentGroup.where(context_id: context, context_type: context.class.to_s, migration_id: hash[:migration_id]).first if hash[:migration_id]
item ||= context.assignment_groups.where(name: hash[:title], migration_id: nil).first item ||= context.assignment_groups.where(name: hash[:title], migration_id: nil).first
item ||= context.assignment_groups.new item ||= context.assignment_groups.new
migration.add_imported_item(item) if migration migration.add_imported_item(item)
item.migration_id = hash[:migration_id] item.migration_id = hash[:migration_id]
item.workflow_state = 'available' if item.deleted? item.workflow_state = 'available' if item.deleted?
item.name = hash[:title] item.name = hash[:title]

View File

@ -26,7 +26,7 @@ module Importers
end end
end end
def self.import_from_migration(hash, context, migration=nil, item=nil, quiz=nil) def self.import_from_migration(hash, context, migration, item=nil, quiz=nil)
hash = hash.with_indifferent_access hash = hash.with_indifferent_access
return nil if hash[:migration_id] && hash[:assignments_to_import] && !hash[:assignments_to_import][hash[:migration_id]] return nil if hash[:migration_id] && hash[:assignments_to_import] && !hash[:assignments_to_import][hash[:migration_id]]
item ||= Assignment.where(context_type: context.class.to_s, context_id: context, id: hash[:id]).first item ||= Assignment.where(context_type: context.class.to_s, context_id: context, id: hash[:id]).first
@ -46,20 +46,14 @@ module Importers
self.extend TextHelper self.extend TextHelper
end end
missing_links = {:description => [], :instructions => []}
description = "" description = ""
if hash[:instructions_in_html] == false if hash[:instructions_in_html] == false
description += ImportedHtmlConverter.convert_text(hash[:description] || "", context) description += migration.convert_text(hash[:description] || "")
description += ImportedHtmlConverter.convert_text(hash[:instructions] || "", context) description += migration.convert_text(hash[:instructions] || "")
else else
description += ImportedHtmlConverter.convert(hash[:description] || "", context, migration) do |warn, link| description += migration.convert_html(hash[:description] || "", :assignment, hash[:migration_id], :description)
missing_links[:description] << link if warn == :missing_link description += migration.convert_html(hash[:instructions] || "", :assignment, hash[:migration_id], :description)
end
description += ImportedHtmlConverter.convert(hash[:instructions] || "", context, migration) do |warn, link|
missing_links[:instructions] << link if warn == :missing_link
end
end end
description += Attachment.attachment_list_from_migration(context, hash[:attachment_ids]) description += Attachment.attachment_list_from_migration(context, hash[:attachment_ids])
item.description = description item.description = description
@ -107,11 +101,12 @@ module Importers
item.submission_types = 'not_graded' item.submission_types = 'not_graded'
end end
end end
if hash[:assignment_group_migration_id]
item.assignment_group = context.assignment_groups.where(migration_id: hash[:assignment_group_migration_id]).first
end
item.assignment_group ||= context.assignment_groups.where(name: t(:imported_assignments_group, "Imported Assignments")).first_or_create
# Associating with a rubric or a quiz might cause item to get saved, no longer indicating item.save_without_broadcasting!
# that it is a new record. We need to know that below, where we add to the list of
# imported items
new_record = item.new_record?
rubric = nil rubric = nil
rubric = context.rubrics.where(migration_id: hash[:rubric_migration_id]).first if hash[:rubric_migration_id] rubric = context.rubrics.where(migration_id: hash[:rubric_migration_id]).first if hash[:rubric_migration_id]
@ -156,10 +151,6 @@ module Importers
item.submission_types = 'online_quiz' item.submission_types = 'online_quiz'
item.saved_by = :quiz item.saved_by = :quiz
end end
if hash[:assignment_group_migration_id]
item.assignment_group = context.assignment_groups.where(migration_id: hash[:assignment_group_migration_id]).first
end
item.assignment_group ||= context.assignment_groups.where(name: t(:imported_assignments_group, "Imported Assignments")).first_or_create
hash[:due_at] ||= hash[:due_date] hash[:due_at] ||= hash[:due_date]
[:due_at, :lock_at, :unlock_at, :peer_reviews_due_at].each do |key| [:due_at, :lock_at, :unlock_at, :peer_reviews_due_at].each do |key|
@ -178,9 +169,9 @@ module Importers
item.send("#{prop}=", hash[prop]) unless hash[prop].nil? item.send("#{prop}=", hash[prop]) unless hash[prop].nil?
end end
migration.add_imported_item(item) if migration migration.add_imported_item(item)
if migration && migration.date_shift_options if migration.date_shift_options
# Unfortunately, we save the assignment here, and then shift dates and # Unfortunately, we save the assignment here, and then shift dates and
# save the assignment again later in the course migration. Saving here # save the assignment again later in the course migration. Saving here
# would normally schedule the auto peer reviews job with the # would normally schedule the auto peer reviews job with the
@ -193,14 +184,6 @@ module Importers
item.save_without_broadcasting! item.save_without_broadcasting!
item.skip_schedule_peer_reviews = nil item.skip_schedule_peer_reviews = nil
if migration
missing_links.each do |field, missing_links|
migration.add_missing_content_links(:class => item.class.to_s,
:id => item.id, :field => field, :missing_links => missing_links,
:url => "/#{context.class.to_s.underscore.pluralize}/#{context.id}/#{item.class.to_s.demodulize.underscore.pluralize}/#{item.id}")
end
end
if item.submission_types == 'external_tool' if item.submission_types == 'external_tool'
tag = item.create_external_tool_tag(:url => hash[:external_tool_url], :new_tab => hash[:external_tool_new_tab]) tag = item.create_external_tool_tag(:url => hash[:external_tool_url], :new_tab => hash[:external_tool_new_tab])
if hash[:external_tool_id] && migration && !migration.cross_institution? if hash[:external_tool_id] && migration && !migration.cross_institution?
@ -212,7 +195,11 @@ module Importers
end end
tag.content_type = 'ContextExternalTool' tag.content_type = 'ContextExternalTool'
if !tag.save if !tag.save
migration.add_warning(t('errors.import.external_tool_url', "The url for the external tool assignment \"%{assignment_name}\" wasn't valid.", :assignment_name => item.title)) if migration && tag.errors["url"] if tag.errors["url"]
migration.add_warning(t('errors.import.external_tool_url',
"The url for the external tool assignment \"%{assignment_name}\" wasn't valid.",
:assignment_name => item.title))
end
item.association(:external_tool_tag).target = nil # otherwise it will trigger destroy on the tag item.association(:external_tool_tag).target = nil # otherwise it will trigger destroy on the tag
end end
end end

View File

@ -57,7 +57,7 @@ module Importers
private private
def self.import_from_migration(hash, context, migration=nil, item=nil, created_usage_rights_map={}) def self.import_from_migration(hash, context, migration, item=nil, created_usage_rights_map={})
return nil if hash[:files_to_import] && !hash[:files_to_import][hash[:migration_id]] return nil if hash[:files_to_import] && !hash[:files_to_import][hash[:migration_id]]
item ||= Attachment.where(context_type: context.class.to_s, context_id: context, id: hash[:id]).first item ||= Attachment.where(context_type: context.class.to_s, context_id: context, id: hash[:id]).first
item ||= Attachment.where(context_type: context.class.to_s, context_id: context, migration_id: hash[:migration_id]).first # if hash[:migration_id] item ||= Attachment.where(context_type: context.class.to_s, context_id: context, migration_id: hash[:migration_id]).first # if hash[:migration_id]
@ -71,7 +71,7 @@ module Importers
item.usage_rights_id = find_or_create_usage_rights(context, hash[:usage_rights], created_usage_rights_map) if hash[:usage_rights] item.usage_rights_id = find_or_create_usage_rights(context, hash[:usage_rights], created_usage_rights_map) if hash[:usage_rights]
item.set_publish_state_for_usage_rights unless hash[:locked] item.set_publish_state_for_usage_rights unless hash[:locked]
item.save_without_broadcasting! item.save_without_broadcasting!
migration.add_imported_item(item) if migration migration.add_imported_item(item)
end end
item item
end end

View File

@ -34,7 +34,7 @@ module Importers
end end
end end
def self.import_from_migration(hash, context, migration=nil, item=nil) def self.import_from_migration(hash, context, migration, item=nil)
hash = hash.with_indifferent_access hash = hash.with_indifferent_access
return nil if hash[:migration_id] && hash[:events_to_import] && !hash[:events_to_import][hash[:migration_id]] return nil if hash[:migration_id] && hash[:events_to_import] && !hash[:events_to_import][hash[:migration_id]]
item ||= CalendarEvent.where(context_type: context.class.to_s, context_id: context, id: hash[:id]).first item ||= CalendarEvent.where(context_type: context.class.to_s, context_id: context, id: hash[:id]).first
@ -44,10 +44,8 @@ module Importers
item.migration_id = hash[:migration_id] item.migration_id = hash[:migration_id]
item.workflow_state = 'active' if item.deleted? item.workflow_state = 'active' if item.deleted?
item.title = hash[:title] || hash[:name] item.title = hash[:title] || hash[:name]
missing_links = []
item.description = ImportedHtmlConverter.convert(hash[:description] || "", context, migration) do |warn, link| item.description = migration.convert_html(hash[:description] || "", :calendar_event, hash[:migration_id], :description)
missing_links << link if warn == :missing_link
end
item.description += import_migration_attachment_suffix(hash, context) item.description += import_migration_attachment_suffix(hash, context)
item.start_at = Canvas::Migration::MigratorHelper.get_utc_time_from_timestamp(hash[:start_at] || hash[:start_date]) item.start_at = Canvas::Migration::MigratorHelper.get_utc_time_from_timestamp(hash[:start_at] || hash[:start_date])
item.end_at = Canvas::Migration::MigratorHelper.get_utc_time_from_timestamp(hash[:end_at] || hash[:end_date]) item.end_at = Canvas::Migration::MigratorHelper.get_utc_time_from_timestamp(hash[:end_at] || hash[:end_date])
@ -55,12 +53,7 @@ module Importers
item.imported = true item.imported = true
item.save_without_broadcasting! item.save_without_broadcasting!
if migration migration.add_imported_item(item)
migration.add_missing_content_links(:class => item.class.to_s,
:id => item.id, :missing_links => missing_links,
:url => "/#{context.class.to_s.demodulize.underscore.pluralize}/#{context.id}/#{item.class.to_s.demodulize.underscore.pluralize}/#{item.id}")
end
migration.add_imported_item(item) if migration
if hash[:all_day] if hash[:all_day]
item.all_day = hash[:all_day] item.all_day = hash[:all_day]
item.save item.save

View File

@ -21,7 +21,7 @@ module Importers
end end
end end
def self.import_from_migration(hash, context, migration=nil, item=nil) def self.import_from_migration(hash, context, migration, item=nil)
hash = hash.with_indifferent_access hash = hash.with_indifferent_access
return nil if hash[:migration_id] && hash[:external_tools_to_import] && !hash[:external_tools_to_import][hash[:migration_id]] return nil if hash[:migration_id] && hash[:external_tools_to_import] && !hash[:external_tools_to_import][hash[:migration_id]]

View File

@ -50,13 +50,13 @@ module Importers
migration.context.touch migration.context.touch
end end
def self.import_from_migration(hash, context, migration=nil, item=nil) def self.import_from_migration(hash, context, migration, item=nil)
hash = hash.with_indifferent_access hash = hash.with_indifferent_access
return nil if hash[:migration_id] && hash[:modules_to_import] && !hash[:modules_to_import][hash[:migration_id]] return nil if hash[:migration_id] && hash[:modules_to_import] && !hash[:modules_to_import][hash[:migration_id]]
item ||= ContextModule.where(context_type: context.class.to_s, context_id: context, id: hash[:id]).first item ||= ContextModule.where(context_type: context.class.to_s, context_id: context, id: hash[:id]).first
item ||= ContextModule.where(context_type: context.class.to_s, context_id: context, migration_id: hash[:migration_id]).first if hash[:migration_id] item ||= ContextModule.where(context_type: context.class.to_s, context_id: context, migration_id: hash[:migration_id]).first if hash[:migration_id]
item ||= ContextModule.new(:context => context) item ||= ContextModule.new(:context => context)
migration.add_imported_item(item) if migration migration.add_imported_item(item)
item.name = hash[:title] || hash[:description] item.name = hash[:title] || hash[:description]
item.migration_id = hash[:migration_id] item.migration_id = hash[:migration_id]
if hash[:workflow_state] == 'unpublished' if hash[:workflow_state] == 'unpublished'
@ -94,7 +94,7 @@ module Importers
begin begin
self.add_module_item_from_migration(item, tag_hash, 0, context, item_map, migration) self.add_module_item_from_migration(item, tag_hash, 0, context, item_map, migration)
rescue rescue
migration.add_import_warning(t(:migration_module_item_type, "Module Item"), tag_hash[:title], $!) if migration migration.add_import_warning(t(:migration_module_item_type, "Module Item"), tag_hash[:title], $!)
end end
end end
@ -117,7 +117,7 @@ module Importers
end end
def self.add_module_item_from_migration(context_module, hash, level, context, item_map, migration=nil) def self.add_module_item_from_migration(context_module, hash, level, context, item_map, migration)
hash = hash.with_indifferent_access hash = hash.with_indifferent_access
hash[:migration_id] ||= hash[:item_migration_id] hash[:migration_id] ||= hash[:item_migration_id]
hash[:migration_id] ||= Digest::MD5.hexdigest(hash[:title]) if hash[:title] hash[:migration_id] ||= Digest::MD5.hexdigest(hash[:title]) if hash[:title]
@ -129,7 +129,7 @@ module Importers
else else
existing_item.workflow_state = 'active' existing_item.workflow_state = 'active'
end end
migration.add_imported_item(existing_item) if migration migration.add_imported_item(existing_item)
existing_item.migration_id = hash[:migration_id] existing_item.migration_id = hash[:migration_id]
hash[:indent] = [hash[:indent] || 0, level].max hash[:indent] = [hash[:indent] || 0, level].max
resource_class = linked_resource_type_class(hash[:linked_resource_type]) resource_class = linked_resource_type_class(hash[:linked_resource_type])
@ -137,7 +137,7 @@ module Importers
wiki = context_module.context.wiki.wiki_pages.where(migration_id: hash[:linked_resource_id]).first if hash[:linked_resource_id] wiki = context_module.context.wiki.wiki_pages.where(migration_id: hash[:linked_resource_id]).first if hash[:linked_resource_id]
if wiki if wiki
item = context_module.add_item({ item = context_module.add_item({
:title => hash[:title] || hash[:linked_resource_title], :title => wiki.title.presence || hash[:title] || hash[:linked_resource_title],
:type => 'wiki_page', :type => 'wiki_page',
:id => wiki.id, :id => wiki.id,
:indent => hash[:indent].to_i :indent => hash[:indent].to_i
@ -158,7 +158,7 @@ module Importers
ass = context_module.context.assignments.where(migration_id: hash[:linked_resource_id]).first if hash[:linked_resource_id] ass = context_module.context.assignments.where(migration_id: hash[:linked_resource_id]).first if hash[:linked_resource_id]
if ass if ass
item = context_module.add_item({ item = context_module.add_item({
:title => hash[:title] || hash[:linked_resource_title], :title => ass.title.presence || hash[:title] || hash[:linked_resource_title],
:type => 'assignment', :type => 'assignment',
:id => ass.id, :id => ass.id,
:indent => hash[:indent].to_i :indent => hash[:indent].to_i
@ -175,7 +175,7 @@ module Importers
# external url # external url
if url = hash[:url] if url = hash[:url]
if (CanvasHttp.validate_url(hash[:url]) rescue nil) if (CanvasHttp.validate_url(hash[:url]) rescue nil)
url = migration.process_domain_substitutions(url) if migration url = migration.process_domain_substitutions(url)
item = context_module.add_item({ item = context_module.add_item({
:title => hash[:title] || hash[:linked_resource_title] || hash['description'], :title => hash[:title] || hash[:linked_resource_title] || hash['description'],
@ -184,7 +184,7 @@ module Importers
:url => url :url => url
}, existing_item, :position => context_module.migration_position) }, existing_item, :position => context_module.migration_position)
else else
migration.add_import_warning(t(:migration_module_item_type, "Module Item"), hash[:title], "#{hash[:url]} is not a valid URL") if migration migration.add_import_warning(t(:migration_module_item_type, "Module Item"), hash[:title], "#{hash[:url]} is not a valid URL")
end end
end end
elsif resource_class == ContextExternalTool elsif resource_class == ContextExternalTool
@ -194,7 +194,7 @@ module Importers
if hash[:linked_resource_global_id] && (!migration || !migration.cross_institution?) if hash[:linked_resource_global_id] && (!migration || !migration.cross_institution?)
external_tool_id = hash[:linked_resource_global_id] external_tool_id = hash[:linked_resource_global_id]
elsif migration && arr = migration.find_external_tool_translation(hash[:linked_resource_id]) elsif arr = migration.find_external_tool_translation(hash[:linked_resource_id])
external_tool_id = arr[0] external_tool_id = arr[0]
custom_fields = arr[1] custom_fields = arr[1]
if custom_fields.present? if custom_fields.present?
@ -206,14 +206,14 @@ module Importers
if external_tool_url if external_tool_url
title = hash[:title] || hash[:linked_resource_title] || hash['description'] title = hash[:title] || hash[:linked_resource_title] || hash['description']
if migration
external_tool_url = migration.process_domain_substitutions(external_tool_url) external_tool_url = migration.process_domain_substitutions(external_tool_url)
if external_tool_id.nil? if external_tool_id.nil?
migration.add_warning(t(:foreign_lti_tool, migration.add_warning(t(:foreign_lti_tool,
%q{The account External Tool for module item "%{title}" must be configured before the item can be launched}, %q{The account External Tool for module item "%{title}" must be configured before the item can be launched},
:title => title)) :title => title))
end
end end
item = context_module.add_item({ item = context_module.add_item({
:title => title, :title => title,
:type => 'context_external_tool', :type => 'context_external_tool',
@ -226,7 +226,7 @@ module Importers
quiz = context_module.context.quizzes.where(migration_id: hash[:linked_resource_id]).first if hash[:linked_resource_id] quiz = context_module.context.quizzes.where(migration_id: hash[:linked_resource_id]).first if hash[:linked_resource_id]
if quiz if quiz
item = context_module.add_item({ item = context_module.add_item({
:title => hash[:title] || hash[:linked_resource_title], :title => quiz.title.presence || hash[:title] || hash[:linked_resource_title],
:type => 'quiz', :type => 'quiz',
:indent => hash[:indent].to_i, :indent => hash[:indent].to_i,
:id => quiz.id :id => quiz.id
@ -236,7 +236,7 @@ module Importers
topic = context_module.context.discussion_topics.where(migration_id: hash[:linked_resource_id]).first if hash[:linked_resource_id] topic = context_module.context.discussion_topics.where(migration_id: hash[:linked_resource_id]).first if hash[:linked_resource_id]
if topic if topic
item = context_module.add_item({ item = context_module.add_item({
:title => hash[:title] || hash[:linked_resource_title], :title => topic.title.presence || hash[:title] || hash[:linked_resource_title],
:type => 'discussion_topic', :type => 'discussion_topic',
:indent => hash[:indent].to_i, :indent => hash[:indent].to_i,
:id => topic.id :id => topic.id

View File

@ -9,14 +9,14 @@ module Importers
data['all_files_export']['file_path'] ||= data['all_files_zip'] data['all_files_export']['file_path'] ||= data['all_files_zip']
return unless data['all_files_export']['file_path'] && File.exist?(data['all_files_export']['file_path']) return unless data['all_files_export']['file_path'] && File.exist?(data['all_files_export']['file_path'])
course.attachment_path_id_lookup ||= {} migration.attachment_path_id_lookup ||= {}
course.attachment_path_id_lookup_lower ||= {} migration.attachment_path_id_lookup_lower ||= {}
params = migration.migration_settings[:migration_ids_to_import] params = migration.migration_settings[:migration_ids_to_import]
valid_paths = [] valid_paths = []
(data['file_map'] || {}).each do |id, file| (data['file_map'] || {}).each do |id, file|
path = file['path_name'].starts_with?('/') ? file['path_name'][1..-1] : file['path_name'] path = file['path_name'].starts_with?('/') ? file['path_name'][1..-1] : file['path_name']
course.attachment_path_id_lookup[path] = file['migration_id'] migration.add_attachment_path(path, file['migration_id'])
course.attachment_path_id_lookup_lower[path.downcase] = file['migration_id']
if migration.import_object?("attachments", file['migration_id']) || migration.import_object?("files", file['migration_id']) if migration.import_object?("attachments", file['migration_id']) || migration.import_object?("files", file['migration_id'])
if file['errored'] if file['errored']
migration.add_warning(t(:file_import_warning, "File %{file} could not be found", :file => File.basename(file['path_name']))) migration.add_warning(t(:file_import_warning, "File %{file} could not be found", :file => File.basename(file['path_name'])))
@ -45,7 +45,7 @@ module Importers
:callback => callback, :callback => callback,
:logger => logger, :logger => logger,
:rename_files => migration.migration_settings[:files_import_allow_rename], :rename_files => migration.migration_settings[:files_import_allow_rename],
:migration_id_map => course.attachment_path_id_lookup, :migration_id_map => migration.attachment_path_id_lookup,
} }
if root_path = migration.migration_settings[:files_import_root_path] if root_path = migration.migration_settings[:files_import_root_path]
unzip_opts[:root_directory] = Folder.assert_path( unzip_opts[:root_directory] = Folder.assert_path(
@ -104,38 +104,29 @@ module Importers
end end
end end
migration.update_import_progress(31) migration.update_import_progress(35)
question_data = Importers::AssessmentQuestionImporter.process_migration(data, migration); migration.update_import_progress(35) question_data = Importers::AssessmentQuestionImporter.process_migration(data, migration); migration.update_import_progress(45)
Importers::GroupImporter.process_migration(data, migration); migration.update_import_progress(36) Importers::GroupImporter.process_migration(data, migration); migration.update_import_progress(48)
Importers::LearningOutcomeImporter.process_migration(data, migration); migration.update_import_progress(37) Importers::LearningOutcomeImporter.process_migration(data, migration); migration.update_import_progress(50)
Importers::RubricImporter.process_migration(data, migration); migration.update_import_progress(38) Importers::RubricImporter.process_migration(data, migration); migration.update_import_progress(52)
course.assignment_group_no_drop_assignments = {} course.assignment_group_no_drop_assignments = {}
Importers::AssignmentGroupImporter.process_migration(data, migration); migration.update_import_progress(39) Importers::AssignmentGroupImporter.process_migration(data, migration); migration.update_import_progress(54)
Importers::ExternalFeedImporter.process_migration(data, migration); migration.update_import_progress(39.5) Importers::ExternalFeedImporter.process_migration(data, migration); migration.update_import_progress(56)
Importers::GradingStandardImporter.process_migration(data, migration); migration.update_import_progress(40) Importers::GradingStandardImporter.process_migration(data, migration); migration.update_import_progress(58)
Importers::ContextExternalToolImporter.process_migration(data, migration); migration.update_import_progress(45) Importers::ContextExternalToolImporter.process_migration(data, migration); migration.update_import_progress(60)
Importers::QuizImporter.process_migration(data, migration, question_data); migration.update_import_progress(65)
#These need to be ran twice because they can reference each other Importers::DiscussionTopicImporter.process_migration(data, migration); migration.update_import_progress(70)
Importers::QuizImporter.process_migration(data, migration, question_data); migration.update_import_progress(50) Importers::WikiPageImporter.process_migration(data, migration); migration.update_import_progress(75)
Importers::DiscussionTopicImporter.process_migration(data, migration);migration.update_import_progress(55) Importers::AssignmentImporter.process_migration(data, migration); migration.update_import_progress(80)
Importers::WikiPageImporter.process_migration(data, migration);migration.update_import_progress(60) Importers::ContextModuleImporter.process_migration(data, migration); migration.update_import_progress(85)
Importers::AssignmentImporter.process_migration(data, migration);migration.update_import_progress(65) Importers::WikiPageImporter.process_migration_course_outline(data, migration)
Importers::CalendarEventImporter.process_migration(data, migration)
# and second time...
Importers::ContextModuleImporter.process_migration(data, migration);migration.update_import_progress(70)
Importers::QuizImporter.process_migration(data, migration, question_data); migration.update_import_progress(72)
Importers::DiscussionTopicImporter.process_migration(data, migration);migration.update_import_progress(75)
Importers::WikiPageImporter.process_migration(data, migration);migration.update_import_progress(80)
Importers::AssignmentImporter.process_migration(data, migration);migration.update_import_progress(85)
#These aren't referenced by anything, but reference other things
Importers::CalendarEventImporter.process_migration(data, migration);migration.update_import_progress(90)
Importers::WikiPageImporter.process_migration_course_outline(data, migration);migration.update_import_progress(95)
everything_selected = !migration.copy_options || migration.is_set?(migration.copy_options[:everything]) everything_selected = !migration.copy_options || migration.is_set?(migration.copy_options[:everything])
if everything_selected || migration.is_set?(migration.copy_options[:all_course_settings]) if everything_selected || migration.is_set?(migration.copy_options[:all_course_settings])
self.import_settings_from_migration(course, data, migration); migration.update_import_progress(96) self.import_settings_from_migration(course, data, migration)
end end
migration.update_import_progress(90)
# be very explicit about draft state courses, but be liberal toward legacy courses # be very explicit about draft state courses, but be liberal toward legacy courses
if course.wiki.has_no_front_page if course.wiki.has_no_front_page
@ -155,7 +146,8 @@ module Importers
self.import_syllabus_from_migration(course, syllabus_body, migration) if syllabus_body self.import_syllabus_from_migration(course, syllabus_body, migration) if syllabus_body
end end
migration.add_warnings_for_missing_content_links migration.resolve_content_links!
migration.update_import_progress(95)
begin begin
#Adjust dates #Adjust dates
@ -226,13 +218,7 @@ module Importers
end end
def self.import_syllabus_from_migration(course, syllabus_body, migration) def self.import_syllabus_from_migration(course, syllabus_body, migration)
missing_links = [] course.syllabus_body = migration.convert_html(syllabus_body, :syllabus, nil, :syllabus)
course.syllabus_body = ImportedHtmlConverter.convert(syllabus_body, course, migration) do |warn, link|
missing_links << link if warn == :missing_link
end
migration.add_missing_content_links(:class => course.class.to_s,
:id => course.id, :field => "syllabus", :missing_links => missing_links,
:url => "/#{course.class.to_s.underscore.pluralize}/#{course.id}/assignments/syllabus")
end end
def self.import_settings_from_migration(course, data, migration) def self.import_settings_from_migration(course, data, migration)

View File

@ -46,7 +46,7 @@ module Importers
migration.import_object?('announcements', topic['migration_id'])) migration.import_object?('announcements', topic['migration_id']))
end end
def self.import_from_migration(hash, context, migration=nil, item=nil) def self.import_from_migration(hash, context, migration, item=nil)
importer = self.new(hash, context, migration, item) importer = self.new(hash, context, migration, item)
importer.run importer.run
end end
@ -79,11 +79,10 @@ module Importers
:require_initial_post].each do |attr| :require_initial_post].each do |attr|
item.send("#{attr}=", options[attr]) item.send("#{attr}=", options[attr])
end end
missing_links = []
type = item.is_a?(Announcement) ? :announcement : :discussion_topic
if options.message if options.message
item.message = ImportedHtmlConverter.convert(options.message, context, migration) do |warn, link| item.message = migration.convert_html(options.message, type, options[:migration_id], :message)
missing_links << link if warn == :missing_link
end
else else
item.message = I18n.t('#discussion_topic.empty_message', 'No message') item.message = I18n.t('#discussion_topic.empty_message', 'No message')
end end
@ -115,7 +114,6 @@ module Importers
item.save_without_broadcasting! item.save_without_broadcasting!
import_migration_item import_migration_item
add_missing_content_links(missing_links)
item.saved_by = nil item.saved_by = nil
item item
end end
@ -136,15 +134,7 @@ module Importers
end end
def import_migration_item def import_migration_item
migration.add_imported_item(item) if migration migration.add_imported_item(item)
end
def add_missing_content_links(missing_links)
if migration
migration.add_missing_content_links(class: item.class.to_s,
id: item.id, missing_links: missing_links,
url: "/#{context.class.to_s.underscore.pluralize}/#{context.id}/#{item.class.to_s.demodulize.underscore.pluralize}/#{item.id}")
end
end end
class DiscussionTopicOptions class DiscussionTopicOptions

View File

@ -17,7 +17,7 @@ module Importers
end end
end end
def self.import_from_migration(hash, context, migration=nil, item=nil) def self.import_from_migration(hash, context, migration, item=nil)
hash = hash.with_indifferent_access hash = hash.with_indifferent_access
return nil if hash[:migration_id] && hash[:external_feeds_to_import] && !hash[:external_feeds_to_import][hash[:migration_id]] return nil if hash[:migration_id] && hash[:external_feeds_to_import] && !hash[:external_feeds_to_import][hash[:migration_id]]
item ||= find_or_initialize_from_migration(hash, context) item ||= find_or_initialize_from_migration(hash, context)
@ -28,7 +28,7 @@ module Importers
item.header_match = hash[:header_match] unless hash[:header_match].blank? item.header_match = hash[:header_match] unless hash[:header_match].blank?
item.save! item.save!
migration.add_imported_item(item) if migration migration.add_imported_item(item)
item item
end end

View File

@ -23,7 +23,7 @@ module Importers
end end
end end
def self.import_from_migration(hash, context, migration=nil, item=nil) def self.import_from_migration(hash, context, migration, item=nil)
hash = hash.with_indifferent_access hash = hash.with_indifferent_access
return nil if hash[:migration_id] && hash[:grading_standards_to_import] && !hash[:grading_standards_to_import][hash[:migration_id]] return nil if hash[:migration_id] && hash[:grading_standards_to_import] && !hash[:grading_standards_to_import][hash[:migration_id]]
item ||= GradingStandard.where(context_id: context, context_type: context.class.to_s, migration_id: hash[:migration_id]).first if hash[:migration_id] item ||= GradingStandard.where(context_id: context, context_type: context.class.to_s, migration_id: hash[:migration_id]).first if hash[:migration_id]
@ -38,7 +38,7 @@ module Importers
end end
item.save! item.save!
migration.add_imported_item(item) if migration migration.add_imported_item(item)
item item
end end
end end

View File

@ -16,13 +16,13 @@ module Importers
end end
end end
def self.import_from_migration(hash, context, migration=nil, item=nil) def self.import_from_migration(hash, context, migration, item=nil)
hash = hash.with_indifferent_access hash = hash.with_indifferent_access
return nil if hash[:migration_id] && hash[:groups_to_import] && !hash[:groups_to_import][hash[:migration_id]] return nil if hash[:migration_id] && hash[:groups_to_import] && !hash[:groups_to_import][hash[:migration_id]]
item ||= Group.where(context_id: context, context_type: context.class.to_s, id: hash[:id]).first item ||= Group.where(context_id: context, context_type: context.class.to_s, id: hash[:id]).first
item ||= Group.where(context_id: context, context_type: context.class.to_s, migration_id: hash[:migration_id]).first if hash[:migration_id] item ||= Group.where(context_id: context, context_type: context.class.to_s, migration_id: hash[:migration_id]).first if hash[:migration_id]
item ||= context.groups.new item ||= context.groups.new
migration.add_imported_item(item) if migration migration.add_imported_item(item)
item.migration_id = hash[:migration_id] item.migration_id = hash[:migration_id]
item.name = hash[:title] item.name = hash[:title]
item.group_category = hash[:group_category].present? ? item.group_category = hash[:group_category].present? ?
@ -30,7 +30,7 @@ module Importers
GroupCategory.imported_for(context) GroupCategory.imported_for(context)
item.save! item.save!
migration.add_imported_item(item) if migration migration.add_imported_item(item)
item item
end end
end end

View File

@ -36,7 +36,7 @@ module Importers
root_outcome_group.adopt_outcome_group(item) root_outcome_group.adopt_outcome_group(item)
end end
migration.add_imported_item(item) if migration migration.add_imported_item(item)
if hash[:outcomes] if hash[:outcomes]
hash[:outcomes].each do |child| hash[:outcomes].each do |child|

View File

@ -80,7 +80,7 @@ module Importers
item.save! item.save!
migration.add_imported_item(item) if migration migration.add_imported_item(item)
else else
item = outcome item = outcome
end end

View File

@ -0,0 +1,139 @@
module Importers
class LinkParser
module Helpers
def context
@context ||= @migration.context
end
def context_path
@context_path ||= "/#{context.class.to_s.underscore.pluralize}/#{context.id}"
end
def relative_url?(url)
ImportedHtmlConverter.relative_url?(url)
end
end
include Helpers
REFERENCE_KEYWORDS = %w{CANVAS_COURSE_REFERENCE CANVAS_OBJECT_REFERENCE WIKI_REFERENCE IMS_CC_FILEBASE IMS-CC-FILEBASE}
LINK_PLACEHOLDER = "LINK.PLACEHOLDER"
attr_reader :unresolved_link_map
def initialize(migration)
@migration = migration
reset!
end
def reset!
@unresolved_link_map = {}
end
def add_unresolved_link(link, item_type, mig_id, field)
key = {:type => item_type, :migration_id => mig_id}
@unresolved_link_map[key] ||= {}
@unresolved_link_map[key][field] ||= []
@unresolved_link_map[key][field] << link
end
def placeholder(old_value)
"#{LINK_PLACEHOLDER}_#{Digest::MD5.hexdigest(old_value)}"
end
def convert_link(node, attr, item_type, mig_id, field)
return unless node[attr].present?
if attr == 'value'
return unless node['name'] && node['name'] == 'src'
end
url = node[attr].dup
REFERENCE_KEYWORDS.each do |ref|
url.gsub!("%24#{ref}%24", "$#{ref}$")
end
result = parse_url(url, node, attr)
if result[:resolved]
# resolved, just replace and carry on
new_url = result[:new_url] || url
if @migration && !relative_url?(new_url) && processed_url = @migration.process_domain_substitutions(new_url)
new_url = processed_url
end
node[attr] = new_url
else
result.delete(:resolved)
if result[:link_type] == :media_object
# because we may actually change the media comment node itself
# (rather than just replacing a value), we're going to
# replace the entire node with a placeholder
result[:old_value] = node.to_xml
result[:placeholder] = placeholder(result[:old_value])
placeholder_node = Nokogiri::HTML::DocumentFragment.parse(result[:placeholder])
node.replace(placeholder_node)
else
result[:old_value] = node[attr]
result[:placeholder] = placeholder(result[:old_value])
node[attr] = result[:placeholder]
end
add_unresolved_link(result, item_type, mig_id, field)
end
end
def unresolved(type, data={})
{:resolved => false, :link_type => type}.merge(data)
end
def resolved(new_url=nil)
{:resolved => true, :new_url => new_url}
end
# returns a hash with resolution status and data to hold onto if unresolved
def parse_url(url, node, attr)
if url =~ /wiki_page_migration_id=(.*)/
unresolved(:wiki_page, :migration_id => $1)
elsif url =~ /discussion_topic_migration_id=(.*)/
unresolved(:discussion_topic, :migration_id => $1)
elsif url =~ %r{\$CANVAS_COURSE_REFERENCE\$/modules/items/(.*)}
unresolved(:module_item, :migration_id => $1)
elsif url =~ %r{(?:\$CANVAS_OBJECT_REFERENCE\$|\$WIKI_REFERENCE\$)/([^/]*)/(.*)}
unresolved(:object, :type => $1, :migration_id => $2)
elsif url =~ %r{\$CANVAS_COURSE_REFERENCE\$/(.*)}
resolved("#{context_path}/#{$1}")
elsif url =~ %r{\$IMS(?:-|_)CC(?:-|_)FILEBASE\$/(.*)}
rel_path = URI.unescape($1)
if attr == 'href' && node['class'] && node['class'] =~ /instructure_inline_media_comment/
unresolved(:media_object, :rel_path => rel_path)
else
unresolved(:file, :rel_path => rel_path)
end
elsif attr == 'href' && node['class'] && node['class'] =~ /instructure_inline_media_comment/
# Course copy media reference, leave it alone
resolved
elsif attr == 'src' && node['class'] && node['class'] =~ /equation_image/
# Equation image, leave it alone
resolved
elsif url =~ %r{\A/assessment_questions/\d+/files/\d+}
# The file is in the context of an AQ, leave the link alone
resolved
elsif url =~ %r{\A/courses/\d+/files/\d+}
# This points to a specific file already, leave it alone
resolved
elsif @migration && @migration.for_course_copy?
# For course copies don't try to fix relative urls. Any url we can
# correctly alter was changed during the 'export' step
resolved
elsif url.start_with?('#')
# It's just a link to an anchor, leave it alone
resolved
elsif relative_url?(url)
unresolved(:file, :rel_path => URI.unescape(url))
else
resolved
end
end
end
end

View File

@ -0,0 +1,198 @@
module Importers
class LinkReplacer
LINK_TYPE_TO_CLASS = {
:announcement => Announcement,
:assessment_question => AssessmentQuestion,
:assignment => Assignment,
:calendar_event => CalendarEvent,
:discussion_topic => DiscussionTopic,
:quiz => Quizzes::Quiz,
:wiki_page => WikiPage
}
include LinkParser::Helpers
def initialize(migration)
@migration = migration
end
def replace_placeholders!(link_map)
load_questions!(link_map)
link_map.each do |item_key, field_links|
begin
item_key[:item] ||= retrieve_item(item_key)
add_missing_link_warnings!(item_key, field_links)
replace_item_placeholders!(item_key, field_links)
rescue
@migration.add_warning("An error occurred while translating content links", $!)
end
end
end
# these don't get added to the list of imported migration items
def load_questions!(link_map)
aq_item_keys = link_map.keys.select{|item_key| item_key[:type] == :assessment_question}
aq_item_keys.each_slice(100) do |item_keys|
context.assessment_questions.where(:migration_id => item_keys.map{|ikey| ikey[:migration_id]}).each do |aq|
item_keys.detect{|ikey| ikey[:migration_id] == aq.migration_id}[:item] = aq
end
end
qq_item_keys = link_map.keys.select{|item_key| item_key[:type] == :quiz_question}
qq_item_keys.each_slice(100) do |item_keys|
context.quiz_questions.where(:migration_id => item_keys.map{|ikey| ikey[:migration_id]}).each do |qq|
item_keys.detect{|ikey| ikey[:migration_id] == qq.migration_id}[:item] = qq
end
end
end
def retrieve_item(item_key)
klass = LINK_TYPE_TO_CLASS[item_key[:type]]
return unless klass
item = @migration.find_imported_migration_item(klass, item_key[:migration_id])
raise "item not found" unless item
item
end
def add_missing_link_warnings!(item_key, field_links)
fix_issue_url = nil
field_links.each do |field, links|
missing_links = links.select{|link| link[:missing_url] || !link[:new_value]}
if missing_links.any?
fix_issue_url ||= fix_issue_url(item_key)
type = item_key[:type].to_s.humanize.titleize
@migration.add_warning_for_missing_content_links(type, field, missing_links, fix_issue_url)
end
end
end
def fix_issue_url(item_key)
item = item_key[:item]
case item_key[:type]
when :assessment_question
"#{context_path}/question_banks/#{item.assessment_question_bank_id}#question_#{item.id}_question_text"
when :syllabus
"#{context_path}/assignments/syllabus"
when :wiki_page
"#{context_path}/pages/#{item.url}"
else
"#{context_path}/#{item.class.to_s.demodulize.underscore.pluralize}/#{item.id}"
end
end
def replace_item_placeholders!(item_key, field_links, skip_associations=false)
case item_key[:type]
when :syllabus
syllabus = context.syllabus_body
if sub_placeholders!(syllabus, field_links.values.flatten)
context.class.where(:id => context.id).update_all(:syllabus_body => syllabus)
end
when :assessment_question
process_assessment_question!(item_key[:item], field_links.values.flatten)
when :quiz_question
process_quiz_question!(item_key[:item], field_links.values.flatten)
else
item = item_key[:item]
item_updates = {}
field_links.each do |field, links|
html = item.read_attribute(field)
if sub_placeholders!(html, links)
item_updates[field] = html
end
end
if item_updates.present?
item.class.where(:id => item.id).update_all(item_updates)
end
unless skip_associations
process_assignment_types!(item, field_links.values.flatten)
end
end
end
# returns false if no substitutions were made
def sub_placeholders!(html, links)
subbed = false
links.each do |link|
new_value = link[:new_value] || link[:old_value]
if html.sub!(link[:placeholder], new_value)
subbed = true
end
end
subbed
end
def process_assignment_types!(item, links)
case item
when Assignment
if item.discussion_topic
replace_item_placeholders!({:item => item.discussion_topic}, {:message => links}, true)
end
if item.quiz
replace_item_placeholders!({:item => item.quiz}, {:description => links}, true)
end
when DiscussionTopic
if item.assignment
replace_item_placeholders!({:item => item.assignment}, {:description => links}, true)
end
when Quizzes::Quiz
if item.assignment
replace_item_placeholders!({:item => item.assignment}, {:description => links}, true)
end
end
end
def process_assessment_question!(aq, links)
# we have to do a little bit more here because the question_data can get copied all over
quiz_ids = []
Quizzes::QuizQuestion.where(:assessment_question_id => aq.id).find_each do |qq|
qq_yaml = qq['question_data'].to_yaml
if sub_placeholders!(qq_yaml, links)
Quizzes::QuizQuestion.where(:id => qq.id).update_all(:question_data => qq_yaml)
quiz_ids << qq.quiz_id
end
end
if quiz_ids.any?
Quizzes::Quiz.where(:id => quiz_ids.uniq).where("quiz_data IS NOT NULL").find_each do |quiz|
quiz_yaml = quiz['quiz_data'].to_yaml
if sub_placeholders!(quiz_yaml, links)
Quizzes::Quiz.where(:id => quiz.id).update_all(:quiz_data => quiz_yaml)
end
end
end
# we have to do some special link translations for files in assessment questions
# because we stopped doing them in the regular importer
# basically just moving them to the question context
links.each do |link|
next unless link[:new_value]
link[:new_value] = aq.translate_file_link(link[:new_value])
end
aq_yaml = aq['question_data'].to_yaml
if sub_placeholders!(aq_yaml, links)
AssessmentQuestion.where(:id => aq.id).update_all(:question_data => aq_yaml)
end
end
def process_quiz_question!(qq, links)
qq_yaml = qq['question_data'].to_yaml
if sub_placeholders!(qq_yaml, links)
Quizzes::QuizQuestion.where(:id => qq.id).update_all(:question_data => qq_yaml)
end
quiz = Quizzes::Quiz.where(:id => qq.quiz_id).where("quiz_data IS NOT NULL").first
if quiz
quiz_yaml = quiz['quiz_data'].to_yaml
if sub_placeholders!(quiz_yaml, links)
Quizzes::Quiz.where(:id => quiz.id).update_all(:quiz_data => quiz_yaml)
end
end
end
end
end

View File

@ -0,0 +1,165 @@
module Importers
class LinkResolver
include LinkParser::Helpers
def initialize(migration)
@migration = migration
end
def resolve_links!(link_map)
link_map.each do |item_key, field_links|
field_links.each do |_field, links|
links.each do |link|
resolve_link!(link)
end
end
end
end
# finds the :new_value to use to replace the placeholder
def resolve_link!(link)
case link[:link_type]
when :wiki_page
if linked_wiki = context.wiki.wiki_pages.where(migration_id: link[:migration_id]).select('url').first
link[:new_value] = "#{context_path}/pages/#{linked_wiki.url}"
end
when :discussion_topic
if linked_topic = context.discussion_topics.where(migration_id: link[:migration_id]).select('id').first
link[:new_value] = "#{context_path}/discussion_topics/#{linked_topic.id}"
end
when :module_item
if tag = context.context_module_tags.where(:migration_id => link[:migration_id]).select('id').first
link[:new_value] = "#{context_path}/modules/items/#{tag.id}"
end
when :object
type = link[:type]
migration_id = link[:migration_id]
type_for_url = type
type = 'context_modules' if type == 'modules'
type = 'pages' if type == 'wiki'
if type == 'pages'
link[:new_value] = "#{context_path}/pages/#{migration_id}"
elsif type == 'attachments'
if att = context.attachments.where(migration_id: migration_id).first
link[:new_value] = "#{context_path}/files/#{att.id}/preview"
end
elsif context.respond_to?(type) && context.send(type).respond_to?(:where)
if object = context.send(type).where(migration_id: migration_id).first
link[:new_value] = "#{context_path}/#{type_for_url}/#{object.id}"
end
end
when :media_object
# because we actually might change the node itself
# this part is a little trickier
# tl;dr we've replaced the entire node with the placeholder
# see LinkParser for details
rel_path = link[:rel_path]
node = Nokogiri::HTML::DocumentFragment.parse(link[:old_value]).children.first
new_url = resolve_media_comment_data(node, rel_path)
new_url ||= resolve_relative_file_url(rel_path)
unless new_url
new_url ||= missing_relative_file_url(rel_path)
link[:missing_url] = new_url
end
node['href'] = new_url
link[:new_value] = node.to_xml
when :file
rel_path = link[:rel_path]
new_url = resolve_relative_file_url(rel_path)
unless new_url
new_url = missing_relative_file_url(rel_path)
link[:missing_url] = new_url
end
link[:new_value] = new_url
else
raise "unrecognized link_type in unresolved link"
end
end
def missing_relative_file_url(rel_path)
# the rel_path should already be escaped
File.join(URI::escape("#{context_path}/file_contents/#{Folder.root_folders(context).first.name}"), rel_path.gsub(" ", "%20"))
end
def find_file_in_context(rel_path)
mig_id = nil
# This is for backward-compatibility: canvas attachment filenames are escaped
# with '+' for spaces and older exports have files with that instead of %20
alt_rel_path = rel_path.gsub('+', ' ')
if @migration.attachment_path_id_lookup
mig_id ||= @migration.attachment_path_id_lookup[rel_path]
mig_id ||= @migration.attachment_path_id_lookup[alt_rel_path]
end
if !mig_id && @migration.attachment_path_id_lookup_lower
mig_id ||= @migration.attachment_path_id_lookup_lower[rel_path.downcase]
mig_id ||= @migration.attachment_path_id_lookup_lower[alt_rel_path.downcase]
end
mig_id && context.attachments.where(migration_id: mig_id).first
end
def resolve_relative_file_url(rel_path)
new_url = nil
split = rel_path.split('?')
qs = split.pop if split.length > 1
rel_path = split.join('?')
rel_path_parts = Pathname.new(rel_path).each_filename.to_a
# e.g. start with "a/b/c.txt" then try "b/c.txt" then try "c.txt"
while new_url.nil? && rel_path_parts.length > 0
sub_path = File.join(rel_path_parts)
if file = find_file_in_context(sub_path)
new_url = "#{context_path}/files/#{file.id}"
# support other params in the query string, that were exported from the
# original path components and query string. see
# CCHelper::file_query_string
params = Rack::Utils.parse_nested_query(qs.presence || "")
qs = []
new_action = ""
params.each do |k,v|
case k
when /canvas_qs_(.*)/
qs << "#{Rack::Utils.escape($1)}=#{Rack::Utils.escape(v)}"
when /canvas_(.*)/
new_action += "/#{$1}"
end
end
if new_action.present?
new_url += new_action
else
new_url += "/preview"
end
new_url += "?#{qs.join("&")}" if qs.present?
end
rel_path_parts.shift
end
new_url
end
def resolve_media_comment_data(node, rel_path)
if context.respond_to?(:attachment_path_id_lookup) &&
context.attachment_path_id_lookup &&
context.attachment_path_id_lookup[rel_path]
file = context.attachments.where(migration_id: context.attachment_path_id_lookup[rel_path]).first
if file && file.media_object
media_id = file.media_object.media_id
node['id'] = "media_comment_#{media_id}"
return "/media_objects/#{media_id}"
end
end
if node['id'] && node['id'] =~ /\Amedia_comment_(.+)\z/
return "/media_objects/#{$1}"
else
node.delete('class')
node.delete('id')
node.delete('style')
return nil
end
end
end
end

View File

@ -160,7 +160,7 @@ module Importers
# Import a quiz from a hash. # Import a quiz from a hash.
# It assumes that all the referenced questions are already in the database # It assumes that all the referenced questions are already in the database
def self.import_from_migration(hash, context, migration=nil, question_data=nil, item=nil, allow_update = false) def self.import_from_migration(hash, context, migration, question_data=nil, item=nil, allow_update = false)
hash = hash.with_indifferent_access hash = hash.with_indifferent_access
# there might not be an import id if it's just a text-only type... # there might not be an import id if it's just a text-only type...
item ||= Quizzes::Quiz.where(context_type: context.class.to_s, context_id: context, id: hash[:id]).first if hash[:id] item ||= Quizzes::Quiz.where(context_type: context.class.to_s, context_id: context, id: hash[:id]).first if hash[:id]
@ -184,10 +184,7 @@ module Importers
item.scoring_policy = hash[:which_attempt_to_keep] if hash[:which_attempt_to_keep] item.scoring_policy = hash[:which_attempt_to_keep] if hash[:which_attempt_to_keep]
missing_links = [] missing_links = []
item.description = ImportedHtmlConverter.convert(hash[:description], context, migration) do |warn, link| item.description = migration.convert_html(hash[:description], :quiz, hash[:migration_id], :description)
missing_links << link if warn == :missing_link
end
%w[ %w[
migration_id migration_id
@ -220,14 +217,6 @@ module Importers
item.saved_by = :migration item.saved_by = :migration
item.save! item.save!
if migration
migration.add_missing_content_links(
:class => item.class.to_s,
:id => item.id, :missing_links => missing_links,
:url => "/#{context.class.to_s.demodulize.underscore.pluralize}/#{context.id}/#{item.class.to_s.demodulize.underscore.pluralize}/#{item.id}"
)
end
if question_data if question_data
question_data[:qq_ids] ||= {} question_data[:qq_ids] ||= {}
hash[:questions] ||= [] hash[:questions] ||= []
@ -295,7 +284,7 @@ module Importers
item.save item.save
item.assignment.save if item.assignment && item.assignment.changed? item.assignment.save if item.assignment && item.assignment.changed?
migration.add_imported_item(item) if migration migration.add_imported_item(item)
item.saved_by = nil item.saved_by = nil
item item
end end

View File

@ -3,9 +3,9 @@ module Importers
self.item_class = Quizzes::QuizQuestion self.item_class = Quizzes::QuizQuestion
def self.import_from_migration(aq_hash, qq_hash, position, qq_ids, context, migration=nil, quiz=nil, quiz_group=nil) def self.import_from_migration(aq_hash, qq_hash, position, qq_ids, context, migration, quiz=nil, quiz_group=nil)
unless aq_hash[:prepped_for_import] unless aq_hash[:prepped_for_import]
Importers::AssessmentQuestionImporter.prep_for_import(aq_hash, context, migration) Importers::AssessmentQuestionImporter.prep_for_import(aq_hash, migration, :quiz_question)
end end
hash = aq_hash.dup hash = aq_hash.dup
@ -14,16 +14,17 @@ module Importers
hash[:points_possible] = 0 if hash[:points_possible].to_f < 0 hash[:points_possible] = 0 if hash[:points_possible].to_f < 0
mig_id = qq_hash['quiz_question_migration_id'] || qq_hash['migration_id'] mig_id = qq_hash['quiz_question_migration_id'] || qq_hash['migration_id']
if id = qq_ids[mig_id] if id = qq_ids[mig_id]
Quizzes::QuizQuestion.where(id: id).update_all(quiz_group_id: quiz_group, Quizzes::QuizQuestion.where(id: id).update_all(quiz_group_id: quiz_group,
assessment_question_id: hash['assessment_question_id'], question_data: hash.to_yaml, assessment_question_id: hash['assessment_question_id'], question_data: hash.to_yaml,
created_at: Time.now.utc, updated_at: Time.now.utc, migration_id: mig_id, created_at: Time.now.utc, updated_at: Time.now.utc, migration_id: mig_id,
position: position) position: position)
else else
query = self.item_class.send(:sanitize_sql, [<<-SQL, quiz && quiz.id, quiz_group && quiz_group.id, hash['assessment_question_id'], hash.to_yaml, Time.now.utc, Time.now.utc, hash[:migration_id], position]) args = [quiz && quiz.id, quiz_group && quiz_group.id, hash['assessment_question_id'],
INSERT INTO quiz_questions (quiz_id, quiz_group_id, assessment_question_id, question_data, created_at, updated_at, migration_id, position) hash.to_yaml, Time.now.utc, Time.now.utc, mig_id, position]
VALUES (?,?,?,?,?,?,?,?) query = self.item_class.send(:sanitize_sql, [<<-SQL, *args])
INSERT INTO quiz_questions (quiz_id, quiz_group_id, assessment_question_id, question_data, created_at, updated_at, migration_id, position)
VALUES (?,?,?,?,?,?,?,?)
SQL SQL
qq_ids[mig_id] = self.item_class.connection.insert(query, "#{self.item_class.name} Create", qq_ids[mig_id] = self.item_class.connection.insert(query, "#{self.item_class.name} Create",
self.item_class.primary_key, nil, self.item_class.sequence_name) self.item_class.primary_key, nil, self.item_class.sequence_name)

View File

@ -60,7 +60,7 @@ module Importers
end end
end end
migration.add_imported_item(item) if migration migration.add_imported_item(item)
item.save! item.save!
end end

View File

@ -39,7 +39,7 @@ module Importers
end end
private_class_method :wiki_page_migration? private_class_method :wiki_page_migration?
def self.import_from_migration(hash, context, migration=nil, item=nil) def self.import_from_migration(hash, context, migration, item=nil)
hash = hash.with_indifferent_access hash = hash.with_indifferent_access
item ||= WikiPage.where(wiki_id: context.wiki, id: hash[:id]).first item ||= WikiPage.where(wiki_id: context.wiki, id: hash[:id]).first
item ||= WikiPage.where(wiki_id: context.wiki, migration_id: hash[:migration_id]).first item ||= WikiPage.where(wiki_id: context.wiki, migration_id: hash[:migration_id]).first
@ -71,7 +71,7 @@ module Importers
end end
item.set_as_front_page! if !!hash[:front_page] && context.wiki.has_no_front_page item.set_as_front_page! if !!hash[:front_page] && context.wiki.has_no_front_page
migration.add_imported_item(item) if migration migration.add_imported_item(item)
item.migration_id = hash[:migration_id] item.migration_id = hash[:migration_id]
(hash[:contents] || []).each do |sub_item| (hash[:contents] || []).each do |sub_item|
@ -81,7 +81,6 @@ module Importers
}), context, migration) }), context, migration)
end end
return if hash[:type] && ['folder', 'FOLDER_TYPE'].member?(hash[:type]) && hash[:linked_resource_id] return if hash[:type] && ['folder', 'FOLDER_TYPE'].member?(hash[:type]) && hash[:linked_resource_id]
missing_links = {}
allow_save = true allow_save = true
if hash[:type] == 'linked_resource' || hash[:type] == "URL_TYPE" if hash[:type] == 'linked_resource' || hash[:type] == "URL_TYPE"
allow_save = false allow_save = false
@ -89,21 +88,15 @@ module Importers
item.title = hash[:title] unless hash[:root_folder] item.title = hash[:title] unless hash[:root_folder]
description = "" description = ""
if hash[:header] if hash[:header]
missing_links[:header] = []
if hash[:header][:is_html] if hash[:header][:is_html]
description += ImportedHtmlConverter.convert(hash[:header][:body] || "", context, migration) do |warn, link| description += migration.convert_html(hash[:header][:body], :wiki_page, hash[:migration_id], :body)
missing_links[:header] << link if warn == :missing_link
end
else else
description += ImportedHtmlConverter.convert_text(hash[:header][:body] || [""], context) description += migration.convert_text(hash[:header][:body] || [""])
end end
end end
missing_links[:description] = []
if hash[:description] if hash[:description]
description += ImportedHtmlConverter.convert(hash[:description], context, migration) do |warn, link| description += migration.convert_html(hash[:description], :wiki_page, hash[:migration_id], :body)
missing_links[:description] << link if warn == :missing_link
end
end end
contents = "" contents = ""
@ -120,11 +113,8 @@ module Importers
end end
description += "\n<h2>#{sub_item[:title]}</h2>\n" if sub_item[:title] description += "\n<h2>#{sub_item[:title]}</h2>\n" if sub_item[:title]
missing_links[:sub_item] = []
if sub_item[:description] if sub_item[:description]
description += ImportedHtmlConverter.convert(sub_item[:description], context, migration) do |warn, link| description += migration.convert_html(sub_item[:description], :wiki_page, hash[:migration_id], :body)
missing_links[:sub_item] << link if warn == :missing_link
end
end end
elsif sub_item[:type] == 'linked_resource' elsif sub_item[:type] == 'linked_resource'
@ -159,13 +149,10 @@ module Importers
description += "<ul>\n#{contents}\n</ul>" if contents && contents.length > 0 description += "<ul>\n#{contents}\n</ul>" if contents && contents.length > 0
if hash[:footer] if hash[:footer]
missing_links[:footer] = []
if hash[:footer][:is_html] if hash[:footer][:is_html]
description += ImportedHtmlConverter.convert(hash[:footer][:body] || "", context, migration) do |warn, link| description += migration.convert_html(hash[:footer][:body], :wiki_page, hash[:migration_id], :body)
missing_links[:footer] << link if warn == :missing_link
end
else else
description += ImportedHtmlConverter.convert_text(hash[:footer][:body] || [""], context) description += migration.convert_text(hash[:footer][:body] || "")
end end
end end
@ -194,16 +181,12 @@ module Importers
#it's an actual wiki page #it's an actual wiki page
item.title = hash[:title].presence || item.url.presence || "unnamed page" item.title = hash[:title].presence || item.url.presence || "unnamed page"
if item.title.length > WikiPage::TITLE_LENGTH if item.title.length > WikiPage::TITLE_LENGTH
if migration migration.add_warning(t('warnings.truncated_wiki_title',
migration.add_warning(t('warnings.truncated_wiki_title', "The title of the following wiki page was truncated: %{title}", :title => item.title)) "The title of the following wiki page was truncated: %{title}", :title => item.title))
end
item.title.splice!(0...WikiPage::TITLE_LENGTH) # truncate too-long titles item.title.splice!(0...WikiPage::TITLE_LENGTH) # truncate too-long titles
end end
missing_links[:body] = [] item.body = migration.convert_html(hash[:text], :wiki_page, hash[:migration_id], :body)
item.body = ImportedHtmlConverter.convert(hash[:text] || "", context, migration) do |warn, link|
missing_links[:body] << link if warn == :missing_link
end
item.editing_roles = hash[:editing_roles] if hash[:editing_roles].present? item.editing_roles = hash[:editing_roles] if hash[:editing_roles].present?
item.notify_of_update = hash[:notify_of_update] if !hash[:notify_of_update].nil? item.notify_of_update = hash[:notify_of_update] if !hash[:notify_of_update].nil?
@ -215,14 +198,7 @@ module Importers
item.user = nil item.user = nil
end end
item.save_without_broadcasting! item.save_without_broadcasting!
migration.add_imported_item(item) if migration migration.add_imported_item(item)
if migration
missing_links.each do |field, missing_links|
migration.add_missing_content_links(:class => item.class.to_s,
:id => item.id, :field => field, :missing_links => missing_links,
:url => "/#{context.class.to_s.underscore.pluralize}/#{context.id}/pages/#{item.url}")
end
end
return item return item
end end
end end

View File

@ -23,141 +23,27 @@ class ImportedHtmlConverter
include HtmlTextHelper include HtmlTextHelper
CONTAINER_TYPES = ['div', 'p', 'body'] CONTAINER_TYPES = ['div', 'p', 'body']
REFERENCE_KEYWORDS = %w{CANVAS_COURSE_REFERENCE CANVAS_OBJECT_REFERENCE WIKI_REFERENCE IMS_CC_FILEBASE IMS-CC-FILEBASE} LINK_ATTRS = ['rel', 'href', 'src', 'data', 'value']
# yields warnings
def self.convert(html, context, migration=nil, opts={}) attr_reader :link_parser, :link_resolver, :link_replacer
def initialize(migration)
@migration = migration
@link_parser = Importers::LinkParser.new(migration)
@link_resolver = Importers::LinkResolver.new(migration)
@link_replacer = Importers::LinkReplacer.new(migration)
end
def convert(html, item_type, mig_id, field, opts={})
doc = Nokogiri::HTML(html || "") doc = Nokogiri::HTML(html || "")
attrs = ['rel', 'href', 'src', 'data', 'value']
course_path = "/#{context.class.to_s.underscore.pluralize}/#{context.id}"
for_course_copy = false
if migration
for_course_copy = true if migration.for_course_copy?
end
doc.search("*").each do |node| doc.search("*").each do |node|
attrs.each do |attr| LINK_ATTRS.each do |attr|
if node[attr].present? @link_parser.convert_link(node, attr, item_type, mig_id, field)
if attr == 'value'
next unless node['name'] && node['name'] == 'src'
end
new_url = nil
missing_relative_url = nil
val = node[attr].dup
REFERENCE_KEYWORDS.each do |ref|
val.gsub!("%24#{ref}%24", "$#{ref}$")
end
if val =~ /wiki_page_migration_id=(.*)/
# This would be from a BB9 migration.
#todo: refactor migration systems to use new $CANVAS...$ flags
#todo: FLAG UNFOUND REFERENCES TO re-attempt in second loop?
if wiki_migration_id = $1
if linked_wiki = context.wiki.wiki_pages.where(migration_id: wiki_migration_id).first
new_url = "#{course_path}/pages/#{linked_wiki.url}"
end
end
elsif val =~ /discussion_topic_migration_id=(.*)/
if topic_migration_id = $1
if linked_topic = context.discussion_topics.where(migration_id: topic_migration_id).first
new_url = "#{course_path}/discussion_topics/#{linked_topic.id}"
end
end
elsif val =~ %r{\$CANVAS_COURSE_REFERENCE\$/modules/items/(.*)}
if tag = context.context_module_tags.where(:migration_id => $1).select('id').first
new_url = "#{course_path}/modules/items/#{tag.id}"
end
elsif val =~ %r{(?:\$CANVAS_OBJECT_REFERENCE\$|\$WIKI_REFERENCE\$)/([^/]*)/(.*)}
type = $1
migration_id = $2
type_for_url = type
type = 'context_modules' if type == 'modules'
type = 'pages' if type == 'wiki'
if type == 'pages'
new_url = "#{course_path}/pages/#{migration_id}"
elsif type == 'attachments'
if att = context.attachments.where(migration_id: migration_id).first
new_url = "#{course_path}/files/#{att.id}/preview"
end
elsif context.respond_to?(type) && context.send(type).respond_to?(:where)
if object = context.send(type).where(migration_id: migration_id).first
new_url = "#{course_path}/#{type_for_url}/#{object.id}"
end
end
elsif val =~ %r{\$CANVAS_COURSE_REFERENCE\$/(.*)}
section = $1
new_url = "#{course_path}/#{section}"
elsif val =~ %r{\$IMS(?:-|_)CC(?:-|_)FILEBASE\$/(.*)}
rel_path = URI.unescape($1)
if attr == 'href' && node['class'] && node['class'] =~ /instructure_inline_media_comment/
new_url = replace_media_comment_data(node, rel_path, context, opts) {|warning, data| yield warning, data if block_given?}
unless new_url
unless new_url = replace_relative_file_url(rel_path, context)
missing_relative_url = rel_path
end
end
else
unless new_url = replace_relative_file_url(rel_path, context)
missing_relative_url = rel_path
end
end
elsif attr == 'href' && node['class'] && node['class'] =~ /instructure_inline_media_comment/
# Course copy media reference, leave it alone
new_url = node[attr]
elsif attr == 'src' && node['class'] && node['class'] =~ /equation_image/
# Equation image, leave it alone
new_url = node[attr]
elsif val =~ %r{\A/assessment_questions/\d+/files/\d+}
# The file is in the context of an AQ, leave the link alone
new_url = node[attr]
elsif val =~ %r{\A/courses/\d+/files/\d+}
# This points to a specific file already, leave it alone
new_url = node[attr]
elsif for_course_copy
# For course copies don't try to fix relative urls. Any url we can
# correctly alter was changed during the 'export' step
new_url = node[attr]
elsif val.start_with?('#')
# It's just a link to an anchor, leave it alone
new_url = node[attr]
else
begin
if relative_url?(node[attr])
unescaped = URI.unescape(val)
unless new_url = replace_relative_file_url(unescaped, context)
missing_relative_url = unescaped
end
else
new_url = node[attr]
end
rescue URI::InvalidURIError
Rails.logger.warn "attempting to translate invalid url: #{node[attr]}"
# leave the url as it was
end
end
if missing_relative_url
node[attr] = replace_missing_relative_url(missing_relative_url, context, course_path)
end
if migration && converted_url = migration.process_domain_substitutions(new_url || val)
if converted_url != (new_url || val)
new_url = converted_url
end
end
if new_url
node[attr] = new_url
else
yield :missing_link, node[attr] if block_given?
end
end
end end
end end
node = doc.at_css('body') node = doc.at_css('body')
return "" unless node
if opts[:remove_outer_nodes_if_one_child] if opts[:remove_outer_nodes_if_one_child]
while node.children.size == 1 && node.child.child while node.children.size == 1 && node.child.child
break unless CONTAINER_TYPES.member? node.child.name break unless CONTAINER_TYPES.member? node.child.name
@ -166,105 +52,29 @@ class ImportedHtmlConverter
end end
node.inner_html node.inner_html
rescue rescue Nokogiri::SyntaxError
"" ""
end end
def self.find_file_in_context(rel_path, context) def convert_text(text)
mig_id = nil format_message(text || "")[0]
# This is for backward-compatibility: canvas attachment filenames are escaped
# with '+' for spaces and older exports have files with that instead of %20
alt_rel_path = rel_path.gsub('+', ' ')
if context.respond_to?(:attachment_path_id_lookup) && context.attachment_path_id_lookup
mig_id ||= context.attachment_path_id_lookup[rel_path]
mig_id ||= context.attachment_path_id_lookup[alt_rel_path]
end
if !mig_id && context.respond_to?(:attachment_path_id_lookup_lower) && context.attachment_path_id_lookup_lower
mig_id ||= context.attachment_path_id_lookup_lower[rel_path.downcase]
mig_id ||= context.attachment_path_id_lookup_lower[alt_rel_path.downcase]
end
mig_id && context.attachments.where(migration_id: mig_id).first
end end
def self.replace_relative_file_url(rel_path, context) def resolve_content_links!
new_url = nil link_map = @link_parser.unresolved_link_map
split = rel_path.split('?') return unless link_map.present?
qs = split.pop if split.length > 1
rel_path = split.join('?')
rel_path_parts = Pathname.new(rel_path).each_filename.to_a @link_resolver.resolve_links!(link_map)
@link_replacer.replace_placeholders!(link_map)
# e.g. start with "a/b/c.txt" then try "b/c.txt" then try "c.txt" @link_parser.reset!
while new_url.nil? && rel_path_parts.length > 0
sub_path = File.join(rel_path_parts)
if file = find_file_in_context(sub_path, context)
new_url = "/courses/#{context.id}/files/#{file.id}"
# support other params in the query string, that were exported from the
# original path components and query string. see
# CCHelper::file_query_string
params = Rack::Utils.parse_nested_query(qs.presence || "")
qs = []
new_action = ""
params.each do |k,v|
case k
when /canvas_qs_(.*)/
qs << "#{Rack::Utils.escape($1)}=#{Rack::Utils.escape(v)}"
when /canvas_(.*)/
new_action += "/#{$1}"
end
end
if new_action.present?
new_url += new_action
else
new_url += "/preview"
end
new_url += "?#{qs.join("&")}" if qs.present?
end
rel_path_parts.shift
end
new_url
end
def self.replace_missing_relative_url(rel_path, context, course_path)
# the rel_path should already be escaped
File.join(URI::escape("#{course_path}/file_contents/#{Folder.root_folders(context).first.name}"), rel_path)
end
def self.replace_media_comment_data(node, rel_path, context, opts={})
if context.respond_to?(:attachment_path_id_lookup) &&
context.attachment_path_id_lookup &&
context.attachment_path_id_lookup[rel_path]
file = context.attachments.where(migration_id: context.attachment_path_id_lookup[rel_path]).first
if file && file.media_object
media_id = file.media_object.media_id
node['id'] = "media_comment_#{media_id}"
return "/media_objects/#{media_id}"
end
end
if node['id'] && node['id'] =~ /\Amedia_comment_(.+)\z/
link = "/media_objects/#{$1}"
yield :missing_link, link
return link
else
node.delete('class')
node.delete('id')
node.delete('style')
yield :missing_link, rel_path
return nil
end
end end
def self.relative_url?(url) def self.relative_url?(url)
URI.parse(url).relative? && !url.to_s.start_with?("//") URI.parse(url).relative? && !url.to_s.start_with?("//")
rescue URI::InvalidURIError
# leave the url as it was
Rails.logger.warn "attempting to translate invalid url: #{url}"
false
end end
def self.convert_text(text, context, import_source=:webct)
instance.format_message(text || "")[0]
end
def self.instance
@@instance ||= self.new
end
end end

View File

@ -50,7 +50,6 @@ def get_import_data(sub_folder, hash_name)
end end
def import_example_questions(context) def import_example_questions(context)
migration = context.content_migrations.new
questions = [] questions = []
QUESTIONS.each do |question| QUESTIONS.each do |question|
if import_data_exists?(['vista', 'quiz'], question[0]) if import_data_exists?(['vista', 'quiz'], question[0])
@ -59,7 +58,7 @@ def import_example_questions(context)
end end
end end
hash = {'assessment_questions' => {'assessment_questions' => questions}} hash = {'assessment_questions' => {'assessment_questions' => questions}}
Importers::AssessmentQuestionImporter.process_migration(hash, migration) Importers::AssessmentQuestionImporter.process_migration(hash, @migration)
end end
def get_import_context(system=nil) def get_import_context(system=nil)

View File

@ -96,7 +96,7 @@ describe "Canvas Cartridge importing" do
hash = {:migration_id=>CC::CCHelper.create_key(a), hash = {:migration_id=>CC::CCHelper.create_key(a),
:title=>a.title, :title=>a.title,
:assignment_group_migration_id=>CC::CCHelper.create_key(ag2)} :assignment_group_migration_id=>CC::CCHelper.create_key(ag2)}
Importers::AssignmentImporter.import_from_migration(hash, @copy_to) Importers::AssignmentImporter.import_from_migration(hash, @copy_to, @migration)
ag2_2.reload ag2_2.reload
expect(ag2_2.assignments.count).to eq 1 expect(ag2_2.assignments.count).to eq 1
@ -567,8 +567,9 @@ describe "Canvas Cartridge importing" do
hash = @converter.convert_wiki(doc, 'some-page') hash = @converter.convert_wiki(doc, 'some-page')
hash = hash.with_indifferent_access hash = hash.with_indifferent_access
#import into new course #import into new course
@copy_to.attachment_path_id_lookup = { 'unfiled/ohai there.txt' => attachment_import.migration_id } @migration.attachment_path_id_lookup = { 'unfiled/ohai there.txt' => attachment_import.migration_id }
Importers::WikiPageImporter.import_from_migration(hash, @copy_to) Importers::WikiPageImporter.import_from_migration(hash, @copy_to, @migration)
@migration.resolve_content_links!
page_2 = @copy_to.wiki.wiki_pages.where(migration_id: migration_id).first page_2 = @copy_to.wiki.wiki_pages.where(migration_id: migration_id).first
expect(page_2.title).to eq page.title expect(page_2.title).to eq page.title
@ -595,7 +596,7 @@ describe "Canvas Cartridge importing" do
to_att.migration_id = CC::CCHelper.create_key(from_att) to_att.migration_id = CC::CCHelper.create_key(from_att)
to_att.save to_att.save
path = to_att.full_display_path.gsub('course files/', '') path = to_att.full_display_path.gsub('course files/', '')
@copy_to.attachment_path_id_lookup = {path => to_att.migration_id} @migration.attachment_path_id_lookup = {path => to_att.migration_id}
body_with_link = %{<p>Watup? <strong>eh?</strong> body_with_link = %{<p>Watup? <strong>eh?</strong>
<a href=\"/courses/%s/assignments\">Assignments</a> <a href=\"/courses/%s/assignments\">Assignments</a>
@ -625,6 +626,7 @@ describe "Canvas Cartridge importing" do
hash = hash.with_indifferent_access hash = hash.with_indifferent_access
#import into new course #import into new course
Importers::WikiPageImporter.process_migration({'wikis' => [hash, nil]}, @migration) Importers::WikiPageImporter.process_migration({'wikis' => [hash, nil]}, @migration)
@migration.resolve_content_links!
expect(ErrorReport.last.message).to match /nil wiki/ expect(ErrorReport.last.message).to match /nil wiki/
@ -650,7 +652,7 @@ describe "Canvas Cartridge importing" do
hash = @converter.convert_wiki(doc, 'blti-link') hash = @converter.convert_wiki(doc, 'blti-link')
hash = hash.with_indifferent_access hash = hash.with_indifferent_access
#import into new course #import into new course
Importers::WikiPageImporter.import_from_migration(hash, @copy_to) Importers::WikiPageImporter.import_from_migration(hash, @copy_to, @migration)
page_2 = @copy_to.wiki.wiki_pages.where(migration_id: migration_id).first page_2 = @copy_to.wiki.wiki_pages.where(migration_id: migration_id).first
expect(page_2.title).to eq page.title expect(page_2.title).to eq page.title
@ -703,7 +705,7 @@ describe "Canvas Cartridge importing" do
hash = @converter.parse_canvas_assignment_data(meta_doc, html_doc) hash = @converter.parse_canvas_assignment_data(meta_doc, html_doc)
hash = hash.with_indifferent_access hash = hash.with_indifferent_access
#import #import
Importers::AssignmentImporter.import_from_migration(hash, @copy_to) Importers::AssignmentImporter.import_from_migration(hash, @copy_to, @migration)
asmnt_2 = @copy_to.assignments.where(migration_id: migration_id).first asmnt_2 = @copy_to.assignments.where(migration_id: migration_id).first
expect(asmnt_2.title).to eq asmnt.title expect(asmnt_2.title).to eq asmnt.title
@ -742,7 +744,7 @@ describe "Canvas Cartridge importing" do
hash = @converter.parse_canvas_assignment_data(meta_doc, html_doc) hash = @converter.parse_canvas_assignment_data(meta_doc, html_doc)
hash = hash.with_indifferent_access hash = hash.with_indifferent_access
#import #import
Importers::AssignmentImporter.import_from_migration(hash, @copy_to) Importers::AssignmentImporter.import_from_migration(hash, @copy_to, @migration)
asmnt_2 = @copy_to.assignments.where(migration_id: migration_id).first asmnt_2 = @copy_to.assignments.where(migration_id: migration_id).first
expect(asmnt_2.submission_types).to eq "external_tool" expect(asmnt_2.submission_types).to eq "external_tool"
@ -803,7 +805,7 @@ XML
hash = @converter.convert_topic(cc_doc, meta_doc) hash = @converter.convert_topic(cc_doc, meta_doc)
hash = hash.with_indifferent_access hash = hash.with_indifferent_access
#import #import
Importers::DiscussionTopicImporter.import_from_migration(hash, @copy_to) Importers::DiscussionTopicImporter.import_from_migration(hash, @copy_to, @migration)
dt_2 = @copy_to.discussion_topics.where(migration_id: migration_id).first dt_2 = @copy_to.discussion_topics.where(migration_id: migration_id).first
expect(dt_2.title).to eq dt.title expect(dt_2.title).to eq dt.title
@ -851,7 +853,7 @@ XML
ag1.migration_id = CC::CCHelper.create_key(assignment.assignment_group) ag1.migration_id = CC::CCHelper.create_key(assignment.assignment_group)
ag1.save! ag1.save!
#import #import
Importers::DiscussionTopicImporter.import_from_migration(hash, @copy_to) Importers::DiscussionTopicImporter.import_from_migration(hash, @copy_to, @migration)
dt_2 = @copy_to.discussion_topics.where(migration_id: migration_id).first dt_2 = @copy_to.discussion_topics.where(migration_id: migration_id).first
expect(dt_2.title).to eq dt.title expect(dt_2.title).to eq dt.title
@ -961,7 +963,7 @@ XML
ag.migration_id = "i713e960ab2685259505efeb08cd48a1d" ag.migration_id = "i713e960ab2685259505efeb08cd48a1d"
ag.save! ag.save!
Importers::QuizImporter.import_from_migration(quiz_hash, @copy_to, nil, {}) Importers::QuizImporter.import_from_migration(quiz_hash, @copy_to, @migration, {})
q = @copy_to.quizzes.where(migration_id: "ie3d8f8adfad423eb225229c539cdc450").first q = @copy_to.quizzes.where(migration_id: "ie3d8f8adfad423eb225229c539cdc450").first
a = q.assignment a = q.assignment
expect(a.assignment_group.id).to eq ag.id expect(a.assignment_group.id).to eq ag.id

View File

@ -19,25 +19,39 @@
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
describe ImportedHtmlConverter do describe ImportedHtmlConverter do
# tests link_parser and link_resolver
context ".convert" do context ".convert" do
before(:each) do before :once do
course course
@path = "/courses/#{@course.id}/" @path = "/courses/#{@course.id}/"
@migration = @course.content_migrations.create!
@converter = @migration.html_converter
end
def convert_and_replace(test_string)
html = @migration.convert_html(test_string, 'sometype', 'somemigid', 'somefield')
link_map = @converter.link_parser.unresolved_link_map
@converter.link_resolver.resolve_links!(link_map)
if link_map.present?
@converter.link_replacer.sub_placeholders!(html, link_map.values.map(&:values).flatten)
end
html
end end
it "should convert a wiki reference" do it "should convert a wiki reference" do
test_string = %{<a href="%24WIKI_REFERENCE%24/wiki/test-wiki-page">Test Wiki Page</a>} test_string = %{<a href="%24WIKI_REFERENCE%24/wiki/test-wiki-page">Test Wiki Page</a>}
@course.wiki.wiki_pages.create!(:title => "Test Wiki Page", :body => "stuff") @course.wiki.wiki_pages.create!(:title => "Test Wiki Page", :body => "stuff")
expect(ImportedHtmlConverter.convert(test_string, @course)).to eq %{<a href="#{@path}pages/test-wiki-page">Test Wiki Page</a>} expect(convert_and_replace(test_string)).to eq %{<a href="#{@path}pages/test-wiki-page">Test Wiki Page</a>}
end end
it "should convert a wiki reference without $ escaped" do it "should convert a wiki reference without $ escaped" do
test_string = %{<a href="$WIKI_REFERENCE$/wiki/test-wiki-page">Test Wiki Page</a>} test_string = %{<a href="$WIKI_REFERENCE$/wiki/test-wiki-page">Test Wiki Page</a>}
@course.wiki.wiki_pages.create!(:title => "Test Wiki Page", :body => "stuff") @course.wiki.wiki_pages.create!(:title => "Test Wiki Page", :body => "stuff")
expect(ImportedHtmlConverter.convert(test_string, @course)).to eq %{<a href="#{@path}pages/test-wiki-page">Test Wiki Page</a>} expect(convert_and_replace(test_string)).to eq %{<a href="#{@path}pages/test-wiki-page">Test Wiki Page</a>}
end end
it "should convert a wiki reference by migration id" do it "should convert a wiki reference by migration id" do
@ -46,7 +60,7 @@ describe ImportedHtmlConverter do
wiki.migration_id = "123456677788" wiki.migration_id = "123456677788"
wiki.save! wiki.save!
expect(ImportedHtmlConverter.convert(test_string, @course)).to eq %{<a href="#{@path}pages/test-wiki-page">Test Wiki Page</a>} expect(convert_and_replace(test_string)).to eq %{<a href="#{@path}pages/test-wiki-page">Test Wiki Page</a>}
end end
it "should convert a discussion reference by migration id" do it "should convert a discussion reference by migration id" do
@ -55,7 +69,7 @@ describe ImportedHtmlConverter do
topic.migration_id = "123456677788" topic.migration_id = "123456677788"
topic.save! topic.save!
expect(ImportedHtmlConverter.convert(test_string, @course)).to eq %{<a href="#{@path}discussion_topics/#{topic.id}">Test topic</a>} expect(convert_and_replace(test_string)).to eq %{<a href="#{@path}discussion_topics/#{topic.id}">Test topic</a>}
end end
def make_test_att def make_test_att
@ -69,7 +83,7 @@ describe ImportedHtmlConverter do
att = make_test_att() att = make_test_att()
test_string = %{<p>This is an image: <br /><img src="%24CANVAS_OBJECT_REFERENCE%24/attachments/1768525836051" alt=":(" /></p>} test_string = %{<p>This is an image: <br /><img src="%24CANVAS_OBJECT_REFERENCE%24/attachments/1768525836051" alt=":(" /></p>}
expect(ImportedHtmlConverter.convert(test_string, @course)).to eq %{<p>This is an image: <br><img src="#{@path}files/#{att.id}/preview" alt=":("></p>} expect(convert_and_replace(test_string)).to eq %{<p>This is an image: <br><img src="#{@path}files/#{att.id}/preview" alt=":("></p>}
end end
it "should find an attachment by path" do it "should find an attachment by path" do
@ -78,71 +92,71 @@ describe ImportedHtmlConverter do
test_string = %{<p>This is an image: <br /><img src="%24IMS_CC_FILEBASE%24/test.png" alt=":(" /></p>} test_string = %{<p>This is an image: <br /><img src="%24IMS_CC_FILEBASE%24/test.png" alt=":(" /></p>}
# if there isn't a path->migration id map it'll be a relative course file path # if there isn't a path->migration id map it'll be a relative course file path
expect(ImportedHtmlConverter.convert(test_string, @course)).to eq %{<p>This is an image: <br><img src="#{@path}file_contents/course%20files/test.png" alt=":("></p>} expect(convert_and_replace(test_string)).to eq %{<p>This is an image: <br><img src="#{@path}file_contents/course%20files/test.png" alt=":("></p>}
@course.attachment_path_id_lookup = {"test.png" => att.migration_id} @migration.attachment_path_id_lookup = {"test.png" => att.migration_id}
expect(ImportedHtmlConverter.convert(test_string, @course)).to eq %{<p>This is an image: <br><img src="#{@path}files/#{att.id}/preview" alt=":("></p>} expect(convert_and_replace(test_string)).to eq %{<p>This is an image: <br><img src="#{@path}files/#{att.id}/preview" alt=":("></p>}
end end
it "should find an attachment by a path with a space" do it "should find an attachment by a path with a space" do
att = make_test_att() att = make_test_att()
@course.attachment_path_id_lookup = {"subfolder/with a space/test.png" => att.migration_id} @migration.attachment_path_id_lookup = {"subfolder/with a space/test.png" => att.migration_id}
test_string = %{<img src="subfolder/with%20a%20space/test.png" alt="nope" />} test_string = %{<img src="subfolder/with%20a%20space/test.png" alt="nope" />}
expect(ImportedHtmlConverter.convert(test_string, @course)).to eq %{<img src="#{@path}files/#{att.id}/preview" alt="nope">} expect(convert_and_replace(test_string)).to eq %{<img src="#{@path}files/#{att.id}/preview" alt="nope">}
test_string = %{<img src="subfolder/with+a+space/test.png" alt="nope" />} test_string = %{<img src="subfolder/with+a+space/test.png" alt="nope" />}
expect(ImportedHtmlConverter.convert(test_string, @course)).to eq %{<img src="#{@path}files/#{att.id}/preview" alt="nope">} expect(convert_and_replace(test_string)).to eq %{<img src="#{@path}files/#{att.id}/preview" alt="nope">}
end end
it "should find an attachment even if the link has an extraneous folder" do it "should find an attachment even if the link has an extraneous folder" do
att = make_test_att() att = make_test_att()
@course.attachment_path_id_lookup = {"subfolder/test.png" => att.migration_id} @migration.attachment_path_id_lookup = {"subfolder/test.png" => att.migration_id}
test_string = %{<img src="anotherfolder/subfolder/test.png" alt="nope" />} test_string = %{<img src="anotherfolder/subfolder/test.png" alt="nope" />}
expect(ImportedHtmlConverter.convert(test_string, @course)).to eq %{<img src="#{@path}files/#{att.id}/preview" alt="nope">} expect(convert_and_replace(test_string)).to eq %{<img src="#{@path}files/#{att.id}/preview" alt="nope">}
end end
it "should find an attachment by path if capitalization is different" do it "should find an attachment by path if capitalization is different" do
att = make_test_att() att = make_test_att()
@course.attachment_path_id_lookup = {"subfolder/withCapital/test.png" => "wrong!"} @migration.attachment_path_id_lookup = {"subfolder/withCapital/test.png" => "wrong!"}
@course.attachment_path_id_lookup_lower = {"subfolder/withcapital/test.png" => att.migration_id} @migration.attachment_path_id_lookup_lower = {"subfolder/withcapital/test.png" => att.migration_id}
test_string = %{<img src="subfolder/WithCapital/TEST.png" alt="nope" />} test_string = %{<img src="subfolder/WithCapital/TEST.png" alt="nope" />}
expect(ImportedHtmlConverter.convert(test_string, @course)).to eq %{<img src="#{@path}files/#{att.id}/preview" alt="nope">} expect(convert_and_replace(test_string)).to eq %{<img src="#{@path}files/#{att.id}/preview" alt="nope">}
end end
it "should find an attachment with query params" do it "should find an attachment with query params" do
att = make_test_att() att = make_test_att()
@course.attachment_path_id_lookup = {"test.png" => att.migration_id} @migration.attachment_path_id_lookup = {"test.png" => att.migration_id}
test_string = %{<img src="%24IMS_CC_FILEBASE%24/test.png?canvas_customaction=1&canvas_qs_customparam=1" alt="nope" />} test_string = %{<img src="%24IMS_CC_FILEBASE%24/test.png?canvas_customaction=1&canvas_qs_customparam=1" alt="nope" />}
expect(ImportedHtmlConverter.convert(test_string, @course)).to eq %{<img src="#{@path}files/#{att.id}/customaction?customparam=1" alt="nope">} expect(convert_and_replace(test_string)).to eq %{<img src="#{@path}files/#{att.id}/customaction?customparam=1" alt="nope">}
test_string = %{<img src="%24IMS_CC_FILEBASE%24/test.png?canvas_qs_customparam2=3" alt="nope" />} test_string = %{<img src="%24IMS_CC_FILEBASE%24/test.png?canvas_qs_customparam2=3" alt="nope" />}
expect(ImportedHtmlConverter.convert(test_string, @course)).to eq %{<img src="#{@path}files/#{att.id}/preview?customparam2=3" alt="nope">} expect(convert_and_replace(test_string)).to eq %{<img src="#{@path}files/#{att.id}/preview?customparam2=3" alt="nope">}
test_string = %{<img src="%24IMS_CC_FILEBASE%24/test.png?notarelevantparam" alt="nope" />} test_string = %{<img src="%24IMS_CC_FILEBASE%24/test.png?notarelevantparam" alt="nope" />}
expect(ImportedHtmlConverter.convert(test_string, @course)).to eq %{<img src="#{@path}files/#{att.id}/preview" alt="nope">} expect(convert_and_replace(test_string)).to eq %{<img src="#{@path}files/#{att.id}/preview" alt="nope">}
end end
it "should convert course section urls" do it "should convert course section urls" do
test_string = %{<a href="%24CANVAS_COURSE_REFERENCE%24/discussion_topics">discussions</a>} test_string = %{<a href="%24CANVAS_COURSE_REFERENCE%24/discussion_topics">discussions</a>}
expect(ImportedHtmlConverter.convert(test_string, @course)).to eq %{<a href="#{@path}discussion_topics">discussions</a>} expect(convert_and_replace(test_string)).to eq %{<a href="#{@path}discussion_topics">discussions</a>}
end end
it "should leave invalid and absolute urls alone" do it "should leave invalid and absolute urls alone" do
test_string = %{<a href="stupid &^%$ url">Linkage</a><br><a href="http://www.example.com/poop">Linkage</a>} test_string = %{<a href="stupid &^%$ url">Linkage</a><br><a href="http://www.example.com/poop">Linkage</a>}
expect(ImportedHtmlConverter.convert(test_string, @course)).to eq %{<a href="stupid%20&amp;%5E%%24%20url">Linkage</a><br><a href="http://www.example.com/poop">Linkage</a>} expect(convert_and_replace(test_string)).to eq %{<a href="stupid%20&amp;%5E%%24%20url">Linkage</a><br><a href="http://www.example.com/poop">Linkage</a>}
end end
it "should prepend course files for unrecognized relative urls" do it "should prepend course files for unrecognized relative urls" do
test_string = %{<a href="/relative/path/to/file">Linkage</a>} test_string = %{<a href="/relative/path/to/file">Linkage</a>}
expect(ImportedHtmlConverter.convert(test_string, @course)).to eq %{<a href="#{@path}file_contents/course%20files/relative/path/to/file">Linkage</a>} expect(convert_and_replace(test_string)).to eq %{<a href="#{@path}file_contents/course%20files/relative/path/to/file">Linkage</a>}
test_string = %{<a href="relative/path/to/file">Linkage</a>} test_string = %{<a href="relative/path/to/file">Linkage</a>}
expect(ImportedHtmlConverter.convert(test_string, @course)).to eq %{<a href="#{@path}file_contents/course%20files/relative/path/to/file">Linkage</a>} expect(convert_and_replace(test_string)).to eq %{<a href="#{@path}file_contents/course%20files/relative/path/to/file">Linkage</a>}
test_string = %{<a href="relative/path/to/file%20with%20space.html">Linkage</a>} test_string = %{<a href="relative/path/to/file%20with%20space.html">Linkage</a>}
expect(ImportedHtmlConverter.convert(test_string, @course)).to eq %{<a href="#{@path}file_contents/course%20files/relative/path/to/file%20with%20space.html">Linkage</a>} expect(convert_and_replace(test_string)).to eq %{<a href="#{@path}file_contents/course%20files/relative/path/to/file%20with%20space.html">Linkage</a>}
end end
it "should preserve media comment links" do it "should preserve media comment links" do
@ -153,13 +167,13 @@ describe ImportedHtmlConverter do
</p> </p>
HTML HTML
expect(ImportedHtmlConverter.convert(test_string, @course)).to eq test_string.gsub("/courses/#{course.id}/file_contents/course%20files",'') expect(convert_and_replace(test_string)).to eq test_string
end end
it "should handle and repair half broken media links" do it "should handle and repair half broken media links" do
test_string = %{<p><a href="/courses/#{@course.id}/file_contents/%24IMS_CC_FILEBASE%24/#" class="instructure_inline_media_comment video_comment" id="media_comment_0_l4l5n0wt">this is a media comment</a><br><br></p>} test_string = %{<p><a href="/courses/#{@course.id}/file_contents/%24IMS_CC_FILEBASE%24/#" class="instructure_inline_media_comment video_comment" id="media_comment_0_l4l5n0wt">this is a media comment</a><br><br></p>}
expect(ImportedHtmlConverter.convert(test_string, @course)).to eq %{<p><a href="/media_objects/0_l4l5n0wt" class="instructure_inline_media_comment video_comment" id="media_comment_0_l4l5n0wt">this is a media comment</a><br><br></p>} expect(convert_and_replace(test_string)).to eq %{<p><a href="/media_objects/0_l4l5n0wt" class="instructure_inline_media_comment video_comment" id="media_comment_0_l4l5n0wt">this is a media comment</a><br><br></p>}
end end
it "should only convert url params" do it "should only convert url params" do
@ -175,7 +189,7 @@ describe ImportedHtmlConverter do
</object> </object>
HTML HTML
expect(ImportedHtmlConverter.convert(test_string, @course)).to match_ignoring_whitespace(<<-HTML.strip) expect(convert_and_replace(test_string)).to match_ignoring_whitespace(<<-HTML.strip)
<object> <object>
<param name="controls" value="CONSOLE"> <param name="controls" value="CONSOLE">
<param name="controller" value="true"> <param name="controller" value="true">
@ -188,7 +202,7 @@ describe ImportedHtmlConverter do
it "should leave an anchor tag alone" do it "should leave an anchor tag alone" do
test_string = '<p><a href="#anchor_ref">ref</a></p>' test_string = '<p><a href="#anchor_ref">ref</a></p>'
expect(ImportedHtmlConverter.convert(test_string, @course)).to eq test_string expect(convert_and_replace(test_string)).to eq test_string
end end
end end
@ -205,8 +219,8 @@ describe ImportedHtmlConverter do
expect(ImportedHtmlConverter.relative_url?("watup/nothing?absolutely=1")).to eq true expect(ImportedHtmlConverter.relative_url?("watup/nothing?absolutely=1")).to eq true
end end
it "should error on invalid urls" do it "should not error on invalid urls" do
expect { ImportedHtmlConverter.relative_url?("stupid &^%$ url") }.to raise_error(URI::InvalidURIError) expect(ImportedHtmlConverter.relative_url?("stupid &^%$ url")).to be_falsey
end end
end end

View File

@ -838,5 +838,69 @@ equation: <img class="equation_image" title="Log_216" src="/equation_images/Log_
expect(quiz_to.assignment).to eq a_to expect(quiz_to.assignment).to eq a_to
expect(a_to).to be_published expect(a_to).to be_published
end end
it "should correctly copy links to quizzes inside assessment questions" do
link_quiz = @copy_from.quizzes.create!(:title => "linked quiz")
html = "<a href=\"/courses/%s/quizzes/%s\">linky</a>"
bank = @copy_from.assessment_question_banks.create!(:title => 'bank')
data = {'question_name' => 'test question', 'question_type' => 'essay_question',
'question_text' => (html % [@copy_from.id, link_quiz.id])}
aq = bank.assessment_questions.create!(:question_data => data)
other_quiz = @copy_from.quizzes.create!(:title => "other quiz")
qq = other_quiz.quiz_questions.create!(:question_data => data)
other_quiz.generate_quiz_data
other_quiz.published_at = Time.now
other_quiz.workflow_state = 'available'
other_quiz.save!
run_course_copy
link_quiz2 = @copy_to.quizzes.where(migration_id: mig_id(link_quiz)).first
expected_html = (html % [@copy_to.id, link_quiz2.id])
other_quiz2 = @copy_to.quizzes.where(migration_id: mig_id(other_quiz)).first
aq2 = @copy_to.assessment_questions.where(migration_id: mig_id(aq)).first
qq2 = other_quiz2.quiz_questions.first
expect(aq2.question_data['question_text']).to eq expected_html
expect(qq2.question_data['question_text']).to eq expected_html
expect(other_quiz2.quiz_data.first['question_text']).to eq expected_html
end
it "should correctly copy links to quizzes inside standalone quiz questions" do
# i.e. quiz questions imported independently from their original assessment question
link_quiz = @copy_from.quizzes.create!(:title => "linked quiz")
html = "<a href=\"/courses/%s/quizzes/%s\">linky</a>"
bank = @copy_from.assessment_question_banks.create!(:title => 'bank')
data = {'question_name' => 'test question', 'question_type' => 'essay_question',
'question_text' => (html % [@copy_from.id, link_quiz.id])}
aq = bank.assessment_questions.create!(:question_data => data)
other_quiz = @copy_from.quizzes.create!(:title => "other quiz")
qq = other_quiz.quiz_questions.create!(:question_data => data)
other_quiz.generate_quiz_data
other_quiz.published_at = Time.now
other_quiz.workflow_state = 'available'
other_quiz.save!
@cm.copy_options = {
:quizzes => {mig_id(link_quiz) => "1", mig_id(other_quiz) => "1"}
}
run_course_copy
link_quiz2 = @copy_to.quizzes.where(migration_id: mig_id(link_quiz)).first
expected_html = (html % [@copy_to.id, link_quiz2.id])
other_quiz2 = @copy_to.quizzes.where(migration_id: mig_id(other_quiz)).first
qq2 = other_quiz2.quiz_questions.first
expect(qq2.question_data['question_text']).to eq expected_html
expect(other_quiz2.quiz_data.first['question_text']).to eq expected_html
end
end end
end end

View File

@ -139,7 +139,7 @@ describe "Assessment Question import from hash" do
question_data[:aq_data][question[:migration_id]] = context.assessment_questions.where(migration_id: question[:migration_id]).first question_data[:aq_data][question[:migration_id]] = context.assessment_questions.where(migration_id: question[:migration_id]).first
quiz = get_import_data 'cengage', 'quiz' quiz = get_import_data 'cengage', 'quiz'
Importers::QuizImporter.import_from_migration(quiz, context, nil, question_data) Importers::QuizImporter.import_from_migration(quiz, context, migration, question_data)
quiz = context.quizzes.where(migration_id: quiz[:migration_id]).first quiz = context.quizzes.where(migration_id: quiz[:migration_id]).first
group = quiz.quiz_groups.first group = quiz.quiz_groups.first

View File

@ -25,16 +25,17 @@ describe "Importing Assignment Groups" do
it "should import from #{system}" do it "should import from #{system}" do
data = get_import_data(system, 'assignment_group') data = get_import_data(system, 'assignment_group')
context = get_import_context(system) context = get_import_context(system)
migration = context.content_migrations.create!
data[:assignment_groups_to_import] = {} data[:assignment_groups_to_import] = {}
expect { expect {
expect(Importers::AssignmentGroupImporter.import_from_migration(data, context)).to be_nil expect(Importers::AssignmentGroupImporter.import_from_migration(data, context, migration)).to be_nil
}.to change(AssignmentGroup, :count).by(0) }.to change(AssignmentGroup, :count).by(0)
data[:assignment_groups_to_import][data[:migration_id]] = true data[:assignment_groups_to_import][data[:migration_id]] = true
expect { expect {
Importers::AssignmentGroupImporter.import_from_migration(data, context) Importers::AssignmentGroupImporter.import_from_migration(data, context, migration)
Importers::AssignmentGroupImporter.import_from_migration(data, context) Importers::AssignmentGroupImporter.import_from_migration(data, context, migration)
}.to change(AssignmentGroup, :count).by(1) }.to change(AssignmentGroup, :count).by(1)
g = AssignmentGroup.where(migration_id: data[:migration_id]).first g = AssignmentGroup.where(migration_id: data[:migration_id]).first
@ -45,20 +46,22 @@ describe "Importing Assignment Groups" do
it "should reuse existing empty assignment groups with the same name" do it "should reuse existing empty assignment groups with the same name" do
course_model course_model
migration = @course.content_migrations.create!
assignment_group = @course.assignment_groups.create! name: 'teh group' assignment_group = @course.assignment_groups.create! name: 'teh group'
assignment_group_json = { 'title' => 'teh group', 'migration_id' => '123' } assignment_group_json = { 'title' => 'teh group', 'migration_id' => '123' }
Importers::AssignmentGroupImporter.import_from_migration(assignment_group_json, @course) Importers::AssignmentGroupImporter.import_from_migration(assignment_group_json, @course, migration)
expect(assignment_group.reload.migration_id).to eq('123') expect(assignment_group.reload.migration_id).to eq('123')
expect(@course.assignment_groups.count).to eq 1 expect(@course.assignment_groups.count).to eq 1
end end
it "should not match assignment groups with migration ids by name" do it "should not match assignment groups with migration ids by name" do
course_model course_model
migration = @course.content_migrations.create!
assignment_group = @course.assignment_groups.create name: 'teh group' assignment_group = @course.assignment_groups.create name: 'teh group'
assignment_group.migration_id = '456' assignment_group.migration_id = '456'
assignment_group.save! assignment_group.save!
assignment_group_json = { 'title' => 'teh group', 'migration_id' => '123' } assignment_group_json = { 'title' => 'teh group', 'migration_id' => '123' }
Importers::AssignmentGroupImporter.import_from_migration(assignment_group_json, @course) Importers::AssignmentGroupImporter.import_from_migration(assignment_group_json, @course, migration)
expect(assignment_group.reload.migration_id).to eq('456') expect(assignment_group.reload.migration_id).to eq('456')
expect(@course.assignment_groups.count).to eq 2 expect(@course.assignment_groups.count).to eq 2
end end
@ -66,12 +69,13 @@ describe "Importing Assignment Groups" do
it "should get attached to an assignment" do it "should get attached to an assignment" do
data = get_import_data('bb8', 'assignment_group') data = get_import_data('bb8', 'assignment_group')
context = get_import_context('bb8') context = get_import_context('bb8')
migration = context.content_migrations.create!
expect { expect {
Importers::AssignmentGroupImporter.import_from_migration(data, context) Importers::AssignmentGroupImporter.import_from_migration(data, context, migration)
}.to change(AssignmentGroup, :count).by(1) }.to change(AssignmentGroup, :count).by(1)
expect { expect {
ass = Importers::AssignmentImporter.import_from_migration(get_import_data('bb8', 'assignment'), context) ass = Importers::AssignmentImporter.import_from_migration(get_import_data('bb8', 'assignment'), context, migration)
expect(ass.assignment_group.name).to eq data[:title] expect(ass.assignment_group.name).to eq data[:title]
}.to change(AssignmentGroup, :count).by(0) }.to change(AssignmentGroup, :count).by(0)
end end

View File

@ -25,16 +25,17 @@ describe "Importing assignments" do
it "should import assignments for #{system}" do it "should import assignments for #{system}" do
data = get_import_data(system, 'assignment') data = get_import_data(system, 'assignment')
context = get_import_context(system) context = get_import_context(system)
migration = context.content_migrations.create!
data[:assignments_to_import] = {} data[:assignments_to_import] = {}
expect { expect {
expect(Importers::AssignmentImporter.import_from_migration(data, context)).to be_nil expect(Importers::AssignmentImporter.import_from_migration(data, context, migration)).to be_nil
}.to change(Assignment, :count).by(0) }.to change(Assignment, :count).by(0)
data[:assignments_to_import][data[:migration_id]] = true data[:assignments_to_import][data[:migration_id]] = true
expect { expect {
Importers::AssignmentImporter.import_from_migration(data, context) Importers::AssignmentImporter.import_from_migration(data, context, migration)
Importers::AssignmentImporter.import_from_migration(data, context) Importers::AssignmentImporter.import_from_migration(data, context, migration)
}.to change(Assignment, :count).by(1) }.to change(Assignment, :count).by(1)
a = Assignment.where(migration_id: data[:migration_id]).first a = Assignment.where(migration_id: data[:migration_id]).first
@ -50,6 +51,7 @@ describe "Importing assignments" do
it "should import grading information when rubric is included" do it "should import grading information when rubric is included" do
file_data = get_import_data('', 'assignment') file_data = get_import_data('', 'assignment')
context = get_import_context('') context = get_import_context('')
migration = context.content_migrations.create!
assignment_hash = file_data.find{|h| h['migration_id'] == '4469882339231'}.with_indifferent_access assignment_hash = file_data.find{|h| h['migration_id'] == '4469882339231'}.with_indifferent_access
@ -58,13 +60,14 @@ describe "Importing assignments" do
rubric.points_possible = 42 rubric.points_possible = 42
rubric.save! rubric.save!
Importers::AssignmentImporter.import_from_migration(assignment_hash, context) Importers::AssignmentImporter.import_from_migration(assignment_hash, context, migration)
a = Assignment.where(migration_id: assignment_hash[:migration_id]).first a = Assignment.where(migration_id: assignment_hash[:migration_id]).first
expect(a.points_possible).to eq rubric.points_possible expect(a.points_possible).to eq rubric.points_possible
end end
it "should infer the default name when importing a nameless assignment" do it "should infer the default name when importing a nameless assignment" do
course_model course_model
migration = @course.content_migrations.create!
nameless_assignment_hash = { nameless_assignment_hash = {
"migration_id" => "ib4834d160d180e2e91572e8b9e3b1bc6", "migration_id" => "ib4834d160d180e2e91572e8b9e3b1bc6",
"assignment_group_migration_id" => "i2bc4b8ea8fac88f1899e5e95d76f3004", "assignment_group_migration_id" => "i2bc4b8ea8fac88f1899e5e95d76f3004",
@ -84,13 +87,14 @@ describe "Importing assignments" do
"position" => 6, "position" => 6,
"peer_review_count" => 0 "peer_review_count" => 0
} }
Importers::AssignmentImporter.import_from_migration(nameless_assignment_hash, @course) Importers::AssignmentImporter.import_from_migration(nameless_assignment_hash, @course, migration)
assignment = @course.assignments.where(migration_id: 'ib4834d160d180e2e91572e8b9e3b1bc6').first assignment = @course.assignments.where(migration_id: 'ib4834d160d180e2e91572e8b9e3b1bc6').first
expect(assignment.title).to eq 'untitled assignment' expect(assignment.title).to eq 'untitled assignment'
end end
it "should schedule auto peer reviews if dates are not shifted " do it "should schedule auto peer reviews if dates are not shifted " do
course_model course_model
migration = @course.content_migrations.create!
assign_hash = { assign_hash = {
"migration_id" => "ib4834d160d180e2e91572e8b9e3b1bc6", "migration_id" => "ib4834d160d180e2e91572e8b9e3b1bc6",
"assignment_group_migration_id" => "i2bc4b8ea8fac88f1899e5e95d76f3004", "assignment_group_migration_id" => "i2bc4b8ea8fac88f1899e5e95d76f3004",
@ -104,7 +108,7 @@ describe "Importing assignments" do
"peer_reviews_due_at" => 1401947999000 "peer_reviews_due_at" => 1401947999000
} }
expects_job_with_tag('Assignment#do_auto_peer_review') { expects_job_with_tag('Assignment#do_auto_peer_review') {
Importers::AssignmentImporter.import_from_migration(assign_hash, @course) Importers::AssignmentImporter.import_from_migration(assign_hash, @course, migration)
} }
end end
@ -122,10 +126,7 @@ describe "Importing assignments" do
"due_at" => 1401947999000, "due_at" => 1401947999000,
"peer_reviews_due_at" => 1401947999000 "peer_reviews_due_at" => 1401947999000
} }
migration = mock() migration = @course.content_migrations.create!
migration.stubs(:for_course_copy?)
migration.stubs(:add_missing_content_links)
migration.stubs(:add_imported_item)
migration.stubs(:date_shift_options).returns(true) migration.stubs(:date_shift_options).returns(true)
expects_job_with_tag('Assignment#do_auto_peer_review', 0) { expects_job_with_tag('Assignment#do_auto_peer_review', 0) {
Importers::AssignmentImporter.import_from_migration(assign_hash, @course, migration) Importers::AssignmentImporter.import_from_migration(assign_hash, @course, migration)

View File

@ -30,6 +30,7 @@ module Importers
let(:attachment) do let(:attachment) do
stub(:context= => true, stub(:context= => true,
:migration_id= => true, :migration_id= => true,
:migration_id => migration_id,
:save_without_broadcasting! => true, :save_without_broadcasting! => true,
:set_publish_state_for_usage_rights => nil) :set_publish_state_for_usage_rights => nil)
end end

View File

@ -23,6 +23,8 @@ describe Importers::CalendarEventImporter do
let_once(:migration_course) { course(active_all: true) } let_once(:migration_course) { course(active_all: true) }
let(:migration) { migration_course.content_migrations.create! }
let(:migration_assignment) do let(:migration_assignment) do
assignment = migration_course.assignments.build(title: 'migration assignment') assignment = migration_course.assignments.build(title: 'migration assignment')
assignment.workflow_state = 'active' assignment.workflow_state = 'active'
@ -80,7 +82,7 @@ describe Importers::CalendarEventImporter do
attachment_type: 'external_url', attachment_type: 'external_url',
attachment_value: 'http://example.com' attachment_value: 'http://example.com'
} }
Importers::CalendarEventImporter.import_from_migration(hash, migration_course, nil, event) Importers::CalendarEventImporter.import_from_migration(hash, migration_course, migration, event)
expect(event).not_to be_new_record expect(event).not_to be_new_record
expect(event.imported).to be_truthy expect(event.imported).to be_truthy
expect(event.migration_id).to eq '42' expect(event.migration_id).to eq '42'
@ -143,14 +145,15 @@ describe Importers::CalendarEventImporter do
it "should import calendar events for #{system}" do it "should import calendar events for #{system}" do
data = get_import_data(system, 'calendar_event') data = get_import_data(system, 'calendar_event')
context = get_import_context(system) context = get_import_context(system)
migration = context.content_migrations.create!
data[:events_to_import] = {} data[:events_to_import] = {}
expect(Importers::CalendarEventImporter.import_from_migration(data, context)).to be_nil expect(Importers::CalendarEventImporter.import_from_migration(data, context, migration)).to be_nil
expect(context.calendar_events.count).to eq 0 expect(context.calendar_events.count).to eq 0
data[:events_to_import][data[:migration_id]] = true data[:events_to_import][data[:migration_id]] = true
Importers::CalendarEventImporter.import_from_migration(data, context) Importers::CalendarEventImporter.import_from_migration(data, context, migration)
Importers::CalendarEventImporter.import_from_migration(data, context) Importers::CalendarEventImporter.import_from_migration(data, context, migration)
expect(context.calendar_events.count).to eq 1 expect(context.calendar_events.count).to eq 1
event = CalendarEvent.where(migration_id: data[:migration_id]).first event = CalendarEvent.where(migration_id: data[:migration_id]).first

View File

@ -3,14 +3,16 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb')
describe Importers::ContextExternalToolImporter do describe Importers::ContextExternalToolImporter do
it "should work for course-level tools" do it "should work for course-level tools" do
course_model course_model
tool = Importers::ContextExternalToolImporter.import_from_migration({:title => 'tool', :url => 'http://example.com'}, @course) migration = @course.content_migrations.create!
tool = Importers::ContextExternalToolImporter.import_from_migration({:title => 'tool', :url => 'http://example.com'}, @course, migration)
expect(tool).not_to be_nil expect(tool).not_to be_nil
expect(tool.context).to eq @course expect(tool.context).to eq @course
end end
it "should work for account-level tools" do it "should work for account-level tools" do
course_model course_model
tool = Importers::ContextExternalToolImporter.import_from_migration({:title => 'tool', :url => 'http://example.com'}, @course.account) migration = @course.account.content_migrations.create!
tool = Importers::ContextExternalToolImporter.import_from_migration({:title => 'tool', :url => 'http://example.com'}, @course.account, migration)
expect(tool).not_to be_nil expect(tool).not_to be_nil
expect(tool.context).to eq @course.account expect(tool.context).to eq @course.account
end end

View File

@ -25,13 +25,14 @@ describe "Importing modules" do
it "should import from #{system}" do it "should import from #{system}" do
data = get_import_data(system, 'module') data = get_import_data(system, 'module')
context = get_import_context(system) context = get_import_context(system)
migration = context.content_migrations.create!
data[:modules_to_import] = {} data[:modules_to_import] = {}
expect(Importers::ContextModuleImporter.import_from_migration(data, context)).to be_nil expect(Importers::ContextModuleImporter.import_from_migration(data, context, migration)).to be_nil
expect(context.context_modules.count).to eq 0 expect(context.context_modules.count).to eq 0
data[:modules_to_import][data[:migration_id]] = true data[:modules_to_import][data[:migration_id]] = true
Importers::ContextModuleImporter.import_from_migration(data, context) Importers::ContextModuleImporter.import_from_migration(data, context, migration)
Importers::ContextModuleImporter.import_from_migration(data, context) Importers::ContextModuleImporter.import_from_migration(data, context, migration)
expect(context.context_modules.count).to eq 1 expect(context.context_modules.count).to eq 1
mod = ContextModule.where(migration_id: data[:migration_id]).first mod = ContextModule.where(migration_id: data[:migration_id]).first
@ -44,26 +45,28 @@ describe "Importing modules" do
it "should link to url objects" do it "should link to url objects" do
data = get_import_data('vista', 'module') data = get_import_data('vista', 'module')
context = get_import_context('vista') context = get_import_context('vista')
migration = context.content_migrations.create!
context.external_url_hash = {} context.external_url_hash = {}
topic = Importers::ContextModuleImporter.import_from_migration(data, context) topic = Importers::ContextModuleImporter.import_from_migration(data, context, migration)
expect(topic.content_tags.count).to eq 2 expect(topic.content_tags.count).to eq 2
end end
it "should link to objects on the second pass" do it "should link to objects on the second pass" do
data = get_import_data('bb8', 'module') data = get_import_data('bb8', 'module')
context = get_import_context('bb8') context = get_import_context('bb8')
migration = context.content_migrations.create!
context.external_url_hash = {} context.external_url_hash = {}
topic = Importers::ContextModuleImporter.import_from_migration(data, context) topic = Importers::ContextModuleImporter.import_from_migration(data, context, migration)
expect(topic.content_tags.count).to eq 0 expect(topic.content_tags.count).to eq 0
ass = get_import_data('bb8', 'assignment') ass = get_import_data('bb8', 'assignment')
Importers::AssignmentImporter.import_from_migration(ass, context) Importers::AssignmentImporter.import_from_migration(ass, context, migration)
expect(context.assignments.count).to eq 1 expect(context.assignments.count).to eq 1
topic = Importers::ContextModuleImporter.import_from_migration(data, context) topic = Importers::ContextModuleImporter.import_from_migration(data, context, migration)
expect(topic.content_tags.count).to eq 1 expect(topic.content_tags.count).to eq 1
end end

View File

@ -30,14 +30,15 @@ describe Importers::DiscussionTopicImporter do
data = data.with_indifferent_access data = data.with_indifferent_access
context = get_import_context(system) context = get_import_context(system)
migration = context.content_migrations.create!
data[:topics_to_import] = {} data[:topics_to_import] = {}
expect(Importers::DiscussionTopicImporter.import_from_migration(data, context)).to be_nil expect(Importers::DiscussionTopicImporter.import_from_migration(data, context, migration)).to be_nil
expect(context.discussion_topics.count).to eq 0 expect(context.discussion_topics.count).to eq 0
data[:topics_to_import][data[:migration_id]] = true data[:topics_to_import][data[:migration_id]] = true
Importers::DiscussionTopicImporter.import_from_migration(data, context) Importers::DiscussionTopicImporter.import_from_migration(data, context, migration)
Importers::DiscussionTopicImporter.import_from_migration(data, context) Importers::DiscussionTopicImporter.import_from_migration(data, context, migration)
expect(context.discussion_topics.count).to eq 1 expect(context.discussion_topics.count).to eq 1
topic = DiscussionTopic.where(migration_id: data[:migration_id]).first topic = DiscussionTopic.where(migration_id: data[:migration_id]).first
@ -62,13 +63,14 @@ describe Importers::DiscussionTopicImporter do
it "should import assignments for #{system}" do it "should import assignments for #{system}" do
data = get_import_data(system, 'announcements') data = get_import_data(system, 'announcements')
context = get_import_context(system) context = get_import_context(system)
migration = context.content_migrations.create!
data[:topics_to_import] = {} data[:topics_to_import] = {}
expect(Importers::DiscussionTopicImporter.import_from_migration(data, context)).to be_nil expect(Importers::DiscussionTopicImporter.import_from_migration(data, context, migration)).to be_nil
expect(context.discussion_topics.count).to eq 0 expect(context.discussion_topics.count).to eq 0
data[:topics_to_import][data[:migration_id]] = true data[:topics_to_import][data[:migration_id]] = true
Importers::DiscussionTopicImporter.import_from_migration(data, context) Importers::DiscussionTopicImporter.import_from_migration(data, context, migration)
Importers::DiscussionTopicImporter.import_from_migration(data, context) Importers::DiscussionTopicImporter.import_from_migration(data, context, migration)
expect(context.discussion_topics.count).to eq 1 expect(context.discussion_topics.count).to eq 1
topic = DiscussionTopic.where(migration_id: data[:migration_id]).first topic = DiscussionTopic.where(migration_id: data[:migration_id]).first
@ -82,12 +84,13 @@ describe Importers::DiscussionTopicImporter do
it "should not attach files when no attachment_migration_id is specified" do it "should not attach files when no attachment_migration_id is specified" do
data = get_import_data('bb8', 'discussion_topic').first.with_indifferent_access data = get_import_data('bb8', 'discussion_topic').first.with_indifferent_access
context = get_import_context('bb8') context = get_import_context('bb8')
migration = context.content_migrations.create!
data[:attachment_migration_id] = nil data[:attachment_migration_id] = nil
attachment_model(:context => context) # create a file with no migration id attachment_model(:context => context) # create a file with no migration id
data[:topics_to_import] = {data[:migration_id] => true} data[:topics_to_import] = {data[:migration_id] => true}
Importers::DiscussionTopicImporter.import_from_migration(data, context) Importers::DiscussionTopicImporter.import_from_migration(data, context, migration)
topic = DiscussionTopic.where(migration_id: data[:migration_id]).first topic = DiscussionTopic.where(migration_id: data[:migration_id]).first
expect(topic.attachment).to be_nil expect(topic.attachment).to be_nil

View File

@ -23,13 +23,14 @@ describe Importers::ExternalFeedImporter do
context ".import_from_migration" do context ".import_from_migration" do
it "creates a feed from the provided hash" do it "creates a feed from the provided hash" do
@course = course @course = course
migration = @course.content_migrations.create!
data = { data = {
url: 'http://www.example.com/feed', url: 'http://www.example.com/feed',
title: 'test feed', title: 'test feed',
verbosity: 'link_only', verbosity: 'link_only',
header_match: '' header_match: ''
} }
feed = Importers::ExternalFeedImporter.import_from_migration(data, @course) feed = Importers::ExternalFeedImporter.import_from_migration(data, @course, migration)
expect(feed.url).to eq data[:url] expect(feed.url).to eq data[:url]
expect(feed.title).to eq data[:title] expect(feed.title).to eq data[:title]
expect(feed.verbosity).to eq data[:verbosity] expect(feed.verbosity).to eq data[:verbosity]

View File

@ -25,14 +25,15 @@ describe "Importing Groups" do
it "should import from #{system}" do it "should import from #{system}" do
data = get_import_data(system, 'group') data = get_import_data(system, 'group')
context = get_import_context(system) context = get_import_context(system)
migration = context.content_migrations.create!
data[:groups_to_import] = {} data[:groups_to_import] = {}
expect(Importers::GroupImporter.import_from_migration(data, context)).to be_nil expect(Importers::GroupImporter.import_from_migration(data, context, migration)).to be_nil
expect(context.groups.count).to eq 0 expect(context.groups.count).to eq 0
data[:groups_to_import][data[:migration_id]] = true data[:groups_to_import][data[:migration_id]] = true
Importers::GroupImporter.import_from_migration(data, context) Importers::GroupImporter.import_from_migration(data, context, migration)
Importers::GroupImporter.import_from_migration(data, context) Importers::GroupImporter.import_from_migration(data, context, migration)
expect(context.groups.count).to eq 1 expect(context.groups.count).to eq 1
g = Group.where(migration_id: data[:migration_id]).first g = Group.where(migration_id: data[:migration_id]).first
@ -44,8 +45,9 @@ describe "Importing Groups" do
it "should attach to a discussion" do it "should attach to a discussion" do
data = get_import_data('bb8', 'group') data = get_import_data('bb8', 'group')
context = get_import_context('bb8') context = get_import_context('bb8')
migration = context.content_migrations.create!
Importers::GroupImporter.import_from_migration(data, context) Importers::GroupImporter.import_from_migration(data, context, migration)
expect(context.groups.count).to eq 1 expect(context.groups.count).to eq 1
category = get_import_data('bb8', 'group_discussion') category = get_import_data('bb8', 'group_discussion')
@ -54,7 +56,7 @@ describe "Importing Groups" do
topic['group_id'] = category['group_id'] topic['group_id'] = category['group_id']
group = Group.where(context_id: context, context_type: context.class.to_s, migration_id: topic['group_id']).first group = Group.where(context_id: context, context_type: context.class.to_s, migration_id: topic['group_id']).first
if group if group
Importers::DiscussionTopicImporter.import_from_migration(topic, group) Importers::DiscussionTopicImporter.import_from_migration(topic, group, migration)
end end
end end
@ -64,15 +66,17 @@ describe "Importing Groups" do
it "should respect group_category from the hash" do it "should respect group_category from the hash" do
course_with_teacher course_with_teacher
migration = @course.content_migrations.create!
group = @course.groups.build group = @course.groups.build
Importers::GroupImporter.import_from_migration({:group_category => "random category"}, @course, nil, group) Importers::GroupImporter.import_from_migration({:group_category => "random category"}, @course, migration, group)
expect(group.group_category.name).to eq "random category" expect(group.group_category.name).to eq "random category"
end end
it "should default group_category to imported if not in the hash" do it "should default group_category to imported if not in the hash" do
course_with_teacher course_with_teacher
migration = @course.content_migrations.create!
group = @course.groups.build group = @course.groups.build
Importers::GroupImporter.import_from_migration({}, @course, nil, group) Importers::GroupImporter.import_from_migration({}, @course, migration, group)
expect(group.group_category).to eq GroupCategory.imported_for(@course) expect(group.group_category).to eq GroupCategory.imported_for(@course)
end end
end end

View File

@ -21,13 +21,14 @@ require File.expand_path(File.dirname(__FILE__) + '../../../import_helper')
describe "Importers::QuizImporter" do describe "Importers::QuizImporter" do
before(:once) do before(:once) do
course_model course_model
@migration = @course.content_migrations.create!
end end
it "should get the quiz properties" do it "should get the quiz properties" do
context = course_model context = course_model
question_data = import_example_questions context question_data = import_example_questions context
data = get_import_data ['vista', 'quiz'], 'simple_quiz_data' data = get_import_data ['vista', 'quiz'], 'simple_quiz_data'
Importers::QuizImporter.import_from_migration(data, context, nil, question_data) Importers::QuizImporter.import_from_migration(data, context, @migration, question_data)
quiz = Quizzes::Quiz.where(migration_id: data[:migration_id]).first quiz = Quizzes::Quiz.where(migration_id: data[:migration_id]).first
expect(quiz.title).to eq data[:title] expect(quiz.title).to eq data[:title]
expect(quiz.scoring_policy).to eq data[:which_attempt_to_keep] expect(quiz.scoring_policy).to eq data[:which_attempt_to_keep]
@ -42,7 +43,7 @@ describe "Importers::QuizImporter" do
context = course_model context = course_model
question_data = import_example_questions context question_data = import_example_questions context
data = get_import_data ['vista', 'quiz'], 'simple_quiz_data' data = get_import_data ['vista', 'quiz'], 'simple_quiz_data'
Importers::QuizImporter.import_from_migration(data, context, nil, question_data) Importers::QuizImporter.import_from_migration(data, context, @migration, question_data)
quiz = Quizzes::Quiz.where(migration_id: data[:migration_id]).first quiz = Quizzes::Quiz.where(migration_id: data[:migration_id]).first
expect(quiz.quiz_questions.active.count).to eq 1 expect(quiz.quiz_questions.active.count).to eq 1
# Check if the expected question name is in there # Check if the expected question name is in there
@ -53,7 +54,7 @@ describe "Importers::QuizImporter" do
context = get_import_context context = get_import_context
question_data = import_example_questions context question_data = import_example_questions context
data = get_import_data ['vista', 'quiz'], 'text_only_quiz_data' data = get_import_data ['vista', 'quiz'], 'text_only_quiz_data'
Importers::QuizImporter.import_from_migration(data, context, nil, question_data) Importers::QuizImporter.import_from_migration(data, context, @migration, question_data)
quiz = Quizzes::Quiz.where(migration_id: data[:migration_id]).first quiz = Quizzes::Quiz.where(migration_id: data[:migration_id]).first
expect(quiz.unpublished_question_count).to eq 2 expect(quiz.unpublished_question_count).to eq 2
expect(quiz.quiz_questions.active.count).to eq 2 expect(quiz.quiz_questions.active.count).to eq 2
@ -66,7 +67,7 @@ describe "Importers::QuizImporter" do
context = get_import_context context = get_import_context
question_data = import_example_questions context question_data = import_example_questions context
data = get_import_data ['vista', 'quiz'], 'group_quiz_data' data = get_import_data ['vista', 'quiz'], 'group_quiz_data'
Importers::QuizImporter.import_from_migration(data, context, nil, question_data) Importers::QuizImporter.import_from_migration(data, context, @migration, question_data)
quiz = Quizzes::Quiz.where(migration_id: data[:migration_id]).first quiz = Quizzes::Quiz.where(migration_id: data[:migration_id]).first
expect(quiz.quiz_groups.count).to eq 1 expect(quiz.quiz_groups.count).to eq 1
expect(quiz.quiz_groups.first.quiz_questions.active.count).to eq 3 expect(quiz.quiz_groups.first.quiz_questions.active.count).to eq 3
@ -78,8 +79,8 @@ describe "Importers::QuizImporter" do
context = get_import_context context = get_import_context
question_data = import_example_questions context question_data = import_example_questions context
data = get_import_data ['vista', 'quiz'], 'text_only_quiz_data' data = get_import_data ['vista', 'quiz'], 'text_only_quiz_data'
Importers::QuizImporter.import_from_migration(data, context, nil, question_data) Importers::QuizImporter.import_from_migration(data, context, @migration, question_data)
Importers::QuizImporter.import_from_migration(data, context, nil, question_data) Importers::QuizImporter.import_from_migration(data, context, @migration, question_data)
expect(Quizzes::Quiz.count).to eq 1 expect(Quizzes::Quiz.count).to eq 1
quiz = Quizzes::Quiz.where(migration_id: data[:migration_id]).first quiz = Quizzes::Quiz.where(migration_id: data[:migration_id]).first
expect(quiz.assignment).to be_nil expect(quiz.assignment).to be_nil
@ -91,7 +92,7 @@ describe "Importers::QuizImporter" do
quiz_hash = get_import_data ['vista', 'quiz'], 'simple_quiz_data' quiz_hash = get_import_data ['vista', 'quiz'], 'simple_quiz_data'
data = {'assessments' => {'assessments' => [quiz_hash]}} data = {'assessments' => {'assessments' => [quiz_hash]}}
migration = context.content_migrations.create! migration = context.content_migrations.create!
Importers::CourseContentImporter.import_content(context, data, nil, migration) Importers::CourseContentImporter.import_content(context, data, @migration, migration)
expect(Assignment.count).to eq 0 expect(Assignment.count).to eq 0
expect(Quizzes::Quiz.count).to eq 1 expect(Quizzes::Quiz.count).to eq 1
@ -111,7 +112,7 @@ describe "Importers::QuizImporter" do
data = {'assessments' => {'assessments' => [quiz_hash]}, 'assignments' => [assignment_hash]} data = {'assessments' => {'assessments' => [quiz_hash]}, 'assignments' => [assignment_hash]}
migration = context.content_migrations.create! migration = context.content_migrations.create!
Importers::CourseContentImporter.import_content(context, data, nil, migration) Importers::CourseContentImporter.import_content(context, data, @migration, migration)
expect(Assignment.count).to eq 1 expect(Assignment.count).to eq 1
expect(Quizzes::Quiz.count).to eq 1 expect(Quizzes::Quiz.count).to eq 1
@ -123,8 +124,10 @@ describe "Importers::QuizImporter" do
end end
it "should convert relative file references to course-relative file references" do it "should convert relative file references to course-relative file references" do
context = course_model context = @course
import_example_questions context import_example_questions context
@migration.resolve_content_links!
question = AssessmentQuestion.where(migration_id: '4393906433391').first question = AssessmentQuestion.where(migration_id: '4393906433391').first
expect(question.data[:question_text]).to eq "Why does that bee/rocket ship company suck? <img src=\"/courses/#{context.id}/file_contents/course%20files/rocket.png\">" expect(question.data[:question_text]).to eq "Why does that bee/rocket ship company suck? <img src=\"/courses/#{context.id}/file_contents/course%20files/rocket.png\">"
question = AssessmentQuestion.where(migration_id: 'URN-X-WEBCT-VISTA_V2-790EA1350E1A681DE0440003BA07D9B4').first question = AssessmentQuestion.where(migration_id: 'URN-X-WEBCT-VISTA_V2-790EA1350E1A681DE0440003BA07D9B4').first
@ -135,13 +138,13 @@ describe "Importers::QuizImporter" do
context = get_import_context context = get_import_context
question_data = import_example_questions context question_data = import_example_questions context
data = get_import_data ['vista', 'quiz'], 'simple_quiz_data' data = get_import_data ['vista', 'quiz'], 'simple_quiz_data'
Importers::QuizImporter.import_from_migration(data, context, nil, question_data) Importers::QuizImporter.import_from_migration(data, context, @migration, question_data)
quiz = Quizzes::Quiz.where(migration_id: data[:migration_id]).first quiz = Quizzes::Quiz.where(migration_id: data[:migration_id]).first
expect(quiz.quiz_questions.active.first.question_data[:question_name]).to eq "Rocket Bee!" expect(quiz.quiz_questions.active.first.question_data[:question_name]).to eq "Rocket Bee!"
question_data[:aq_data][data['questions'].first[:migration_id]]['question_name'] = "Not Rocket Bee?" question_data[:aq_data][data['questions'].first[:migration_id]]['question_name'] = "Not Rocket Bee?"
Importers::QuizImporter.import_from_migration(data, context, nil, question_data) Importers::QuizImporter.import_from_migration(data, context, @migration, question_data)
expect(quiz.quiz_questions.active.first.question_data[:question_name]).to eq "Not Rocket Bee?" expect(quiz.quiz_questions.active.first.question_data[:question_name]).to eq "Not Rocket Bee?"
end end

View File

@ -25,9 +25,10 @@ describe "Importing wikis" do
it "should import for #{system}" do it "should import for #{system}" do
data = get_import_data(system, 'wiki') data = get_import_data(system, 'wiki')
context = get_import_context(system) context = get_import_context(system)
migration = context.content_migrations.create!
Importers::WikiPageImporter.import_from_migration(data, context) Importers::WikiPageImporter.import_from_migration(data, context, migration)
Importers::WikiPageImporter.import_from_migration(data, context) Importers::WikiPageImporter.import_from_migration(data, context, migration)
expect(context.wiki.wiki_pages.count).to eq 1 expect(context.wiki.wiki_pages.count).to eq 1
wiki = WikiPage.where(migration_id: data[:migration_id]).first wiki = WikiPage.where(migration_id: data[:migration_id]).first
@ -39,11 +40,13 @@ describe "Importing wikis" do
it "should update BB9 wiki page links to the correct url" do it "should update BB9 wiki page links to the correct url" do
data = get_import_data('bb9', 'wikis') data = get_import_data('bb9', 'wikis')
context = get_import_context('bb9') context = get_import_context('bb9')
migration = context.content_migrations.create!
2.times do 2.times do
data.each do |wiki| data.each do |wiki|
Importers::WikiPageImporter.import_from_migration(wiki, context) Importers::WikiPageImporter.import_from_migration(wiki, context, migration)
end end
end end
migration.resolve_content_links!
# The wiki references should resolve to course urls # The wiki references should resolve to course urls
expect(context.wiki.wiki_pages.count).to eq 18 expect(context.wiki.wiki_pages.count).to eq 18