canvas_sync 0.16.5 → 0.17.0.beta5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +49 -137
  3. data/app/models/canvas_sync/sync_batch.rb +5 -0
  4. data/db/migrate/20201018210836_create_canvas_sync_sync_batches.rb +11 -0
  5. data/lib/canvas_sync.rb +35 -97
  6. data/lib/canvas_sync/importers/bulk_importer.rb +4 -7
  7. data/lib/canvas_sync/job.rb +4 -10
  8. data/lib/canvas_sync/job_batches/batch.rb +403 -0
  9. data/lib/canvas_sync/job_batches/batch_aware_job.rb +62 -0
  10. data/lib/canvas_sync/job_batches/callback.rb +152 -0
  11. data/lib/canvas_sync/job_batches/chain_builder.rb +220 -0
  12. data/lib/canvas_sync/job_batches/context_hash.rb +147 -0
  13. data/lib/canvas_sync/job_batches/jobs/base_job.rb +7 -0
  14. data/lib/canvas_sync/job_batches/jobs/concurrent_batch_job.rb +19 -0
  15. data/lib/canvas_sync/job_batches/jobs/serial_batch_job.rb +75 -0
  16. data/lib/canvas_sync/job_batches/sidekiq.rb +93 -0
  17. data/lib/canvas_sync/job_batches/status.rb +83 -0
  18. data/lib/canvas_sync/jobs/begin_sync_chain_job.rb +35 -0
  19. data/lib/canvas_sync/jobs/report_checker.rb +3 -6
  20. data/lib/canvas_sync/jobs/report_processor_job.rb +2 -5
  21. data/lib/canvas_sync/jobs/report_starter.rb +28 -20
  22. data/lib/canvas_sync/jobs/sync_accounts_job.rb +3 -5
  23. data/lib/canvas_sync/jobs/sync_admins_job.rb +2 -4
  24. data/lib/canvas_sync/jobs/sync_assignment_groups_job.rb +2 -4
  25. data/lib/canvas_sync/jobs/sync_assignments_job.rb +2 -4
  26. data/lib/canvas_sync/jobs/sync_context_module_items_job.rb +2 -4
  27. data/lib/canvas_sync/jobs/sync_context_modules_job.rb +2 -4
  28. data/lib/canvas_sync/jobs/sync_provisioning_report_job.rb +4 -34
  29. data/lib/canvas_sync/jobs/sync_roles_job.rb +2 -5
  30. data/lib/canvas_sync/jobs/sync_simple_table_job.rb +11 -32
  31. data/lib/canvas_sync/jobs/sync_submissions_job.rb +2 -4
  32. data/lib/canvas_sync/jobs/sync_terms_job.rb +25 -8
  33. data/lib/canvas_sync/processors/assignment_groups_processor.rb +2 -3
  34. data/lib/canvas_sync/processors/assignments_processor.rb +2 -3
  35. data/lib/canvas_sync/processors/context_module_items_processor.rb +2 -3
  36. data/lib/canvas_sync/processors/context_modules_processor.rb +2 -3
  37. data/lib/canvas_sync/processors/normal_processor.rb +1 -2
  38. data/lib/canvas_sync/processors/provisioning_report_processor.rb +2 -10
  39. data/lib/canvas_sync/processors/submissions_processor.rb +2 -3
  40. data/lib/canvas_sync/version.rb +1 -1
  41. data/spec/canvas_sync/canvas_sync_spec.rb +136 -153
  42. data/spec/canvas_sync/jobs/job_spec.rb +9 -17
  43. data/spec/canvas_sync/jobs/report_checker_spec.rb +1 -3
  44. data/spec/canvas_sync/jobs/report_processor_job_spec.rb +0 -3
  45. data/spec/canvas_sync/jobs/report_starter_spec.rb +19 -28
  46. data/spec/canvas_sync/jobs/sync_admins_job_spec.rb +1 -4
  47. data/spec/canvas_sync/jobs/sync_assignment_groups_job_spec.rb +2 -1
  48. data/spec/canvas_sync/jobs/sync_assignments_job_spec.rb +3 -2
  49. data/spec/canvas_sync/jobs/sync_context_module_items_job_spec.rb +3 -2
  50. data/spec/canvas_sync/jobs/sync_context_modules_job_spec.rb +3 -2
  51. data/spec/canvas_sync/jobs/sync_provisioning_report_job_spec.rb +3 -35
  52. data/spec/canvas_sync/jobs/sync_roles_job_spec.rb +1 -4
  53. data/spec/canvas_sync/jobs/sync_simple_table_job_spec.rb +5 -12
  54. data/spec/canvas_sync/jobs/sync_submissions_job_spec.rb +2 -1
  55. data/spec/canvas_sync/jobs/sync_terms_job_spec.rb +1 -4
  56. data/spec/dummy/config/environments/test.rb +2 -0
  57. data/spec/dummy/db/schema.rb +9 -1
  58. data/spec/job_batching/batch_aware_job_spec.rb +100 -0
  59. data/spec/job_batching/batch_spec.rb +372 -0
  60. data/spec/job_batching/callback_spec.rb +38 -0
  61. data/spec/job_batching/flow_spec.rb +88 -0
  62. data/spec/job_batching/integration/integration.rb +57 -0
  63. data/spec/job_batching/integration/nested.rb +88 -0
  64. data/spec/job_batching/integration/simple.rb +47 -0
  65. data/spec/job_batching/integration/workflow.rb +134 -0
  66. data/spec/job_batching/integration_helper.rb +48 -0
  67. data/spec/job_batching/sidekiq_spec.rb +124 -0
  68. data/spec/job_batching/status_spec.rb +92 -0
  69. data/spec/job_batching/support/base_job.rb +14 -0
  70. data/spec/job_batching/support/sample_callback.rb +2 -0
  71. data/spec/spec_helper.rb +17 -0
  72. metadata +85 -8
  73. data/lib/canvas_sync/job_chain.rb +0 -102
  74. data/lib/canvas_sync/jobs/fork_gather.rb +0 -74
  75. data/spec/canvas_sync/jobs/fork_gather_spec.rb +0 -73
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9be3b81c22d5b45a02d88fa29b5d57afa152ee7dec6ce0ecf3d81c041507c619
4
- data.tar.gz: 0c42577064cbf018f8fd8d5f665e1adfbc542e38e704a675e735491543b3ab40
3
+ metadata.gz: eba7624a65721ad2183b188372b167c47bde879c37c595ef13398e4ec4b835b5
4
+ data.tar.gz: a69c72c000c782bff566016ca1cc7813c0c1d6829f2516291721672e3ee7d5ec
5
5
  SHA512:
