canvas_sync 0.22.5 → 0.22.8

Sign up to get free protection for your applications and to get access to all the features.
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"