canvas_sync 0.14.0 → 0.16.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/canvas_sync.rb +68 -16
- data/lib/canvas_sync/concerns/api_syncable.rb +1 -1
- data/lib/canvas_sync/concerns/legacy_columns.rb +5 -4
- data/lib/canvas_sync/generators/templates/migrations/create_pseudonyms.rb +18 -0
- data/lib/canvas_sync/generators/templates/migrations/create_submissions.rb +1 -0
- data/lib/canvas_sync/generators/templates/models/account.rb +3 -0
- data/lib/canvas_sync/generators/templates/models/pseudonym.rb +8 -0
- data/lib/canvas_sync/generators/templates/models/submission.rb +1 -0
- data/lib/canvas_sync/generators/templates/models/user.rb +1 -0
- data/lib/canvas_sync/jobs/sync_provisioning_report_job.rb +1 -1
- data/lib/canvas_sync/processors/model_mappings.yml +22 -0
- data/lib/canvas_sync/processors/provisioning_report_processor.rb +9 -0
- data/lib/canvas_sync/version.rb +1 -1
- data/spec/canvas_sync/canvas_sync_spec.rb +5 -2
- data/spec/dummy/db/migrate/20190702203627_create_submissions.rb +1 -0
- data/spec/dummy/db/schema.rb +1 -0
- data/spec/support/fixtures/reports/submissions.csv +3 -3
- metadata +4 -5
- data/lib/canvas_sync/jobs/sync_users_job.rb +0 -26
- data/spec/canvas_sync/jobs/sync_users_job_spec.rb +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8a38d324ba9fe5a06ad6cd7f9bc4fa3686ad8389
|
4
|
+
data.tar.gz: 9f67e4ad9dd7dc8be237871dd2224ee19a6e6e48
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 500266ca331f28db32e08c8e5d4f780f9e303299f07c3497f22a43236d50901b1f482dd08cd593338f11760aeb91693a1b43880809b024fb7ff6fe0bffe6fcfd
|
7
|
+
data.tar.gz: 18add737517d8ee376c4905b953dfdd3519cefebd7787530ed46f8c33fd78f58c21794c8853a8bc3d392a85aecadca536a29ca4479b4fda569a256fcc9a43d6f
|
data/lib/canvas_sync.rb
CHANGED
@@ -22,6 +22,7 @@ Dir[File.dirname(__FILE__) + "/canvas_sync/concerns/**/*.rb"].each { |file| req
|
|
22
22
|
module CanvasSync
|
23
23
|
SUPPORTED_MODELS = %w[
|
24
24
|
users
|
25
|
+
pseudonyms
|
25
26
|
courses
|
26
27
|
groups
|
27
28
|
group_memberships
|
@@ -131,7 +132,7 @@ module CanvasSync
|
|
131
132
|
duped_job_chain[:global_options][:fork_path] ||= []
|
132
133
|
duped_job_chain[:global_options][:fork_keys] ||= []
|
133
134
|
duped_job_chain[:global_options][:fork_path] << job_log.job_id
|
134
|
-
duped_job_chain[:global_options][:fork_keys] <<
|
135
|
+
duped_job_chain[:global_options][:fork_keys] << keys
|
135
136
|
duped_job_chain[:global_options][:on_failure] ||= 'CanvasSync::Jobs::ForkGather.handle_branch_error'
|
136
137
|
sub_items = yield duped_job_chain
|
137
138
|
sub_count = sub_items.respond_to?(:count) ? sub_items.count : sub_items
|
@@ -145,6 +146,10 @@ module CanvasSync
|
|
145
146
|
terms.each do |t|
|
146
147
|
return scope.send(t) if scope.respond_to?(t)
|
147
148
|
end
|
149
|
+
model = scope.try(:model) || scope
|
150
|
+
if model.try(:column_names)&.include?(:workflow_state)
|
151
|
+
return scope.where.not(workflow_state: %w[deleted])
|
152
|
+
end
|
148
153
|
Rails.logger.warn("Could not filter Syncable Scope for model '#{scope.try(:model)&.name || scope.name}'")
|
149
154
|
scope
|
150
155
|
end
|
@@ -195,7 +200,6 @@ module CanvasSync
|
|
195
200
|
model_job_map = {
|
196
201
|
terms: CanvasSync::Jobs::SyncTermsJob,
|
197
202
|
accounts: CanvasSync::Jobs::SyncAccountsJob,
|
198
|
-
users: CanvasSync::Jobs::SyncUsersJob,
|
199
203
|
roles: CanvasSync::Jobs::SyncRolesJob,
|
200
204
|
admins: CanvasSync::Jobs::SyncAdminsJob,
|
201
205
|
|
@@ -223,35 +227,41 @@ module CanvasSync
|
|
223
227
|
|
224
228
|
# Accounts, users, roles, and admins are synced before provisioning because they cannot be scoped to term
|
225
229
|
try_add_model_job.call('accounts')
|
226
|
-
|
230
|
+
|
231
|
+
# These Models use the provisioning report, but are not term-scoped,
|
232
|
+
# so we sync them before to ensure work is not duplicated
|
233
|
+
if term_scope.present?
|
234
|
+
models -= (first_provisioning_models = models & ['users', 'pseudonyms'])
|
235
|
+
jobs.concat(
|
236
|
+
generate_provisioning_jobs(first_provisioning_models, options)
|
237
|
+
)
|
238
|
+
end
|
239
|
+
|
227
240
|
try_add_model_job.call('roles')
|
228
241
|
try_add_model_job.call('admins')
|
229
|
-
|
230
242
|
pre_provisioning_jobs = jobs
|
231
|
-
jobs = []
|
232
243
|
|
233
244
|
###############################
|
234
245
|
# Post provisioning report jobs
|
235
246
|
###############################
|
236
247
|
|
248
|
+
jobs = []
|
237
249
|
try_add_model_job.call('assignments')
|
238
250
|
try_add_model_job.call('submissions')
|
239
251
|
try_add_model_job.call('assignment_groups')
|
240
252
|
try_add_model_job.call('context_modules')
|
241
253
|
try_add_model_job.call('context_module_items')
|
242
|
-
|
243
254
|
post_provisioning_jobs = jobs
|
244
255
|
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
jobs += post_provisioning_jobs
|
256
|
+
###############################
|
257
|
+
# Main provisioning job and queueing
|
258
|
+
###############################
|
259
|
+
|
260
|
+
jobs = [
|
261
|
+
*pre_provisioning_jobs,
|
262
|
+
*generate_provisioning_jobs(models, options, job_options: { term_scope: term_scope }, only_split: ['users']),
|
263
|
+
*post_provisioning_jobs,
|
264
|
+
]
|
255
265
|
|
256
266
|
global_options = { legacy_support: legacy_support }
|
257
267
|
global_options[:account_id] = account_id if account_id.present?
|
@@ -260,6 +270,48 @@ module CanvasSync
|
|
260
270
|
JobChain.new(jobs: jobs, global_options: global_options)
|
261
271
|
end
|
262
272
|
|
273
|
+
def group_by_job_options(model_list, options_hash, only_split: nil, default_key: :provisioning)
|
274
|
+
dup_models = [ *model_list ]
|
275
|
+
unique_option_models = {}
|
276
|
+
|
277
|
+
filtered_models = only_split ? (only_split & model_list) : model_list
|
278
|
+
filtered_models.each do |m|
|
279
|
+
mopts = options_hash[m.to_sym] || options_hash[default_key]
|
280
|
+
unique_option_models[mopts] ||= []
|
281
|
+
unique_option_models[mopts] << m
|
282
|
+
dup_models.delete(m)
|
283
|
+
end
|
284
|
+
|
285
|
+
if dup_models.present?
|
286
|
+
mopts = options_hash[default_key]
|
287
|
+
unique_option_models[mopts] ||= []
|
288
|
+
unique_option_models[mopts].concat(dup_models)
|
289
|
+
end
|
290
|
+
|
291
|
+
unique_option_models
|
292
|
+
end
|
293
|
+
|
294
|
+
def generate_provisioning_jobs(model_list, options_hash, job_options: {}, only_split: nil, default_key: :provisioning)
|
295
|
+
# Group the model options as best we can.
|
296
|
+
# This is mainly for backwards compatibility, since 'users' was previously it's own job
|
297
|
+
unique_option_models = group_by_job_options(
|
298
|
+
model_list,
|
299
|
+
options_hash,
|
300
|
+
only_split: only_split,
|
301
|
+
default_key: default_key,
|
302
|
+
)
|
303
|
+
|
304
|
+
unique_option_models.map do |mopts, models|
|
305
|
+
opts = { models: models }
|
306
|
+
opts.merge!(job_options)
|
307
|
+
opts.merge!(mopts) if mopts.present?
|
308
|
+
{
|
309
|
+
job: CanvasSync::Jobs::SyncProvisioningReportJob.to_s,
|
310
|
+
options: opts,
|
311
|
+
}
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
263
315
|
# Calls the canvas_sync_client in your app. If you have specified an account
|
264
316
|
# ID when starting the job it will pass the account ID to your canvas_sync_client method.
|
265
317
|
#
|
@@ -146,7 +146,7 @@ module CanvasSync::Concerns
|
|
146
146
|
# Apply a response Hash from the API to this model's attributes and save if changed?
|
147
147
|
# @param [Hash] api_params API-format Hash
|
148
148
|
# @return [self] self
|
149
|
-
def update_from_api_params(
|
149
|
+
def update_from_api_params(*args)
|
150
150
|
assign_from_api_params(*args)
|
151
151
|
save if changed?
|
152
152
|
end
|
@@ -15,13 +15,14 @@ module CanvasSync::Concerns
|
|
15
15
|
private
|
16
16
|
|
17
17
|
def legacy_column_apply(cls)
|
18
|
-
|
19
|
-
|
18
|
+
return if cls.abstract_class
|
19
|
+
cid_column = "canvas_#{cls.name.downcase}_id"
|
20
|
+
column_names = cls.columns.map(&:name)
|
20
21
|
return if column_names.include?('canvas_id') && column_names.include?(cid_column)
|
21
22
|
if column_names.include?('canvas_id')
|
22
|
-
|
23
|
+
cls.alias_attribute(cid_column.to_sym, :canvas_id)
|
23
24
|
elsif column_names.include?(cid_column)
|
24
|
-
|
25
|
+
cls.alias_attribute(:canvas_id, cid_column.to_sym)
|
25
26
|
end
|
26
27
|
rescue ActiveRecord::StatementInvalid
|
27
28
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# <%= autogenerated_migration_warning %>
|
2
|
+
|
3
|
+
class CreatePseudonyms < ActiveRecord::Migration[5.1]
|
4
|
+
def change
|
5
|
+
create_table :pseudonyms do |t|
|
6
|
+
t.bigint :canvas_id, null: false
|
7
|
+
t.bigint :canvas_user_id
|
8
|
+
t.string :sis_id
|
9
|
+
t.string :unique_id
|
10
|
+
t.string :workflow_state
|
11
|
+
|
12
|
+
t.timestamps
|
13
|
+
end
|
14
|
+
|
15
|
+
add_index :pseudonyms, :canvas_id, unique: true
|
16
|
+
add_index :pseudonyms, :canvas_user_id
|
17
|
+
end
|
18
|
+
end
|
@@ -14,6 +14,9 @@ class Account < ApplicationRecord
|
|
14
14
|
primary_key: :canvas_id, foreign_key: :canvas_parent_account_id
|
15
15
|
has_many :groups, primary_key: :canvas_id, foreign_key: :canvas_account_id
|
16
16
|
|
17
|
+
scope :active, -> { where.not(workflow_state: 'deleted') }
|
18
|
+
# scope :should_canvas_sync, -> { active } # Optional - uses .active if not given
|
19
|
+
|
17
20
|
api_syncable({
|
18
21
|
name: :name,
|
19
22
|
workflow_state: :workflow_state,
|
@@ -5,6 +5,7 @@ class User < ApplicationRecord
|
|
5
5
|
include CanvasSync::Concerns::ApiSyncable
|
6
6
|
|
7
7
|
validates :canvas_id, uniqueness: true, presence: true
|
8
|
+
has_many :pseudonyms, primary_key: :canvas_id, foreign_key: :canvas_user_id
|
8
9
|
has_many :enrollments, primary_key: :canvas_id, foreign_key: :canvas_user_id
|
9
10
|
has_many :admins, primary_key: :canvas_id, foreign_key: :canvas_user_id
|
10
11
|
has_many :admin_roles, through: :admins, source: :role
|
@@ -29,6 +29,25 @@ users:
|
|
29
29
|
database_column_name: login_id
|
30
30
|
type: string
|
31
31
|
|
32
|
+
pseudonyms:
|
33
|
+
conflict_target: pseudonym_id
|
34
|
+
report_columns:
|
35
|
+
pseudonym_id:
|
36
|
+
database_column_name: canvas_id
|
37
|
+
type: integer
|
38
|
+
canvas_user_id:
|
39
|
+
database_column_name: canvas_user_id
|
40
|
+
type: integer
|
41
|
+
sis_user_id:
|
42
|
+
database_column_name: sis_id
|
43
|
+
type: string
|
44
|
+
unique_id:
|
45
|
+
database_column_name: unique_id
|
46
|
+
type: string
|
47
|
+
status:
|
48
|
+
database_column_name: workflow_state
|
49
|
+
type: string
|
50
|
+
|
32
51
|
accounts:
|
33
52
|
conflict_target: canvas_account_id
|
34
53
|
report_columns:
|
@@ -230,6 +249,9 @@ submissions:
|
|
230
249
|
submission_date:
|
231
250
|
database_column_name: submitted_at
|
232
251
|
type: datetime
|
252
|
+
due_at:
|
253
|
+
database_column_name: due_at
|
254
|
+
type: datetime
|
233
255
|
graded_date:
|
234
256
|
database_column_name: graded_at
|
235
257
|
type: datetime
|
@@ -78,6 +78,15 @@ module CanvasSync
|
|
78
78
|
)
|
79
79
|
end
|
80
80
|
|
81
|
+
def bulk_process_pseudonyms(report_file_path)
|
82
|
+
CanvasSync::Importers::BulkImporter.import(
|
83
|
+
report_file_path,
|
84
|
+
mapping[:pseudonyms][:report_columns],
|
85
|
+
Pseudonym,
|
86
|
+
mapping[:pseudonyms][:conflict_target].to_sym,
|
87
|
+
)
|
88
|
+
end
|
89
|
+
|
81
90
|
def bulk_process_accounts(report_file_path)
|
82
91
|
CanvasSync::Importers::BulkImporter.import(
|
83
92
|
report_file_path,
|
data/lib/canvas_sync/version.rb
CHANGED
@@ -34,7 +34,7 @@ RSpec.describe CanvasSync do
|
|
34
34
|
expect(chain.chain_data).to eq({
|
35
35
|
jobs: [
|
36
36
|
{ job: CanvasSync::Jobs::SyncTermsJob.to_s, options: { a: 1 } },
|
37
|
-
{ job: CanvasSync::Jobs::
|
37
|
+
{ job: CanvasSync::Jobs::SyncProvisioningReportJob.to_s, options: { models: ['users'], b: 2 } },
|
38
38
|
{ job: CanvasSync::Jobs::SyncProvisioningReportJob.to_s, options: { term_scope: 'active', models: ['courses'], c: 3 } }
|
39
39
|
],
|
40
40
|
global_options: { legacy_support: false, d: 4 }
|
@@ -47,7 +47,7 @@ RSpec.describe CanvasSync do
|
|
47
47
|
expect(chain.chain_data).to eq({
|
48
48
|
jobs: [
|
49
49
|
{ job: CanvasSync::Jobs::SyncTermsJob.to_s, options: {} },
|
50
|
-
{ job: CanvasSync::Jobs::
|
50
|
+
{ job: CanvasSync::Jobs::SyncProvisioningReportJob.to_s, options: { models: ['users'] } },
|
51
51
|
{ job: CanvasSync::Jobs::SyncProvisioningReportJob.to_s, options: { term_scope: 'active', models: ['courses'] } }
|
52
52
|
],
|
53
53
|
global_options: { legacy_support: false }
|
@@ -247,4 +247,7 @@ RSpec.describe CanvasSync do
|
|
247
247
|
end
|
248
248
|
end
|
249
249
|
|
250
|
+
describe ".sync_scope" do
|
251
|
+
|
252
|
+
end
|
250
253
|
end
|
data/spec/dummy/db/schema.rb
CHANGED
@@ -225,6 +225,7 @@ ActiveRecord::Schema.define(version: 2020_04_16_214248) do
|
|
225
225
|
t.bigint "canvas_assignment_id"
|
226
226
|
t.bigint "canvas_user_id"
|
227
227
|
t.datetime "submitted_at"
|
228
|
+
t.datetime "due_at"
|
228
229
|
t.datetime "graded_at"
|
229
230
|
t.float "score"
|
230
231
|
t.float "points_possible"
|
@@ -1,3 +1,3 @@
|
|
1
|
-
"canvas user id","sis user id","user name","canvas course id","sis course id","course name","assignment id","assignment name","submission date","graded date","score","points possible","submission id","workflow state","excused"
|
2
|
-
1,1,userName,1,1,courseName,1,assignmentName,2017-09-21 18:28:15 UTC,2017-09-21 19:36:23 UTC,5,10,1,graded,true
|
3
|
-
1,1,userName,1,1,courseName,2,assignment2Name,2017-09-21 18:28:15 UTC,2017-09-21 19:36:23 UTC,5,10,2,submitted,false
|
1
|
+
"canvas user id","sis user id","user name","canvas course id","sis course id","course name","assignment id","assignment name","submission date","graded date","score","points possible","submission id","workflow state","excused","due at"
|
2
|
+
1,1,userName,1,1,courseName,1,assignmentName,2017-09-21 18:28:15 UTC,2017-09-21 19:36:23 UTC,5,10,1,graded,true,2017-09-27 18:28:15 UTC
|
3
|
+
1,1,userName,1,1,courseName,2,assignment2Name,2017-09-21 18:28:15 UTC,2017-09-21 19:36:23 UTC,5,10,2,submitted,false,2017-09-28 18:28:15 UTC
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: canvas_sync
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.16.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nate Collings
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-09-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -340,6 +340,7 @@ files:
|
|
340
340
|
- lib/canvas_sync/generators/templates/migrations/create_enrollments.rb
|
341
341
|
- lib/canvas_sync/generators/templates/migrations/create_group_memberships.rb
|
342
342
|
- lib/canvas_sync/generators/templates/migrations/create_groups.rb
|
343
|
+
- lib/canvas_sync/generators/templates/migrations/create_pseudonyms.rb
|
343
344
|
- lib/canvas_sync/generators/templates/migrations/create_roles.rb
|
344
345
|
- lib/canvas_sync/generators/templates/migrations/create_sections.rb
|
345
346
|
- lib/canvas_sync/generators/templates/migrations/create_submissions.rb
|
@@ -355,6 +356,7 @@ files:
|
|
355
356
|
- lib/canvas_sync/generators/templates/models/enrollment.rb
|
356
357
|
- lib/canvas_sync/generators/templates/models/group.rb
|
357
358
|
- lib/canvas_sync/generators/templates/models/group_membership.rb
|
359
|
+
- lib/canvas_sync/generators/templates/models/pseudonym.rb
|
358
360
|
- lib/canvas_sync/generators/templates/models/role.rb
|
359
361
|
- lib/canvas_sync/generators/templates/models/section.rb
|
360
362
|
- lib/canvas_sync/generators/templates/models/submission.rb
|
@@ -391,7 +393,6 @@ files:
|
|
391
393
|
- lib/canvas_sync/jobs/sync_simple_table_job.rb
|
392
394
|
- lib/canvas_sync/jobs/sync_submissions_job.rb
|
393
395
|
- lib/canvas_sync/jobs/sync_terms_job.rb
|
394
|
-
- lib/canvas_sync/jobs/sync_users_job.rb
|
395
396
|
- lib/canvas_sync/processors/assignment_groups_processor.rb
|
396
397
|
- lib/canvas_sync/processors/assignments_processor.rb
|
397
398
|
- lib/canvas_sync/processors/context_module_items_processor.rb
|
@@ -420,7 +421,6 @@ files:
|
|
420
421
|
- spec/canvas_sync/jobs/sync_simple_table_job_spec.rb
|
421
422
|
- spec/canvas_sync/jobs/sync_submissions_job_spec.rb
|
422
423
|
- spec/canvas_sync/jobs/sync_terms_job_spec.rb
|
423
|
-
- spec/canvas_sync/jobs/sync_users_job_spec.rb
|
424
424
|
- spec/canvas_sync/models/accounts_spec.rb
|
425
425
|
- spec/canvas_sync/models/admins_spec.rb
|
426
426
|
- spec/canvas_sync/models/assignment_group_spec.rb
|
@@ -596,7 +596,6 @@ test_files:
|
|
596
596
|
- spec/canvas_sync/jobs/sync_simple_table_job_spec.rb
|
597
597
|
- spec/canvas_sync/jobs/sync_submissions_job_spec.rb
|
598
598
|
- spec/canvas_sync/jobs/sync_terms_job_spec.rb
|
599
|
-
- spec/canvas_sync/jobs/sync_users_job_spec.rb
|
600
599
|
- spec/canvas_sync/models/accounts_spec.rb
|
601
600
|
- spec/canvas_sync/models/admins_spec.rb
|
602
601
|
- spec/canvas_sync/models/assignment_group_spec.rb
|
@@ -1,26 +0,0 @@
|
|
1
|
-
module CanvasSync
|
2
|
-
module Jobs
|
3
|
-
class SyncUsersJob < ReportStarter
|
4
|
-
# Starts a provisioning report for just users.
|
5
|
-
#
|
6
|
-
# Provisioning reports do not scope users by term, so when we are
|
7
|
-
# running provisioning by term we sync users first so we don't duplicate
|
8
|
-
# the work of syncing all users for each term.
|
9
|
-
#
|
10
|
-
# @param job_chain [Hash]
|
11
|
-
# @param options [Hash]
|
12
|
-
def perform(job_chain, options)
|
13
|
-
super(
|
14
|
-
job_chain,
|
15
|
-
"proservices_provisioning_csv",
|
16
|
-
merge_report_params(job_chain, options, {
|
17
|
-
users: true,
|
18
|
-
include_deleted: true,
|
19
|
-
}, term_scope: false),
|
20
|
-
CanvasSync::Processors::ProvisioningReportProcessor.to_s,
|
21
|
-
{ models: ["users"] },
|
22
|
-
)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
@@ -1,15 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
RSpec.describe CanvasSync::Jobs::SyncUsersJob do
|
4
|
-
describe '#perform' do
|
5
|
-
it 'enqueues a ReportStarter for a provisioning report across all terms with just users' do
|
6
|
-
expect_any_instance_of(Bearcat::Client).to receive(:start_report)
|
7
|
-
.with('self', 'proservices_provisioning_csv', { parameters: { users: true, include_deleted: true } })
|
8
|
-
.and_return({ 'id' => 1 })
|
9
|
-
|
10
|
-
expect(CanvasSync::Jobs::ReportChecker).to receive(:set).and_call_original
|
11
|
-
|
12
|
-
CanvasSync::Jobs::SyncUsersJob.perform_now({ jobs: [], global_options: {}}, {})
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|