canvas_sync 0.10.3 → 0.11.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +57 -2
- data/db/migrate/20190916154829_add_fork_count_to_canvas_sync_job_logs.rb +13 -0
- data/lib/canvas_sync.rb +43 -8
- data/lib/canvas_sync/api_syncable.rb +8 -0
- data/lib/canvas_sync/generators/templates/migrations/create_admins.rb +7 -4
- data/lib/canvas_sync/generators/templates/migrations/create_roles.rb +3 -4
- data/lib/canvas_sync/generators/templates/models/account.rb +2 -0
- data/lib/canvas_sync/generators/templates/models/admin.rb +4 -5
- data/lib/canvas_sync/generators/templates/models/role.rb +0 -4
- data/lib/canvas_sync/generators/templates/models/term.rb +0 -2
- data/lib/canvas_sync/importers/bulk_importer.rb +7 -4
- data/lib/canvas_sync/job.rb +24 -1
- data/lib/canvas_sync/jobs/fork_gather.rb +59 -0
- data/lib/canvas_sync/jobs/report_starter.rb +12 -1
- data/lib/canvas_sync/jobs/sync_admins_job.rb +9 -5
- data/lib/canvas_sync/jobs/sync_assignment_groups_job.rb +2 -8
- data/lib/canvas_sync/jobs/sync_assignments_job.rb +2 -8
- data/lib/canvas_sync/jobs/sync_context_module_items_job.rb +2 -8
- data/lib/canvas_sync/jobs/sync_context_modules_job.rb +2 -8
- data/lib/canvas_sync/jobs/sync_provisioning_report_job.rb +23 -11
- data/lib/canvas_sync/jobs/sync_roles_job.rb +8 -5
- data/lib/canvas_sync/jobs/sync_simple_table_job.rb +17 -7
- data/lib/canvas_sync/jobs/sync_submissions_job.rb +1 -5
- data/lib/canvas_sync/jobs/sync_terms_job.rb +8 -2
- data/lib/canvas_sync/jobs/sync_users_job.rb +5 -7
- data/lib/canvas_sync/processors/assignment_groups_processor.rb +3 -2
- data/lib/canvas_sync/processors/assignments_processor.rb +3 -2
- data/lib/canvas_sync/processors/context_module_items_processor.rb +3 -2
- data/lib/canvas_sync/processors/context_modules_processor.rb +3 -2
- data/lib/canvas_sync/processors/normal_processor.rb +2 -1
- data/lib/canvas_sync/processors/provisioning_report_processor.rb +7 -2
- data/lib/canvas_sync/processors/submissions_processor.rb +3 -2
- data/lib/canvas_sync/version.rb +1 -1
- data/spec/canvas_sync/canvas_sync_spec.rb +17 -0
- data/spec/canvas_sync/jobs/fork_gather_spec.rb +73 -0
- data/spec/canvas_sync/jobs/job_spec.rb +39 -5
- data/spec/canvas_sync/jobs/sync_admins_job_spec.rb +2 -1
- data/spec/canvas_sync/jobs/sync_assignment_groups_job_spec.rb +1 -1
- data/spec/canvas_sync/jobs/sync_assignments_job_spec.rb +2 -2
- data/spec/canvas_sync/jobs/sync_context_module_items_job_spec.rb +2 -2
- data/spec/canvas_sync/jobs/sync_context_modules_job_spec.rb +2 -2
- data/spec/canvas_sync/jobs/sync_provisioning_report_job_spec.rb +20 -11
- data/spec/canvas_sync/jobs/sync_roles_job_spec.rb +2 -1
- data/spec/canvas_sync/jobs/sync_simple_table_job_spec.rb +1 -0
- data/spec/canvas_sync/jobs/sync_submissions_job_spec.rb +1 -1
- data/spec/canvas_sync/jobs/sync_users_job_spec.rb +1 -1
- data/spec/canvas_sync/models/admins_spec.rb +3 -5
- data/spec/canvas_sync/models/roles_spec.rb +5 -5
- data/spec/canvas_sync/models/term_spec.rb +3 -3
- data/spec/dummy/app/models/account.rb +2 -0
- data/spec/dummy/app/models/admin.rb +4 -5
- data/spec/dummy/app/models/role.rb +0 -4
- data/spec/dummy/app/models/term.rb +0 -2
- data/spec/dummy/db/migrate/{20190702203628_create_roles.rb → 20190927204545_create_roles.rb} +3 -4
- data/spec/dummy/db/migrate/{20190702203629_create_admins.rb → 20190927204546_create_admins.rb} +7 -4
- data/spec/dummy/db/schema.rb +12 -9
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +1248 -0
- data/spec/dummy/log/test.log +43258 -0
- data/spec/factories/admin_factory.rb +0 -1
- data/spec/support/fake_canvas.rb +2 -2
- data/spec/support/fixtures/canvas_responses/roles.json +6 -0
- data/spec/support/fixtures/reports/provisioning_csv_unzipped/courses.csv +3 -0
- data/spec/support/fixtures/reports/provisioning_csv_unzipped/users.csv +4 -0
- metadata +25 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: bff01b4de68a53faba86076d5390dd4c95d1feec6be899cc28a21ccf2c80a918
|
4
|
+
data.tar.gz: c676786d55e600ed58167c00290cd29e119b62b1bf7974c7ff4df46697759223
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c7340c9158ecc077e4f2246c24c6b4a608229cc2568a74b25b2afe8d7bebfd42e79deb23b49d22a7d7842e9a7923caaa1a43843fc68fa04497901477d480e9a2
|
7
|
+
data.tar.gz: a4f9ca68735ccc0ceb42cc9f8b486dd18b02e508b7ece0242b1a56c49c33db7e426126f0cb3d7a5bd5ba1057ee69082fe66ecfad252cab0fcb5d16c9c3fc12f4
|
data/README.md
CHANGED
@@ -91,6 +91,29 @@ 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
|
+
### Extensible chain
|
95
|
+
It is sometimes desired to extend or customize the chain of jobs that are run with CanvasSync.
|
96
|
+
This can be achieved with the following pattern:
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
job_chain = CanvasSync.default_provisioning_report_chain(
|
100
|
+
%i[list of models to sync]
|
101
|
+
)
|
102
|
+
|
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: {} }
|
110
|
+
|
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 } }
|
113
|
+
|
114
|
+
CanvasSync.invoke_next(job_chain)
|
115
|
+
```
|
116
|
+
|
94
117
|
### Processor
|
95
118
|
|
96
119
|
Your processor class must implement a `process` class method that receives a `report_file_path` and a hash of `options`. (See the `CanvasSync::Processors::ProvisioningReportProcessor` for an example.) The gem handles the work of enqueueing and downloading the report and then passes the file path to your class to process as needed. A simple example might be:
|
@@ -141,7 +164,7 @@ The `CanvasSync.process_jobs` method allows you to pass in a chain of jobs to ru
|
|
141
164
|
|
142
165
|
Here is an example that runs our new report job first followed by the builtin provisioning job:
|
143
166
|
|
144
|
-
```
|
167
|
+
```ruby
|
145
168
|
job_chain = {
|
146
169
|
jobs: [
|
147
170
|
{ job: MyReallyCoolReportJob, options: {} },
|
@@ -156,7 +179,7 @@ CanvasSync.process_jobs(job_chain)
|
|
156
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:
|
157
180
|
|
158
181
|
|
159
|
-
```
|
182
|
+
```ruby
|
160
183
|
class SomeRandomJob < CanvasSync::Job
|
161
184
|
def perform(job_chain, options)
|
162
185
|
i_dunno_do_something!
|
@@ -375,6 +398,38 @@ Available config options (if you add more, please update this!):
|
|
375
398
|
|
376
399
|
* `config.classes_to_only_log_errors_on` - use this if you are utilizing the `CanvasSync::JobLog` table, but want certain classes to only persist in the `job_logs` table if an error is encountered. This is useful if you've got a very frequently used job that's filling up your database, and only really care about tracking failures.
|
377
400
|
|
401
|
+
## Handling Job errors
|
402
|
+
|
403
|
+
If you need custom handling for when a CanvasSync Job fails, you can add an `:on_failure` option to you Job Chain's `:global_options`.
|
404
|
+
The value should be a String in the following format: `ModuleOrClass::AnotherModuleOrClass.class_method`.
|
405
|
+
The given method of the given class will be called when an error occurs.
|
406
|
+
The handling method should accept 2 arguments: `[error, **options]`
|
407
|
+
|
408
|
+
The current parameters provided in `**options` are:
|
409
|
+
- `job_chain`
|
410
|
+
- `job_log`
|
411
|
+
|
412
|
+
Example:
|
413
|
+
```ruby
|
414
|
+
class CanvasSyncStarterWorker
|
415
|
+
def perform
|
416
|
+
job_chain = CanvasSync.default_provisioning_report_chain(
|
417
|
+
%w[desired models],
|
418
|
+
options: {
|
419
|
+
global: {
|
420
|
+
on_failure: 'CanvasSyncStarterWorker.handle_canvas_sync_error',
|
421
|
+
}
|
422
|
+
}
|
423
|
+
)
|
424
|
+
CanvasSync.invoke_next(job_chain)
|
425
|
+
end
|
426
|
+
|
427
|
+
def self.handle_canvas_sync_error(error, **options)
|
428
|
+
# Do Stuff
|
429
|
+
end
|
430
|
+
end
|
431
|
+
```
|
432
|
+
|
378
433
|
## Upgrading
|
379
434
|
|
380
435
|
Re-running the generator when there's been a gem change will give you several choices if it detects conflicts between your local files and the updated generators. You can either view a diff or allow the generator to overwrite your local file. In most cases you may just want to add the code from the diff yourself so as not to break any of your customizations.
|
@@ -0,0 +1,13 @@
|
|
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
|
12
|
+
end
|
13
|
+
end
|
data/lib/canvas_sync.rb
CHANGED
@@ -6,6 +6,7 @@ require "canvas_sync/sidekiq_job"
|
|
6
6
|
require "canvas_sync/api_syncable"
|
7
7
|
require "canvas_sync/jobs/report_starter"
|
8
8
|
require "canvas_sync/jobs/report_checker"
|
9
|
+
require "canvas_sync/jobs/fork_gather"
|
9
10
|
require "canvas_sync/jobs/report_processor_job"
|
10
11
|
require "canvas_sync/jobs/sync_provisioning_report_job"
|
11
12
|
require "canvas_sync/jobs/sync_simple_table_job.rb"
|
@@ -100,12 +101,16 @@ module CanvasSync
|
|
100
101
|
invoke_next(job_chain)
|
101
102
|
end
|
102
103
|
|
104
|
+
def duplicate_chain(job_chain)
|
105
|
+
Marshal.load(Marshal.dump(job_chain))
|
106
|
+
end
|
107
|
+
|
103
108
|
# Invokes the next job in a chain of jobs.
|
104
109
|
#
|
105
110
|
# This should typically be called automatically by the gem where necessary.
|
106
111
|
#
|
107
112
|
# @param job_chain [Hash] A chain of jobs to execute
|
108
|
-
def invoke_next(job_chain)
|
113
|
+
def invoke_next(job_chain, extra_options: {})
|
109
114
|
return if job_chain[:jobs].empty?
|
110
115
|
|
111
116
|
# Make sure all job classes are serialized as strings
|
@@ -115,7 +120,32 @@ module CanvasSync
|
|
115
120
|
jobs = duped_job_chain[:jobs]
|
116
121
|
next_job = jobs.shift
|
117
122
|
next_job_class = next_job[:job].constantize
|
118
|
-
|
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
|
+
duped_job_chain = Marshal.load(Marshal.dump(job_chain))
|
130
|
+
duped_job_chain[:global_options][:fork_path] ||= []
|
131
|
+
duped_job_chain[:global_options][:fork_keys] ||= []
|
132
|
+
duped_job_chain[:global_options][:fork_path] << job_log.job_id
|
133
|
+
duped_job_chain[:global_options][:fork_keys] << ['canvas_term_id']
|
134
|
+
duped_job_chain[:global_options][:on_failure] ||= 'CanvasSync::Jobs::ForkGather.handle_branch_error'
|
135
|
+
sub_items = yield duped_job_chain
|
136
|
+
sub_count = sub_items.respond_to?(:count) ? sub_items.count : sub_items
|
137
|
+
job_log.fork_count = sub_count
|
138
|
+
sub_items
|
139
|
+
end
|
140
|
+
|
141
|
+
# Given a Model or Relation, scope it down to items that should be synced
|
142
|
+
def sync_scope(scope)
|
143
|
+
terms = %i[should_canvas_sync active_for_canvas_sync should_sync active_for_sync active]
|
144
|
+
terms.each do |t|
|
145
|
+
return scope.send(t) if scope.respond_to?(t)
|
146
|
+
end
|
147
|
+
Rails.logger.warn("Could not filter Syncable Scope for model '#{scope.try(:model)&.name || scope.name}'")
|
148
|
+
scope
|
119
149
|
end
|
120
150
|
|
121
151
|
# Syn any report to an specific set of models
|
@@ -159,7 +189,7 @@ module CanvasSync
|
|
159
189
|
return unless models.present?
|
160
190
|
models.map! &:to_s
|
161
191
|
term_scope = term_scope.to_s if term_scope
|
162
|
-
options = options.
|
192
|
+
options = options.deep_symbolize_keys!
|
163
193
|
|
164
194
|
model_job_map = {
|
165
195
|
terms: CanvasSync::Jobs::SyncTermsJob,
|
@@ -177,7 +207,7 @@ module CanvasSync
|
|
177
207
|
jobs = []
|
178
208
|
try_add_model_job = ->(model) {
|
179
209
|
return unless models.include?(model)
|
180
|
-
jobs.push(job: model_job_map[model].to_s, options: options[model] || {})
|
210
|
+
jobs.push(job: model_job_map[model].to_s, options: options[model.to_sym] || {})
|
181
211
|
models -= [model]
|
182
212
|
}
|
183
213
|
|
@@ -210,14 +240,19 @@ module CanvasSync
|
|
210
240
|
post_provisioning_jobs = jobs
|
211
241
|
|
212
242
|
jobs = pre_provisioning_jobs
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
243
|
+
if models.present?
|
244
|
+
provisioning_job = {
|
245
|
+
job: CanvasSync::Jobs::SyncProvisioningReportJob.to_s,
|
246
|
+
options: { term_scope: term_scope, models: models },
|
247
|
+
}
|
248
|
+
provisioning_job[:options].merge!(options[:provisioning]) if options[:provisioning].present?
|
249
|
+
jobs += Array.wrap(provisioning_job)
|
250
|
+
end
|
217
251
|
jobs += post_provisioning_jobs
|
218
252
|
|
219
253
|
global_options = { legacy_support: legacy_support }
|
220
254
|
global_options[:account_id] = account_id if account_id.present?
|
255
|
+
global_options.merge!(options[:global]) if options[:global].present?
|
221
256
|
|
222
257
|
{ jobs: jobs, global_options: global_options }
|
223
258
|
end
|
@@ -21,6 +21,14 @@ module CanvasSync::ApiSyncable
|
|
21
21
|
inst
|
22
22
|
end
|
23
23
|
|
24
|
+
def create_or_update_from_api_params(api_params)
|
25
|
+
api_params = api_params.with_indifferent_access
|
26
|
+
inst = find_or_initialize_by(canvas_id: api_params[:id])
|
27
|
+
inst.update_from_api_params(api_params)
|
28
|
+
inst.save! if inst.changed?
|
29
|
+
inst
|
30
|
+
end
|
31
|
+
|
24
32
|
def api_sync_options=(opts)
|
25
33
|
@api_sync_options = opts
|
26
34
|
end
|
@@ -5,13 +5,16 @@ class CreateAdmins < ActiveRecord::Migration[5.1]
|
|
5
5
|
create_table :admins do |t|
|
6
6
|
t.bigint :canvas_id, null: false
|
7
7
|
t.string :role_name
|
8
|
-
t.bigint :
|
9
|
-
t.
|
10
|
-
t.bigint :canvas_user_id
|
11
|
-
t.string :workflow_state
|
8
|
+
t.bigint :canvas_account_id
|
9
|
+
t.bigint :canvas_role_id
|
10
|
+
t.bigint :canvas_user_id
|
11
|
+
t.string :workflow_state
|
12
12
|
|
13
13
|
t.timestamps
|
14
14
|
end
|
15
15
|
add_index :admins, :canvas_id, unique: true
|
16
|
+
add_index :admins, :canvas_role_id
|
17
|
+
add_index :admins, :canvas_user_id
|
18
|
+
add_index :admins, :canvas_account_id
|
16
19
|
end
|
17
20
|
end
|
@@ -4,11 +4,10 @@ class CreateRoles < ActiveRecord::Migration[5.1]
|
|
4
4
|
def change
|
5
5
|
create_table :roles do |t|
|
6
6
|
t.integer :canvas_id, null: false
|
7
|
-
t.string :label
|
8
|
-
t.string :base_role_type
|
9
|
-
t.json :account
|
7
|
+
t.string :label
|
8
|
+
t.string :base_role_type
|
10
9
|
t.integer :canvas_account_id
|
11
|
-
t.string :workflow_state
|
10
|
+
t.string :workflow_state
|
12
11
|
t.json :permissions
|
13
12
|
|
14
13
|
t.timestamps
|
@@ -4,6 +4,7 @@ class Admin < ApplicationRecord
|
|
4
4
|
include CanvasSync::ApiSyncable
|
5
5
|
|
6
6
|
validates :canvas_id, uniqueness: true, presence: true
|
7
|
+
belongs_to :account, primary_key: :canvas_id, foreign_key: :canvas_account_id, optional: true
|
7
8
|
belongs_to :user, primary_key: :canvas_id, foreign_key: :canvas_user_id, optional: true
|
8
9
|
belongs_to :role, primary_key: :canvas_id, foreign_key: :canvas_role_id, optional: true
|
9
10
|
|
@@ -11,16 +12,14 @@ class Admin < ApplicationRecord
|
|
11
12
|
canvas_id: :id,
|
12
13
|
role_name: :role,
|
13
14
|
canvas_role_id: :role_id,
|
14
|
-
user_data: :user,
|
15
15
|
canvas_user_id: ->(r) { r['user']['id'] },
|
16
|
+
# NOTICE: The :account_id field is added by CanvasSync - it is not included in the Canvas API response
|
17
|
+
canvas_account_id: :account_id,
|
16
18
|
workflow_state: :workflow_state,
|
17
19
|
}, -> (api) {
|
18
|
-
admins = api.account_admins(
|
20
|
+
admins = api.account_admins(canvas_account_id).all_pages!
|
19
21
|
admin_data = admins.find{|admin| admin['id'] == canvas_id }
|
20
22
|
raise Footrest::HttpError::NotFound unless admin_data.present?
|
21
23
|
admin_data
|
22
24
|
})
|
23
|
-
|
24
|
-
def self.create_or_update(params); find_or_initialize_by(canvas_id: params['id']).update_from_api_params!(params); end
|
25
|
-
|
26
25
|
end
|
@@ -10,12 +10,8 @@ class Role < ApplicationRecord
|
|
10
10
|
canvas_id: :id,
|
11
11
|
label: :label,
|
12
12
|
base_role_type: :base_role_type,
|
13
|
-
account: :account,
|
14
13
|
canvas_account_id: ->(r) { r.dig('account', 'id') },
|
15
14
|
permissions: :permissions,
|
16
15
|
workflow_state: :workflow_state,
|
17
16
|
}, -> (api) { api.get("/api/v1/accounts/#{canvas_account_id}/roles/#{canvas_id}") })
|
18
|
-
|
19
|
-
def self.create_or_update(params); find_or_initialize_by(canvas_id: params['id']).update_from_api_params!(params); end
|
20
|
-
|
21
17
|
end
|
@@ -21,8 +21,6 @@ class Term < ApplicationRecord
|
|
21
21
|
term_data
|
22
22
|
})
|
23
23
|
|
24
|
-
def self.create_or_update(params); find_or_initialize_by(canvas_id: params['id']).update_from_api_params!(params); end
|
25
|
-
|
26
24
|
# This is a sample scope created by the CanvasSync gem; feel
|
27
25
|
# free to customize it for your tool's requirements.
|
28
26
|
scope :active, -> {
|
@@ -58,13 +58,12 @@ module CanvasSync
|
|
58
58
|
columns = columns.dup
|
59
59
|
|
60
60
|
update_conditions = {
|
61
|
-
condition: condition_sql(klass, columns),
|
61
|
+
condition: condition_sql(klass, columns, import_args[:sync_start_time]),
|
62
62
|
columns: columns
|
63
63
|
}
|
64
64
|
update_conditions[:conflict_target] = conflict_target if conflict_target
|
65
65
|
|
66
66
|
options = { validate: false, on_duplicate_key_update: update_conditions }.merge(import_args)
|
67
|
-
|
68
67
|
options.delete(:on_duplicate_key_update) if options.key?(:on_duplicate_key_ignore)
|
69
68
|
klass.import(columns, rows, options)
|
70
69
|
end
|
@@ -79,10 +78,14 @@ module CanvasSync
|
|
79
78
|
# started_at = Time.now
|
80
79
|
# run_the_users_sync!
|
81
80
|
# changed = User.where("updated_at >= ?", started_at)
|
82
|
-
def self.condition_sql(klass, columns)
|
81
|
+
def self.condition_sql(klass, columns, report_start)
|
83
82
|
columns_str = columns.map { |c| "#{klass.quoted_table_name}.#{c}" }.join(", ")
|
84
83
|
excluded_str = columns.map { |c| "EXCLUDED.#{c}" }.join(", ")
|
85
|
-
"(#{columns_str}) IS DISTINCT FROM (#{excluded_str})"
|
84
|
+
condition_sql = "(#{columns_str}) IS DISTINCT FROM (#{excluded_str})"
|
85
|
+
if klass.column_names.include?("updated_at") && report_start
|
86
|
+
condition_sql += " AND #{klass.quoted_table_name}.updated_at < '#{report_start}'"
|
87
|
+
end
|
88
|
+
condition_sql
|
86
89
|
end
|
87
90
|
|
88
91
|
def self.batch_size
|
data/lib/canvas_sync/job.rb
CHANGED
@@ -13,13 +13,26 @@ module CanvasSync
|
|
13
13
|
@job_log.started_at = Time.now
|
14
14
|
@job_log.save
|
15
15
|
|
16
|
+
@job_chain = job.arguments[0] if job.arguments[0].is_a?(Hash) && job.arguments[0].include?(:jobs)
|
17
|
+
|
16
18
|
begin
|
17
19
|
block.call
|
18
20
|
@job_log.status = JobLog::SUCCESS_STATUS
|
19
21
|
rescue => e # rubocop:disable Style/RescueStandardError
|
20
22
|
@job_log.exception = "#{e.class}: #{e.message}"
|
21
|
-
@job_log.backtrace = e.backtrace
|
23
|
+
@job_log.backtrace = e.backtrace.join('\n')
|
22
24
|
@job_log.status = JobLog::ERROR_STATUS
|
25
|
+
if @job_chain&.[](:global_options)&.[](:on_failure)&.present?
|
26
|
+
begin
|
27
|
+
class_name, method = @job_chain[:global_options][:on_failure].split('.')
|
28
|
+
klass = class_name.constantize
|
29
|
+
klass.send(method.to_sym, e, job_chain: @job_chain, job_log: @job_log)
|
30
|
+
rescue => e2
|
31
|
+
@job_log.backtrace += "\n\nError Occurred while handling an Error: #{e2.class}: #{e2.message}"
|
32
|
+
@job_log.backtrace += "\n" + e2.backtrace.join('\n')
|
33
|
+
Raven.captureException(e2) if defined? Raven
|
34
|
+
end
|
35
|
+
end
|
23
36
|
raise e
|
24
37
|
ensure
|
25
38
|
if CanvasSync.config.classes_to_only_log_errors_on.include?(@job_log.job_class) && @job_log.status != JobLog::ERROR_STATUS
|
@@ -43,5 +56,15 @@ module CanvasSync
|
|
43
56
|
status: JobLog::ENQUEUED_STATUS,
|
44
57
|
)
|
45
58
|
end
|
59
|
+
|
60
|
+
def update_or_create_model(model, params)
|
61
|
+
if model.respond_to? :create_or_update
|
62
|
+
model.create_or_update(params)
|
63
|
+
elsif model.method_defined? :update_from_api_params!
|
64
|
+
model.find_or_initialize_by(canvas_id: params['id']).update_from_api_params!(params)
|
65
|
+
else
|
66
|
+
raise "Could not create/update #{model.name}. It must have a create_or_update(params) ClassMethod or implement ApiSyncable."
|
67
|
+
end
|
68
|
+
end
|
46
69
|
end
|
47
70
|
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module CanvasSync
|
2
|
+
module Jobs
|
3
|
+
class ForkGather < CanvasSync::Job
|
4
|
+
def perform(job_chain, options)
|
5
|
+
forked_job = self.class.forked_at_job(job_chain)
|
6
|
+
|
7
|
+
if forked_job.present?
|
8
|
+
forked_job.with_lock do
|
9
|
+
forked_job.fork_count -= 1
|
10
|
+
forked_job.save!
|
11
|
+
end
|
12
|
+
|
13
|
+
if forked_job.fork_count <= 0
|
14
|
+
(job_chain[:global_options][:fork_keys] || []).pop&.each do |k|
|
15
|
+
job_chain[:global_options].delete(k.to_sym)
|
16
|
+
end
|
17
|
+
CanvasSync.invoke_next(job_chain)
|
18
|
+
end
|
19
|
+
else
|
20
|
+
CanvasSync.invoke_next(job_chain)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.handle_branch_error(e, job_chain:, skip_invoke: false, **kwargs)
|
25
|
+
return nil unless job_chain&.[](:global_options)&.[](:fork_path).present?
|
26
|
+
|
27
|
+
duped_chain = CanvasSync.duplicate_chain(job_chain)
|
28
|
+
job_list = duped_chain[:jobs]
|
29
|
+
while job_list.count > 0
|
30
|
+
job_class_name = job_list[0][:job]
|
31
|
+
job_class = job_class_name.constantize
|
32
|
+
break if job_class <= CanvasSync::Jobs::ForkGather
|
33
|
+
job_list.shift
|
34
|
+
end
|
35
|
+
|
36
|
+
return nil unless job_list.present?
|
37
|
+
|
38
|
+
if skip_invoke
|
39
|
+
duped_chain
|
40
|
+
else
|
41
|
+
CanvasSync.invoke_next(duped_chain)
|
42
|
+
true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
def self.forked_at_job(job_chain)
|
49
|
+
fork_item = (job_chain[:global_options][:fork_path] || []).pop
|
50
|
+
|
51
|
+
if fork_item.present?
|
52
|
+
CanvasSync::JobLog.find_by(job_id: fork_item)
|
53
|
+
else
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|