canvas_sync 0.15.1 → 0.17.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) 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/20170915210836_create_canvas_sync_job_log.rb +12 -31
  5. data/db/migrate/20180725155729_add_job_id_to_canvas_sync_job_logs.rb +4 -13
  6. data/db/migrate/20190916154829_add_fork_count_to_canvas_sync_job_logs.rb +3 -11
  7. data/db/migrate/20201018210836_create_canvas_sync_sync_batches.rb +11 -0
  8. data/lib/canvas_sync.rb +39 -118
  9. data/lib/canvas_sync/concerns/api_syncable.rb +27 -0
  10. data/lib/canvas_sync/concerns/legacy_columns.rb +5 -4
  11. data/lib/canvas_sync/generators/templates/migrations/create_submissions.rb +1 -0
  12. data/lib/canvas_sync/generators/templates/models/account.rb +3 -0
  13. data/lib/canvas_sync/generators/templates/models/submission.rb +1 -0
  14. data/lib/canvas_sync/job.rb +5 -5
  15. data/lib/canvas_sync/job_batches/batch.rb +399 -0
  16. data/lib/canvas_sync/job_batches/batch_aware_job.rb +62 -0
  17. data/lib/canvas_sync/job_batches/callback.rb +153 -0
  18. data/lib/canvas_sync/job_batches/chain_builder.rb +203 -0
  19. data/lib/canvas_sync/job_batches/context_hash.rb +147 -0
  20. data/lib/canvas_sync/job_batches/jobs/base_job.rb +7 -0
  21. data/lib/canvas_sync/job_batches/jobs/concurrent_batch_job.rb +18 -0
  22. data/lib/canvas_sync/job_batches/jobs/serial_batch_job.rb +73 -0
  23. data/lib/canvas_sync/job_batches/sidekiq.rb +91 -0
  24. data/lib/canvas_sync/job_batches/status.rb +63 -0
  25. data/lib/canvas_sync/jobs/begin_sync_chain_job.rb +34 -0
  26. data/lib/canvas_sync/jobs/report_checker.rb +3 -6
  27. data/lib/canvas_sync/jobs/report_processor_job.rb +2 -5
  28. data/lib/canvas_sync/jobs/report_starter.rb +27 -19
  29. data/lib/canvas_sync/jobs/sync_accounts_job.rb +3 -5
  30. data/lib/canvas_sync/jobs/sync_admins_job.rb +2 -4
  31. data/lib/canvas_sync/jobs/sync_assignment_groups_job.rb +2 -4
  32. data/lib/canvas_sync/jobs/sync_assignments_job.rb +2 -4
  33. data/lib/canvas_sync/jobs/sync_context_module_items_job.rb +2 -4
  34. data/lib/canvas_sync/jobs/sync_context_modules_job.rb +2 -4
  35. data/lib/canvas_sync/jobs/sync_provisioning_report_job.rb +5 -32
  36. data/lib/canvas_sync/jobs/sync_roles_job.rb +2 -5
  37. data/lib/canvas_sync/jobs/sync_simple_table_job.rb +11 -32
  38. data/lib/canvas_sync/jobs/sync_submissions_job.rb +2 -4
  39. data/lib/canvas_sync/jobs/sync_terms_job.rb +22 -7
  40. data/lib/canvas_sync/misc_helper.rb +15 -0
  41. data/lib/canvas_sync/processors/model_mappings.yml +3 -0
  42. data/lib/canvas_sync/version.rb +1 -1
  43. data/spec/canvas_sync/canvas_sync_spec.rb +126 -153
  44. data/spec/canvas_sync/jobs/job_spec.rb +9 -17
  45. data/spec/canvas_sync/jobs/report_checker_spec.rb +1 -3
  46. data/spec/canvas_sync/jobs/report_processor_job_spec.rb +0 -3
  47. data/spec/canvas_sync/jobs/report_starter_spec.rb +19 -28
  48. data/spec/canvas_sync/jobs/sync_admins_job_spec.rb +1 -4
  49. data/spec/canvas_sync/jobs/sync_assignment_groups_job_spec.rb +2 -1
  50. data/spec/canvas_sync/jobs/sync_assignments_job_spec.rb +3 -2
  51. data/spec/canvas_sync/jobs/sync_context_module_items_job_spec.rb +3 -2
  52. data/spec/canvas_sync/jobs/sync_context_modules_job_spec.rb +3 -2
  53. data/spec/canvas_sync/jobs/sync_provisioning_report_job_spec.rb +3 -35
  54. data/spec/canvas_sync/jobs/sync_roles_job_spec.rb +1 -4
  55. data/spec/canvas_sync/jobs/sync_simple_table_job_spec.rb +5 -12
  56. data/spec/canvas_sync/jobs/sync_submissions_job_spec.rb +2 -1
  57. data/spec/canvas_sync/jobs/sync_terms_job_spec.rb +1 -4
  58. data/spec/dummy/app/models/account.rb +3 -0
  59. data/spec/dummy/app/models/pseudonym.rb +14 -0
  60. data/spec/dummy/app/models/submission.rb +1 -0
  61. data/spec/dummy/app/models/user.rb +1 -0
  62. data/spec/dummy/config/environments/test.rb +2 -0
  63. data/spec/dummy/db/migrate/20190702203627_create_submissions.rb +1 -0
  64. data/spec/dummy/db/migrate/20201016181346_create_pseudonyms.rb +24 -0
  65. data/spec/dummy/db/schema.rb +25 -4
  66. data/spec/job_batching/batch_aware_job_spec.rb +100 -0
  67. data/spec/job_batching/batch_spec.rb +363 -0
  68. data/spec/job_batching/callback_spec.rb +38 -0
  69. data/spec/job_batching/flow_spec.rb +91 -0
  70. data/spec/job_batching/integration/integration.rb +57 -0
  71. data/spec/job_batching/integration/nested.rb +88 -0
  72. data/spec/job_batching/integration/simple.rb +47 -0
  73. data/spec/job_batching/integration/workflow.rb +134 -0
  74. data/spec/job_batching/integration_helper.rb +48 -0
  75. data/spec/job_batching/sidekiq_spec.rb +124 -0
  76. data/spec/job_batching/status_spec.rb +92 -0
  77. data/spec/job_batching/support/base_job.rb +14 -0
  78. data/spec/job_batching/support/sample_callback.rb +2 -0
  79. data/spec/spec_helper.rb +10 -0
  80. data/spec/support/fixtures/reports/submissions.csv +3 -3
  81. metadata +91 -9
  82. data/lib/canvas_sync/job_chain.rb +0 -57
  83. data/lib/canvas_sync/jobs/fork_gather.rb +0 -59
  84. data/spec/canvas_sync/jobs/fork_gather_spec.rb +0 -73
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 498fd2c3e3801c477077eced06fda11055ef8924
4
- data.tar.gz: 2a8b2979721246d70bd29fbb7a7a596480fb3b8e
3
+ metadata.gz: b4ffe8d3a42b647f8c30f1d831c4a86998bc7c1b
4
+ data.tar.gz: 2ba762a479d2df35e22a69b7930d051a2e43bd30
5
5
  SHA512:
