give content_exports polymorphic context

test plan:
* content export regressions

closes #CNVS-13468

Change-Id: I44dcf077967d4273c0ae9056074b03853d66a5dc
Reviewed-on: https://gerrit.instructure.com/35745
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: Cody Cutrer <cody@instructure.com>
Reviewed-by: Jon Willesen <jonw@instructure.com>
QA-Review: Clare Strong <clare@instructure.com>
Product-Review: James Williams  <jamesw@instructure.com>
This commit is contained in:
Jeremy Stanley 2014-06-02 09:24:19 -06:00 committed by James Williams
parent 8611d9ce09
commit 3fa2e9c3db
19 changed files with 147 additions and 41 deletions

View File

@ -127,8 +127,7 @@ class ContentExportsApiController < ApplicationController
def create
if authorized_action(@context, @current_user, :read_as_admin)
return render json: { message: 'invalid export_type' }, status: :bad_request unless %w(qti common_cartridge).include?(params[:export_type])
export = ContentExport.new
export.course = @context
export = @context.content_exports.build
export.user = @current_user
export.workflow_state = 'created'

View File

@ -44,8 +44,7 @@ class ContentExportsController < ApplicationController
return render_unauthorized_action unless @context.grants_all_rights?(@current_user, :read, :read_as_admin)
if @context.content_exports.running.count == 0
export = ContentExport.new
export.course = @context
export = @context.content_exports.build
export.user = @current_user
export.workflow_state = 'created'
if params[:export_type] == 'qti'
@ -60,7 +59,7 @@ class ContentExportsController < ApplicationController
export.export_course
render_export(export)
else
render :json => {:error_message => t('errors.couldnt_create', "Couldn't create course export.")}
render :json => {:error_message => t('errors.couldnt_create', "Couldn't create content export.")}
end
else
# an export is already running, just return it

View File

@ -1,12 +1,12 @@
<% define_content :link do %>
<%= HostUrl.protocol %>://<%= HostUrl.context_host(asset.course) %>/<%= asset.course.class.to_s.downcase.pluralize %>/<%= asset.course_id %>/content_exports
<%= HostUrl.protocol %>://<%= HostUrl.context_host(asset.context) %>/<%= asset.context.class.to_s.downcase.pluralize %>/<%= asset.context_id %>/content_exports
<% end %>
<% define_content :subject do %>
<%= t :subject, "Course Export Failed: %{course}", :course => asset.course.name %>
<%= t :subject, "Course Export Failed: %{course}", :course => asset.context.name %>
<% end %>
<%= t :body, <<-BODY, :course => asset.course.name, :id => %{"ContentExport:#{asset.id}"}
<%= t :body, <<-BODY, :course => asset.context.name, :id => %{"ContentExport:#{asset.id}"}
There was a problem exporting the course "%{course}".
Please notify your system administrator, and give them the following export identifier: %{id}.
BODY

View File

@ -1,9 +1,9 @@
<% define_content :link do %>
<%= HostUrl.protocol %>://<%= HostUrl.context_host(asset.course) %>/<%= asset.course.class.to_s.downcase.pluralize %>/<%= asset.course_id %>/content_exports
<%= HostUrl.protocol %>://<%= HostUrl.context_host(asset.context) %>/<%= asset.context.class.to_s.downcase.pluralize %>/<%= asset.context_id %>/content_exports
<% end %>
<% define_content :subject do %>
<%= t :subject, "Course Export Failed: %{course}", :course => asset.course.name %>
<%= t :subject, "Course Export Failed: %{course}", :course => asset.context.name %>
<% end %>
<% define_content :footer_link do %>
@ -13,7 +13,7 @@
<% end %>
<p>
<%= t :body2, <<-BODY, :course => asset.course.name, :wrapper => '<em>\1</em>'
<%= t :body2, <<-BODY, :course => asset.context.name, :wrapper => '<em>\1</em>'
There was a problem exporting the course *%{course}*.
Please notify your system administrator, and give them the following export identifier:
BODY

