content migration creation api

allows creating content migration through the api

Test Plan:
 * Create a content migration through the api along with a file upload
 * You should be able to follow along with the progress and
   the migration should be complete when the progress is done
 * Try to update the migration type, you should get an error
 * Upload a wrong file type the first time, then go through
   the file upload process again on the update endpoint.

refs CNVS-4228

Change-Id: Iab1082ca68b61d1b0493c191b48169a164444d06
Reviewed-on: https://gerrit.instructure.com/19108
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: James Williams  <jamesw@instructure.com>
QA-Review: Clare Strong <clare@instructure.com>
Product-Review: Bracken Mosbacker <bracken@instructure.com>
Reviewed-by: Jeremy Stanley <jeremy@instructure.com>
This commit is contained in:
Bracken Mosbacker 2013-03-28 13:02:06 -06:00
parent 690dd65081
commit 93643f6d6c
24 changed files with 597 additions and 166 deletions

View File

@ -123,7 +123,7 @@ class ContentImportsController < ApplicationController
def migrate_content_choose
if authorized_action(@context, @current_user, :manage_content)
@content_migration = ContentMigration.for_context(@context).find(params[:id]) #)_all_by_context_id_and_context_type(@context.id, @context.class.to_s).last
if @content_migration.progress && @content_migration.progress >= 100
if @content_migration.workflow_state == "imported"
flash[:notice] = t 'notices.already_imported', "That extraction has already been imported into the course"
redirect_to named_context_url(@context, :context_url)
return

View File

@ -17,6 +17,7 @@
#
# @API Content Migrations
# @beta
#
# API for accessing content migrations and migration issues
# @object ContentMigration
@ -24,28 +25,44 @@
# // the unique identifier for the migration
# id: 370663,
#
# // API url to the content migration's issues
# migration_issues_url: "https://example.com/api/v1/courses/1/content_migrations/1/migration_issues"
# // the type of content migration
# migration_type: common_cartridge_importer,
#
# // url to download the file uploaded for this migration
# // the name of the content migration type
# migration_type_title: "Canvas Cartridge Importer",
#
# // API url to the content migration's issues
# migration_issues_url: "https://example.com/api/v1/courses/1/content_migrations/1/migration_issues",
#
# // attachment api object for the uploaded file
# // may not be present for all migrations
# content_archive_download_url: "https://example.com/api/v1/courses/1/content_migrations/1/download_archive"
# attachment: {url:"https://example.com/api/v1/courses/1/content_migrations/1/download_archive"},
#
# // The api endpoint for polling the current progress
# progress_url: "https://example.com/api/v1/progress/4",
#
# // The user who started the migration
# user_id: 4
# user_id: 4,
#
# // Current state of the issue: created pre_processing pre_processed pre_process_error converting converted importing imported failed
# workflow_state: "exporting"
# // Current state of the content migration: pre_processing pre_processed running waiting_for_select completed failed
# workflow_state: "running",
#
# // timestamps
# started_at: "2012-06-01T00:00:00-06:00",
# finished_at: "2012-06-01T00:00:00-06:00",
#
# // file uploading data, see {file:file_uploads.html File Upload Documentation} for file upload workflow
# // This works a little differently in that all the file data is in the pre_attachment hash
# // if there is no upload_url then there was an attachment pre-processing error, the error message will be in the message key
# // This data will only be here after a create or update call
# pre_attachment:{upload_url: "", message: "file exceeded quota", upload_params: {...}}
#
# }
class ContentMigrationsController < ApplicationController
include Api::V1::ContentMigration
before_filter :require_context
before_filter :require_auth
# @API List content migrations
#
@ -75,30 +92,139 @@ class ContentMigrationsController < ApplicationController
#
# @returns ContentMigration
def show
return unless require_content_migration
@content_migration = @context.content_migrations.find(params[:id])
render :json => content_migration_json(@content_migration, @current_user, session)
end
def download_archive
return unless require_content_migration
if @content_migration.attachment
if Attachment.s3_storage?
redirect_to @content_migration.attachment.cacheable_s3_download_url
else
cancel_cache_buster
send_file(@content_migration.attachment.full_filename, :type => @content_migration.attachment.content_type, :disposition => 'attachment')
end
else
render :status => 404, :json => {:message => t('no_archive', "There is no archive for this content migration")}
# @API Create a content migration
#
# Create a content migration. If the migration requires a file to be uploaded
# the actual processing of the file will start once the file upload process is completed.
# File uploading works as described in the {file:file_uploads.html File Upload Documentation}
# except that the values are set on a *pre_attachment* sub-hash.
#
# For migrations that don't require a file to be uploaded, like course copy, the
# processing will begin as soon as the migration is created.
#
# You can use the {api:ProgressController#show Progress API} to track the
# progress of the migration. The migration's progress is linked to with the
# _progress_url_ value
#
# The two general workflows are:
#
# If no file upload is needed:
#
# 1. POST to create
# 2. Use the {api:ProgressController#show Progress} specified in _progress_url_ to monitor progress
#
# For file uploading:
#
# 1. POST to create with file info in *pre_attachment*
# 2. Do {file:file_uploads.html file upload processing} using the data in the *pre_attachment* data
# 3. {api:ContentMigrationsController#show GET} the ContentMigration
# 4. Use the {api:ProgressController#show Progress} specified in _progress_url_ to monitor progress
#
# @argument migration_type [string] The type of the migration. Allowed values: canvas_cartridge_importer, common_cartridge_importer, qti_converter, moodle_converter
#
# @argument pre_attachment[name] [string] Required if uploading a file. This is the first step in uploading a file to the content migration. See the {file:file_uploads.html File Upload Documentation} for details on the file upload workflow.
#
# @argument pre_attachment[*] (optional) Other file upload properties, See {file:file_uploads.html File Upload Documentation}
#
# @argument settings[overwrite_quizzes] [boolean] (optional) Whether to overwrite quizzes with the same identifiers between content packages
#
# @argument settings[question_bank_id] [integer] (optional) The existing question bank ID to import questions into if not specified in the content package
# @argument settings[question_bank_name] [string] (optional) The question bank to import questions into if not specified in the content package, if both bank id and name are set, id will take precedence.
#
# @argument date_shift_options[shift_dates] [boolean] (optional) Whether to shift dates
# @argument date_shift_options[old_start_date] [yyyy-mm-dd] (optional) The original start date of the source content/course
# @argument date_shift_options[old_end_date] [yyyy-mm-dd] (optional) The original end date of the source content/course
# @argument date_shift_options[new_start_date] [yyyy-mm-dd] (optional) The new start date for the content/course
# @argument date_shift_options[new_end_date] [yyyy-mm-dd] (optional) The new end date for the source content/course
# @argument date_shift_options[day_substitutions][x] [integer] (optional) Move anything scheduled for day 'x' to the specified day. (0-Sunday, 1-Monday, 2-Tuesday, 3-Wednesday, 4-Thursday, 5-Friday, 6-Saturday)
#
# @example_request
#
# curl 'https://<canvas>/api/v1/courses/<course_id>/content_migrations' \
# -F 'migration_type=common_cartridge_importer' \
# -F 'settings[question_bank_name]=importquestions' \
# -F 'date_shift_options[old_start_date]=1999-01-01' \
# -F 'date_shift_options[new_start_date]=2013-09-01' \
# -F 'date_shift_options[old_end_date]=1999-04-15' \
# -F 'date_shift_options[new_end_date]=2013-12-15' \
# -F 'date_shift_options[day_substitutions][1]=2' \
# -F 'date_shift_options[day_substitutions][2]=3' \
# -F 'date_shift_options[shift_dates]=true' \
# -F 'pre_attachment[name]=mycourse.imscc' \
# -F 'pre_attachment[size]=12345' \
# -H 'Authorization: Bearer <token>'
#
# @returns ContentMigration
def create
plugin = Canvas::Plugin.find(params[:migration_type])
if !plugin
return render(:json => { :message => t('bad_migration_type', "Invalid migration_type") }, :status => :bad_request)
end
if plugin.settings && plugin.settings[:requires_file_upload]
if !params[:pre_attachment] || params[:pre_attachment][:name].blank?
return render(:json => { :message => t('must_upload_file', "File upload is required") }, :status => :bad_request)
end
end
@content_migration = @context.content_migrations.build(:user => @current_user, :context => @context, :migration_type => params[:migration_type])
@content_migration.workflow_state = 'created'
update_migration
end
# @API Update a content migration
#
# Update a content migration. Takes same arguments as create except that you can't change the migration type.
# However, changing most settings after the migration process has started will not do anything.
# Generally updating the content migration will be used when there is a file upload problem.
# If the first upload has a problem you can supply new _pre_attachment_ values to start the process again.
#
# @returns ContentMigration
def update
@content_migration = @context.content_migrations.find(params[:id])
update_migration
end
protected
def require_content_migration
@content_migration = @context.content_migrations.find(params[:id])
return authorized_action(@context, @current_user, :manage_content)
def require_auth
authorized_action(@context, @current_user, :manage_content)
end
def update_migration
@content_migration.update_migration_settings(params[:settings]) if params[:settings]
@content_migration.set_date_shift_options(params[:date_shift_options])
if Canvas::Plugin.value_to_boolean(params[:selective_import])
#todo selective import options
else
@content_migration.migration_settings[:import_immediately] = true
@content_migration.copy_options = {:everything => true}
@content_migration.migration_settings[:migration_ids_to_import] = {:copy => {:everything => true}}
end
if @content_migration.save
preflight_json = nil
if params[:pre_attachment]
@content_migration.workflow_state = 'pre_processing'
preflight_json = api_attachment_preflight(@content_migration, request, :params => params[:pre_attachment], :check_quota => true, :do_submit_to_scribd => false, :return_json => true)
if preflight_json[:error]
@content_migration.workflow_state = 'pre_process_error'
end
@content_migration.save!
elsif !params.has_key?(:do_not_run) || !Canvas::Plugin.value_to_boolean(params[:do_not_run])
@content_migration.queue_migration
end
render :json => content_migration_json(@content_migration, @current_user, session, preflight_json)
else
render :json => @content_migration.errors, :status => :bad_request
end
end
end