6
- metadata.gz: 9ea295b8cc43aa6b27bf6067cf684d024430a9fd85608bdc0a243113920b1b65b72c257e6a99644afb5d20455ecf2265949ce941a9ae1d9bea160ab1bee803ba
7
- data.tar.gz: e63b7f557e792cb850a5799753f9effe436ad391bc6e4442768683b8c4d7c8eea1d1e9bbd6419127f2f4401f6cce6129019fb73192496f7efc1d731d7ef48847
6
+ metadata.gz: 00e85f161794e1526cf734aabaaf364b55b92b93a832248bd8f17b4eeb97081e20fc8ef7e84ae7029dbd654ad46410939d10ec4db5ba79394b9326de49948342
7
+ data.tar.gz: d30bb87abb822cd0f5ad10b60b6941326ac8a2e964241fa6e8403c5a5bf303e3d6945bd79ff500a181bd21a6805161281f4fe05fdd091f42b295ebf820772362
data/README.md CHANGED
@@ -91,27 +91,45 @@ This gem also helps with syncing and processing other reports if needed. In orde
91
91
  - Integrate your reports with the `ReportStarter`
92
92
  - Tell the gem what jobs to run
93
93
 
94
+ ### `updated_after`
95
+ An `updated_after` param may be passed when triggering a provision or making a chain:
96
+ ```ruby
97
+ CanvasSync.default_provisioning_report_chain(
98
+ %i[list of models to sync], updated_after: false
99
+ )
100
+ ```
101
+ It may be one of the following values:
102
+ * `false` - Will not apply any `updated_after` filtering to the requested reports
103
+ * An ISO-8601 Date - Will pass the supplied date ad the `updated_after` param for the requested reports
104
+ * `true` (Default) - Will use the start date of the last successful sync
105
+
94
106
  ### Extensible chain