View File

@ -1,11 +1,11 @@
<% define_content :link do %>
<%= HostUrl.protocol %>://<%= HostUrl.context_host(asset.course) %>/<%= asset.course.class.to_s.downcase.pluralize %>/<%= asset.course_id %>/content_exports
<%= HostUrl.protocol %>://<%= HostUrl.context_host(asset.context) %>/<%= asset.context.class.to_s.downcase.pluralize %>/<%= asset.context_id %>/content_exports
<% end %>
<% define_content :subject do %>
<%= t :subject, "Course Export Finished: %{course}", :course => asset.course.name %>
<%= t :subject, "Course Export Finished: %{course}", :course => asset.context.name %>
<% end %>
<%= t :body, %{Your course export for "%{course}" has finished.}, :course => asset.course.name %>
<%= t :body, %{Your content export for "%{course}" has finished.}, :course => asset.context.name %>
<%= content :link %>

View File

@ -1,9 +1,9 @@
<% define_content :link do %>
<%= HostUrl.protocol %>://<%= HostUrl.context_host(asset.course) %>/<%= asset.course.class.to_s.downcase.pluralize %>/<%= asset.course_id %>/content_exports
<%= HostUrl.protocol %>://<%= HostUrl.context_host(asset.context) %>/<%= asset.context.class.to_s.downcase.pluralize %>/<%= asset.context_id %>/content_exports
<% end %>
<% define_content :subject do %>
<%= t :subject, "Course Export Finished: %{course}", :course => asset.course.name %>
<%= t :subject, "Course Export Finished: %{course}", :course => asset.context.name %>
<% end %>
<p><%= t :body2, %{Your course export for *%{course}* has finished.}, :course => asset.course.name, :wrapper => '<em>\1</em>' %></p>
<p><%= t :body2, %{Your course export for *%{course}* has finished.}, :course => asset.context.name, :wrapper => '<em>\1</em>' %></p>
<p><a href="<%= content :link %>"><%= t :link, "Click to view exports" %></a></p>

View File

@ -18,24 +18,25 @@
class ContentExport < ActiveRecord::Base
include Workflow
belongs_to :course
belongs_to :context, :polymorphic => true
belongs_to :user
belongs_to :attachment
belongs_to :content_migration
has_many :attachments, :as => :context, :dependent => :destroy
has_a_broadcast_policy
serialize :settings
attr_accessible :course
validates_presence_of :course_id, :workflow_state
has_one :job_progress, :class_name => 'Progress', :as => :context
attr_accessible :context
validates_presence_of :context_id, :workflow_state
validates_inclusion_of :context_type, :in => ['Course']
alias_method :context, :course
has_one :job_progress, :class_name => 'Progress', :as => :context
#export types
COMMON_CARTRIDGE = 'common_cartridge'
COURSE_COPY = 'course_copy'
QTI = 'qti'
workflow do
state :created
state :exporting
@ -49,21 +50,25 @@ class ContentExport < ActiveRecord::Base
p.dispatch :content_export_finished
p.to { [user] }
p.whenever {|record|
record.changed_state(:exported) && self.content_migration.blank?
record.context_type == 'Course' && record.changed_state(:exported) && self.content_migration.blank?
}
p.dispatch :content_export_failed
p.to { [user] }
p.whenever {|record|
record.changed_state(:failed) && self.content_migration.blank?
record.context_type == 'Course' && record.changed_state(:failed) && self.content_migration.blank?
}
end
set_policy do
given { |user, session| self.course.grants_right?(user, session, :manage_files) }
given { |user, session| self.context.grants_right?(user, session, :manage_files) }
can :manage_files and can :read
end
def course
raise "Use context instead"
end
def export_course(opts={})
self.workflow_state = 'exporting'
self.save
@ -185,7 +190,7 @@ class ContentExport < ActiveRecord::Base
end
def root_account
self.course.root_account
self.context.try_rescue(:root_account)
end
def running?