View File

@ -564,7 +564,7 @@ class FilesController < ApplicationController
def api_create_success
@attachment = Attachment.find_by_id_and_uuid(params[:id], params[:uuid])
return render(:nothing => true, :status => :bad_request) unless @attachment.try(:file_state) == 'deleted'
duplicate_handling = check_duplicate_handling_option(request)
duplicate_handling = check_duplicate_handling_option(request.params)
return unless duplicate_handling
return unless check_quota_after_attachment(request)
if Attachment.s3_storage?
@ -576,6 +576,11 @@ class FilesController < ApplicationController
@attachment.save!
end
@attachment.handle_duplicates(duplicate_handling)
if @attachment.context.respond_to?(:file_upload_success_callback)
@attachment.context.file_upload_success_callback(@attachment)
end
json = attachment_json(@attachment,@current_user)
# render as_text for IE, otherwise it'll prompt
# to download the JSON response

View File

@ -458,33 +458,42 @@ class AssessmentQuestion < ActiveRecord::Base
logger.debug "adding #{total} assessment questions"
default_bank = migration.question_bank_id ? migration.context.assessment_question_banks.find_by_id(migration.question_bank_id) : nil
banks = {}
questions.each do |question|
question_bank = nil
question[:question_bank_name] = nil if question[:question_bank_name] == ''
question[:question_bank_name], question[:question_bank_migration_id] = bank_map[question[:migration_id]] if question[:question_bank_name].blank?
question[:question_bank_name] ||= migration.question_bank_name
question[:question_bank_name] ||= AssessmentQuestionBank.default_imported_title
if default_bank
question_bank = default_bank
else
question[:question_bank_name] ||= migration.question_bank_name
question[:question_bank_name] ||= AssessmentQuestionBank.default_imported_title
end
if question[:assessment_question_migration_id]
question_data[:qq_data][question['migration_id']] = question
next
end
hash_id = "#{question[:question_bank_id]}_#{question[:question_bank_name]}"
if !banks[hash_id]
unless bank = migration.context.assessment_question_banks.find_by_title_and_migration_id(question[:question_bank_name], question[:question_bank_id])
bank = migration.context.assessment_question_banks.new
bank.title = question[:question_bank_name]
bank.migration_id = question[:question_bank_id]
bank.save!
if !question_bank
hash_id = "#{question[:question_bank_id]}_#{question[:question_bank_name]}"
if !banks[hash_id]
unless bank = migration.context.assessment_question_banks.find_by_title_and_migration_id(question[:question_bank_name], question[:question_bank_id])
bank = migration.context.assessment_question_banks.new
bank.title = question[:question_bank_name]
bank.migration_id = question[:question_bank_id]
bank.save!
end
if bank.workflow_state == 'deleted'
bank.workflow_state = 'active'
bank.save!
end
banks[hash_id] = bank
end
if bank.workflow_state == 'deleted'
bank.workflow_state = 'active'
bank.save!
end
banks[hash_id] = bank
question_bank = banks[hash_id]
end
begin
question = AssessmentQuestion.import_from_migration(question, migration.context, banks[hash_id])
question = AssessmentQuestion.import_from_migration(question, migration.context, 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

View File

@ -668,6 +668,7 @@ class Attachment < ActiveRecord::Base
def self.get_quota(context)
quota = 0
quota_used = 0
context = context.quota_context if context.respond_to?(:quota_context) && context.quota_context
if context
Shackles.activate(:slave) do
quota = Setting.get_cached('context_default_quota', 50.megabytes.to_s).to_i

View File

@ -178,6 +178,7 @@ class ContentExport < ActiveRecord::Base
end
def fast_update_progress(val)
content_migration.update_conversion_progress(val) if content_migration
self.progress = val
ContentExport.where(:id => self).update_all(:progress=>val)
end

View File

@ -27,6 +27,7 @@ class ContentMigration < ActiveRecord::Base
belongs_to :source_course, :class_name => 'Course'
has_one :content_export
has_many :migration_issues
has_one :job_progress, :class_name => 'Progress', :as => :context
has_a_broadcast_policy
serialize :migration_settings
before_save :infer_defaults
@ -53,7 +54,7 @@ class ContentMigration < ActiveRecord::Base
'web_links' => false,
'wikis' => false
}
attr_accessible :context, :migration_settings, :user, :source_course, :copy_options
attr_accessible :context, :migration_settings, :user, :source_course, :copy_options, :migration_type
attr_accessor :outcome_to_id_map
workflow do
@ -94,6 +95,11 @@ class ContentMigration < ActiveRecord::Base
exclude_hidden ? plugins.select{|p|!p.meta[:hide_from_users]} : plugins
end
set_policy do
given { |user, session| self.context.grants_right?(user, session, :manage_files) }
can :manage_files and can :read
end
# the stream item context is decided by calling asset.context(user), i guess
# to differentiate from the normal asset.context() call that may not give us
# the context we want. in this case, they're one and the same.
@ -102,6 +108,10 @@ class ContentMigration < ActiveRecord::Base
self.original_context
end
def quota_context
self.context
end
def migration_settings
read_attribute(:migration_settings) || write_attribute(:migration_settings,{}.with_indifferent_access)
end
@ -138,6 +148,7 @@ class ContentMigration < ActiveRecord::Base
def migration_ids_to_import=(val)
migration_settings[:migration_ids_to_import] = val
set_date_shift_options val[:copy]
end
def infer_defaults
@ -174,6 +185,14 @@ class ContentMigration < ActiveRecord::Base
migration_settings[:question_bank_name]
end
def question_bank_id=(bank_id)
migration_settings[:question_bank_id] = bank_id
end
def question_bank_id
migration_settings[:question_bank_id]
end
def course_archive_download_url=(url)
migration_settings[:course_archive_download_url] = url
end
@ -182,8 +201,12 @@ class ContentMigration < ActiveRecord::Base
self.context.root_account rescue nil
end
def migration_type
read_attribute(:migration_type) || migration_settings['migration_type']
end
def plugin_type
if plugin = Canvas::Plugin.find(migration_settings['migration_type'])
if plugin = Canvas::Plugin.find(migration_type)
plugin.metadata(:select_text) || plugin.name
else
t(:unknown, 'Unknown')
@ -255,10 +278,39 @@ class ContentMigration < ActiveRecord::Base
def warnings
old_warnings_format.map(&:first)
end
# This will be called by the files api after the attachment finishes uploading
def file_upload_success_callback(att)
if att.file_state == "available"
self.attachment = att
self.workflow_state = :pre_processed
self.save
self.queue_migration
else
self.workflow_state = :pre_process_error
self.add_warning(t('bad_attachment', "The file was not successfully uploaded."))
end
end
def reset_job_progress
self.progress = 0
if self.job_progress
p = self.job_progress
else
p = Progress.new(:context => self, :tag => "content_migration")
self.job_progress = p
end
p.workflow_state = :queued
p.completion = 0
p.user = self.user
p.save!
p
end
def export_content
reset_job_progress
check_quiz_id_prepender
plugin = Canvas::Plugin.find(migration_settings['migration_type'])
plugin = Canvas::Plugin.find(migration_type)
if plugin
begin
if Canvas::Migration::Worker.const_defined?(plugin.settings['worker'])
@ -271,7 +323,7 @@ class ContentMigration < ActiveRecord::Base
end
rescue NameError
self.workflow_state = 'failed'
message = "The migration plugin #{migration_settings['migration_type']} doesn't have a worker."
message = "The migration plugin #{migration_type} doesn't have a worker."
migration_settings[:last_error] = message
ErrorReport.log_exception(:content_migration, $!)
logger.error message
@ -279,15 +331,16 @@ class ContentMigration < ActiveRecord::Base
end
else
self.workflow_state = 'failed'
message = "No migration plugin of type #{migration_settings['migration_type']} found."
message = "No migration plugin of type #{migration_type} found."
migration_settings[:last_error] = message
logger.error message
self.save
end
end
alias_method :queue_migration, :export_content
def check_quiz_id_prepender
if !migration_settings[:id_prepender] && !migration_settings[:overwrite_questions]
if !migration_settings[:id_prepender] && (!migration_settings[:overwrite_questions] || !migration_settings[:overwrite_quizzes])
# only prepend an id if the course already has some migrated questions/quizzes
if self.context.assessment_questions.where('assessment_questions.migration_id IS NOT NULL').exists? ||
(self.context.respond_to?(:quizzes) && self.context.quizzes.where('quizzes.migration_id IS NOT NULL').exists?)
@ -304,6 +357,7 @@ class ContentMigration < ActiveRecord::Base
return false unless mig_id
return true unless migration_settings[:migration_ids_to_import] && migration_settings[:migration_ids_to_import][:copy] && migration_settings[:migration_ids_to_import][:copy].length > 0
return true if is_set?(to_import(:everything))
return true if copy_options && copy_options[:everything]
return true if is_set?(to_import("all_#{asset_type}"))
@ -317,6 +371,7 @@ class ContentMigration < ActiveRecord::Base
end
def import_content
reset_job_progress unless import_immediately?
self.workflow_state = :importing
self.save
@ -366,15 +421,26 @@ class ContentMigration < ActiveRecord::Base
def copy_options=(options)
self.migration_settings[:copy_options] = options
set_date_shift_options options
end
def for_course_copy?
!!self.source_course
end
def set_date_shift_options(opts)
if opts && Canvas::Plugin.value_to_boolean(opts[:shift_dates])
self.migration_settings[:date_shift_options] = opts.slice(:shift_dates, :old_start_date, :old_end_date, :new_start_date, :new_end_date, :day_substitutions, :time_zone)
end
end
def date_shift_options
self.migration_settings[:date_shift_options]
end
def copy_course
self.workflow_state = :pre_processing
self.progress = 0
reset_job_progress
self.migration_settings[:skip_import_notification] = true
self.save
@ -395,14 +461,6 @@ class ContentMigration < ActiveRecord::Base
self.attachment = ce.attachment
migration_settings[:migration_ids_to_import] ||= {:copy=>{}}
migration_settings[:migration_ids_to_import][:copy][:everything] = true
if copy_options[:shift_dates]
migration_settings[:migration_ids_to_import][:copy][:shift_dates] = copy_options[:shift_dates]
migration_settings[:migration_ids_to_import][:copy][:old_start_date] = copy_options[:old_start_date]
migration_settings[:migration_ids_to_import][:copy][:old_end_date] = copy_options[:old_end_date]
migration_settings[:migration_ids_to_import][:copy][:new_start_date] = copy_options[:new_start_date]
migration_settings[:migration_ids_to_import][:copy][:new_end_date] = copy_options[:new_end_date]
migration_settings[:migration_ids_to_import][:copy][:day_substitutions] = copy_options[:day_substitutions]
end
# set any attachments referenced in html to be copied
ce.selected_content['attachments'] ||= {}
ce.referenced_files.values.each do |att_mig_id|
@ -417,10 +475,10 @@ class ContentMigration < ActiveRecord::Base
self.reload
if self.workflow_state == 'exported'
self.workflow_state = :pre_processed
self.progress = 10
self.update_import_progress(10)
self.context.copy_attachments_from_course(self.source_course, :content_export => ce, :content_migration => self)
self.progress = 20
self.update_import_progress(20)
self.import_content_without_send_later
end
@ -469,6 +527,28 @@ class ContentMigration < ActiveRecord::Base
@zip_file.close if @zip_file
@zip_file = nil
end
def finished_converting
#todo finish progress if selective
end
# expects values between 0 and 100 for the conversion process
def update_conversion_progress(prog)
if import_immediately?
fast_update_progress(prog * 0.5)
else
fast_update_progress(prog)
end
end
# expects values between 0 and 100 for the import process
def update_import_progress(prog)
if import_immediately?
fast_update_progress(50 + (prog * 0.5))
else
fast_update_progress(prog)
end
end
def progress
return nil if self.workflow_state == 'created'
@ -489,6 +569,9 @@ class ContentMigration < ActiveRecord::Base
end
def fast_update_progress(val)
reset_job_progress unless job_progress
job_progress.update_completion!(val)
# Until this progress is phased out
self.progress = val
ContentMigration.where(:id => self).update_all(:progress=>val)
end