95
107
  It is sometimes desired to extend or customize the chain of jobs that are run with CanvasSync.
96
108
  This can be achieved with the following pattern:
97
109
 
98
110
  ```ruby
99
- job_chain = CanvasSync.default_provisioning_report_chain(
111
+ chain = CanvasSync.default_provisioning_report_chain(
100
112
  %i[list of models to sync]
101
113
  )
102
114
 
103
- # CanvasSync forks the chain for each term within a term scope. The ForkGather Job can be used to unfork the chain.
104
- # For example multiple Terms are in the term_scope. CanvasSync syncs Accounts, Terms, and Users (if enabled) and then
105
- # forks the chain to sync other models (eg Course, Sections, etc.) per-Term.
106
- # ForkGather will wait until all the forked chains are complete before continuing.
107
- # TL;DR: Jobs placed after SyncProvisioningReportJob and before ForkGather will run once per Term per Sync;
108
- # Jobs placed before SyncProvisioningReportJob or after ForkGather will run once per Sync
109
- job_chain[:jobs] << { job: CanvasSync::Jobs::ForkGather, options: {} }
115
+ # Add a custom job to the end of the chain.
116
+ chain << { job: CanvasSyncCompleteWorker, parameters: [{ job_id: job.id }] }
117
+ chain << { job: CanvasSyncCompleteWorker, options: { job_id: job.id } } # If an options key is provided, it will be automatically appended to the end of the :parameters array
118
+
119
+ chain.process!
110
120
 
111
- # Add a custom job to the end of the chain. Custom jobs must accept 2 arguments (job_chain, options) and call CanvasSync.invoke_next(job_chain) when complete
112
- job_chain[:jobs] << { job: CanvasSyncCompleteWorker, options: { job_id: job.id } }
121
+ # The chain object provides a fairly extensive API:
122
+ chain.insert({ job: SomeOtherJob }) # Adds the job to the end of the chain
123
+ chain.insert_at(0, { job: SomeOtherJob }) # Adds the job to the beginning of the chain
124
+ chain.insert({ job: SomeOtherJob }, after: 'CanvasSync::Jobs::SyncTermsJob') # Adds the job right after the SyncTermsJob
125
+ chain.insert({ job: SomeOtherJob }, before: 'CanvasSync::Jobs::SyncTermsJob') # Adds the job right before the SyncTermsJob
126
+ chain.insert({ job: SomeOtherJob }, with: 'CanvasSync::Jobs::SyncTermsJob') # Adds the job to be performed concurrently with the SyncTermsJob
113
127
 
114
- CanvasSync.invoke_next(job_chain)
128
+ # Some Jobs (such as the SyncTermsJob) have a sub-chain for, eg, Courses.
129
+ # chain.insert is aware of these sub-chains and will recurse into them when looking for a before:/after:/with: reference
130
+ chain.insert({ job: SomeOtherJob }, after: 'CanvasSync::Jobs::SyncCoursesJob') # Adds the job to be performed after SyncCoursesJob (which is a sub-job of the terms job and is duplicated for each term in the term_scope:)
131
+ # You can also retrieve the sub-chain like so:
132
+ chain.get_sub_chain('CanvasSync::Jobs::SyncTermsJob')
115
133
  ```
116
134
 
117
135
  ### Processor
@@ -134,9 +152,8 @@ Let's say we have a custom Canvas report called "my_really_cool_report_csv". Fir
134
152
 
