2011-02-01 09:57:29 +08:00
#
# 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/>.
#
class SisBatch < ActiveRecord :: Base
include Workflow
belongs_to :account
has_many :sis_batch_log_entries , :order = > :created_at
serialize :data
2011-10-13 07:28:30 +08:00
serialize :options
2011-02-01 09:57:29 +08:00
serialize :processing_errors , Array
serialize :processing_warnings , Array
belongs_to :attachment
2011-05-21 06:29:34 +08:00
belongs_to :batch_mode_term , :class_name = > 'EnrollmentTerm'
2011-02-01 09:57:29 +08:00
attr_accessor :zip_path
2011-05-26 21:48:35 +08:00
attr_accessible :batch_mode , :batch_mode_term
2011-02-01 09:57:29 +08:00
def self . max_attempts
5
end
2011-02-25 02:43:56 +08:00
def self . valid_import_types
@valid_import_types || = {
2011-04-06 05:56:50 +08:00
" instructure_csv " = > {
2011-06-22 01:36:57 +08:00
:name = > lambda { t ( :instructure_csv , " Instructure formatted CSV or zipfile of CSVs " ) } ,
2011-02-25 02:43:56 +08:00
:callback = > lambda { | batch | batch . process_instructure_csv_zip } ,
:default = > true
}
}
end
2011-02-01 09:57:29 +08:00
2011-04-06 05:56:50 +08:00
def self . create_with_attachment ( account , import_type , attachment )
batch = SisBatch . new
batch . account = account
batch . progress = 0
batch . workflow_state = :created
batch . data = { :import_type = > import_type }
batch . save
2011-05-11 06:28:38 +08:00
Attachment . skip_scribd_submits ( true )
2011-04-06 05:56:50 +08:00
att = Attachment . new
att . context = batch
att . uploaded_data = attachment
2011-06-22 01:36:57 +08:00
att . display_name = t :upload_filename , " sis_upload_%{id}.zip " , :id = > batch . id
2011-09-01 00:23:51 +08:00
att . position = 0
2011-04-06 05:56:50 +08:00
att . save
2011-05-11 06:28:38 +08:00
Attachment . skip_scribd_submits ( false )
2011-04-06 05:56:50 +08:00
batch . attachment = att
batch . save
batch
end
2011-02-01 09:57:29 +08:00
workflow do
state :created
state :importing
state :imported
state :imported_with_messages
state :failed
state :failed_with_messages
end
def process
2011-10-13 07:28:30 +08:00
self . options || = { }
2011-02-01 09:57:29 +08:00
if self . workflow_state == 'created'
self . workflow_state = :importing
self . progress = 0
self . save
2011-02-25 02:43:56 +08:00
import_scheme = SisBatch . valid_import_types [ self . data [ :import_type ] ]
if import_scheme . nil?
2011-06-22 01:36:57 +08:00
self . data [ :error_message ] = t 'errors.unrecorgnized_type' , " Unrecognized import type "
2011-02-25 02:43:56 +08:00
self . workflow_state = :failed
self . save
else
import_scheme [ :callback ] . call ( self )
2011-02-01 09:57:29 +08:00
end
end
rescue = > e
self . data [ :error_message ] = e . to_s
self . data [ :stack_trace ] = " #{ e . to_s } \n #{ e . backtrace . join ( " \n " ) } "
self . workflow_state = " failed "
self . save
end
2011-05-11 06:28:38 +08:00
handle_asynchronously :process , :strand = > proc { | sis_batch | " sis_batch:account: #{ sis_batch . account_id } " } , :priority = > Delayed :: LOW_PRIORITY
2011-02-01 09:57:29 +08:00
2011-08-31 23:37:30 +08:00
named_scope :needs_processing , :conditions = > { :workflow_state = > 'created' } , :order = > :created_at
2011-02-01 09:57:29 +08:00
def fast_update_progress ( val )
self . progress = val
SisBatch . update_all ( { :progress = > val } , " id= #{ self . id } " )
end
def importing?
self . workflow_state == 'importing' || self . workflow_state == 'created'
end
def process_instructure_csv_zip
require 'sis'
download_zip
2011-10-13 07:28:30 +08:00
importer = SIS :: CSV :: Import . process ( self . account , :files = > [ @data_file . path ] , :batch = > self , :override_sis_stickiness = > options [ :override_sis_stickiness ] , :add_sis_stickiness = > options [ :add_sis_stickiness ] , :clear_sis_stickiness = > options [ :clear_sis_stickiness ] )
2011-02-01 09:57:29 +08:00
finish importer . finished
end
def download_zip
if self . data [ :file_path ]
2011-04-29 01:16:26 +08:00
@data_file = File . open ( self . data [ :file_path ] , 'rb' )
2011-02-01 09:57:29 +08:00
else
2011-04-29 01:16:26 +08:00
@data_file = self . attachment . open ( :need_local_file = > true )
2011-02-01 09:57:29 +08:00
end
2011-04-29 01:16:26 +08:00
@data_file
2011-02-01 09:57:29 +08:00
end
def finish ( import_finished )
2011-05-11 06:28:38 +08:00
@data_file . close if @data_file
@data_file = nil
2011-02-01 09:57:29 +08:00
if import_finished
2011-05-21 06:29:34 +08:00
remove_previous_imports if self . batch_mode?
2011-02-01 09:57:29 +08:00
self . workflow_state = :imported
self . progress = 100
self . workflow_state = :imported_with_messages if messages?
else
self . workflow_state = :failed
self . workflow_state = :failed_with_messages if messages?
end
2011-04-06 05:56:50 +08:00
self . ended_at = Time . now
2011-02-01 09:57:29 +08:00
self . save
end
2011-05-06 02:25:24 +08:00
2011-05-21 06:29:34 +08:00
def remove_previous_imports
2011-06-03 02:09:02 +08:00
# delete courses that weren't in this batch, in the selected term
2011-06-07 05:28:29 +08:00
scope = Course . active . for_term ( self . batch_mode_term ) . scoped ( :conditions = > [ " courses.root_account_id = ? " , self . account . id ] )
2011-05-21 06:29:34 +08:00
scope . scoped ( :conditions = > [ " sis_batch_id is not null and sis_batch_id <> ? " , self . id . to_s ] ) . find_each do | course |
course . destroy
end
2011-06-03 02:09:02 +08:00
# delete sections who weren't in this batch, whose course was in the selected term
2011-05-21 06:29:34 +08:00
scope = CourseSection . scoped ( :conditions = > [ " course_sections.workflow_state = ? and course_sections.root_account_id = ? and course_sections.sis_batch_id is not null and course_sections.sis_batch_id <> ? " , 'active' , self . account . id , self . id . to_s ] )
2011-06-03 02:09:02 +08:00
scope = scope . scoped ( :include = > :course , :select = > " course_sections.* " , :conditions = > [ " courses.enrollment_term_id = ? " , self . batch_mode_term . id ] )
2011-05-21 06:29:34 +08:00
scope . find_each do | section |
section . destroy
end
2011-06-03 02:09:02 +08:00
# delete enrollments for courses that weren't in this batch, in the selected term
2011-06-07 05:28:29 +08:00
scope = Enrollment . active . scoped ( :include = > :course , :select = > " enrollments.* " , :conditions = > [ " courses.root_account_id = ? and enrollments.sis_batch_id is not null and enrollments.sis_batch_id <> ? " , self . account . id , self . id . to_s ] )
2011-06-03 02:09:02 +08:00
scope = scope . scoped ( :conditions = > [ " courses.enrollment_term_id = ? " , self . batch_mode_term . id ] )
2011-05-21 06:29:34 +08:00
scope . find_each do | enrollment |
enrollment . destroy
end
end
2011-05-06 02:25:24 +08:00
def api_json
data = {
" created_at " = > self . created_at ,
" ended_at " = > self . ended_at ,
" updated_at " = > self . updated_at ,
" progress " = > self . progress ,
" id " = > self . id ,
" workflow_state " = > self . workflow_state ,
" data " = > self . data
}
data [ " processing_errors " ] = self . processing_errors if self . processing_errors . present?
data [ " processing_warnings " ] = self . processing_warnings if self . processing_warnings . present?
data [ " sis_batch_log_entries " ] = self . sis_batch_log_entries if self . sis_batch_log_entries . present?
return data . to_json
end
2011-02-25 08:10:23 +08:00
private
def messages?
( self . processing_errors && self . processing_errors . length > 0 ) || ( self . processing_warnings && self . processing_warnings . length > 0 )
end
2011-05-06 02:25:24 +08:00
2011-02-01 09:57:29 +08:00
end