canvas_sync 0.17.0.beta10 → 0.17.0.beta15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +14 -0
- data/db/migrate/20201030210836_add_full_sync_to_canvas_sync_sync_batch.rb +7 -0
- data/lib/canvas_sync.rb +40 -7
- data/lib/canvas_sync/importers/bulk_importer.rb +2 -2
- data/lib/canvas_sync/job_batches/batch.rb +13 -15
- data/lib/canvas_sync/job_batches/chain_builder.rb +22 -2
- data/lib/canvas_sync/jobs/begin_sync_chain_job.rb +51 -3
- data/lib/canvas_sync/jobs/report_starter.rb +2 -1
- data/lib/canvas_sync/jobs/sync_provisioning_report_job.rb +7 -21
- data/lib/canvas_sync/version.rb +1 -1
- data/spec/canvas_sync/canvas_sync_spec.rb +10 -10
- data/spec/canvas_sync/jobs/sync_provisioning_report_job_spec.rb +5 -7
- data/spec/dummy/db/schema.rb +4 -1
- data/spec/dummy/log/development.log +474 -0
- data/spec/dummy/log/test.log +14962 -0
- metadata +3 -3
- data/lib/canvas_sync/concerns/role/launch_querying.rb +0 -42
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f242a2d6641d3d51bff9b73eaf2165588405a468a1806183d20e68d7c324d976
|
4
|
+
data.tar.gz: ca2dc6bf47c7c861c42f6550707028d15d1b54469e1a12e3ce7fedead6204102
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4d25fa9c0d9155780a087ac94b9a72cadddd9805c60760f5bdf719870245b8f1b8c23e14650646565b7a03b6a7983e329049fb1057ccfd990f662ee479322ffd
|
7
|
+
data.tar.gz: f551da63e671e43194c8a2bd2cfe8f95499417d74599d14307f1470ed678a712492f7ad6a70d7208f57a6b3a22e9fb5e5ced209afb292b93b2f0ce88db02d609
|
data/README.md
CHANGED
@@ -103,6 +103,20 @@ It may be one of the following values:
|
|
103
103
|
* An ISO-8601 Date - Will pass the supplied date ad the `updated_after` param for the requested reports
|
104
104
|
* `true` (Default) - Will use the start date of the last successful sync
|
105
105
|
|
106
|
+
If `updated_after` is true, CanvasSync will, by default, perform a full sync every other Sunday.
|
107
|
+
This logic can be customized by passing `full_sync_every` parameter.
|
108
|
+
If you pass a date to `updated_after`, this logic will be disabled unless you explicitly pass a `full_sync_every` parameter.
|
109
|
+
`full_sync_every` accepts the following format strings:
|
110
|
+
- `15%` - Each sync will have a 15% chance of running a full sync
|
111
|
+
- `10 days` - A full sync will be run every 10 days
|
112
|
+
- `sunday` - A full sync will run every Sunday
|
113
|
+
- `saturday/4` - A full sync will run every fourth Saturday
|
114
|
+
|
115
|
+
#### Multiple Sync Chains
|
116
|
+
If your app uses multiple Sync Chains, you may run into issues with the automatic `updated_after` and `full_sync_every` logic.
|
117
|
+
You can fix this by using custom logic or by setting the `batch_genre` parameter when creating the Job Chain. Chains will only
|
118
|
+
use chains of the same genre when computing `updated_after` and `full_sync_every`.
|
119
|
+
|
106
120
|
### Extensible chain
|
107
121
|
It is sometimes desired to extend or customize the chain of jobs that are run with CanvasSync.
|
108
122
|
This can be achieved with the following pattern:
|
@@ -0,0 +1,7 @@
|
|
1
|
+
class AddFullSyncToCanvasSyncSyncBatch < CanvasSync::MiscHelper::MigrationClass
|
2
|
+
def change
|
3
|
+
add_column :canvas_sync_sync_batches, :full_sync, :boolean, default: false
|
4
|
+
add_column :canvas_sync_sync_batches, :batch_genre, :string
|
5
|
+
add_column :canvas_sync_sync_batches, :batch_bid, :string
|
6
|
+
end
|
7
|
+
end
|
data/lib/canvas_sync.rb
CHANGED
@@ -42,6 +42,22 @@ module CanvasSync
|
|
42
42
|
xlist
|
43
43
|
].freeze
|
44
44
|
|
45
|
+
SUPPORTED_TERM_SCOPE_MODELS = %w[
|
46
|
+
assignments
|
47
|
+
submissions
|
48
|
+
assignment_groups
|
49
|
+
context_modules
|
50
|
+
context_module_items
|
51
|
+
].freeze
|
52
|
+
|
53
|
+
DEFAULT_TERM_SCOPE_MODELS = %w[
|
54
|
+
assignments
|
55
|
+
submissions
|
56
|
+
assignment_groups
|
57
|
+
context_modules
|
58
|
+
context_module_items
|
59
|
+
].freeze
|
60
|
+
|
45
61
|
SUPPORTED_LIVE_EVENTS = %w[
|
46
62
|
course
|
47
63
|
enrollment
|
@@ -106,7 +122,17 @@ module CanvasSync
|
|
106
122
|
# @param account_id [Integer, nil] This optional parameter can be used if your Term creation and
|
107
123
|
# canvas_sync_client methods require an account ID.
|
108
124
|
# @return [Hash]
|
109
|
-
def default_provisioning_report_chain(
|
125
|
+
def default_provisioning_report_chain(
|
126
|
+
models,
|
127
|
+
term_scope: nil,
|
128
|
+
term_scoped_models: DEFAULT_TERM_SCOPE_MODELS,
|
129
|
+
legacy_support: false,
|
130
|
+
account_id: nil,
|
131
|
+
updated_after: nil,
|
132
|
+
full_sync_every: nil,
|
133
|
+
batch_genre: nil,
|
134
|
+
options: {}
|
135
|
+
) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/LineLength
|
110
136
|
return unless models.present?
|
111
137
|
models.map! &:to_s
|
112
138
|
term_scope = term_scope.to_s if term_scope
|
@@ -156,6 +182,10 @@ module CanvasSync
|
|
156
182
|
try_add_model_job.call('roles')
|
157
183
|
try_add_model_job.call('admins')
|
158
184
|
|
185
|
+
(SUPPORTED_TERM_SCOPE_MODELS - term_scoped_models).each do |mdl|
|
186
|
+
try_add_model_job.call(mdl)
|
187
|
+
end
|
188
|
+
|
159
189
|
###############################
|
160
190
|
# Per-term provisioning jobs
|
161
191
|
###############################
|
@@ -165,11 +195,9 @@ module CanvasSync
|
|
165
195
|
current_chain << per_term_chain
|
166
196
|
current_chain = per_term_chain
|
167
197
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
try_add_model_job.call('context_modules')
|
172
|
-
try_add_model_job.call('context_module_items')
|
198
|
+
term_scoped_models.each do |mdl|
|
199
|
+
try_add_model_job.call(mdl)
|
200
|
+
end
|
173
201
|
|
174
202
|
current_chain.insert(
|
175
203
|
generate_provisioning_jobs(models, options, only_split: ['users'])
|
@@ -179,7 +207,12 @@ module CanvasSync
|
|
179
207
|
# Wrap it all up
|
180
208
|
###############################
|
181
209
|
|
182
|
-
global_options = {
|
210
|
+
global_options = {
|
211
|
+
legacy_support: legacy_support,
|
212
|
+
updated_after: updated_after,
|
213
|
+
full_sync_every: full_sync_every,
|
214
|
+
batch_genre: batch_genre,
|
215
|
+
}
|
183
216
|
global_options[:account_id] = account_id if account_id.present?
|
184
217
|
global_options.merge!(options[:global]) if options[:global].present?
|
185
218
|
|
@@ -28,7 +28,7 @@ module CanvasSync
|
|
28
28
|
database_column_names = mapping.values.map { |value| value[:database_column_name] }
|
29
29
|
rows = []
|
30
30
|
row_ids = {}
|
31
|
-
database_conflict_column_name =
|
31
|
+
database_conflict_column_name = conflict_target ? mapping[conflict_target][:database_column_name] : nil
|
32
32
|
|
33
33
|
CSV.foreach(report_file_path, headers: true, header_converters: :symbol) do |row|
|
34
34
|
row = yield(row) if block_given?
|
@@ -67,7 +67,7 @@ module CanvasSync
|
|
67
67
|
condition: condition_sql(klass, columns),
|
68
68
|
columns: columns
|
69
69
|
}
|
70
|
-
update_conditions[:conflict_target] = conflict_target if conflict_target
|
70
|
+
update_conditions[:conflict_target] = conflict_target if conflict_target
|
71
71
|
|
72
72
|
options = { validate: false, on_duplicate_key_update: update_conditions }.merge(import_args)
|
73
73
|
|
@@ -126,8 +126,6 @@ module CanvasSync
|
|
126
126
|
|
127
127
|
job_queue = @ready_to_queue = []
|
128
128
|
|
129
|
-
puts "Beginning Batch #{bid}"
|
130
|
-
|
131
129
|
begin
|
132
130
|
parent = Thread.current[:batch]
|
133
131
|
Thread.current[:batch] = self
|
@@ -373,19 +371,19 @@ module CanvasSync
|
|
373
371
|
|
374
372
|
def cleanup_redis(bid)
|
375
373
|
logger.debug {"Cleaning redis of batch #{bid}"}
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
374
|
+
redis do |r|
|
375
|
+
r.del(
|
376
|
+
"BID-#{bid}",
|
377
|
+
"BID-#{bid}-callbacks-complete",
|
378
|
+
"BID-#{bid}-callbacks-success",
|
379
|
+
"BID-#{bid}-failed",
|
380
|
+
|
381
|
+
"BID-#{bid}-batches-success",
|
382
|
+
"BID-#{bid}-batches-complete",
|
383
|
+
"BID-#{bid}-batches-failed",
|
384
|
+
"BID-#{bid}-jids",
|
385
|
+
)
|
386
|
+
end
|
389
387
|
end
|
390
388
|
|
391
389
|
def redis(*args, &blk)
|
@@ -60,10 +60,12 @@ module CanvasSync
|
|
60
60
|
raise "Could not find a \"#{relative_to}\" job in the chain" if matching_jobs.count == 0
|
61
61
|
raise "Found multiple \"#{relative_to}\" jobs in the chain" if matching_jobs.count > 1
|
62
62
|
|
63
|
-
|
64
|
-
|
63
|
+
relative_job, sub_index = matching_jobs[0]
|
64
|
+
parent_job = find_parent_job(relative_job)
|
65
65
|
needed_parent_type = placement == :with ? ConcurrentBatchJob : SerialBatchJob
|
66
66
|
|
67
|
+
chain = self.class.get_chain_parameter(parent_job)
|
68
|
+
|
67
69
|
if parent_job[:job] != needed_parent_type
|
68
70
|
old_job = chain[sub_index]
|
69
71
|
parent_job = chain[sub_index] = {
|
@@ -129,6 +131,24 @@ module CanvasSync
|
|
129
131
|
end
|
130
132
|
end
|
131
133
|
|
134
|
+
def find_parent_job(job_def)
|
135
|
+
iterate_job_tree do |job, path|
|
136
|
+
return path[-1] if job == job_def
|
137
|
+
end
|
138
|
+
nil
|
139
|
+
end
|
140
|
+
|
141
|
+
def iterate_job_tree(root: self.base_job, path: [], &blk)
|
142
|
+
blk.call(root, path)
|
143
|
+
|
144
|
+
if self.class._job_type_definitions[root[:job]]
|
145
|
+
sub_jobs = self.class.get_chain_parameter(root)
|
146
|
+
sub_jobs.each_with_index do |sub_job, i|
|
147
|
+
iterate_job_tree(root: sub_job, path: [*path, root], &blk)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
132
152
|
class << self
|
133
153
|
def _job_type_definitions
|
134
154
|
@job_type_definitions ||= {}
|
@@ -1,28 +1,76 @@
|
|
1
1
|
module CanvasSync
|
2
2
|
module Jobs
|
3
3
|
class BeginSyncChainJob < CanvasSync::Job
|
4
|
+
attr_reader :globals
|
5
|
+
|
4
6
|
def perform(chain_definition, globals = {})
|
7
|
+
@globals = globals
|
8
|
+
|
5
9
|
if !globals[:updated_after].present? || globals[:updated_after] == true
|
6
|
-
last_batch = SyncBatch.where(status: 'completed').last
|
10
|
+
last_batch = SyncBatch.where(status: 'completed', batch_genre: genre).last
|
11
|
+
globals[:full_sync_every] ||= "sunday/2"
|
7
12
|
globals[:updated_after] = last_batch&.started_at&.iso8601
|
8
13
|
end
|
9
14
|
|
15
|
+
if should_full_sync?(globals[:full_sync_every])
|
16
|
+
globals[:updated_after] = nil
|
17
|
+
end
|
18
|
+
|
10
19
|
sync_batch = SyncBatch.create!(
|
11
20
|
started_at: DateTime.now,
|
12
|
-
|
21
|
+
full_sync: globals[:updated_after] == nil,
|
22
|
+
batch_genre: genre,
|
23
|
+
status: 'processing',
|
13
24
|
)
|
14
25
|
|
15
26
|
JobBatches::Batch.new.tap do |b|
|
16
|
-
b.description = "CanvasSync Root Batch"
|
27
|
+
b.description = "CanvasSync Root Batch (SyncBatch##{sync_batch.id})"
|
17
28
|
b.on(:complete, "#{self.class.to_s}.batch_completed", sync_batch_id: sync_batch.id)
|
18
29
|
b.on(:success, "#{self.class.to_s}.batch_completed", sync_batch_id: sync_batch.id)
|
19
30
|
b.context = globals
|
20
31
|
b.jobs do
|
21
32
|
JobBatches::SerialBatchJob.perform_now(chain_definition)
|
22
33
|
end
|
34
|
+
sync_batch.update(batch_bid: b.bid)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def should_full_sync?(opt)
|
39
|
+
return true unless last_full_sync.present?
|
40
|
+
return false unless opt.is_a?(String)
|
41
|
+
|
42
|
+
case r.strip
|
43
|
+
when %r{^(sunday|monday|tuesday|wednesday|thursday|friday|saturday)(?:/(\d+))?$}
|
44
|
+
m = Regexp.last_match
|
45
|
+
day = m[1]
|
46
|
+
skip = m[2] || "1"
|
47
|
+
Date.new.send(:"#{day}?") && last_full_sync.end_of_day <= (skip.to_i.weeks.ago.end_of_day)
|
48
|
+
when opt.match?(%r{^(\d+)\%$})
|
49
|
+
m = Regexp.last_match
|
50
|
+
rand(100) < m[1].to_i
|
51
|
+
when opt.match?(%r{^(\d+) ?days$})
|
52
|
+
m = Regexp.last_match
|
53
|
+
last_full_sync.end_of_day <= m[1].to_i.days.ago.end_of_day
|
54
|
+
when opt.match?(%r{^(\d+)$}) # N.days is converted to a string of seconds
|
55
|
+
m = Regexp.last_match
|
56
|
+
last_full_sync.end_of_day <= m[1].to_i.seconds.ago.end_of_day
|
57
|
+
else
|
58
|
+
false
|
23
59
|
end
|
24
60
|
end
|
25
61
|
|
62
|
+
def last_full_sync_record
|
63
|
+
@last_full_sync_record ||= SyncBatch.where(status: 'completed', full_sync: true, batch_genre: genre).last
|
64
|
+
end
|
65
|
+
|
66
|
+
def last_full_sync
|
67
|
+
last_full_sync_record&.started_at
|
68
|
+
end
|
69
|
+
|
70
|
+
def genre
|
71
|
+
globals[:batch_genre] || "default"
|
72
|
+
end
|
73
|
+
|
26
74
|
def self.batch_completed(status, options)
|
27
75
|
sbatch = SyncBatch.find(options['sync_batch_id'])
|
28
76
|
sbatch.update!(
|
@@ -35,7 +35,7 @@ module CanvasSync
|
|
35
35
|
protected
|
36
36
|
|
37
37
|
def merge_report_params(options={}, params={}, term_scope: true)
|
38
|
-
term_scope = batch_context[:canvas_term_id] if term_scope == true
|
38
|
+
term_scope = options[:canvas_term_id] || batch_context[:canvas_term_id] if term_scope == true
|
39
39
|
if term_scope.present?
|
40
40
|
params[:enrollment_term_id] = term_scope
|
41
41
|
end
|
@@ -43,6 +43,7 @@ module CanvasSync
|
|
43
43
|
params[:updated_after] = updated_after
|
44
44
|
end
|
45
45
|
params.merge!(options[:report_params]) if options[:report_params].present?
|
46
|
+
params.merge!(options[:report_parameters]) if options[:report_parameters].present?
|
46
47
|
{ parameters: params }
|
47
48
|
end
|
48
49
|
|
@@ -1,23 +1,8 @@
|
|
1
1
|
module CanvasSync
|
2
2
|
module Jobs
|
3
3
|
# ActiveJob class that starts a Canvas provisioning report
|
4
|
-
class SyncProvisioningReportJob <
|
4
|
+
class SyncProvisioningReportJob < ReportStarter
|
5
5
|
def perform(options)
|
6
|
-
start_report(report_params(options), options)
|
7
|
-
end
|
8
|
-
|
9
|
-
protected
|
10
|
-
|
11
|
-
def start_report(report_params, options)
|
12
|
-
CanvasSync::Jobs::ReportStarter.perform_later(
|
13
|
-
"proservices_provisioning_csv",
|
14
|
-
report_params,
|
15
|
-
CanvasSync::Processors::ProvisioningReportProcessor.to_s,
|
16
|
-
options,
|
17
|
-
)
|
18
|
-
end
|
19
|
-
|
20
|
-
def report_params(options, canvas_term_id = options[:canvas_term_id] || batch_context[:canvas_term_id])
|
21
6
|
params = {
|
22
7
|
include_deleted: true,
|
23
8
|
}
|
@@ -28,11 +13,12 @@ module CanvasSync
|
|
28
13
|
params[model] = true
|
29
14
|
end
|
30
15
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
16
|
+
super(
|
17
|
+
"proservices_provisioning_csv",
|
18
|
+
merge_report_params(options, params, {}),
|
19
|
+
CanvasSync::Processors::ProvisioningReportProcessor.to_s,
|
20
|
+
options,
|
21
|
+
)
|
36
22
|
end
|
37
23
|
end
|
38
24
|
end
|
data/lib/canvas_sync/version.rb
CHANGED
@@ -42,7 +42,7 @@ RSpec.describe CanvasSync do
|
|
42
42
|
]
|
43
43
|
}]}
|
44
44
|
]]}
|
45
|
-
], {:legacy_support=>false, :updated_after=>nil, :d=>4}],
|
45
|
+
], {:legacy_support=>false, :updated_after=>nil, :full_sync_every=>nil, :batch_genre=>nil, :d=>4}],
|
46
46
|
})
|
47
47
|
end
|
48
48
|
|
@@ -61,7 +61,7 @@ RSpec.describe CanvasSync do
|
|
61
61
|
]
|
62
62
|
}]}
|
63
63
|
]]}
|
64
|
-
], {:legacy_support=>false, :updated_after=>nil}]
|
64
|
+
], {:legacy_support=>false, :updated_after=>nil, :full_sync_every=>nil, :batch_genre=>nil}]
|
65
65
|
})
|
66
66
|
end
|
67
67
|
end
|
@@ -80,7 +80,7 @@ RSpec.describe CanvasSync do
|
|
80
80
|
]
|
81
81
|
}]}
|
82
82
|
]]}
|
83
|
-
], {:legacy_support=>false, :updated_after=>nil}],
|
83
|
+
], {:legacy_support=>false, :updated_after=>nil, :full_sync_every=>nil, :batch_genre=>nil}],
|
84
84
|
})
|
85
85
|
end
|
86
86
|
end
|
@@ -100,7 +100,7 @@ RSpec.describe CanvasSync do
|
|
100
100
|
]
|
101
101
|
}]}
|
102
102
|
]]}
|
103
|
-
], {:legacy_support=>false, :updated_after=>nil}],
|
103
|
+
], {:legacy_support=>false, :updated_after=>nil, :full_sync_every=>nil, :batch_genre=>nil}],
|
104
104
|
})
|
105
105
|
end
|
106
106
|
end
|
@@ -120,7 +120,7 @@ RSpec.describe CanvasSync do
|
|
120
120
|
]
|
121
121
|
}]}
|
122
122
|
]]}
|
123
|
-
], {:legacy_support=>false, :updated_after=>nil}],
|
123
|
+
], {:legacy_support=>false, :updated_after=>nil, :full_sync_every=>nil, :batch_genre=>nil}],
|
124
124
|
})
|
125
125
|
end
|
126
126
|
end
|
@@ -140,7 +140,7 @@ RSpec.describe CanvasSync do
|
|
140
140
|
]
|
141
141
|
}]}
|
142
142
|
]]}
|
143
|
-
], {:legacy_support=>false, :updated_after=>nil}],
|
143
|
+
], {:legacy_support=>false, :updated_after=>nil, :full_sync_every=>nil, :batch_genre=>nil}],
|
144
144
|
})
|
145
145
|
end
|
146
146
|
end
|
@@ -160,7 +160,7 @@ RSpec.describe CanvasSync do
|
|
160
160
|
]
|
161
161
|
}]}
|
162
162
|
]]}
|
163
|
-
], {:legacy_support=>false, :updated_after=>nil}],
|
163
|
+
], {:legacy_support=>false, :updated_after=>nil, :full_sync_every=>nil, :batch_genre=>nil}],
|
164
164
|
})
|
165
165
|
end
|
166
166
|
end
|
@@ -181,7 +181,7 @@ RSpec.describe CanvasSync do
|
|
181
181
|
]
|
182
182
|
}]}
|
183
183
|
]]}
|
184
|
-
], {:legacy_support=>false, :updated_after=>nil}],
|
184
|
+
], {:legacy_support=>false, :updated_after=>nil, :full_sync_every=>nil, :batch_genre=>nil}],
|
185
185
|
)
|
186
186
|
end
|
187
187
|
end
|
@@ -202,7 +202,7 @@ RSpec.describe CanvasSync do
|
|
202
202
|
]
|
203
203
|
}]}
|
204
204
|
]]}
|
205
|
-
], {:legacy_support=>false, :updated_after=>nil}],
|
205
|
+
], {:legacy_support=>false, :updated_after=>nil, :full_sync_every=>nil, :batch_genre=>nil}],
|
206
206
|
)
|
207
207
|
end
|
208
208
|
end
|
@@ -223,7 +223,7 @@ RSpec.describe CanvasSync do
|
|
223
223
|
]
|
224
224
|
}]}
|
225
225
|
]]}
|
226
|
-
], {:legacy_support=>false, :updated_after=>nil}],
|
226
|
+
], {:legacy_support=>false, :updated_after=>nil, :full_sync_every=>nil, :batch_genre=>nil}],
|
227
227
|
)
|
228
228
|
end
|
229
229
|
end
|