135
153
  ```ruby
136
154
  class MyReallyCoolReportJob < CanvasSync::Jobs::ReportStarter
137
- def perform(job_chain, options)
155
+ def perform(options)
138
156
  super(
139
- job_chain,
140
157
  'my_really_cool_report_csv', # Report name
141
158
  { "parameters[param1]" => true }, # Report parameters
142
159
  MyCoolProcessor.to_s, # Your processor class as a string
@@ -148,57 +165,6 @@ end
148
165
 
149
166
  You can also see examples in `lib/canvas_sync/jobs/sync_users_job.rb` and `lib/canvas_sync/jobs/sync_provisioning_report.rb`.
150
167
 
151
- ### Start the jobs
152
-
153
- The `CanvasSync.process_jobs` method allows you to pass in a chain of jobs to run. The job chain must be formatted like:
154
-
155
- ```ruby
156
- {
157
- jobs: [
158
- { job: JobClass, options: {} },
159
- { job: JobClass2, options: {} }
160
- ],
161
- global_options: {}
162
- }
163
- ```
164
-
165
- Here is an example that runs our new report job first followed by the builtin provisioning job:
166
-
167
- ```ruby
168
- job_chain = {
169
- jobs: [
170
- { job: MyReallyCoolReportJob, options: {} },
171
- { job: CanvasSync::Jobs::SyncProvisioningReportJob, options: { models: ['users', 'courses'] } }
172
- ],
173
- global_options: {}
174
- }
175
-
176
- CanvasSync.process_jobs(job_chain)
177
- ```
178
-
179
- What if you've got some other job that you want run that doesn't deal with a report? No problem! Just make sure you call `CanvasSync.invoke_next` at the end of your job. Example:
180
-
181
-
182
- ```ruby
183
- class SomeRandomJob < CanvasSync::Job
184
- def perform(job_chain, options)
185
- i_dunno_do_something!
186
-
187
- CanvasSync.invoke_next(job_chain)
188
- end
189
- end
190
-
191
- job_chain = {
192
- jobs: [
193
- { job: SomeRandomJob, options: {} },
194
- { job: CanvasSync::Jobs::SyncProvisioningReportJob, options: { models: ['users', 'courses'] } }
195
- ],
196
- global_options: {}
197
- }
198
-
199
- CanvasSync.process_jobs(job_chain)
200
- ```
201
-
202
168
  ### Batching
203
169
 
204
170
  The provisioning report uses the `CanvasSync::Importers::BulkImporter` class to bulk import rows with the activerecord-import gem. It inserts rows in batches of 10,000 by default. This can be customized by setting the `BULK_IMPORTER_BATCH_SIZE` environment variable.
@@ -257,6 +223,14 @@ class CanvasSyncModel < ApplicationRecord
257
223
  end
258
224
  ```
259
225
 
226
+ ### Job Batching
227
+ CanvasSync adds a `CanvasSync::JobBatches` module. It adds Sidekiq/sidekiq-batch like support for Job Batches.
228
+ It integrates automatically with both Sidekiq and ActiveJob. The API is highly similar to the Sidekiq-batch implementation,
229
+ documentation for which can be found at https://github.com/mperham/sidekiq/wiki/Batches
230
+
231
+ A batch can be created using `Sidekiq::Batch` or `CanvasSync::JobBatching::Batch`.
232
+
233
+ Also see `canvas_sync/jobs/begin_sync_chain_job`, `canvas_sync/Job_batches/jobs/serial_batch_job`, or `canvas_sync/Job_batches/jobs/concurrent_batch_job` for example usage.
260
234
 
261
235
  ## Legacy Support
262
236
 
@@ -308,80 +282,19 @@ end
308
282
  ## Syncronize different reports
309
283
  CanvasSync provides the functionality to import data from other reports into an specific table.
310
284
 
311
- This can be achived by using the followin method
312
-
313
- ```ruby
314
- CanvasSync.provisioning_sync(<array of models to sync>, term_scope: <optional term scope>)
315
- CanvasSync
316
- .simple_report_sync(
317
- [
318
- {
319
- report_name: <report name>,
320
- model: <model to sync>,
321
- params: <hash with the require parameters the report needs to sync>
322
- },
323
- {
324
- report_name: <report name>,
325
- model: <model to sync>,
326
- params: <hash with the require parameters the report needs to sync>
327
- },
328
- ...
329
- ],
330
- term_scope: <optional term scope>
331
- )
332
- ```
333
-
334
- Example:
285
+ This can be achieved by using the following method
335
286
 