View File

@ -195,7 +195,7 @@ class Course < ActiveRecord::Base
has_many :asset_user_accesses, :as => :context
has_many :role_overrides, :as => :context
has_many :content_migrations, :as => :context
has_many :content_exports
has_many :content_exports, :as => :context
has_many :course_imports
has_many :alerts, :as => :context, :include => :criteria
has_many :appointment_group_contexts, :as => :context

View File

@ -26,6 +26,7 @@ class ActiveRecord::Base
'assignments' => %w(sequence_position minimum_required_blog_posts minimum_required_blog_comments reminders_created_for_due_at publishing_reminder_sent previously_published before_quiz_submission_types),
'attachments' => %w(enrollment_id cached_s3_url s3_url_cached_at scribd_account_id scribd_user),
'calendar_events' => %w(calendar_event_repeat_id for_repeat_on),
'content_exports' => %w(course_id),
'content_tags' => %w(sequence_position context_module_association_id),
'context_modules' => %w(downstream_modules),
'conversation_messages' => %w(context_message_id),

View File

@ -0,0 +1,38 @@
class AddContextToContentExports < ActiveRecord::Migration
tag :predeploy
def self.up
add_column :content_exports, :context_type, :string
add_column :content_exports, :context_id, :integer, :limit => 8
remove_foreign_key :content_exports, :courses
if connection.adapter_name == 'PostgreSQL'
create_trigger("content_export_after_insert_row_when_context_id_is_null__tr", :generated => true).
on("content_exports").
after(:insert).
where("NEW.context_id IS NULL") do
<<-SQL_ACTIONS
UPDATE content_exports
SET context_id = NEW.course_id
WHERE id = NEW.id
SQL_ACTIONS
end
execute("ALTER TABLE content_exports ALTER context_type SET DEFAULT 'Course'")
end
while ContentExport.where("context_id IS NULL AND course_id IS NOT NULL").limit(1000).
update_all("context_id = course_id, context_type = 'Course'") > 0; end
end
def self.down
while ContentExport.where("course_id IS NULL AND context_id IS NOT NULL AND context_type = ?", "Course").
limit(1000).update_all("course_id = context_id") > 0; end
add_foreign_key :content_exports, :courses
remove_column :content_exports, :context_type
remove_column :content_exports, :context_id
end
end

View File

@ -0,0 +1,16 @@
class RemoveCourseIdFromContentExports < ActiveRecord::Migration
tag :postdeploy
def self.up
if connection.adapter_name == 'PostgreSQL'
drop_trigger("content_export_after_insert_row_when_context_id_is_null__tr", "content_exports", :generated => true)
execute("ALTER TABLE content_exports ALTER context_type DROP DEFAULT")
end
remove_column :content_exports, :course_id
end
def self.down
add_column :content_exports, :course_id, :integer, :limit => 8
end
end

View File

@ -21,7 +21,8 @@ module Api::V1::ContentExport
include Api::V1::Attachment
def content_export_json(export, current_user, session)
json = api_json(export, current_user, session, :only => %w(id user_id created_at course_id workflow_state export_type))
json = api_json(export, current_user, session, :only => %w(id user_id created_at workflow_state export_type))
json['course_id'] = export.context_id if export.context_type == 'Course'
if export.attachment && !export.for_course_copy?
json[:attachment] = attachment_json(export.attachment, current_user, {}, {:can_manage_files => true})
end

View File

@ -27,11 +27,11 @@ class Canvas::Migration::Worker::CourseCopyWorker < Struct.new(:migration_id)
cm.job_progress.start
begin
source = cm.source_course || Course.find(cm.migration_settings[:source_course_id])
ce = ContentExport.new
ce.context = source
ce.content_migration = cm
ce.selected_content = cm.copy_options
source = cm.source_course || Course.find(cm.migration_settings[:source_course_id])
ce.course = source
ce.export_type = ContentExport::COURSE_COPY
ce.user = cm.user
ce.save!