View File

@ -1849,7 +1849,7 @@ class Course < ActiveRecord::Base
current += 1
if (current - last) > 10
last = current
migration.fast_update_progress((current.to_f/total) * 18.0)
migration.update_import_progress((current.to_f/total) * 18.0)
end
end
unzip_opts = {
@ -1866,7 +1866,7 @@ class Course < ActiveRecord::Base
root_path, migration.context)
end
unzipper = UnzipAttachment.new(unzip_opts)
migration.fast_update_progress(1.0)
migration.update_import_progress(1.0)
unzipper.process
end
end
@ -1895,42 +1895,42 @@ class Course < ActiveRecord::Base
if !migration.for_course_copy?
# These only need to be processed once
Attachment.skip_media_object_creation do
process_migration_files(data, migration); migration.fast_update_progress(18)
Attachment.process_migration(data, migration); migration.fast_update_progress(20)
process_migration_files(data, migration); migration.update_import_progress(18)
Attachment.process_migration(data, migration); migration.update_import_progress(20)
mo_attachments = self.imported_migration_items.find_all { |i| i.is_a?(Attachment) && i.media_entry_id.present? }
import_media_objects(mo_attachments, migration)
end
end
migration.fast_update_progress(31)
question_data = AssessmentQuestion.process_migration(data, migration); migration.fast_update_progress(35)
Group.process_migration(data, migration); migration.fast_update_progress(36)
LearningOutcome.process_migration(data, migration); migration.fast_update_progress(37)
Rubric.process_migration(data, migration); migration.fast_update_progress(38)
migration.update_import_progress(31)
question_data = AssessmentQuestion.process_migration(data, migration); migration.update_import_progress(35)
Group.process_migration(data, migration); migration.update_import_progress(36)
LearningOutcome.process_migration(data, migration); migration.update_import_progress(37)
Rubric.process_migration(data, migration); migration.update_import_progress(38)
@assignment_group_no_drop_assignments = {}
AssignmentGroup.process_migration(data, migration); migration.fast_update_progress(39)
ExternalFeed.process_migration(data, migration); migration.fast_update_progress(39.5)
GradingStandard.process_migration(data, migration); migration.fast_update_progress(40)
Quiz.process_migration(data, migration, question_data); migration.fast_update_progress(50)
ContextExternalTool.process_migration(data, migration); migration.fast_update_progress(54)
AssignmentGroup.process_migration(data, migration); migration.update_import_progress(39)
ExternalFeed.process_migration(data, migration); migration.update_import_progress(39.5)
GradingStandard.process_migration(data, migration); migration.update_import_progress(40)
Quiz.process_migration(data, migration, question_data); migration.update_import_progress(50)
ContextExternalTool.process_migration(data, migration); migration.update_import_progress(54)
#These need to be ran twice because they can reference each other
DiscussionTopic.process_migration(data, migration);migration.fast_update_progress(55)
WikiPage.process_migration(data, migration);migration.fast_update_progress(60)
Assignment.process_migration(data, migration);migration.fast_update_progress(65)
ContextModule.process_migration(data, migration);migration.fast_update_progress(70)
DiscussionTopic.process_migration(data, migration);migration.update_import_progress(55)
WikiPage.process_migration(data, migration);migration.update_import_progress(60)
Assignment.process_migration(data, migration);migration.update_import_progress(65)
ContextModule.process_migration(data, migration);migration.update_import_progress(70)
# and second time...
DiscussionTopic.process_migration(data, migration);migration.fast_update_progress(75)
WikiPage.process_migration(data, migration);migration.fast_update_progress(80)
Assignment.process_migration(data, migration);migration.fast_update_progress(85)
DiscussionTopic.process_migration(data, migration);migration.update_import_progress(75)
WikiPage.process_migration(data, migration);migration.update_import_progress(80)
Assignment.process_migration(data, migration);migration.update_import_progress(85)
#These aren't referenced by anything, but reference other things
CalendarEvent.process_migration(data, migration);migration.fast_update_progress(90)
WikiPage.process_migration_course_outline(data, migration);migration.fast_update_progress(95)
CalendarEvent.process_migration(data, migration);migration.update_import_progress(90)
WikiPage.process_migration_course_outline(data, migration);migration.update_import_progress(95)
everything_selected = !migration.copy_options || migration.is_set?(migration.copy_options[:everything])
if everything_selected || migration.is_set?(migration.copy_options[:all_course_settings])
import_settings_from_migration(data, migration); migration.fast_update_progress(96)
import_settings_from_migration(data, migration); migration.update_import_progress(96)
end
syllabus_should_be_added = everything_selected || migration.copy_options[:syllabus_body]
@ -1943,8 +1943,7 @@ class Course < ActiveRecord::Base
begin
#Adjust dates
if bool_res(params[:copy][:shift_dates])
shift_options = (bool_res(params[:copy][:shift_dates]) rescue false) ? params[:copy] : {}
if shift_options = migration.date_shift_options
shift_options = shift_date_options(self, shift_options)
@imported_migration_items.each do |event|
if event.is_a?(Assignment)
@ -2089,7 +2088,7 @@ class Course < ActiveRecord::Base
total = attachments.count + 1
attachments.each_with_index do |file, i|
cm.fast_update_progress((i.to_f/total) * 18.0) if cm && (i % 10 == 0)
cm.update_import_progress((i.to_f/total) * 18.0) if cm && (i % 10 == 0)
if !ce || ce.export_object?(file)
begin

