canvas_sync 0.22.5 → 0.22.8

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.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +0 -0
  3. data/lib/canvas_sync/concerns/api_syncable.rb +9 -6
  4. data/lib/canvas_sync/concerns/sync_mapping.rb +11 -1
  5. data/lib/canvas_sync/generators/templates/migrations/create_enrollments.rb +1 -0
  6. data/lib/canvas_sync/generators/templates/models/course_progress.rb +8 -0
  7. data/lib/canvas_sync/importers/bulk_importer.rb +39 -69
  8. data/lib/canvas_sync/job.rb +0 -0
  9. data/lib/canvas_sync/job_batches/batch.rb +1 -1
  10. data/lib/canvas_sync/job_batches/chain_builder.rb +3 -24
  11. data/lib/canvas_sync/job_batches/jobs/managed_batch_job.rb +0 -4
  12. data/lib/canvas_sync/job_batches/status.rb +0 -1
  13. data/lib/canvas_sync/job_uniqueness/lock_context.rb +3 -15
  14. data/lib/canvas_sync/jobs/term_batches_job.rb +1 -4
  15. data/lib/canvas_sync/processors/model_mappings.yml +3 -0
  16. data/lib/canvas_sync/version.rb +1 -1
  17. data/spec/canvas_sync/canvas_sync_spec.rb +41 -59
  18. data/spec/canvas_sync/processors/provisioning_report_processor_spec.rb +4 -0
  19. data/spec/dummy/app/models/course_progress.rb +8 -0
  20. data/spec/dummy/app/models/learning_outcome_result.rb +0 -0
  21. data/spec/dummy/app/models/rubric.rb +0 -0
  22. data/spec/dummy/app/models/rubric_assessment.rb +0 -0
  23. data/spec/dummy/app/models/rubric_association.rb +0 -0
  24. data/spec/dummy/app/models/user.rb +0 -0
  25. data/spec/dummy/db/migrate/20190702203624_create_enrollments.rb +1 -0
  26. data/spec/dummy/db/migrate/20240408223326_create_course_nicknames.rb +0 -0
  27. data/spec/dummy/db/migrate/20240509105100_create_rubrics.rb +0 -0
  28. data/spec/dummy/db/schema.rb +1 -0
  29. metadata +196 -211
  30. data/lib/canvas_sync/concerns/auto_relations.rb +0 -11
@@ -2,6 +2,18 @@ require 'spec_helper'
2
2
 
3
3
  RSpec.describe CanvasSync do
4
4
  describe '.provisioning_sync' do
5
+ it 'invokes the first job in the queue and passes on the rest of the job chain' do
6
+ expected_job_chain = CanvasSync.default_provisioning_report_chain(['courses'], term_scope: nil).normalize![:args][0]
7
+
8
+ expect(CanvasSync::Jobs::BeginSyncChainJob).to receive(:perform_later)
9
+ .with(
10
+ expected_job_chain,
11
+ anything
12
+ )
13
+
14
+ CanvasSync.provisioning_sync(['courses'])
15
+ end
16
+
5
17
  context 'an invalid model is passed in' do
6
18
  it 'raises a helpful exception' do
7
19
  expect {
@@ -18,25 +30,22 @@ RSpec.describe CanvasSync do
18
30
  provisioning: { c: 3 },
19
31
  global: { d: 4 },
20
32
  })