View File

@ -28,7 +28,8 @@ module CC
def initialize(content_export, opts={})
@content_export = content_export
@course = opts[:course] || @content_export.course
@course = opts[:course] || @content_export.context
raise "CCExporter supports only Courses" unless @course.is_a?(Course) # a Course is a Course, of course, of course
@user = opts[:user] || @content_export.user
@export_dir = nil
@manifest = nil

View File

@ -54,7 +54,7 @@ describe ContentExportsApiController, type: :request do
def course_copy_export
export = t_course.content_exports.create
export.export_type = 'course_copy'
export.save
export.save!
export
end

View File

@ -7,7 +7,7 @@ describe "Common Cartridge exporting" do
message = "fail"
course.stubs(:wiki).raises(message)
content_export = ContentExport.new
content_export.course = course
content_export.context = course
content_export.user = user
content_export.save!
@ -25,9 +25,8 @@ describe "Common Cartridge exporting" do
before do
course_with_teacher
@ce = ContentExport.new
@ce = @course.content_exports.build
@ce.export_type = ContentExport::COURSE_COPY
@ce.course = @course
@ce.user = @user
end

View File

@ -0,0 +1,46 @@
#
# Copyright (C) 2011 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb')
require 'db/migrate/20140530195058_add_context_to_content_exports.rb'
require 'db/migrate/20140530195059_remove_course_id_from_content_exports.rb'
describe 'AddContextToContentExports' do
describe "up" do
it "should populate all content exports with course context type and context id" do
pending("PostgreSQL specific") unless ContentExport.connection.adapter_name == 'PostgreSQL'
course1 = course
course2 = course
RemoveCourseIdFromContentExports.down
AddContextToContentExports.down
ContentExport.connection.execute "INSERT INTO content_exports(course_id, workflow_state, created_at, updated_at) VALUES(#{course1.id}, '', '2014-07-07', '2014-07-07')"
AddContextToContentExports.up
ContentExport.connection.execute "INSERT INTO content_exports(course_id, workflow_state, created_at, updated_at) VALUES(#{course2.id}, '', '2014-07-07', '2014-07-07')"
RemoveCourseIdFromContentExports.up
ce1 = course1.content_exports.first
ce2 = course2.content_exports.first
ce1.context_type.should == 'Course'
ce1.context_id = course1.id
ce2.context_type.should == 'Course'
ce2.context_id.should == course2.id
end
end
end

View File

@ -22,7 +22,8 @@ describe ContentExport do
context "export_object?" do
before :once do
@ce = ContentExport.new(course: Account.default.courses.create!)
course = Account.default.courses.create!
@ce = course.content_exports.create!
end
it "should return true for everything if there are no copy options" do
@ -62,7 +63,8 @@ describe ContentExport do
context "add_item_to_export" do
before :once do
@ce = ContentExport.new(course: Account.default.courses.create!)
course = Account.default.courses.create!
@ce = course.content_exports.create!
end
it "should not add nil" do
@ -98,7 +100,7 @@ describe ContentExport do
context "notifications" do
before :once do
course_with_teacher(:active_all => true)
@ce = ContentExport.create! { |ce| ce.user = @user; ce.course = @course }
@ce = @course.content_exports.create! { |ce| ce.user = @user }
Notification.create!(:name => 'Content Export Finished', :category => 'Migration')
Notification.create!(:name => 'Content Export Failed', :category => 'Migration')

View File

@ -41,10 +41,9 @@ describe ContentMigration do
end
it "should show correct progress" do
ce = ContentExport.new
ce = @course.content_exports.build
ce.export_type = ContentExport::COMMON_CARTRIDGE
ce.content_migration = @cm
ce.course = @course
@cm.content_export = ce
ce.save!