canvas_sync 0.26.1 → 0.27.1.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +117 -20
- data/app/controllers/canvas_sync/api/v1/live_events_controller.rb +1 -0
- data/lib/canvas_sync/config.rb +1 -1
- data/lib/canvas_sync/importers/bulk_importer.rb +2 -0
- data/lib/canvas_sync/jobs/begin_sync_chain_job.rb +1 -1
- data/lib/canvas_sync/jobs/beta_cleanup/create_temp_tables_job.rb +30 -0
- data/lib/canvas_sync/jobs/beta_cleanup/delete_related_records_job.rb +125 -0
- data/lib/canvas_sync/jobs/beta_cleanup/delete_temp_tables_job.rb +16 -0
- data/lib/canvas_sync/jobs/report_starter.rb +33 -46
- data/lib/canvas_sync/jobs/report_sync_task.rb +263 -0
- data/lib/canvas_sync/jobs/sync_accounts_job.rb +10 -7
- data/lib/canvas_sync/jobs/sync_assignment_groups_job.rb +2 -15
- data/lib/canvas_sync/jobs/sync_assignment_overrides_job.rb +26 -14
- data/lib/canvas_sync/jobs/sync_assignments_job.rb +2 -15
- data/lib/canvas_sync/jobs/sync_content_migrations_job.rb +2 -15
- data/lib/canvas_sync/jobs/sync_context_module_items_job.rb +2 -15
- data/lib/canvas_sync/jobs/sync_context_modules_job.rb +2 -15
- data/lib/canvas_sync/jobs/sync_course_progresses_job.rb +2 -16
- data/lib/canvas_sync/jobs/sync_provisioning_report_job.rb +135 -14
- data/lib/canvas_sync/jobs/sync_rubric_assessments_job.rb +2 -10
- data/lib/canvas_sync/jobs/sync_rubric_associations_job.rb +2 -10
- data/lib/canvas_sync/jobs/sync_rubrics_job.rb +2 -10
- data/lib/canvas_sync/jobs/sync_scores_job.rb +2 -13
- data/lib/canvas_sync/jobs/sync_submissions_job.rb +9 -18
- data/lib/canvas_sync/jobs/term_batches_job.rb +4 -2
- data/lib/canvas_sync/version.rb +1 -1
- data/lib/canvas_sync.rb +31 -4
- data/spec/canvas_sync/canvas_sync_spec.rb +62 -22
- data/spec/canvas_sync/jobs/report_starter_spec.rb +102 -55
- data/spec/canvas_sync/jobs/report_sync_task_spec.rb +367 -0
- data/spec/canvas_sync/jobs/sync_provisioning_report_job_spec.rb +24 -35
- data/spec/canvas_sync/processors/assignment_groups_processor_spec.rb +3 -4
- data/spec/canvas_sync/processors/assignment_overrides_processor_spec.rb +7 -4
- data/spec/canvas_sync/processors/assignments_processor_spec.rb +3 -4
- data/spec/canvas_sync/processors/content_migrations_processor_spec.rb +3 -4
- data/spec/canvas_sync/processors/context_module_items_processor_spec.rb +4 -5
- data/spec/canvas_sync/processors/context_modules_processor_spec.rb +3 -4
- data/spec/canvas_sync/processors/course_completion_report_processor_spec.rb +7 -4
- data/spec/canvas_sync/processors/provisioning_report_processor_spec.rb +46 -24
- data/spec/canvas_sync/processors/rubric_assessments_spec.rb +3 -4
- data/spec/canvas_sync/processors/rubric_associations_spec.rb +3 -4
- data/spec/canvas_sync/processors/rubrics_processor_spec.rb +3 -4
- data/spec/canvas_sync/processors/submissions_processor_spec.rb +3 -4
- data/spec/factories/account_factory.rb +1 -1
- metadata +7 -33
- data/lib/canvas_sync/jobs/report_checker.rb +0 -108
- data/lib/canvas_sync/jobs/report_processor_job.rb +0 -35
- data/lib/canvas_sync/processors/assignment_groups_processor.rb +0 -19
- data/lib/canvas_sync/processors/assignment_overrides_processor.rb +0 -41
- data/lib/canvas_sync/processors/assignments_processor.rb +0 -19
- data/lib/canvas_sync/processors/content_migrations_processor.rb +0 -19
- data/lib/canvas_sync/processors/context_module_items_processor.rb +0 -19
- data/lib/canvas_sync/processors/context_modules_processor.rb +0 -19
- data/lib/canvas_sync/processors/course_completion_report_processor.rb +0 -20
- data/lib/canvas_sync/processors/provisioning_report_processor.rb +0 -149
- data/lib/canvas_sync/processors/rubric_assessments_processor.rb +0 -19
- data/lib/canvas_sync/processors/rubric_associations_processor.rb +0 -19
- data/lib/canvas_sync/processors/rubrics_processor.rb +0 -19
- data/lib/canvas_sync/processors/submissions_processor.rb +0 -19
- data/spec/canvas_sync/jobs/report_checker_spec.rb +0 -57
- data/spec/canvas_sync/jobs/report_processor_job_spec.rb +0 -25
- data/spec/canvas_sync/jobs/sync_assignment_groups_job_spec.rb +0 -18
- data/spec/canvas_sync/jobs/sync_assignments_job_spec.rb +0 -30
- data/spec/canvas_sync/jobs/sync_content_migrations_job_spec.rb +0 -30
- data/spec/canvas_sync/jobs/sync_context_module_items_job_spec.rb +0 -30
- data/spec/canvas_sync/jobs/sync_context_modules_job_spec.rb +0 -30
- data/spec/canvas_sync/jobs/sync_scores_job_spec.rb +0 -15
- data/spec/canvas_sync/jobs/sync_submissions_job_spec.rb +0 -23
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
require "csv"
|
|
2
|
+
|
|
3
|
+
module CanvasSync
|
|
4
|
+
module Jobs
|
|
5
|
+
class ReportSyncTask
|
|
6
|
+
REPORT_TIMEOUT = 24.hours
|
|
7
|
+
COMPILATION_TIMEOUT = 3.hours
|
|
8
|
+
MAX_TRIES = 3
|
|
9
|
+
|
|
10
|
+
class FatalReportError < ::RuntimeError; end
|
|
11
|
+
|
|
12
|
+
# Hash passed specifically to this report; read-only
|
|
13
|
+
attr_reader :options
|
|
14
|
+
# Batch context _before_ initializing the report batch; read-only
|
|
15
|
+
attr_reader :context
|
|
16
|
+
# Escape hatch to store additional state on the report batch
|
|
17
|
+
attr_reader :state
|
|
18
|
+
|
|
19
|
+
def initialize(options, context, state)
|
|
20
|
+
@options = options
|
|
21
|
+
@context = context
|
|
22
|
+
@state = state
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
delegate :merge_report_params, to: :class
|
|
26
|
+
|
|
27
|
+
class << self
|
|
28
|
+
def from_context
|
|
29
|
+
report_batch = JobBatches::Batch.current
|
|
30
|
+
base_context = report_batch.parent.context
|
|
31
|
+
state = report_batch.context
|
|
32
|
+
cls = state[:report_class].constantize
|
|
33
|
+
cls.new(state[:report_options], base_context, state)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def perform_later(options)
|
|
37
|
+
_with_batch(options) do |batch|
|
|
38
|
+
StarterJob.perform_later
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def perform_now(options)
|
|
43
|
+
_with_batch(options) do |batch|
|
|
44
|
+
StarterJob.perform_now
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def merge_report_params(options, params={}, term_scope: true)
|
|
49
|
+
batch_context = JobBatches::Batch.current_context
|
|
50
|
+
term_scope = options[:canvas_term_id] || batch_context[:canvas_term_id] if term_scope == true
|
|
51
|
+
if term_scope.present?
|
|
52
|
+
params[:enrollment_term_id] = term_scope
|
|
53
|
+
end
|
|
54
|
+
if (updated_after = batch_context[:updated_after]).present?
|
|
55
|
+
params[:updated_after] = updated_after
|
|
56
|
+
end
|
|
57
|
+
params.merge!(options[:report_params]) if options[:report_params].present?
|
|
58
|
+
params.merge!(options[:report_parameters]) if options[:report_parameters].present?
|
|
59
|
+
{ parameters: params }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def report_name(n)
|
|
63
|
+
define_method(:report_name) { n }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def _with_batch(options, &blk)
|
|
69
|
+
batch = JobBatches::Batch.new
|
|
70
|
+
batch.description = "CanvasSync RunReport[#{self.name}] Fiber"
|
|
71
|
+
batch.allow_context_changes = true
|
|
72
|
+
batch.context[:report_class] = self.to_s
|
|
73
|
+
batch.context[:report_options] = options
|
|
74
|
+
batch.jobs do
|
|
75
|
+
blk.call(batch)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def report_name
|
|
81
|
+
raise NotImplementedError
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def report_parameters
|
|
85
|
+
merge_report_params(options)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def process(file)
|
|
89
|
+
if (m = self.class.name.match(/Sync(\w+)Job$/)) && m[1].present?
|
|
90
|
+
model_name = m[1].singularize
|
|
91
|
+
model = model_name.safe_constantize
|
|
92
|
+
return do_bulk_import(file, model) if model.present?
|
|
93
|
+
end
|
|
94
|
+
raise NotImplementedError
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def check_frequency
|
|
98
|
+
Rails.env.development? ? 5.seconds : 1.minute
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def max_tries
|
|
102
|
+
3
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def caching_key
|
|
106
|
+
Digest::MD5.hexdigest(report_parameters.to_s)[0..8]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
protected
|
|
110
|
+
|
|
111
|
+
def do_bulk_import(report_file_path, model, options: {}, mapping_key: nil, &blk)
|
|
112
|
+
Processors::ReportProcessor.new().do_bulk_import(report_file_path, model, options: options, mapping_key: mapping_key, &blk)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
class ReportTaskJob < CanvasSync::Job
|
|
116
|
+
protected
|
|
117
|
+
|
|
118
|
+
def canvas_account_id
|
|
119
|
+
report_task.options[:account_id] || batch_context[:account_id] || "self"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def trigger_canvas_report
|
|
123
|
+
report_name = report_task.report_name
|
|
124
|
+
report_params = report_task.report_parameters
|
|
125
|
+
|
|
126
|
+
caching_config = report_task.options[:caching] || batch_context[:report_caching] || nil
|
|
127
|
+
cache_key = caching_config && report_task.caching_key
|
|
128
|
+
|
|
129
|
+
if cache_key.present?
|
|
130
|
+
# Be default, only cache reports w/i the same SyncBatch
|
|
131
|
+
cache_scope = caching_config[:scope]&.to_sym || :sync_batch
|
|
132
|
+
cache_key = "SyncBatch[#{batch_context[:sync_batch_id]}]:#{cache_key}" if cache_scope == :sync_batch
|
|
133
|
+
|
|
134
|
+
batch_context[:caching_key] = cache_key
|
|
135
|
+
|
|
136
|
+
cached_report = CanvasSync.redis.hgetall(cache_key)
|
|
137
|
+
if cached_report.present? && cached_report["started_at"] > (caching_config[:max_age] || 23.hours).ago.iso8601
|
|
138
|
+
batch_context[:report_id] = cached_report["report_id"]
|
|
139
|
+
batch_context[:sync_start_time] = cached_report["started_at"]
|
|
140
|
+
Rails.logger.info("Using cached report execution for #{report_name} with cache key #{cache_key}")
|
|
141
|
+
return
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
report = CanvasSync.get_canvas_sync_client(batch_context).start_report(canvas_account_id, report_name, report_params)
|
|
146
|
+
batch_context[:report_id] = "#{report_name}/#{report['id']}"
|
|
147
|
+
batch_context[:sync_start_time] = DateTime.now.utc.iso8601 # "report_start_time" would be a better name, but backwards compat
|
|
148
|
+
batch_context.delete(:compiling_since)
|
|
149
|
+
|
|
150
|
+
if cache_key.present?
|
|
151
|
+
CanvasSync.redis do |r|
|
|
152
|
+
r.hset(cache_key, {
|
|
153
|
+
report_id: batch_context[:report_id],
|
|
154
|
+
started_at: batch_context[:sync_start_time],
|
|
155
|
+
})
|
|
156
|
+
r.expire(cache_key, (caching_config[:max_age] || 23.hours).to_i)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
report
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def report_task
|
|
164
|
+
@report_task ||= ReportSyncTask.from_context
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def enqueue_checker!
|
|
168
|
+
report_task.class::CheckerJob.set(wait: report_task.check_frequency).perform_later()
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
class StarterJob < ReportTaskJob
|
|
173
|
+
def perform()
|
|
174
|
+
batch_context[:report_attempt] = 1
|
|
175
|
+
trigger_canvas_report
|
|
176
|
+
enqueue_checker!
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
class CheckerJob < ReportTaskJob
|
|
181
|
+
def perform()
|
|
182
|
+
options = report_task.options
|
|
183
|
+
report_status = CanvasSync.get_canvas_sync_client(batch_context).report_status(canvas_account_id, *batch_context[:report_id].split("/"))
|
|
184
|
+
|
|
185
|
+
case report_status["status"].downcase
|
|
186
|
+
when "complete"
|
|
187
|
+
ProcessJob.perform_later(report_status["attachment"]["url"])
|
|
188
|
+
when "error", "deleted"
|
|
189
|
+
max_tries = options[:report_max_tries] || batch_context[:report_max_tries] || report_task.max_tries || MAX_TRIES
|
|
190
|
+
report_attempt = batch_context[:report_attempt]
|
|
191
|
+
|
|
192
|
+
message = "Report failed to process; status was #{report_status} for #{batch_context[:report_id]}. This report has now failed #{batch_context[:report_attempt]} times."
|
|
193
|
+
Rails.logger.error(message)
|
|
194
|
+
|
|
195
|
+
if report_attempt >= max_tries
|
|
196
|
+
Rails.logger.error("This report has failed #{report_attempt} times. Giving up.")
|
|
197
|
+
raise FatalReportError, message
|
|
198
|
+
else
|
|
199
|
+
batch_context[:report_attempt] += 1
|
|
200
|
+
|
|
201
|
+
# Remove the cache entry (if it wasn't already restarted by some other cacher) so that we actually start a new report
|
|
202
|
+
if batch_context[:caching_key].present?
|
|
203
|
+
CanvasSync.redis do |r|
|
|
204
|
+
# This could _technically_ result in a race condition, but the chances are quite low and the
|
|
205
|
+
# consequences are just that an extra report is triggered (which Canvas may de-dup anyway)
|
|
206
|
+
centry = r.hgetall(batch_context[:caching_key])
|
|
207
|
+
if centry.present? && centry["report_id"] == batch_context[:report_id]
|
|
208
|
+
r.del(batch_context[:caching_key])
|
|
209
|
+
Rails.logger.info("Deleted cache entry for #{batch_context[:report_id]} at #{batch_context[:caching_key]} due to report failure")
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
trigger_canvas_report
|
|
215
|
+
enqueue_checker!
|
|
216
|
+
end
|
|
217
|
+
else
|
|
218
|
+
report_timeout = options[:report_timeout] || batch_context[:report_timeout] || REPORT_TIMEOUT
|
|
219
|
+
if timeout_met?(batch_context[:sync_start_time], report_timeout)
|
|
220
|
+
raise FatalReportError, "Report appears to be stuck #{batch_context[:report_id]}"
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
if report_status["status"].downcase == 'compiling'
|
|
224
|
+
batch_context[:compiling_since] ||= DateTime.now.iso8601
|
|
225
|
+
compilation_timeout = options[:report_compilation_timeout] || batch_context[:report_compilation_timeout] || COMPILATION_TIMEOUT
|
|
226
|
+
if timeout_met?(batch_context[:compiling_since], compilation_timeout)
|
|
227
|
+
raise FatalReportError, "Report appears to be stuck #{batch_context[:report_id]}"
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
enqueue_checker!
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
protected
|
|
236
|
+
|
|
237
|
+
def timeout_met?(base_time, timeout_length)
|
|
238
|
+
return false unless base_time.present? && timeout_length.present?
|
|
239
|
+
DateTime.now > (DateTime.parse(base_time) + timeout_length)
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
class ProcessJob < ReportTaskJob
|
|
244
|
+
def perform(report_url)
|
|
245
|
+
report_name = batch_context[:report_id].gsub(/\//, "_")
|
|
246
|
+
download(report_name, report_url) do |file_path|
|
|
247
|
+
report_task.process(file_path)
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
private
|
|
252
|
+
|
|
253
|
+
def download(report_name, report_url)
|
|
254
|
+
Dir.mktmpdir do |dir|
|
|
255
|
+
file_path = "#{dir}/#{report_name}"
|
|
256
|
+
canvas_sync_client.download_report(report_url, file_path)
|
|
257
|
+
yield file_path
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module CanvasSync
|
|
2
2
|
module Jobs
|
|
3
|
-
class SyncAccountsJob <
|
|
3
|
+
class SyncAccountsJob < CanvasSync::Job
|
|
4
4
|
# Starts a provisioning report for just accounts.
|
|
5
5
|
#
|
|
6
6
|
# Provisioning reports do not scope accounts by term, so when we are
|
|
@@ -14,15 +14,18 @@ module CanvasSync
|
|
|
14
14
|
update_or_create_model(Account, acc_params)
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
ReportTask.perform_later(options)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class ReportTask < ReportSyncTask
|
|
21
|
+
report_name "proservices_provisioning_csv"
|
|
22
|
+
|
|
23
|
+
def report_parameters
|
|
19
24
|
merge_report_params(options, {
|
|
20
25
|
accounts: true,
|
|
21
26
|
include_deleted: true,
|
|
22
|
-
}, term_scope: false)
|
|
23
|
-
|
|
24
|
-
{ models: ["accounts"] },
|
|
25
|
-
)
|
|
27
|
+
}, term_scope: false)
|
|
28
|
+
end
|
|
26
29
|
end
|
|
27
30
|
end
|
|
28
31
|
end
|
|
@@ -1,20 +1,7 @@
|
|
|
1
1
|
module CanvasSync
|
|
2
2
|
module Jobs
|
|
3
|
-
class SyncAssignmentGroupsJob <
|
|
4
|
-
|
|
5
|
-
#
|
|
6
|
-
# Starts a report processor for the assignment_groups report
|
|
7
|
-
# (the proserv_assignment_group_export_csv report must be enabled)
|
|
8
|
-
#
|
|
9
|
-
# @param options [Hash]
|
|
10
|
-
def perform(options)
|
|
11
|
-
super(
|
|
12
|
-
"proserv_assignment_group_export_csv",
|
|
13
|
-
merge_report_params(options),
|
|
14
|
-
CanvasSync::Processors::AssignmentGroupsProcessor.to_s,
|
|
15
|
-
{},
|
|
16
|
-
)
|
|
17
|
-
end
|
|
3
|
+
class SyncAssignmentGroupsJob < ReportSyncTask
|
|
4
|
+
report_name "proserv_assignment_group_export_csv"
|
|
18
5
|
end
|
|
19
6
|
end
|
|
20
7
|
end
|
|
@@ -1,19 +1,31 @@
|
|
|
1
1
|
module CanvasSync
|
|
2
2
|
module Jobs
|
|
3
|
-
class SyncAssignmentOverridesJob <
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
3
|
+
class SyncAssignmentOverridesJob < ReportSyncTask
|
|
4
|
+
report_name "proserv_assignment_overrides_csv"
|
|
5
|
+
|
|
6
|
+
def process(report_file_path)
|
|
7
|
+
do_bulk_import(report_file_path, AssignmentOverride) do |row|
|
|
8
|
+
# Handle transforms here instead of in mappings
|
|
9
|
+
row[:student_ids] = parse_student_ids(row[:student_ids])
|
|
10
|
+
|
|
11
|
+
# Convert empty/null/'0' values to nil
|
|
12
|
+
# For Ruby 2.7
|
|
13
|
+
row[:group_id] = parse_nil_value(row[:group_id])
|
|
14
|
+
row[:course_section_id] = parse_nil_value(row[:course_section_id])
|
|
15
|
+
row
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def parse_student_ids(value)
|
|
22
|
+
value.present? ? JSON.parse(value) : nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def parse_nil_value(value)
|
|
26
|
+
# Convert "0", "null", or empty string to nil
|
|
27
|
+
return nil if %w[0 null].include?(value) || value == ''
|
|
28
|
+
value
|
|
17
29
|
end
|
|
18
30
|
end
|
|
19
31
|
end
|
|
@@ -1,20 +1,7 @@
|
|
|
1
1
|
module CanvasSync
|
|
2
2
|
module Jobs
|
|
3
|
-
class SyncAssignmentsJob <
|
|
4
|
-
|
|
5
|
-
#
|
|
6
|
-
# Starts a report processor for the assignment report
|
|
7
|
-
# (the proserv_assignment_export_csv report must be enabled)
|
|
8
|
-
#
|
|
9
|
-
# @param options [Hash]
|
|
10
|
-
def perform(options)
|
|
11
|
-
super(
|
|
12
|
-
"proserv_assignment_export_csv",
|
|
13
|
-
merge_report_params(options),
|
|
14
|
-
CanvasSync::Processors::AssignmentsProcessor.to_s,
|
|
15
|
-
{},
|
|
16
|
-
)
|
|
17
|
-
end
|
|
3
|
+
class SyncAssignmentsJob < ReportSyncTask
|
|
4
|
+
report_name "proserv_assignment_export_csv"
|
|
18
5
|
end
|
|
19
6
|
end
|
|
20
7
|
end
|
|
@@ -1,20 +1,7 @@
|
|
|
1
1
|
module CanvasSync
|
|
2
2
|
module Jobs
|
|
3
|
-
class SyncContentMigrationsJob <
|
|
4
|
-
|
|
5
|
-
#
|
|
6
|
-
# Starts a report processor for the content migrations report
|
|
7
|
-
# (the proserv_content_migrations_csv report must be enabled)
|
|
8
|
-
#
|
|
9
|
-
# @param options [Hash]
|
|
10
|
-
def perform(options)
|
|
11
|
-
super(
|
|
12
|
-
"proserv_content_migrations_csv",
|
|
13
|
-
merge_report_params(options),
|
|
14
|
-
CanvasSync::Processors::ContentMigrationsProcessor.to_s,
|
|
15
|
-
{},
|
|
16
|
-
)
|
|
17
|
-
end
|
|
3
|
+
class SyncContentMigrationsJob < ReportSyncTask
|
|
4
|
+
report_name "proserv_content_migrations_csv"
|
|
18
5
|
end
|
|
19
6
|
end
|
|
20
7
|
end
|
|
@@ -1,20 +1,7 @@
|
|
|
1
1
|
module CanvasSync
|
|
2
2
|
module Jobs
|
|
3
|
-
class SyncContextModuleItemsJob <
|
|
4
|
-
|
|
5
|
-
#
|
|
6
|
-
# Starts a report processor for the context modules report
|
|
7
|
-
# (the proserv_context_module_items_csv report must be enabled)
|
|
8
|
-
#
|
|
9
|
-
# @param options [Hash]
|
|
10
|
-
def perform(options)
|
|
11
|
-
super(
|
|
12
|
-
"proserv_context_module_items_csv",
|
|
13
|
-
merge_report_params(options),
|
|
14
|
-
CanvasSync::Processors::ContextModuleItemsProcessor.to_s,
|
|
15
|
-
{},
|
|
16
|
-
)
|
|
17
|
-
end
|
|
3
|
+
class SyncContextModuleItemsJob < ReportSyncTask
|
|
4
|
+
report_name "proserv_context_module_items_csv"
|
|
18
5
|
end
|
|
19
6
|
end
|
|
20
7
|
end
|
|
@@ -1,20 +1,7 @@
|
|
|
1
1
|
module CanvasSync
|
|
2
2
|
module Jobs
|
|
3
|
-
class SyncContextModulesJob <
|
|
4
|
-
|
|
5
|
-
#
|
|
6
|
-
# Starts a report processor for the context modules report
|
|
7
|
-
# (the proserv_context_modules_csv report must be enabled)
|
|
8
|
-
#
|
|
9
|
-
# @param options [Hash]
|
|
10
|
-
def perform(options)
|
|
11
|
-
super(
|
|
12
|
-
"proserv_context_modules_csv",
|
|
13
|
-
merge_report_params(options),
|
|
14
|
-
CanvasSync::Processors::ContextModulesProcessor.to_s,
|
|
15
|
-
{},
|
|
16
|
-
)
|
|
17
|
-
end
|
|
3
|
+
class SyncContextModulesJob < ReportSyncTask
|
|
4
|
+
report_name "proserv_context_modules_csv"
|
|
18
5
|
end
|
|
19
6
|
end
|
|
20
7
|
end
|
|
@@ -1,21 +1,7 @@
|
|
|
1
1
|
module CanvasSync
|
|
2
2
|
module Jobs
|
|
3
|
-
class SyncCourseProgressesJob <
|
|
4
|
-
|
|
5
|
-
#
|
|
6
|
-
# Starts a report processor for the course completion report
|
|
7
|
-
# (the proserv_course_completion_csv report must be enabled)
|
|
8
|
-
#
|
|
9
|
-
# @param options [Hash]
|
|
10
|
-
def perform(options)
|
|
11
|
-
super(
|
|
12
|
-
'proserv_course_completion_csv',
|
|
13
|
-
merge_report_params(options),
|
|
14
|
-
CanvasSync::Processors::CourseCompletionReportProcessor.to_s,
|
|
15
|
-
{},
|
|
16
|
-
)
|
|
17
|
-
end
|
|
3
|
+
class SyncCourseProgressesJob < ReportSyncTask
|
|
4
|
+
report_name "proserv_course_completion_csv"
|
|
18
5
|
end
|
|
19
6
|
end
|
|
20
|
-
|
|
21
7
|
end
|
|
@@ -1,20 +1,22 @@
|
|
|
1
|
+
require "zip"
|
|
2
|
+
require "zip/version"
|
|
3
|
+
|
|
1
4
|
module CanvasSync
|
|
2
5
|
module Jobs
|
|
3
|
-
#
|
|
4
|
-
class SyncProvisioningReportJob <
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
6
|
+
# Job that syncs a Canvas provisioning report
|
|
7
|
+
class SyncProvisioningReportJob < ReportSyncTask
|
|
8
|
+
report_name "proservices_provisioning_csv"
|
|
9
|
+
|
|
10
|
+
def report_parameters
|
|
11
|
+
params = { include_deleted: true }
|
|
9
12
|
|
|
10
13
|
options[:models].each do |model|
|
|
11
14
|
# group_membership is the only model param that is singular :(
|
|
12
15
|
model = 'group_membership' if model == 'group_memberships'
|
|
13
|
-
|
|
14
16
|
params[model] = true
|
|
15
17
|
end
|
|
16
18
|
|
|
17
|
-
merged_params = merge_report_params(options, params
|
|
19
|
+
merged_params = merge_report_params(options, params)
|
|
18
20
|
|
|
19
21
|
# Make sure the report also checks last_activity_at when checking updated_at
|
|
20
22
|
if options[:models].include?("enrollments")
|
|
@@ -23,12 +25,131 @@ module CanvasSync
|
|
|
23
25
|
end
|
|
24
26
|
end
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
merged_params
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def process(file)
|
|
32
|
+
if options[:models].length == 1
|
|
33
|
+
run_import(options[:models][0], file)
|
|
34
|
+
else
|
|
35
|
+
unzipped_file_dir = extract(file)
|
|
36
|
+
Dir[unzipped_file_dir + "/*.csv"].sort.each do |file_path|
|
|
37
|
+
model_name = file_path.split("/").last.split(".").first
|
|
38
|
+
run_import(model_name, file_path)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def extract(file_path)
|
|
46
|
+
unzipped_file_dir = "#{file_path}_unzipped"
|
|
47
|
+
|
|
48
|
+
Zip::File.open(file_path) do |zip_file|
|
|
49
|
+
zip_file.each do |f|
|
|
50
|
+
f_path = File.join(unzipped_file_dir, f.name)
|
|
51
|
+
FileUtils.mkdir_p(File.dirname(f_path))
|
|
52
|
+
next if File.exist?(f_path)
|
|
53
|
+
|
|
54
|
+
if Zip::VERSION >= "3.0.0"
|
|
55
|
+
zip_file.extract(f, destination_directory: unzipped_file_dir)
|
|
56
|
+
else
|
|
57
|
+
zip_file.extract(f, f_path)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
unzipped_file_dir
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def run_import(model_name, report_file_path)
|
|
66
|
+
if legacy?(model_name)
|
|
67
|
+
CanvasSync::Importers::LegacyImporter.import(
|
|
68
|
+
report_file_path,
|
|
69
|
+
model_name.singularize.capitalize.constantize,
|
|
70
|
+
options[:account_id],
|
|
71
|
+
options,
|
|
72
|
+
)
|
|
73
|
+
else
|
|
74
|
+
send("bulk_process_#{model_name}", report_file_path)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def legacy?(model_name)
|
|
79
|
+
opt = options[:legacy_support]
|
|
80
|
+
return false if opt == false || opt.nil?
|
|
81
|
+
return true if opt == true
|
|
82
|
+
return opt.include?(model_name)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def bulk_process_users(report_file_path)
|
|
86
|
+
do_bulk_import(report_file_path, User)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def bulk_process_user_observers(report_file_path)
|
|
90
|
+
do_bulk_import(report_file_path, UserObserver)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def bulk_process_pseudonyms(report_file_path)
|
|
94
|
+
do_bulk_import(report_file_path, Pseudonym)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def bulk_process_accounts(report_file_path)
|
|
98
|
+
do_bulk_import(report_file_path, Account)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def bulk_process_courses(report_file_path)
|
|
102
|
+
do_bulk_import(report_file_path, Course)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def bulk_process_enrollments(report_file_path)
|
|
106
|
+
do_bulk_import(report_file_path, Enrollment)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def bulk_process_sections(report_file_path)
|
|
110
|
+
do_bulk_import(report_file_path, Section)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def bulk_process_xlist(report_file_path)
|
|
114
|
+
do_bulk_import(report_file_path, Section, mapping_key: :xlist)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def bulk_process_groups(report_file_path)
|
|
118
|
+
do_bulk_import(report_file_path, Group)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def bulk_process_grading_periods(report_file_path)
|
|
122
|
+
do_bulk_import(report_file_path, GradingPeriod)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def bulk_process_grading_period_groups(report_file_path)
|
|
126
|
+
do_bulk_import(report_file_path, GradingPeriodGroup)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Note that group membership is singular because we override the model name param in sync_provisioning_report_job
|
|
130
|
+
def bulk_process_group_membership(report_file_path)
|
|
131
|
+
do_bulk_import(report_file_path, GroupMembership)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def bulk_process_learning_outcomes(report_file_path)
|
|
135
|
+
do_bulk_import(report_file_path, LearningOutcome) do |row|
|
|
136
|
+
row[:root_account_ids] = JSON.parse row[:root_account_ids]
|
|
137
|
+
row
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def bulk_process_learning_outcome_results(report_file_path)
|
|
142
|
+
do_bulk_import(report_file_path, LearningOutcomeResult)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def bulk_process_course_nicknames(report_file_path)
|
|
146
|
+
exists = []
|
|
147
|
+
do_bulk_import(report_file_path, CourseNickname) do |row|
|
|
148
|
+
exists << row[:user_preference_value_id]
|
|
149
|
+
row
|
|
150
|
+
end
|
|
151
|
+
# Canvas does not soft delete UserPreferenceValues so there's no point in doing the same
|
|
152
|
+
CourseNickname.where.not(canvas_user_preference_value_id: exists).or(CourseNickname.where(canvas_user_preference_value_id: nil)).delete_all
|
|
32
153
|
end
|
|
33
154
|
end
|
|
34
155
|
end
|
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
module CanvasSync
|
|
2
2
|
module Jobs
|
|
3
|
-
class SyncRubricAssessmentsJob <
|
|
4
|
-
|
|
5
|
-
def perform(options)
|
|
6
|
-
super(
|
|
7
|
-
"rubric_assessments_csv",
|
|
8
|
-
merge_report_params(options),
|
|
9
|
-
CanvasSync::Processors::RubricAssessmentsProcessor.to_s,
|
|
10
|
-
{},
|
|
11
|
-
)
|
|
12
|
-
end
|
|
3
|
+
class SyncRubricAssessmentsJob < ReportSyncTask
|
|
4
|
+
report_name "rubric_assessments_csv"
|
|
13
5
|
end
|
|
14
6
|
end
|
|
15
7
|
end
|
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
module CanvasSync
|
|
2
2
|
module Jobs
|
|
3
|
-
class SyncRubricAssociationsJob <
|
|
4
|
-
|
|
5
|
-
def perform(options)
|
|
6
|
-
super(
|
|
7
|
-
"rubric_associations_csv",
|
|
8
|
-
merge_report_params(options),
|
|
9
|
-
CanvasSync::Processors::RubricAssociationsProcessor.to_s,
|
|
10
|
-
{},
|
|
11
|
-
)
|
|
12
|
-
end
|
|
3
|
+
class SyncRubricAssociationsJob < ReportSyncTask
|
|
4
|
+
report_name "rubric_associations_csv"
|
|
13
5
|
end
|
|
14
6
|
end
|
|
15
7
|
end
|