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.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +117 -20
  3. data/app/controllers/canvas_sync/api/v1/live_events_controller.rb +1 -0
  4. data/lib/canvas_sync/config.rb +1 -1
  5. data/lib/canvas_sync/importers/bulk_importer.rb +2 -0
  6. data/lib/canvas_sync/jobs/begin_sync_chain_job.rb +1 -1
  7. data/lib/canvas_sync/jobs/beta_cleanup/create_temp_tables_job.rb +30 -0
  8. data/lib/canvas_sync/jobs/beta_cleanup/delete_related_records_job.rb +125 -0
  9. data/lib/canvas_sync/jobs/beta_cleanup/delete_temp_tables_job.rb +16 -0
  10. data/lib/canvas_sync/jobs/report_starter.rb +33 -46
  11. data/lib/canvas_sync/jobs/report_sync_task.rb +263 -0
  12. data/lib/canvas_sync/jobs/sync_accounts_job.rb +10 -7
  13. data/lib/canvas_sync/jobs/sync_assignment_groups_job.rb +2 -15
  14. data/lib/canvas_sync/jobs/sync_assignment_overrides_job.rb +26 -14
  15. data/lib/canvas_sync/jobs/sync_assignments_job.rb +2 -15
  16. data/lib/canvas_sync/jobs/sync_content_migrations_job.rb +2 -15
  17. data/lib/canvas_sync/jobs/sync_context_module_items_job.rb +2 -15
  18. data/lib/canvas_sync/jobs/sync_context_modules_job.rb +2 -15
  19. data/lib/canvas_sync/jobs/sync_course_progresses_job.rb +2 -16
  20. data/lib/canvas_sync/jobs/sync_provisioning_report_job.rb +135 -14
  21. data/lib/canvas_sync/jobs/sync_rubric_assessments_job.rb +2 -10
  22. data/lib/canvas_sync/jobs/sync_rubric_associations_job.rb +2 -10
  23. data/lib/canvas_sync/jobs/sync_rubrics_job.rb +2 -10
  24. data/lib/canvas_sync/jobs/sync_scores_job.rb +2 -13
  25. data/lib/canvas_sync/jobs/sync_submissions_job.rb +9 -18
  26. data/lib/canvas_sync/jobs/term_batches_job.rb +4 -2
  27. data/lib/canvas_sync/version.rb +1 -1
  28. data/lib/canvas_sync.rb +31 -4
  29. data/spec/canvas_sync/canvas_sync_spec.rb +62 -22
  30. data/spec/canvas_sync/jobs/report_starter_spec.rb +102 -55
  31. data/spec/canvas_sync/jobs/report_sync_task_spec.rb +367 -0
  32. data/spec/canvas_sync/jobs/sync_provisioning_report_job_spec.rb +24 -35
  33. data/spec/canvas_sync/processors/assignment_groups_processor_spec.rb +3 -4
  34. data/spec/canvas_sync/processors/assignment_overrides_processor_spec.rb +7 -4
  35. data/spec/canvas_sync/processors/assignments_processor_spec.rb +3 -4
  36. data/spec/canvas_sync/processors/content_migrations_processor_spec.rb +3 -4
  37. data/spec/canvas_sync/processors/context_module_items_processor_spec.rb +4 -5
  38. data/spec/canvas_sync/processors/context_modules_processor_spec.rb +3 -4
  39. data/spec/canvas_sync/processors/course_completion_report_processor_spec.rb +7 -4
  40. data/spec/canvas_sync/processors/provisioning_report_processor_spec.rb +46 -24
  41. data/spec/canvas_sync/processors/rubric_assessments_spec.rb +3 -4
  42. data/spec/canvas_sync/processors/rubric_associations_spec.rb +3 -4
  43. data/spec/canvas_sync/processors/rubrics_processor_spec.rb +3 -4
  44. data/spec/canvas_sync/processors/submissions_processor_spec.rb +3 -4
  45. data/spec/factories/account_factory.rb +1 -1
  46. metadata +7 -33
  47. data/lib/canvas_sync/jobs/report_checker.rb +0 -108
  48. data/lib/canvas_sync/jobs/report_processor_job.rb +0 -35
  49. data/lib/canvas_sync/processors/assignment_groups_processor.rb +0 -19
  50. data/lib/canvas_sync/processors/assignment_overrides_processor.rb +0 -41
  51. data/lib/canvas_sync/processors/assignments_processor.rb +0 -19
  52. data/lib/canvas_sync/processors/content_migrations_processor.rb +0 -19
  53. data/lib/canvas_sync/processors/context_module_items_processor.rb +0 -19
  54. data/lib/canvas_sync/processors/context_modules_processor.rb +0 -19
  55. data/lib/canvas_sync/processors/course_completion_report_processor.rb +0 -20
  56. data/lib/canvas_sync/processors/provisioning_report_processor.rb +0 -149
  57. data/lib/canvas_sync/processors/rubric_assessments_processor.rb +0 -19
  58. data/lib/canvas_sync/processors/rubric_associations_processor.rb +0 -19
  59. data/lib/canvas_sync/processors/rubrics_processor.rb +0 -19
  60. data/lib/canvas_sync/processors/submissions_processor.rb +0 -19
  61. data/spec/canvas_sync/jobs/report_checker_spec.rb +0 -57
  62. data/spec/canvas_sync/jobs/report_processor_job_spec.rb +0 -25
  63. data/spec/canvas_sync/jobs/sync_assignment_groups_job_spec.rb +0 -18
  64. data/spec/canvas_sync/jobs/sync_assignments_job_spec.rb +0 -30
  65. data/spec/canvas_sync/jobs/sync_content_migrations_job_spec.rb +0 -30
  66. data/spec/canvas_sync/jobs/sync_context_module_items_job_spec.rb +0 -30
  67. data/spec/canvas_sync/jobs/sync_context_modules_job_spec.rb +0 -30
  68. data/spec/canvas_sync/jobs/sync_scores_job_spec.rb +0 -15
  69. 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 < ReportStarter
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
- super(
18
- "proservices_provisioning_csv",
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
- CanvasSync::Processors::ProvisioningReportProcessor.to_s,
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 < ReportStarter
4
- # Syncs AssignmentGroups
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 < ReportStarter
4
- # Syncs SyncAssignmentOverrides
5
- #
6
- # Starts a report processor for the assignment overrides report
7
- # (the proserv_assignment_overrides_csv report must be enabled)
8
- #
9
- # @param options [Hash]
10
- def perform(options)
11
- super(
12
- "proserv_assignment_overrides_csv",
13
- merge_report_params(options),
14
- CanvasSync::Processors::AssignmentOverridesProcessor.to_s,
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 < ReportStarter
4
- # Syncs Assignments
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 < ReportStarter
4
- # Syncs ContentMigrations
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 < ReportStarter
4
- # Syncs ContextModuleItems
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 < ReportStarter
4
- # Syncs ContextModules
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 < ReportStarter
4
- # Syncs CourseProgresses
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
- # ActiveJob class that starts a Canvas provisioning report
4
- class SyncProvisioningReportJob < ReportStarter
5
- def perform(options)
6
- params = {
7
- include_deleted: true,
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, {}).with_indifferent_access
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
- super(
27
- "proservices_provisioning_csv",
28
- merged_params,
29
- CanvasSync::Processors::ProvisioningReportProcessor.to_s,
30
- options,
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 < ReportStarter
4
- # @param options [Hash]
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 < ReportStarter
4
- # @param options [Hash]
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