forked from instructure/canvas-lms
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcourse_importer.rb
More file actions
240 lines (217 loc) · 10.8 KB
/
Copy pathcourse_importer.rb
File metadata and controls
240 lines (217 loc) · 10.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
#
# 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/>.
#
module SIS
class CourseImporter < BaseImporter
def process(messages)
start = Time.now
courses_to_update_sis_batch_id = []
course_ids_to_update_associations = [].to_set
importer = Work.new(@batch, @root_account, @logger, courses_to_update_sis_batch_id, course_ids_to_update_associations, messages, @batch_user)
Course.suspend_callbacks(:update_enrollments_later) do
Course.process_as_sis(@sis_options) do
Course.skip_updating_account_associations do
yield importer
end
end
end
Course.update_account_associations(course_ids_to_update_associations.to_a) unless course_ids_to_update_associations.empty?
courses_to_update_sis_batch_id.in_groups_of(1000, false) do |batch|
Course.where(:id => batch).update_all(:sis_batch_id => @batch.id)
end if @batch
@logger.debug("Courses took #{Time.now - start} seconds")
return importer.success_count
end
private
class Work
attr_accessor :success_count
def initialize(batch, root_account, logger, a1, a2, m, batch_user)
@batch = batch
@batch_user = batch_user
@root_account = root_account
@courses_to_update_sis_batch_id = a1
@course_ids_to_update_associations = a2
@messages = m
@logger = logger
@success_count = 0
end
def add_course(course_id, term_id, account_id, fallback_account_id, status, start_date, end_date, abstract_course_id, short_name, long_name, integration_id, course_format)
state_changes = []
@logger.debug("Processing Course #{[course_id, term_id, account_id, fallback_account_id, status, start_date, end_date, abstract_course_id, short_name, long_name].inspect}")
raise ImportError, "No course_id given for a course" if course_id.blank?
raise ImportError, "No short_name given for course #{course_id}" if short_name.blank? && abstract_course_id.blank?
raise ImportError, "No long_name given for course #{course_id}" if long_name.blank? && abstract_course_id.blank?
raise ImportError, "Improper status \"#{status}\" for course #{course_id}" unless status =~ /\A(active|deleted|completed)/i
raise ImportError, "Invalid course_format \"#{course_format}\" for course #{course_id}" unless course_format.blank? || course_format =~ /\A(online|on_campus|blended)/i
course = @root_account.all_courses.where(sis_source_id: course_id).take
if course.nil?
course = Course.new
state_changes << :created
else
state_changes << :updated
end
course_enrollment_term_id_stuck = course.stuck_sis_fields.include?(:enrollment_term_id)
if !course_enrollment_term_id_stuck && term_id
term = @root_account.enrollment_terms.active.where(sis_source_id: term_id).take
end
course.enrollment_term = term if term
course.root_account = @root_account
account = nil
account = @root_account.all_accounts.where(sis_source_id: account_id).take if account_id.present?
account ||= @root_account.all_accounts.where(sis_source_id: fallback_account_id).take if fallback_account_id.present?
course.account = account if account
course.account ||= @root_account
update_account_associations = course.account_id_changed? || course.root_account_id_changed?
course.integration_id = integration_id
course.sis_source_id = course_id
if !course.stuck_sis_fields.include?(:workflow_state)
if status =~ /active/i
case course.workflow_state
when 'completed'
course.workflow_state = 'available'
state_changes << :unconcluded
when 'deleted'
course.workflow_state = 'claimed'
state_changes << :restored
when 'created', nil
course.workflow_state = 'claimed'
end
elsif status =~ /deleted/i
course.workflow_state = 'deleted'
state_changes << :deleted
elsif status =~ /completed/i
course.workflow_state = 'completed'
state_changes << :concluded
end
end
course_dates_stuck = !(course.stuck_sis_fields & [:start_at, :conclude_at]).empty?
if !course_dates_stuck
course.start_at = start_date
course.conclude_at = end_date
unless course.stuck_sis_fields.include?(:restrict_enrollments_to_course_dates)
course.restrict_enrollments_to_course_dates = (start_date.present? || end_date.present?)
end
end
abstract_course = nil
if abstract_course_id.present?
abstract_course = @root_account.root_abstract_courses.where(sis_source_id: abstract_course_id).take
@messages << "unknown abstract course id #{abstract_course_id}, ignoring abstract course reference" unless abstract_course
end
if abstract_course
if term_id.blank? && course.enrollment_term_id != abstract_course.enrollment_term && !course_enrollment_term_id_stuck
course.send(:association_instance_set, :enrollment_term, nil)
course.enrollment_term_id = abstract_course.enrollment_term_id
end
if account_id.blank? && course.account_id != abstract_course.account_id
course.send(:association_instance_set, :account, nil)
course.account_id = abstract_course.account_id
end
end
course.abstract_course = abstract_course
# only update the name/short_name on new records, and ones that haven't been changed
# since the last sis import
course_course_code_stuck = course.stuck_sis_fields.include?(:course_code)
if course.course_code.blank? || !course_course_code_stuck
if short_name.present?
course.course_code = short_name
elsif abstract_course && course.course_code.blank?
course.course_code = abstract_course.short_name
end
end
course_name_stuck = course.stuck_sis_fields.include?(:name)
if course.name.blank? || !course_name_stuck
if long_name.present?
course.name = long_name
elsif abstract_course && course.name.blank?
course.name = abstract_course.name
end
end
update_enrollments = !course.new_record? && !(course.changes.keys & ['workflow_state', 'name', 'course_code']).empty?
if course_format != course.course_format
course.settings_will_change!
course.course_format = course_format
end
if course.changed?
course.templated_courses.each do |templated_course|
templated_course.root_account = @root_account
templated_course.account = course.account
templated_course.name = course.name if !templated_course.stuck_sis_fields.include?(:name) && !course_name_stuck
templated_course.course_code = course.course_code if !templated_course.stuck_sis_fields.include?(:course_code) && !course_course_code_stuck
templated_course.enrollment_term = course.enrollment_term if !templated_course.stuck_sis_fields.include?(:enrollment_term_id) && !course_enrollment_term_id_stuck
if (templated_course.stuck_sis_fields & [:start_at, :conclude_at, :restrict_enrollments_to_course_dates]).empty? && !course_dates_stuck
templated_course.start_at = course.start_at
templated_course.conclude_at = course.conclude_at
templated_course.restrict_enrollments_to_course_dates = course.restrict_enrollments_to_course_dates
end
templated_course.sis_batch_id = @batch.id if @batch
@course_ids_to_update_associations.add(templated_course.id) if templated_course.account_id_changed? || templated_course.root_account_id_changed?
if templated_course.valid?
changes = templated_course.changes
templated_course.save_without_broadcasting!
Auditors::Course.record_updated(templated_course, @batch_user, changes, source: :sis, sis_batch_id: @batch_id)
else
msg = "A (templated) course did not pass validation "
msg += "(" + "course: #{course_id} / #{short_name}, error: " +
msg += templated_course.errors.full_messages.join(",") + ")"
raise ImportError, msg
end
end
course.sis_batch_id = @batch.id if @batch
if course.valid?
course_changes = course.changes
course.save_without_broadcasting!
auditor_state_changes(course, state_changes, course_changes)
else
msg = "A course did not pass validation "
msg += "(" + "course: #{course_id} / #{short_name}, error: " +
msg += course.errors.full_messages.join(",") + ")"
raise ImportError, msg
end
@course_ids_to_update_associations.add(course.id) if update_account_associations
elsif @batch
@courses_to_update_sis_batch_id << course.id
end
course.update_enrolled_users if update_enrollments
@success_count += 1
end
def auditor_state_changes(course, state_changes, changes = {})
options = {
source: :sis,
sis_batch: @batch
}
state_changes.each do |state_change|
case state_change
when :created
Auditors::Course.record_created(course, @batch_user, changes, options)
when :updated
Auditors::Course.record_updated(course, @batch_user, changes, options)
when :concluded
Auditors::Course.record_concluded(course, @batch_user, options)
when :unconcluded
Auditors::Course.record_unconcluded(course, @batch_user, options)
when :published
Auditors::Course.record_published(course, @batch_user, options)
when :deleted
Auditors::Course.record_deleted(course, @batch_user, options)
when :restored
Auditors::Course.record_restored(course, @batch_user, options)
end
end
end
end
end
end