336
287
  ```ruby
337
- CanvasSync
338
- .simple_report_sync(
339
- [
340
- {
341
- report_name: 'proservices_provisioning_csv',
342
- model: 'users',
343
- params: {
344
- "parameters[include_deleted]" => true,
345
- "parameters[users]" => true
346
- }
347
- },
348
- {
349
- report_name: 'proservices_provisioning_csv',
350
- model: 'accounts',
351
- params: {
352
- "parameters[include_deleted]" => true,
353
- "parameters[accounts]" => true
354
- }
355
- }
356
- ]
357
- )
358
- ```
359
-
360
- Example with the term_scope active:
361
-
362
- ```ruby
363
- CanvasSync
364
- .simple_report_sync(
365
- [
366
- {
367
- report_name: 'proservices_provisioning_csv',
368
- model: 'sections',
369
- params: {
370
- "parameters[include_deleted]" => true,
371
- "parameters[sections]" => true
372
- }
373
- },
374
- {
375
- report_name: 'proservices_provisioning_csv',
376
- model: 'courses',
377
- params: {
378
- "parameters[include_deleted]" => true,
379
- "parameters[courses]" => true
380
- }
381
- }
382
- ],
383
- term_scope: 'active'
384
- )
288
+ chain = CanvasSync.default_provisioning_report_chain
289
+ chain << {
290
+ job: CanvasSync::Jobs::SyncSimpleTableJob,
291
+ options: {
292
+ report_name: <report name>,
293
+ model: <model to sync>,
294
+ params: <hash with the require parameters the report needs to sync>
295
+ },
296
+ }
297
+ chain.process!
385
298
  ```
386
299
 
387
300
  ## Configuration
@@ -421,7 +334,6 @@ class CanvasSyncStarterWorker
421
334
  }
422
335
  }
423
336
  )
424
- CanvasSync.invoke_next(job_chain)
425
337
  end
426
338
 
427
339
  def self.handle_canvas_sync_error(error, **options)
@@ -0,0 +1,5 @@
1
+ module CanvasSync
2
+ class SyncBatch < ApplicationRecord
3
+ serialize :job_arguments, Array
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ class CreateCanvasSyncSyncBatches < CanvasSync::MiscHelper::MigrationClass
2
+ def change
3
+ create_table :canvas_sync_sync_batches do |t|
4
+ t.datetime :started_at
5
+ t.datetime :completed_at
6
+ t.string :status
7
+
8
+ t.timestamps
9
+ end
10
+ end
11
+ end
@@ -5,7 +5,6 @@ require "canvas_sync/engine"
5
5
  require "canvas_sync/misc_helper"
6
6
  require "canvas_sync/class_callback_executor"
7
7
  require "canvas_sync/job"
8
- require "canvas_sync/job_chain"
9
8
  require "canvas_sync/sidekiq_job"
10
9
  require "canvas_sync/api_syncable"
11
10
  require "canvas_sync/record"
@@ -14,6 +13,8 @@ require "canvas_sync/jobs/report_checker"
14
13
  require "canvas_sync/jobs/report_processor_job"
15
14
  require "canvas_sync/config"
16
15
 
16
+ require "canvas_sync/job_batches/batch"
17
+
17
18
  Dir[File.dirname(__FILE__) + "/canvas_sync/jobs/*.rb"].each { |file| require file }
18
19
  Dir[File.dirname(__FILE__) + "/canvas_sync/processors/*.rb"].each { |file| require file }
19
20
  Dir[File.dirname(__FILE__) + "/canvas_sync/importers/*.rb"].each { |file| require file }
@@ -58,6 +59,9 @@ module CanvasSync
58
59
  graded_submissions
59
60
  ].freeze
60
61
 
62
+ JobBatches::ChainBuilder.register_chain_job(CanvasSync::Jobs::SyncTermsJob, :sub_jobs)
63
+ JobBatches::ChainBuilder.register_chain_job(CanvasSync::Jobs::BeginSyncChainJob, 0)
64
+
61
65
  class << self
62
66
  # Runs a standard provisioning sync job with no extra report types.
63
67
  # Terms will be synced first using the API. If you are syncing users/roles/admins
@@ -73,51 +77,9 @@ module CanvasSync
73
77
  # and inserts it into the database. If an array of model names is provided then only those models will use legacy support.
74
78
  # @param account_id [Integer, nil] This optional parameter can be used if your Term creation and
75
79
  # canvas_sync_client methods require an account ID.