6
- metadata.gz: e645ffde592d741f67abddf9067816f4e2c440c7fc44d7af344def28dcc0c277f0573855bd0dceb9e4b883be830516b210d5ef43be79679c5a4bab42f3acb41e
7
- data.tar.gz: c415bffe6b53ca6fc92202119300ff71cf39d8cbfeec7d0387104bcfe1777740c1085d0e2e5ef2694ab7fc9dc3a99346ceda1470c148d844178804622c640d1d
6
+ metadata.gz: 6702f6754b217edfe2c7e4ff51c586940e7073993b5e3a79464e49308edd274af9bc8165f38aa919ef3c288ec86f1623065a91f42121f68c4da1389f27a57cb2
7
+ data.tar.gz: 71365002bcf14b762b00b73ae805862d8691a2b78ecd0254c0ef7fcf56eea6caeae838652e7ca4a685834eb380a08e782cf78e591b5af2c8950b4d4fd763e704
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
@@ -1,35 +1,16 @@
1
- if Rails.version.to_f >= 5.0
2
- class CreateCanvasSyncJobLog < ActiveRecord::Migration[Rails.version.to_f]
3
- def change
4
- create_table :canvas_sync_job_logs do |t|
5
- t.datetime :started_at
6
- t.datetime :completed_at
7
- t.string :exception
8
- t.text :backtrace
9
- t.string :job_class
10
- t.string :status
11
- t.text :metadata
12
- t.text :job_arguments
1
+ class CreateCanvasSyncJobLog < CanvasSync::MiscHelper::MigrationClass
2
+ def change
3
+ create_table :canvas_sync_job_logs do |t|
4
+ t.datetime :started_at
5
+ t.datetime :completed_at
6
+ t.string :exception
7
+ t.text :backtrace
8
+ t.string :job_class
9
+ t.string :status
10
+ t.text :metadata
11
+ t.text :job_arguments
13
12
 