21
- expect(chain.normalize!).to match({
33
+ expect(chain.normalize!).to eq({
22
34
  :job => "CanvasSync::Jobs::BeginSyncChainJob",
23
- :chain_link=>String,
24
35
  :args => [
25
36
  [
26
37
  {
27
38
  :job=>"CanvasSync::JobBatches::ConcurrentBatchJob",
28
- :chain_link=>String,
29
39
  :args=>[
30
40
  [
31
- {:job=>"CanvasSync::Jobs::SyncProvisioningReportJob", :chain_link=>String, :options=>{:models=>["users"], :b=>2}},
41
+ {:job=>"CanvasSync::Jobs::SyncProvisioningReportJob", :options=>{:models=>["users"], :b=>2}},
32
42
  {
33
43
  :job=>"CanvasSync::Jobs::SyncTermsJob",
34
- :chain_link=>String,
35
44
  :args=>[],
36
45
  :kwargs=>{
37
46
  :term_scope=>"active",
38
47
  :sub_jobs=>[
39
- {:job=>"CanvasSync::Jobs::SyncProvisioningReportJob", :chain_link=>String, :options=>{:models=>["courses"], :c=>3}},
48
+ {:job=>"CanvasSync::Jobs::SyncProvisioningReportJob", :options=>{:models=>["courses"], :c=>3}},
40
49
  ],
41
50
  },
42
51
  },
@@ -53,23 +62,20 @@ RSpec.describe CanvasSync do
53
62
  context 'we are syncing users with a term scope' do
54
63
  it 'syncs the users in a separate job that runs first' do
55
64
  chain = CanvasSync.default_provisioning_report_chain(['users', 'courses'], term_scope: :active)
56
- expect(chain.normalize!).to match({
65
+ expect(chain.normalize!).to eq({
57
66
  :job => "CanvasSync::Jobs::BeginSyncChainJob",
58
- :chain_link=>String,
59
67
  :args => [[
60
68
  {
61
69
  :job=>"CanvasSync::JobBatches::ConcurrentBatchJob",
62
- :chain_link=>String,
63
70
  :args=>[
64
71
  [
65
- {:job=>"CanvasSync::Jobs::SyncProvisioningReportJob", :chain_link=>String, :options=>{:models=>["users"]}},
72
+ {:job=>"CanvasSync::Jobs::SyncProvisioningReportJob", :options=>{:models=>["users"]}},
66
73
  {
67
74
  :job=>"CanvasSync::Jobs::SyncTermsJob",
68
- :chain_link=>String,
69
75
  :args=>[],
70
76
  :kwargs=>{
71
77
  :sub_jobs=>[
72
- {:job=>"CanvasSync::Jobs::SyncProvisioningReportJob", :chain_link=>String, :options=>{:models=>["courses"]}}
78
+ {:job=>"CanvasSync::Jobs::SyncProvisioningReportJob", :options=>{:models=>["courses"]}}
73
79
  ],
74
80
  :term_scope=>"active",
75
81
  }
@@ -87,20 +93,17 @@ RSpec.describe CanvasSync do
87
93
  context 'we are syncing users without a term scope' do
88
94
  it 'syncs users along with the rest of the provisioning report' do
89
95
  chain = CanvasSync.default_provisioning_report_chain(['users', 'courses'])
90
- expect(chain.normalize!).to match({
96
+ expect(chain.normalize!).to eq({
91
97
  :job => "CanvasSync::Jobs::BeginSyncChainJob",
92
- :chain_link=>String,
93
98
  :args => [[
94
99
  {
95
100
  :job=>"CanvasSync::JobBatches::ConcurrentBatchJob",
96
- :chain_link=>String,
97
101
  :args=>[[
98
102
  {
99
103
  :job=>"CanvasSync::Jobs::SyncTermsJob",
100
- :chain_link=>String,
101
104
  :args=>[],
102
105
  :kwargs=>{
103
- :sub_jobs=>[{:job=>"CanvasSync::Jobs::SyncProvisioningReportJob", :chain_link=>String, :options=>{:models=>["users", "courses"]}}],
106
+ :sub_jobs=>[{:job=>"CanvasSync::Jobs::SyncProvisioningReportJob", :options=>{:models=>["users", "courses"]}}],
104
107
  :term_scope=>nil,
105
108
  }
106
109
  }
@@ -116,21 +119,18 @@ RSpec.describe CanvasSync do
116
119
  context 'we are syncing roles with a term scope' do
117
120
  it 'syncs the roles in a separate job that runs first' do
118
121
  chain = CanvasSync.default_provisioning_report_chain(['roles', 'courses'], term_scope: :active)
119
- expect(chain.normalize!).to match({
122
+ expect(chain.normalize!).to eq({
120
123
  :job => "CanvasSync::Jobs::BeginSyncChainJob",
121
- :chain_link=>String,
122
124
  :args => [[
123
125
  {
124
126
  :job=>"CanvasSync::JobBatches::ConcurrentBatchJob",
125
- :chain_link=>String,
126
127
  :args=>[[
127
- {:job=>"CanvasSync::Jobs::SyncRolesJob", :chain_link=>String, :options=>{}},
128
+ {:job=>"CanvasSync::Jobs::SyncRolesJob", :options=>{}},
128
129
  {
129
130
  :job=>"CanvasSync::Jobs::SyncTermsJob",
130
- :chain_link=>String,
131
131
  :args=>[],
132
132
  :kwargs=>{
133
- :sub_jobs=>[{:job=>"CanvasSync::Jobs::SyncProvisioningReportJob", :chain_link=>String, :options=>{:models=>["courses"]}}],
133
+ :sub_jobs=>[{:job=>"CanvasSync::Jobs::SyncProvisioningReportJob", :options=>{:models=>["courses"]}}],
134
134
  :term_scope=>"active",
135
135
  }
136
136
  },
@@ -146,21 +146,18 @@ RSpec.describe CanvasSync do
146
146
  context 'we are syncing roles without a term scope' do
147
147
  it 'syncs roles separately even with no term scope' do
148
148
  chain = CanvasSync.default_provisioning_report_chain(['roles', 'courses'])
149
- expect(chain.normalize!).to match({
149
+ expect(chain.normalize!).to eq({
150
150
  :job => "CanvasSync::Jobs::BeginSyncChainJob",
151
- :chain_link=>String,
152
151
  :args => [[
153
152
  {
154
153
  :job=>"CanvasSync::JobBatches::ConcurrentBatchJob",
155
- :chain_link=>String,
156
154
  :args=>[[
157
- {:job=>"CanvasSync::Jobs::SyncRolesJob", :chain_link=>String, :options=>{}},
155
+ {:job=>"CanvasSync::Jobs::SyncRolesJob", :options=>{}},
158
156
  {
159
157
  :job=>"CanvasSync::Jobs::SyncTermsJob",
160
- :chain_link=>String,
161
158
  :args=>[],
162
159
  :kwargs=>{
163
- :sub_jobs=>[{:job=>"CanvasSync::Jobs::SyncProvisioningReportJob", :chain_link=>String, :options=>{:models=>["courses"]}}],
160
+ :sub_jobs=>[{:job=>"CanvasSync::Jobs::SyncProvisioningReportJob", :options=>{:models=>["courses"]}}],
164
161
  :term_scope=>nil,
165
162
  }
166
163
  }
@@ -176,21 +173,18 @@ RSpec.describe CanvasSync do
176
173
  context 'we are syncing admins with a term scope' do
177
174
  it 'syncs the admins in a separate job that runs first' do
178
175
  chain = CanvasSync.default_provisioning_report_chain(['admins', 'courses'], term_scope: :active)
179
- expect(chain.normalize!).to match({
176
+ expect(chain.normalize!).to eq({
180
177
  :job => "CanvasSync::Jobs::BeginSyncChainJob",
181
- :chain_link=>String,
182
178
  :args => [[
183
179
  {
184
180
  :job=>"CanvasSync::JobBatches::ConcurrentBatchJob",
185
- :chain_link=>String,
186
181
  :args=>[[
187
- {:job=>"CanvasSync::Jobs::SyncAdminsJob", :chain_link=>String, :options=>{}},
182
+ {:job=>"CanvasSync::Jobs::SyncAdminsJob", :options=>{}},
188
183
  {
189
184
  :job=>"CanvasSync::Jobs::SyncTermsJob",
190
- :chain_link=>String,
191
185
  :args=>[],
192
186
  :kwargs=>{
193
- :sub_jobs=>[{:job=>"CanvasSync::Jobs::SyncProvisioningReportJob", :chain_link=>String, :options=>{:models=>["courses"]}}],
187
+ :sub_jobs=>[{:job=>"CanvasSync::Jobs::SyncProvisioningReportJob", :options=>{:models=>["courses"]}}],
194
188
  :term_scope=>"active"
195
189
  }
196
190
  }
@@ -206,21 +200,18 @@ RSpec.describe CanvasSync do
206
200
  context 'we are syncing admins without a term scope' do
207
201
  it 'syncs admins separately even with no term scope' do
208
202
  chain = CanvasSync.default_provisioning_report_chain(['admins', 'courses'])
209
- expect(chain.normalize!).to match({
203
+ expect(chain.normalize!).to eq({
210
204
  :job => "CanvasSync::Jobs::BeginSyncChainJob",
211
- :chain_link=>String,
212
205
  :args => [[
213
206
  {
214
207
  :job=>"CanvasSync::JobBatches::ConcurrentBatchJob",
215
- :chain_link=>String,
216
208
  :args=>[[
217
- {:job=>"CanvasSync::Jobs::SyncAdminsJob", :chain_link=>String, :options=>{}},
209
+ {:job=>"CanvasSync::Jobs::SyncAdminsJob", :options=>{}},
218
210
  {
219
211
  :job=>"CanvasSync::Jobs::SyncTermsJob",
220
- :chain_link=>String,
221
212
  :args=>[],
222
213
  :kwargs=>{
223
- :sub_jobs=>[{:job=>"CanvasSync::Jobs::SyncProvisioningReportJob", :chain_link=>String, :options=>{:models=>["courses"]}}],
214
+ :sub_jobs=>[{:job=>"CanvasSync::Jobs::SyncProvisioningReportJob", :options=>{:models=>["courses"]}}],
224
215
  :term_scope=>nil
225
216
  }
226
217
  }
@@ -237,22 +228,19 @@ RSpec.describe CanvasSync do
237
228
  it "appends the SyncAssignmentsJob" do
238
229
  chain = CanvasSync.default_provisioning_report_chain(%w[users enrollments assignments])
239
230
 
240
- expect(chain.normalize!).to match({
231
+ expect(chain.normalize!).to eq({
241
232
  :job => "CanvasSync::Jobs::BeginSyncChainJob",
242
- :chain_link=>String,
243
233
  :args => [[
244
234
  {
245
235
  :job=>"CanvasSync::JobBatches::ConcurrentBatchJob",
246
- :chain_link=>String,
247
236
  :args=>[[
248
237
  {
249
238
  :job=>"CanvasSync::Jobs::SyncTermsJob",
250
- :chain_link=>String,
251
239
  :args=>[],
252
240
  :kwargs=>{
253
241
  :sub_jobs=>[
254
- {:job=>"CanvasSync::Jobs::SyncAssignmentsJob", :chain_link=>String, :options=>{}},
255
- {:job=>"CanvasSync::Jobs::SyncProvisioningReportJob", :chain_link=>String, :options=>{:models=>["users", "enrollments"]}}
242
+ {:job=>"CanvasSync::Jobs::SyncAssignmentsJob", :options=>{}},
243
+ {:job=>"CanvasSync::Jobs::SyncProvisioningReportJob", :options=>{:models=>["users", "enrollments"]}}
256
244
  ],
257
245
  :term_scope=>nil
258
246
  }
@@ -270,22 +258,19 @@ RSpec.describe CanvasSync do
270
258
  it "appends the SyncSubmissionsJob" do
271
259
  chain = CanvasSync.default_provisioning_report_chain(%w[users enrollments submissions])
272
260
 
273
- expect(chain.normalize!).to match({
261
+ expect(chain.normalize!).to eq({
274
262
  :job => "CanvasSync::Jobs::BeginSyncChainJob",
275
- :chain_link=>String,
276
263
  :args => [[
277
264
  {
278
265
  :job=>"CanvasSync::JobBatches::ConcurrentBatchJob",
279
- :chain_link=>String,
280
266
  :args=>[[
281
267
  {
282
268
  :job=>"CanvasSync::Jobs::SyncTermsJob",
283
- :chain_link=>String,
284
269
  :args=>[],
285
270
  :kwargs=>{
286
271
  :sub_jobs=>[
287
- {:job=>"CanvasSync::Jobs::SyncSubmissionsJob", :chain_link=>String, :options=>{}},
288
- {:job=>"CanvasSync::Jobs::SyncProvisioningReportJob", :chain_link=>String, :options=>{:models=>["users", "enrollments"]}}
272
+ {:job=>"CanvasSync::Jobs::SyncSubmissionsJob", :options=>{}},
273
+ {:job=>"CanvasSync::Jobs::SyncProvisioningReportJob", :options=>{:models=>["users", "enrollments"]}}
289
274
  ],
290
275
  :term_scope=>nil
291
276
  }
@@ -303,22 +288,19 @@ RSpec.describe CanvasSync do
303
288
  it "appends the SyncAssignmentGroupsJob" do
304
289
  chain = CanvasSync.default_provisioning_report_chain(%w[users enrollments assignment_groups])
305
290
 
306
- expect(chain.normalize!).to match({
291
+ expect(chain.normalize!).to eq({
307
292
  :job => "CanvasSync::Jobs::BeginSyncChainJob",
308
- :chain_link=>String,
309
293
  :args => [[
310
294
  {
311
295
  :job=>"CanvasSync::JobBatches::ConcurrentBatchJob",
312
- :chain_link=>String,
313
296
  :args=>[[
314
297
  {
315
298
  :job=>"CanvasSync::Jobs::SyncTermsJob",
316
- :chain_link=>String,
317
299
  :args=>[],
318
300
  :kwargs=>{
319
301
  :sub_jobs=>[
320
- {:job=>"CanvasSync::Jobs::SyncAssignmentGroupsJob", :chain_link=>String, :options=>{}},
321
- {:job=>"CanvasSync::Jobs::SyncProvisioningReportJob", :chain_link=>String, :options=>{:models=>["users", "enrollments"]}}
302
+ {:job=>"CanvasSync::Jobs::SyncAssignmentGroupsJob", :options=>{}},
303
+ {:job=>"CanvasSync::Jobs::SyncProvisioningReportJob", :options=>{:models=>["users", "enrollments"]}}
322
304
  ],
323
305
  :term_scope=>nil
324
306
  }
@@ -12,6 +12,10 @@ RSpec.describe CanvasSync::Processors::ProvisioningReportProcessor do
12
12
 
13
13
  context "with User#sis_id column" do
14
14
  before do
15
+ # this could be refactored into a method that prevents caching the variables unless the environment is prod
16
+ CanvasSync::Concerns::SyncMapping::Mapping.instance_variable_set(:@mappings, nil)
17
+ CanvasSync::Concerns::SyncMapping::Mapping.instance_variable_set(:@legacy_mappings, nil)
18
+
15
19
  self.use_transactional_tests = false
16
20
  ActiveRecord::Migration.add_column :users, :sis_id, :string
17
21
  User.reset_column_information
@@ -9,6 +9,7 @@
9
9
  # CourseProgress is not a Canvas model. It is a table built from the Custom Report
10
10
  class CourseProgress < ApplicationRecord
11
11
  include CanvasSync::Record
12
+ include CanvasSync::Concerns::ApiSyncable
12
13
 
13
14
  canvas_sync_features :defaults
14
15
 
@@ -17,4 +18,11 @@ class CourseProgress < ApplicationRecord
17
18
 
18
19
  validates_presence_of :canvas_user_id, :canvas_course_id
19
20
  validates_uniqueness_of :canvas_user_id, scope: :canvas_course_id
21
+
22
+ api_syncable({
23
+ requirement_count: :requirement_count,
24
+ requirement_completed_count: :requirement_completed_count,
25
+ # provisioning report has completion_date instead of completed_at in the API
26
+ completion_date: :completed_at
27
+ }, -> (api) { api.course_progress(canvas_course_id, canvas_user_id) })
20
28
  end
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -19,6 +19,7 @@ class CreateEnrollments < ActiveRecord::Migration[5.1]
19
19
  t.bigint :canvas_section_id
20
20
  t.string :workflow_state
21
21
  t.string :base_role_type
22
+ t.datetime :completed_at
22
23
 
23
24
  t.timestamps
24
25
  end
@@ -210,6 +210,7 @@ ActiveRecord::Schema.define(version: 2024_05_23_101010) do
210
210
  t.bigint "canvas_section_id"
211
211
  t.string "workflow_state"
212
212
  t.string "base_role_type"
213
+ t.datetime "completed_at"
213
214
  t.datetime "created_at", null: false
214
215
  t.datetime "updated_at", null: false
215
216
  t.index ["canvas_course_id"], name: "index_enrollments_on_canvas_course_id"