76
- def provisioning_sync(models, term_scope: nil, legacy_support: false, account_id: nil)
80
+ def provisioning_sync(models, **kwargs)
77
81
  validate_models!(models)
78
- invoke_next(default_provisioning_report_chain(models, term_scope, legacy_support, account_id))
79
- end
80
-
81
- # Runs a report different from provisioning sync job with no extra report types.
82
- #
83
- # @param reports_mapping [Array<Hash>] An Array of hash that list the model and params with their report you
84
- # want to import:
85
- # [{model: 'submissions', report_name: 'my_report_name_csv', params: { "parameters[include_deleted]" => true } }, ...]
86
- # @param term_scope [Symbol, nil] An optional symbol representing a scope that exists on the Term model.
87
- # The provisioning report will be run for each of the terms contained in that scope.
88
- # @param account_id [Integer, nil] This optional parameter can be used if your Term creation and
89
- # canvas_sync_client methods require an account ID.
90
- def simple_report_sync(reports_mapping, term_scope: nil, account_id: nil)
91
- invoke_next(simple_report_chain(reports_mapping, term_scope, account_id))
92
- end
93
-
94
- # Runs a chain of ordered jobs
95
- #
96
- # See the README for usage and examples
97
- #
98
- # @param job_chain [Hash]
99
- def process_jobs(job_chain)
100
- invoke_next(job_chain)
101
- end
102
-
103
- # @deprecated
104
- def duplicate_chain(job_chain)
105
- Marshal.load(Marshal.dump(job_chain))
106
- end
107
-
108
- # Invokes the next job in a chain of jobs.
109
- #
110
- # This should typically be called automatically by the gem where necessary.
111
- #
112
- # @param job_chain [Hash] A chain of jobs to execute
113
- def invoke_next(job_chain, extra_options: {})
114
- job_chain = JobChain.new(job_chain) unless job_chain.is_a?(JobChain)
115
- job_chain.perform_next(extra_options)
116
- end
117
-
118
- def fork(job_log, job_chain, keys: [], &blk)
119
- job_chain = JobChain.new(job_chain) unless job_chain.is_a?(JobChain)
120
- job_chain.fork(job_log, keys: keys, &blk)
82
+ default_provisioning_report_chain(models, **kwargs).process!
121
83
  end
122
84
 
123
85
  # Given a Model or Relation, scope it down to items that should be synced
@@ -134,33 +96,6 @@ module CanvasSync
134
96
  scope
135
97
  end
136
98
 
137
- # Syn any report to an specific set of models
138
- #
139
- # @param reports_mapping [Array<Hash>] List of reports with their specific model and params
140
- # @param term_scope [String]
141
- # @param account_id [Integer, nil] This optional parameter can be used if your Term creation and
142
- # canvas_sync_client methods require an account ID.
143
- def simple_report_chain(reports_mapping, term_scope=nil, account_id=nil)
144
- jobs = reports_mapping.map do |report|
145
- {
146
- job: CanvasSync::Jobs::SyncSimpleTableJob.to_s,
147
- options: {
148
- report_name: report[:report_name],
149
- model: report[:model],
150
- mapping: report[:model],
151
- klass: report[:model].singularize.capitalize.to_s,
152
- term_scope: term_scope,
153
- params: report[:params]
154
- }
155
- }
156
- end
157
-
158
- global_options = {}
159
- global_options[:account_id] = account_id if account_id.present?
160
-
161
- JobChain.new(jobs: jobs, global_options: global_options)
162
- end
163
-
164
99
  # Syncs terms, users/roles/admins if necessary, then the rest of the specified models.
165
100
  #
166
101
  # @param models [Array<String>]
@@ -171,7 +106,7 @@ module CanvasSync
171
106
  # @param account_id [Integer, nil] This optional parameter can be used if your Term creation and
172
107
  # canvas_sync_client methods require an account ID.
173
108
  # @return [Hash]
174
- def default_provisioning_report_chain(models, term_scope=nil, legacy_support=false, account_id=nil, options: {}) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/LineLength
109
+ def default_provisioning_report_chain(models, term_scope: nil, legacy_support: false, account_id: nil, updated_after: nil, options: {}) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/LineLength
175
110
  return unless models.present?