14
- t.timestamps
15
- end
16
- end
17
- end
18
- else
19
- class CreateCanvasSyncJobLog < ActiveRecord::Migration
20
- def change
21
- create_table :canvas_sync_job_logs do |t|
22
- t.datetime :started_at
23
- t.datetime :completed_at
24
- t.string :exception
25
- t.text :backtrace
26
- t.string :job_class
27
- t.string :status
28
- t.text :metadata
29
- t.text :job_arguments
30
-
31
- t.timestamps
32
- end
13
+ t.timestamps
33
14
  end
34
15
  end
35
16
  end
@@ -1,15 +1,6 @@
1
- if Rails.version.to_f >= 5.0
2
- class AddJobIdToCanvasSyncJobLogs < ActiveRecord::Migration[Rails.version.to_f]
3
- def change
4
- add_column :canvas_sync_job_logs, :job_id, :string
5
- add_index :canvas_sync_job_logs, :job_id
6
- end
7
- end
8
- else
9
- class AddJobIdToCanvasSyncJobLogs < ActiveRecord::Migration
10
- def change
11
- add_column :canvas_sync_job_logs, :job_id, :string
12
- add_index :canvas_sync_job_logs, :job_id
13
- end
1
+ class AddJobIdToCanvasSyncJobLogs < CanvasSync::MiscHelper::MigrationClass
2
+ def change
3
+ add_column :canvas_sync_job_logs, :job_id, :string
4
+ add_index :canvas_sync_job_logs, :job_id
14
5
  end
15
6
  end
@@ -1,13 +1,5 @@
1
- if Rails.version.to_f >= 5.0
2
- class AddForkCountToCanvasSyncJobLogs < ActiveRecord::Migration[Rails.version.to_f]
3
- def change
4
- add_column :canvas_sync_job_logs, :fork_count, :integer
5
- end
6
- end
7
- else
8
- class AddForkCountToCanvasSyncJobLogs < ActiveRecord::Migration
9
- def change
10
- add_column :canvas_sync_job_logs, :fork_count, :integer
11
- end
1
+ class AddForkCountToCanvasSyncJobLogs < CanvasSync::MiscHelper::MigrationClass
2
+ def change
3
+ add_column :canvas_sync_job_logs, :fork_count, :integer
12
4
  end
13
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
@@ -2,9 +2,9 @@ require "bearcat"
2
2
 
3
3
  require "canvas_sync/version"
4
4
  require "canvas_sync/engine"
5
+ require "canvas_sync/misc_helper"
5
6
  require "canvas_sync/class_callback_executor"
6
7
  require "canvas_sync/job"
7
- require "canvas_sync/job_chain"
8
8
  require "canvas_sync/sidekiq_job"
9
9
  require "canvas_sync/api_syncable"
10
10
  require "canvas_sync/record"
@@ -13,6 +13,8 @@ require "canvas_sync/jobs/report_checker"
13
13
  require "canvas_sync/jobs/report_processor_job"
14
14
  require "canvas_sync/config"
15
15
 
16
+ require "canvas_sync/job_batches/batch"
17
+
16
18
  Dir[File.dirname(__FILE__) + "/canvas_sync/jobs/*.rb"].each { |file| require file }
17
19
  Dir[File.dirname(__FILE__) + "/canvas_sync/processors/*.rb"].each { |file| require file }
18
20
  Dir[File.dirname(__FILE__) + "/canvas_sync/importers/*.rb"].each { |file| require file }
@@ -57,6 +59,9 @@ module CanvasSync
57
59
  graded_submissions
58
60
  ].freeze
59
61
 
62
+ JobBatches::ChainBuilder.register_chain_job(CanvasSync::Jobs::SyncTermsJob, :sub_jobs)
63
+ JobBatches::ChainBuilder.register_chain_job(CanvasSync::Jobs::BeginSyncChainJob, 0)
64
+
60
65
  class << self
61
66
  # Runs a standard provisioning sync job with no extra report types.
62
67
  # Terms will be synced first using the API. If you are syncing users/roles/admins
@@ -72,72 +77,9 @@ module CanvasSync
72
77
  # and inserts it into the database. If an array of model names is provided then only those models will use legacy support.
73
78
  # @param account_id [Integer, nil] This optional parameter can be used if your Term creation and
74
79
  # canvas_sync_client methods require an account ID.