View File

@ -18,6 +18,7 @@
class Progress < ActiveRecord::Base
belongs_to :context, :polymorphic => true
belongs_to :user
attr_accessible :context, :tag, :completion, :message
validates_presence_of :context_id

View File

@ -762,7 +762,8 @@ ActionController::Routing::Routes.draw do |map|
api.with_options(:controller => :content_migrations) do |cm|
cm.get 'courses/:course_id/content_migrations/:id', :action => :show, :path_name => 'course_content_migration'
cm.get 'courses/:course_id/content_migrations', :action => :index, :path_name => 'course_content_migration_list'
cm.get 'courses/:course_id/content_migrations/:id/download_archive', :action => 'download_archive', :conditions => {:method => :get}, :path_name => 'course_content_migration_download'
cm.post 'courses/:course_id/content_migrations', :action => :create, :path_name => 'course_content_migration_create'
cm.put 'courses/:course_id/content_migrations/:id', :action => :update, :path_name => 'course_content_migration_update'
end
api.with_options(:controller => :migration_issues) do |mi|

View File

@ -0,0 +1,11 @@
class AddTypeToContentMigration < ActiveRecord::Migration
tag :predeploy
def self.up
add_column :content_migrations, :migration_type, :string
end
def self.down
remove_column :content_migrations, :migration_type
end
end