176
111
  models.map! &:to_s
177
112
  term_scope = term_scope.to_s if term_scope
@@ -190,64 +125,67 @@ module CanvasSync
190
125
  context_module_items: CanvasSync::Jobs::SyncContextModuleItemsJob,
191
126
  }.with_indifferent_access
192
127
 
193
- jobs = []
128
+ root_chain = JobBatches::ChainBuilder.new(CanvasSync::Jobs::BeginSyncChainJob)
129
+ concurrent_root_chain = JobBatches::ChainBuilder.new(JobBatches::ConcurrentBatchJob)
130
+ root_chain << concurrent_root_chain
131
+ current_chain = concurrent_root_chain
132
+
194
133
  try_add_model_job = ->(model) {
195
134
  return unless models.include?(model)
196
- jobs.push(job: model_job_map[model].to_s, options: options[model.to_sym] || {})
135
+ current_chain << { job: model_job_map[model].to_s, options: options[model.to_sym] || {} }
197
136
  models -= [model]
198
137
  }
199
138
 
200
139
  ##############################
201
- # Pre provisioning report jobs
140
+ # General provisioning jobs (not term-scoped)
202
141
  ##############################
203
142
 
204
- # Always sync Terms first
205
- models.unshift('terms') unless models.include?('terms')
206
- try_add_model_job.call('terms')
143
+ # Terms are always synced regardless of the models option
144
+ models -= ['terms']
207
145
 
208
- # Accounts, users, roles, and admins are synced before provisioning because they cannot be scoped to term
146
+ # Accounts, users, roles, and admins cannot be scoped to term
209
147
  try_add_model_job.call('accounts')
210
148
 
211
149
  # These Models use the provisioning report, but are not term-scoped,
212
- # so we sync them before to ensure work is not duplicated
150
+ # so we sync them outside of the term scoping to ensure work is not duplicated
213
151
  if term_scope.present?
214
152
  models -= (first_provisioning_models = models & ['users', 'pseudonyms'])
215
- jobs.concat(
216
- generate_provisioning_jobs(first_provisioning_models, options)
217
- )
153
+ current_chain.insert(generate_provisioning_jobs(first_provisioning_models, options))
218
154
  end
219
155
 
220
156
  try_add_model_job.call('roles')
221
157
  try_add_model_job.call('admins')
222
- pre_provisioning_jobs = jobs
223
158
 
224
159
  ###############################
225
- # Post provisioning report jobs
160
+ # Per-term provisioning jobs
226
161
  ###############################
227
162
 
228
- jobs = []
163
+ per_term_chain = JobBatches::ChainBuilder.new(model_job_map[:terms])
164
+ per_term_chain.params[:term_scope] = term_scope
165
+ current_chain << per_term_chain
166
+ current_chain = per_term_chain
167
+
229
168
  try_add_model_job.call('assignments')
230
169
  try_add_model_job.call('submissions')
231
170
  try_add_model_job.call('assignment_groups')
232
171
  try_add_model_job.call('context_modules')
233
172
  try_add_model_job.call('context_module_items')
234
- post_provisioning_jobs = jobs
173
+
174
+ current_chain.insert(
175
+ generate_provisioning_jobs(models, options, only_split: ['users'])
176
+ )
235
177
 
236
178
  ###############################
237
- # Main provisioning job and queueing
179
+ # Wrap it all up
238
180
  ###############################
239
181
 
240
- jobs = [
241
- *pre_provisioning_jobs,
242
- *generate_provisioning_jobs(models, options, job_options: { term_scope: term_scope }, only_split: ['users']),
243
- *post_provisioning_jobs,
244
- ]
245
-
246
- global_options = { legacy_support: legacy_support }
182
+ global_options = { legacy_support: legacy_support, updated_after: updated_after }
247
183
  global_options[:account_id] = account_id if account_id.present?
248
184
  global_options.merge!(options[:global]) if options[:global].present?
249
185
 
250
- JobChain.new(jobs: jobs, global_options: global_options)
186
+ root_chain.params[1] = global_options
187
+
188
+ root_chain
251
189
  end
252
190
 
253
191
  def group_by_job_options(model_list, options_hash, only_split: nil, default_key: :provisioning)