75
- def provisioning_sync(models, term_scope: nil, legacy_support: false, account_id: nil)
80
+ def provisioning_sync(models, **kwargs)
76
81
  validate_models!(models)
77
- invoke_next(default_provisioning_report_chain(models, term_scope, legacy_support, account_id))
78
- end
79
-
80
- # Runs a report different from provisioning sync job with no extra report types.
81
- #
82
- # @param reports_mapping [Array<Hash>] An Array of hash that list the model and params with their report you
83
- # want to import:
84
- # [{model: 'submissions', report_name: 'my_report_name_csv', params: { "parameters[include_deleted]" => true } }, ...]
85
- # @param term_scope [Symbol, nil] An optional symbol representing a scope that exists on the Term model.
86
- # The provisioning report will be run for each of the terms contained in that scope.
87
- # @param account_id [Integer, nil] This optional parameter can be used if your Term creation and
88
- # canvas_sync_client methods require an account ID.
89
- def simple_report_sync(reports_mapping, term_scope: nil, account_id: nil)
90
- invoke_next(simple_report_chain(reports_mapping, term_scope, account_id))
91
- end
92
-
93
- # Runs a chain of ordered jobs
94
- #
95
- # See the README for usage and examples
96
- #
97
- # @param job_chain [Hash]
98
- def process_jobs(job_chain)
99
- invoke_next(job_chain)
100
- end
101
-
102
- def duplicate_chain(job_chain)
103
- Marshal.load(Marshal.dump(job_chain))
104
- end
105
-
106
- # Invokes the next job in a chain of jobs.
107
- #
108
- # This should typically be called automatically by the gem where necessary.
109
- #
110
- # @param job_chain [Hash] A chain of jobs to execute
111
- def invoke_next(job_chain, extra_options: {})
112
- job_chain = job_chain.chain_data if job_chain.is_a?(JobChain)
113
-
114
- return if job_chain[:jobs].empty?
115
-
116
- # Make sure all job classes are serialized as strings
117
- job_chain[:jobs].each { |job| job[:job] = job[:job].to_s }
118
-
119
- duped_job_chain = Marshal.load(Marshal.dump(job_chain))
120
- jobs = duped_job_chain[:jobs]
121
- next_job = jobs.shift
122
- next_job_class = next_job[:job].constantize
123
- next_options = next_job[:options] || {}
124
- next_options.merge!(extra_options)
125
- next_job_class.perform_later(duped_job_chain, next_options)
126
- end
127
-
128
- def fork(job_log, job_chain, keys: [])
129
- job_chain = job_chain.chain_data if job_chain.is_a?(JobChain)
130
-
131
- duped_job_chain = Marshal.load(Marshal.dump(job_chain))
132
- duped_job_chain[:global_options][:fork_path] ||= []
133
- duped_job_chain[:global_options][:fork_keys] ||= []
134
- duped_job_chain[:global_options][:fork_path] << job_log.job_id
135
- duped_job_chain[:global_options][:fork_keys] << ['canvas_term_id']
136
- duped_job_chain[:global_options][:on_failure] ||= 'CanvasSync::Jobs::ForkGather.handle_branch_error'
137
- sub_items = yield duped_job_chain
138
- sub_count = sub_items.respond_to?(:count) ? sub_items.count : sub_items
139
- job_log.fork_count = sub_count
140
- sub_items
82
+ default_provisioning_report_chain(models, **kwargs).process!
141
83
  end
142
84
 
143
85
  # Given a Model or Relation, scope it down to items that should be synced
@@ -146,37 +88,14 @@ module CanvasSync
146
88
  terms.each do |t|
147
89
  return scope.send(t) if scope.respond_to?(t)
148
90
  end
91
+ model = scope.try(:model) || scope
92
+ if model.try(:column_names)&.include?(:workflow_state)
93
+ return scope.where.not(workflow_state: %w[deleted])
94
+ end
149
95
  Rails.logger.warn("Could not filter Syncable Scope for model '#{scope.try(:model)&.name || scope.name}'")
150
96
  scope
151
97
  end
152
98
 