View File

@ -57,49 +57,62 @@ module Api::V1::Attachment
# render the attachment ajax_upload_params on success, or a relevant
# error on failure.
def api_attachment_preflight(context, request, opts = {})
params = opts[:params] || request.params
@attachment = Attachment.new
@attachment.context = context
@attachment.filename = request.params[:name]
@attachment.filename = params[:name]
atts = process_attachment_params(params)
atts.delete(:display_name)
@attachment.attributes = atts
@attachment.submission_attachment = true if opts[:submission_attachment]
@attachment.file_state = 'deleted'
@attachment.workflow_state = 'unattached'
@attachment.content_type = request.params[:content_type].presence || Attachment.mimetype(@attachment.filename)
@attachment.content_type = params[:content_type].presence || Attachment.mimetype(@attachment.filename)
# Handle deprecated folder path
request.params[:parent_folder_path] ||= request.params[:folder]
params[:parent_folder_path] ||= params[:folder]
if opts.key?(:folder)
@attachment.folder = folder
elsif request.params[:parent_folder_path] && request.params[:parent_folder_id]
elsif params[:parent_folder_path] && params[:parent_folder_id]
render :json => {:message => I18n.t('lib.api.attachments.only_one_folder', "Can't set folder path and folder id")}, :status => 400
return
elsif request.params[:parent_folder_id]
@attachment.folder = context.folders.find(request.params.delete(:parent_folder_id))
elsif context.respond_to?(:folders) && request.params[:parent_folder_path].is_a?(String)
@attachment.folder = Folder.assert_path(request.params[:parent_folder_path], context)
elsif params[:parent_folder_id]
@attachment.folder = context.folders.find(params.delete(:parent_folder_id))
elsif context.respond_to?(:folders) && params[:parent_folder_path].is_a?(String)
@attachment.folder = Folder.assert_path(params[:parent_folder_path], context)
end
duplicate_handling = check_duplicate_handling_option(request)
duplicate_handling = check_duplicate_handling_option(params)
if opts[:check_quota]
get_quota
if request.params[:size] && @quota < @quota_used + request.params[:size].to_i
render(:json => { :message => 'file size exceeds quota' }, :status => :bad_request)
return
if params[:size] && @quota < @quota_used + params[:size].to_i
message = { :message => I18n.t('lib.api.over_quota', 'file size exceeds quota') }
if opts[:return_json]
message[:error] = true
return message
else
render(:json => message, :status => :bad_request)
return
end
end
end
@attachment.save!
if request.params[:url]
@attachment.send_later_enqueue_args(:clone_url, { :priority => Delayed::LOW_PRIORITY, :max_attempts => 1, :n_strand => 'file_download' }, request.params[:url], duplicate_handling, opts[:check_quota])
render :json => { :id => @attachment.id, :upload_status => 'pending', :status_url => api_v1_file_status_url(@attachment, @attachment.uuid) }
if params[:url]
@attachment.send_later_enqueue_args(:clone_url, { :priority => Delayed::LOW_PRIORITY, :max_attempts => 1, :n_strand => 'file_download' }, params[:url], duplicate_handling, opts[:check_quota])
json = { :id => @attachment.id, :upload_status => 'pending', :status_url => api_v1_file_status_url(@attachment, @attachment.uuid) }
else
duplicate_handling = nil if duplicate_handling == 'overwrite'
quota_exemption = opts[:check_quota] ? nil : @attachment.quota_exemption_key
render :json => @attachment.ajax_upload_params(@current_pseudonym,
json = @attachment.ajax_upload_params(@current_pseudonym,
api_v1_files_create_url(:on_duplicate => duplicate_handling, :quota_exemption => quota_exemption),
api_v1_files_create_success_url(@attachment, :uuid => @attachment.uuid, :on_duplicate => duplicate_handling, :quota_exemption => quota_exemption),
:ssl => request.ssl?, :file_param => opts[:file_param]).
slice(:upload_url,:upload_params,:file_param,:remote_url)
end
if opts[:return_json]
json
else
render :json => json
end
end
def check_quota_after_attachment(request)
@ -111,8 +124,8 @@ module Api::V1::Attachment
return true
end
def check_duplicate_handling_option(request)
duplicate_handling = request.params[:on_duplicate].presence || 'overwrite'
def check_duplicate_handling_option(params)
duplicate_handling = params[:on_duplicate].presence || 'overwrite'
unless %w(rename overwrite).include?(duplicate_handling)
render(:json => { :message => 'invalid on_duplicate option' }, :status => :bad_request)
return nil

View File

@ -18,6 +18,7 @@
module Api::V1::ContentMigration
include Api::V1::Json
include Api::V1::Attachment
def content_migrations_json(migrations, current_user, session)
migrations.map do |migration|
@ -25,14 +26,33 @@ module Api::V1::ContentMigration
end
end
def content_migration_json(migration, current_user, session)
json = api_json(migration, current_user, session, :only => %w(id user_id workflow_state started_at finished_at))
json[:workflow_state] = 'converting' if json[:workflow_state] == 'exporting'
json[:workflow_state] = 'converted' if json[:workflow_state] == 'exported'
def content_migration_json(migration, current_user, session, attachment_preflight=nil)
json = api_json(migration, current_user, session, :only => %w(id user_id workflow_state started_at finished_at migration_type))
if json[:workflow_state] == 'created'
json[:workflow_state] = 'pre_processing'
elsif json[:workflow_state] == 'pre_process_error'
json[:workflow_state] = 'failed'
elsif json[:workflow_state] == 'exported' && !migration.import_immediately?
json[:workflow_state] = 'waiting_for_select'
elsif ['exporting', 'importing', 'exported'].member?(json[:workflow_state])
json[:workflow_state] = 'running'
elsif json[:workflow_state] == 'imported'
json[:workflow_state] = 'completed'
end
json[:migration_issues_url] = api_v1_course_content_migration_migration_issue_list_url(migration.context_id, migration.id)
json[:migration_issues_count] = migration.migration_issues.count
if migration.attachment
json[:content_archive_download_url] = api_v1_course_content_migration_download_url(migration.context_id, migration.id)
if attachment_preflight
json[:pre_attachment] = attachment_preflight
elsif migration.attachment
json[:attachment] = attachment_json(migration.attachment, current_user, {}, {:can_manage_files => true})
end
if migration.job_progress
json['progress_url'] = polymorphic_url([:api_v1, migration.job_progress])
end
if plugin = Canvas::Plugin.find(migration.migration_type)
if plugin.meta[:name] && plugin.meta[:name].respond_to?(:call)
json['migration_type_title'] = plugin.meta[:name].call
end
end
json
end

View File

@ -93,8 +93,8 @@ module MigratorHelper
end
def set_progress(progress)
if content_migration && content_migration.respond_to?(:fast_update_progress)
content_migration.fast_update_progress(progress)
if content_migration && content_migration.respond_to?(:update_conversion_progress)
content_migration.update_conversion_progress(progress)
end
end

View File

@ -125,28 +125,30 @@ Canvas::Plugin.register('tinychat', nil, {
})
require_dependency 'cc/importer/cc_worker'
Canvas::Plugin.register 'canvas_cartridge_importer', :export_system, {
:name => lambda{ t :name, 'Canvas Cartridge Importer' },
:name => lambda{ I18n.t 'canvas_cartridge_name', 'Canvas Cartridge Importer' },
:author => 'Instructure',
:author_website => 'http://www.instructure.com',
:description => lambda{ t :description, 'This enables converting a canvas export to the intermediary json format to be imported' },
:description => lambda{ I18n.t :canvas_cartridge_description, 'This enables converting a canvas export to the intermediary json format to be imported' },
:version => '1.0.0',
:select_text => lambda{ t :file_description, "Canvas Course Export Package" },
:select_text => lambda{ I18n.t :canvas_cartridge_file_description, "Canvas Course Export Package" },
:settings => {
:worker => 'CCWorker',
:migration_partial => 'canvas_config',
:requires_file_upload => true,
:provides =>{:canvas_cartridge => CC::Importer::Canvas::Converter}
},
}
Canvas::Plugin.register 'common_cartridge_importer', :export_system, {
:name => lambda{ t :name, 'Common Cartridge Importer' },
:name => lambda{ I18n.t :common_cartridge_name, 'Common Cartridge Importer' },
:author => 'Instructure',
:author_website => 'http://www.instructure.com',
:description => lambda{ t :description, 'This enables converting a Common Cartridge packages in the intermediary json format to be imported' },
:description => lambda{ I18n.t :common_cartridge_description, 'This enables converting a Common Cartridge packages in the intermediary json format to be imported' },
:version => '1.0.0',
:select_text => lambda{ t :file_description, "Common Cartridge 1.0/1.1/1.2 Package" },
:select_text => lambda{ I18n.t :common_cartridge_file_description, "Common Cartridge 1.0/1.1/1.2 Package" },
:settings => {
:worker => 'CCWorker',
:migration_partial => 'cc_config',
:requires_file_upload => true,
:provides =>{:common_cartridge=>CC::Importer::Standard::Converter,
:common_cartridge_1_0=>CC::Importer::Standard::Converter,
:common_cartridge_1_1=>CC::Importer::Standard::Converter,

View File

@ -19,7 +19,7 @@ class Canvas::Migration::Worker::CCWorker < Struct.new(:migration_id)
def perform
cm = ContentMigration.find_by_id migration_id
begin
cm.fast_update_progress(1)
cm.update_conversion_progress(1)
settings = cm.migration_settings.clone
settings[:content_migration_id] = migration_id
settings[:user_id] = cm.user_id
@ -36,12 +36,12 @@ class Canvas::Migration::Worker::CCWorker < Struct.new(:migration_id)
if overview_file_path
file = File.new(overview_file_path)
Canvas::Migration::Worker::upload_overview_file(file, cm)
cm.fast_update_progress(95)
cm.update_conversion_progress(95)
end
if export_folder_path
Canvas::Migration::Worker::upload_exported_data(export_folder_path, cm)
Canvas::Migration::Worker::clear_exported_data(export_folder_path)
cm.fast_update_progress(100)
cm.update_conversion_progress(100)
end
cm.migration_settings[:worker_class] = converter_class.name
@ -49,12 +49,11 @@ class Canvas::Migration::Worker::CCWorker < Struct.new(:migration_id)
cm.migration_settings[:migration_ids_to_import] = {:copy=>{:everything => true}}
end
cm.workflow_state = :exported
cm.progress = 0
saved = cm.save
if cm.import_immediately?
cm.import_content_without_send_later
cm.progress = 100
cm.update_import_progress(100)
saved = cm.save
if converter.respond_to?(:post_process)
converter.post_process

View File

@ -285,9 +285,12 @@ define([
var progress = 0;
if (course_import) {
progress = Math.max($(".copy_progress").progressbar('option', 'value') || 0, course_import.progress);
if( course_import.workflow_state == "exported") {
progress = 0;
}
$(".copy_progress").progressbar('option', 'value', progress);
}
if (course_import && course_import.progress >= 100) {
if (course_import && course_import.workflow_state == 'imported') {
$.flashMessage(I18n.t('messages.import_complete', "Import Complete! Returning to the Course Page..."));
location.href = $(".course_url").attr('href');
} else if (course_import && course_import.workflow_state == 'failed') {

View File

@ -25,6 +25,8 @@ describe ContentMigrationsController, :type => :integration do
@params = { :controller => 'content_migrations', :format => 'json', :course_id => @course.id.to_param}
@migration = @course.content_migrations.create
@migration.migration_type = 'common_cartridge_importer'
@migration.context = @course
@migration.user = @user
@migration.started_at = 1.week.ago
@migration.finished_at = 1.day.ago
@ -67,16 +69,28 @@ describe ContentMigrationsController, :type => :integration do
it "should return migration" do
@migration.attachment = Attachment.create!(:context => @migration, :filename => "test.txt", :uploaded_data => StringIO.new("test file"))
@migration.save!
progress = Progress.create!(:tag => "content_migration", :context => @migration)
json = api_call(:get, @migration_url, @params)
json['id'].should == @migration.id
json['migration_type'].should == @migration.migration_type
json['finished_at'].should_not be_nil
json['started_at'].should_not be_nil
json['user_id'].should == @user.id
json["workflow_state"].should == "created"
json["workflow_state"].should == "pre_processing"
json["migration_issues_url"].should == "http://www.example.com/api/v1/courses/#{@course.id}/content_migrations/#{@migration.id}/migration_issues"
json["migration_issues_count"].should == 0
json["content_archive_download_url"].should == "http://www.example.com/api/v1/courses/#{@course.id}/content_migrations/#{@migration.id}/download_archive"
json["attachment"]["url"].should =~ %r{/files/#{@migration.attachment.id}/download}
json['progress_url'].should == "http://www.example.com/api/v1/progress/#{progress.id}"
json['migration_type_title'].should == 'Common Cartridge Importer'
end
it "should return waiting_for_select when it's supposed to" do
@migration.workflow_state = 'exported'
@migration.migration_settings[:import_immediately] = false
@migration.save!
json = api_call(:get, @migration_url, @params)
json['workflow_state'].should == 'waiting_for_select'
end
it "should 404" do
@ -89,22 +103,138 @@ describe ContentMigrationsController, :type => :integration do
end
end
describe 'download_archive' do
describe 'create' do
before do
@migration_url = @migration_url + "/#{@migration.id}/download_archive"
@params = @params.merge({:action => 'download_archive', :id => @migration.id.to_param})
@params = {:controller => 'content_migrations', :format => 'json', :course_id => @course.id.to_param, :action => 'create'}
@post_params = {:migration_type => 'common_cartridge_importer', :pre_attachment => {:name => "test.zip"}}
end
it "should send file" do
@migration.attachment = Attachment.create!(:context => @migration, :filename => "test.txt", :uploaded_data => StringIO.new("test file"))
@migration.save!
raw_api_call :get, @migration_url, @params
response.header['Content-Disposition'].should == 'attachment; filename="test.txt"'
it "should error for unknown type" do
json = api_call(:post, @migration_url, @params, {:migration_type => 'jerk'}, {}, :expected_status => 400)
json.should == {"message"=>"Invalid migration_type"}
end
it "should 404" do
api_call(:get, @migration_url, @params, {}, {}, :expected_status => 404)
it "should queue a migration" do
@post_params.delete :pre_attachment
p = Canvas::Plugin.new("hi")
p.stubs(:settings).returns('worker' => 'CCWorker')
Canvas::Plugin.stubs(:find).returns(p)
json = api_call(:post, @migration_url, @params, @post_params)
json["workflow_state"].should == 'running'
migration = ContentMigration.find json['id']
migration.workflow_state.should == "exporting"
migration.job_progress.workflow_state.should == 'queued'
end
it "should not queue a migration if do_not_run flag is set" do
@post_params.delete :pre_attachment
Canvas::Plugin.stubs(:find).returns(Canvas::Plugin.new("oi"))
json = api_call(:post, @migration_url, @params, @post_params.merge(:do_not_run => true))
json["workflow_state"].should == 'pre_processing'
migration = ContentMigration.find json['id']
migration.workflow_state.should == "created"
migration.job_progress.should be_nil
end
context "migration file upload" do
it "should set attachment pre-flight data" do
json = api_call(:post, @migration_url, @params, @post_params)
json['pre_attachment'].should_not be_nil
json['pre_attachment']["upload_params"]["key"].end_with?("test.zip").should == true
end
it "should not queue migration with pre_attachent on create" do
json = api_call(:post, @migration_url, @params, @post_params)
json["workflow_state"].should == 'pre_processing'
migration = ContentMigration.find json['id']
migration.workflow_state.should == "pre_processing"
end
it "should error if upload file required but not provided" do
@post_params.delete :pre_attachment
json = api_call(:post, @migration_url, @params, @post_params, {}, :expected_status => 400)
json.should == {"message"=>"File upload is required"}
end
it "should queue the migration when file finishes uploading" do
local_storage!
@attachment = Attachment.create!(:context => @migration, :filename => "test.zip", :uploaded_data => StringIO.new("test file"))
@attachment.file_state = "deleted"
@attachment.workflow_state = "unattached"
@attachment.save
@migration.attachment = @attachment
@migration.save!
@attachment.workflow_state = nil
@content = Tempfile.new(["test", ".zip"])
def @content.content_type
"application/zip"
end
@content.write("test file")
@content.rewind
@attachment.uploaded_data = @content
@attachment.save!
api_call(:post, "/api/v1/files/#{@attachment.id}/create_success?uuid=#{@attachment.uuid}",
{:controller => "files", :action => "api_create_success", :format => "json", :id => @attachment.to_param, :uuid => @attachment.uuid})
@migration.reload
@migration.attachment.should_not be_nil
@migration.workflow_state.should == "exporting"
@migration.job_progress.workflow_state.should == 'queued'
end
it "should error if course quota exceeded" do
@post_params.merge!(:pre_attachment => {:name => "test.zip", :size => 1.gigabyte})
json = api_call(:post, @migration_url, @params, @post_params)
json['pre_attachment'].should == {"message"=>"file size exceeds quota", "error" => true}
json["workflow_state"].should == 'failed'
migration = ContentMigration.find json['id']
migration.workflow_state = 'pre_process_error'
end
end
end
describe 'update' do
before do
@migration_url = "/api/v1/courses/#{@course.id}/content_migrations/#{@migration.id}"
@params = {:controller => 'content_migrations', :format => 'json', :course_id => @course.id.to_param, :action => 'update', :id => @migration.id.to_param}
@post_params = {}
end
it "should queue a migration" do
json = api_call(:put, @migration_url, @params, @post_params)
json["workflow_state"].should == 'running'
@migration.reload
@migration.workflow_state.should == "exporting"
@migration.job_progress.workflow_state.should == 'queued'
end
it "should not queue a migration if do_not_run flag is set" do
json = api_call(:put, @migration_url, @params, @post_params.merge(:do_not_run => true))
json["workflow_state"].should == 'pre_processing'
migration = ContentMigration.find json['id']
migration.workflow_state.should == "created"
migration.job_progress.should be_nil
end
it "should not change migration_type" do
json = api_call(:put, @migration_url, @params, @post_params.merge(:migration_type => "oioioi"))
json['migration_type'].should == 'common_cartridge_importer'
end
it "should reset progress after queue" do
p = @migration.reset_job_progress
p.completion = 100
p.workflow_state = 'completed'
p.save!
api_call(:put, @migration_url, @params, @post_params)
p.reload
p.completion.should == 0
p.workflow_state.should == 'queued'
end
end
end

View File

@ -46,11 +46,15 @@ describe "Files API", :type => :integration do
@attachment.save!
end
def call_create_success
api_call(:post, "/api/v1/files/#{@attachment.id}/create_success?uuid=#{@attachment.uuid}",
{:controller => "files", :action => "api_create_success", :format => "json", :id => @attachment.to_param, :uuid => @attachment.uuid})
end
it "should set the attachment to available (local storage)" do
local_storage!
upload_data
json = api_call(:post, "/api/v1/files/#{@attachment.id}/create_success?uuid=#{@attachment.uuid}",
{ :controller => "files", :action => "api_create_success", :format => "json", :id => @attachment.to_param, :uuid => @attachment.uuid })
json = call_create_success
@attachment.reload
json.should == {
'id' => @attachment.id,
@ -80,8 +84,7 @@ describe "Files API", :type => :integration do
:content_length => 1234,
})
json = api_call(:post, "/api/v1/files/#{@attachment.id}/create_success?uuid=#{@attachment.uuid}",
{ :controller => "files", :action => "api_create_success", :format => "json", :id => @attachment.to_param, :uuid => @attachment.uuid })
json = call_create_success
@attachment.reload
json.should == {
'id' => @attachment.id,
@ -117,6 +120,29 @@ describe "Files API", :type => :integration do
{ :controller => "files", :action => "api_create_success", :format => "json", :id => @attachment.to_param, :uuid => @attachment.uuid })
response.status.to_i.should == 400
end
context "upload success context callback" do
before do
Course.any_instance.stubs(:file_upload_success_callback)
Course.any_instance.expects(:file_upload_success_callback).with(@attachment)
end
it "should call back for s3" do
s3_storage!
AWS::S3::S3Object.any_instance.expects(:head).returns({
:content_type => 'text/plain',
:content_length => 1234,
})
json = call_create_success
end
it "should call back for local storage" do
local_storage!
upload_data
json = call_create_success
end
end
end
describe "#index" do

View File

@ -1076,12 +1076,10 @@ describe ContentMigration do
asmnt.save!
@copy_from.reload
@cm.migration_settings[:migration_ids_to_import] = {
:copy => {
:shift_dates => true,
:day_substitutions => {today.wday.to_s => (today.wday + 1).to_s}
}
}
@cm.copy_options = @cm.copy_options.merge(
:shift_dates => true,
:day_substitutions => {today.wday.to_s => (today.wday + 1).to_s}
)
@cm.save!
run_course_copy
@ -1127,9 +1125,7 @@ describe ContentMigration do
cm.end_at = old_start + 3.days
cm.save!
@cm.migration_settings[:migration_ids_to_import] = {
:copy => options
}
@cm.copy_options = options
@cm.save!
run_course_copy
@ -1208,18 +1204,16 @@ describe ContentMigration do
assignment = @copy_from.assignments.create! :title => 'Assignment', :due_at => old_date
assignment.save!
migration_settings = {
:copy => {
:everything => true,
:shift_dates => true,
:old_start_date => old_start_date,
:old_end_date => old_end_date,
:new_start_date => new_start_date,
:new_end_date => new_end_date
}
opts = {
:everything => true,
:shift_dates => true,
:old_start_date => old_start_date,
:old_end_date => old_end_date,
:new_start_date => new_start_date,
:new_end_date => new_end_date
}
migration_settings[:copy][:time_zone] = options[:time_zone].name if options.include?(:time_zone)
@cm.migration_settings[:migration_ids_to_import] = migration_settings
opts[:time_zone] = options[:time_zone].name if options.include?(:time_zone)
@cm.copy_options = @cm.copy_options.merge(opts)
@cm.save!
run_course_copy

View File

@ -1,11 +1,11 @@
Rails.configuration.to_prepare do
Canvas::Plugin.register :moodle_converter, :export_system, {
:name => proc { t(:name, 'Moodle Importer') },
:name => proc { I18n.t(:m2c_name, 'Moodle Importer') },
:author => 'Divergent Logic',
:description => 'This enables importing Moodle 1.9 .zip files to Canvas.',
:version => '1.0.0',
:select_text => proc { t(:file_description, 'Moodle 1.9 .zip file') },
:select_text => proc { I18n.t(:m2c_file_description, 'Moodle 1.9 .zip file') },
:settings => {
:migration_partial => 'moodle_config',
:worker=> 'CCWorker',

View File

@ -2,12 +2,12 @@ Rails.configuration.to_prepare do
python_converter_found = Qti.migration_executable ? true : false
Canvas::Plugin.register :qti_converter, :export_system, {
:name => proc { t(:name, 'QTI Converter') },
:name => proc { I18n.t(:qti_name, 'QTI Converter') },
:author => 'Instructure',
:description => 'This enables converting QTI .zip files to Canvas quiz json.',
:version => '1.0.0',
:settings_partial => 'plugins/qti_converter_settings',
:select_text => proc { t(:file_description, 'QTI .zip file') },
:select_text => proc { I18n.t(:qti_file_description, 'QTI .zip file') },
:settings => {
:enabled => python_converter_found,
:migration_partial => 'qti_config',

View File

@ -5,6 +5,9 @@ module Canvas::Migration
def perform
cm = ContentMigration.find_by_id migration_id
begin
cm.reset_job_progress
cm.job_progress.start
cm.update_conversion_progress(1)
plugin = Canvas::Plugin.find(:qti_converter)
unless plugin && plugin.settings[:enabled]
raise "Can't export QTI without the python converter tool installed."
@ -19,6 +22,7 @@ module Canvas::Migration
assessments = converter.export
export_folder_path = assessments[:export_folder_path]
overview_file_path = assessments[:overview_file_path]
cm.update_conversion_progress(50)
if overview_file_path
file = File.new(overview_file_path)
@ -28,13 +32,17 @@ module Canvas::Migration
Canvas::Migration::Worker::upload_exported_data(export_folder_path, cm)
Canvas::Migration::Worker::clear_exported_data(export_folder_path)
end
cm.update_conversion_progress(100)
cm.migration_settings[:migration_ids_to_import] = {:copy=>{:everything=>true}}.merge(cm.migration_settings[:migration_ids_to_import] || {})
if path = converter.course[:files_import_root_path]
cm.migration_settings[:files_import_root_path] = path
end
cm.save
cm.import_content
cm.import_content_without_send_later
cm.workflow_state = :imported
cm.save
cm.update_import_progress(100)
rescue => e
report = ErrorReport.log_exception(:content_migration, e)
if cm

View File

@ -168,7 +168,6 @@ describe Qti::Converter do
def do_migration
Canvas::Migration::Worker::QtiWorker.new(@migration.id).perform
@migration.reload
@migration.import_content_without_send_later
@migration.should be_imported
end