153
- # Syn any report to an specific set of models
154
- #
155
- # @param reports_mapping [Array<Hash>] List of reports with their specific model and params
156
- # @param term_scope [String]
157
- # @param account_id [Integer, nil] This optional parameter can be used if your Term creation and
158
- # canvas_sync_client methods require an account ID.
159
- def simple_report_chain(reports_mapping, term_scope=nil, account_id=nil)
160
- jobs = reports_mapping.map do |report|
161
- {
162
- job: CanvasSync::Jobs::SyncSimpleTableJob.to_s,
163
- options: {
164
- report_name: report[:report_name],
165
- model: report[:model],
166
- mapping: report[:model],
167
- klass: report[:model].singularize.capitalize.to_s,
168
- term_scope: term_scope,
169
- params: report[:params]
170
- }
171
- }
172
- end
173
-
174
- global_options = {}
175
- global_options[:account_id] = account_id if account_id.present?
176
-
177
- JobChain.new(jobs: jobs, global_options: global_options)
178
- end
179
-
180
99
  # Syncs terms, users/roles/admins if necessary, then the rest of the specified models.
181
100
  #
182
101
  # @param models [Array<String>]
@@ -187,7 +106,7 @@ module CanvasSync
187
106
  # @param account_id [Integer, nil] This optional parameter can be used if your Term creation and
188
107
  # canvas_sync_client methods require an account ID.
189
108
  # @return [Hash]
190
- 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
191
110
  return unless models.present?
192
111
  models.map! &:to_s
193
112
  term_scope = term_scope.to_s if term_scope
@@ -206,64 +125,66 @@ module CanvasSync
206
125
  context_module_items: CanvasSync::Jobs::SyncContextModuleItemsJob,
207
126
  }.with_indifferent_access
208
127
 
209
- 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
+
210
133
  try_add_model_job = ->(model) {
211
134
  return unless models.include?(model)
212
- 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] || {} }
213
136
  models -= [model]
214
137
  }
215
138
 
216
139
  ##############################
217
- # Pre provisioning report jobs
140
+ # General provisioning jobs (not term-scoped)
218
141
  ##############################
219
142
 
220
- # Always sync Terms first
221
- models.unshift('terms') unless models.include?('terms')
222
- try_add_model_job.call('terms')
143
+ # Terms are always synced regardless of the models option
144
+ models -= ['terms']
223
145
 
224
- # 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
225
147
  try_add_model_job.call('accounts')
226
148
 
227
149
  # These Models use the provisioning report, but are not term-scoped,
228
- # 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
229
151
  if term_scope.present?
230
152
  models -= (first_provisioning_models = models & ['users', 'pseudonyms'])
231
- jobs.concat(
232
- generate_provisioning_jobs(first_provisioning_models, options)
233
- )
153
+ current_chain.insert(generate_provisioning_jobs(first_provisioning_models, options))
234
154
  end
235
155
 
236
156
  try_add_model_job.call('roles')
237
157
  try_add_model_job.call('admins')
238
- pre_provisioning_jobs = jobs
239
158
 
240
159
  ###############################
241
- # Post provisioning report jobs
160
+ # Per-term provisioning jobs
242
161
  ###############################
243
162
 
244
- jobs = []
163
+ per_term_chain = JobBatches::ChainBuilder.new(model_job_map[:terms])
164
+ current_chain << per_term_chain
165
+ current_chain = per_term_chain
166
+
245
167
  try_add_model_job.call('assignments')
246
168
  try_add_model_job.call('submissions')
247
169
  try_add_model_job.call('assignment_groups')
248
170
  try_add_model_job.call('context_modules')
249
171
  try_add_model_job.call('context_module_items')
250
- post_provisioning_jobs = jobs
172
+
173
+ current_chain.insert(
174
+ generate_provisioning_jobs(models, options, job_options: { term_scope: term_scope }, only_split: ['users'])
175
+ )
251
176
 
252
177
  ###############################
253
- # Main provisioning job and queueing
178
+ # Wrap it all up
254
179
  ###############################
255
180
 
256
- jobs = [
257
- *pre_provisioning_jobs,
258
- *generate_provisioning_jobs(models, options, job_options: { term_scope: term_scope }, only_split: ['users']),
259
- *post_provisioning_jobs,
260
- ]
261
-
262
- global_options = { legacy_support: legacy_support }
181
+ global_options = { legacy_support: legacy_support, updated_after: updated_after }
263
182
  global_options[:account_id] = account_id if account_id.present?
264
183
  global_options.merge!(options[:global]) if options[:global].present?
265
184
 
266
- JobChain.new(jobs: jobs, global_options: global_options)
185
+ root_chain.params[1] = global_options
186
+
187
+ root_chain
267
188
  end
268
189
 
269
190
  def group_by_job_options(model_list, options_hash, only_split: nil, default_key: :provisioning)