canvas_sync 0.16.4 → 0.21.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (260) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +235 -151
  3. data/app/controllers/{api → canvas_sync/api}/v1/health_check_controller.rb +1 -1
  4. data/app/controllers/canvas_sync/api/v1/live_events_controller.rb +122 -0
  5. data/app/models/canvas_sync/sync_batch.rb +5 -0
  6. data/config/initializers/apartment.rb +10 -1
  7. data/config/routes.rb +7 -0
  8. data/db/migrate/20170915210836_create_canvas_sync_job_log.rb +12 -31
  9. data/db/migrate/20180725155729_add_job_id_to_canvas_sync_job_logs.rb +4 -13
  10. data/db/migrate/20190916154829_add_fork_count_to_canvas_sync_job_logs.rb +3 -11
  11. data/db/migrate/20201018210836_create_canvas_sync_sync_batches.rb +11 -0
  12. data/db/migrate/20201030210836_add_full_sync_to_canvas_sync_sync_batch.rb +7 -0
  13. data/lib/canvas_sync/batch_processor.rb +41 -0
  14. data/lib/canvas_sync/concerns/ability_helper.rb +72 -0
  15. data/lib/canvas_sync/concerns/account/ancestry.rb +2 -0
  16. data/lib/canvas_sync/concerns/account/base.rb +15 -0
  17. data/lib/canvas_sync/concerns/api_syncable.rb +17 -10
  18. data/lib/canvas_sync/concerns/live_event_sync.rb +46 -0
  19. data/lib/canvas_sync/concerns/role/base.rb +57 -0
  20. data/lib/canvas_sync/concerns/sync_mapping.rb +120 -0
  21. data/lib/canvas_sync/engine.rb +80 -0
  22. data/lib/canvas_sync/generators/install_generator.rb +1 -0
  23. data/lib/canvas_sync/generators/install_live_events_generator.rb +0 -1
  24. data/lib/canvas_sync/generators/templates/migrations/create_content_migrations.rb +24 -0
  25. data/lib/canvas_sync/generators/templates/migrations/create_course_nicknames.rb +17 -0
  26. data/lib/canvas_sync/generators/templates/migrations/create_grading_period_groups.rb +18 -0
  27. data/lib/canvas_sync/generators/templates/migrations/create_grading_periods.rb +22 -0
  28. data/lib/canvas_sync/generators/templates/migrations/create_learning_outcome_results.rb +46 -0
  29. data/lib/canvas_sync/generators/templates/migrations/create_learning_outcomes.rb +30 -0
  30. data/lib/canvas_sync/generators/templates/migrations/create_rubric_assessments.rb +31 -0
  31. data/lib/canvas_sync/generators/templates/migrations/create_rubric_associations.rb +36 -0
  32. data/lib/canvas_sync/generators/templates/migrations/create_rubrics.rb +38 -0
  33. data/lib/canvas_sync/generators/templates/migrations/create_user_observers.rb +17 -0
  34. data/lib/canvas_sync/generators/templates/migrations/create_users.rb +0 -1
  35. data/lib/canvas_sync/generators/templates/models/account.rb +3 -0
  36. data/lib/canvas_sync/generators/templates/models/admin.rb +2 -0
  37. data/lib/canvas_sync/generators/templates/models/assignment.rb +3 -0
  38. data/lib/canvas_sync/generators/templates/models/assignment_group.rb +3 -0
  39. data/lib/canvas_sync/generators/templates/models/content_migration.rb +12 -0
  40. data/lib/canvas_sync/generators/templates/models/context_module.rb +3 -0
  41. data/lib/canvas_sync/generators/templates/models/context_module_item.rb +3 -0
  42. data/lib/canvas_sync/generators/templates/models/course.rb +11 -0
  43. data/lib/canvas_sync/generators/templates/models/course_nickname.rb +13 -0
  44. data/lib/canvas_sync/generators/templates/models/enrollment.rb +14 -0
  45. data/lib/canvas_sync/generators/templates/models/grading_period.rb +10 -0
  46. data/lib/canvas_sync/generators/templates/models/grading_period_group.rb +9 -0
  47. data/lib/canvas_sync/generators/templates/models/group.rb +2 -0
  48. data/lib/canvas_sync/generators/templates/models/group_membership.rb +2 -0
  49. data/lib/canvas_sync/generators/templates/models/learning_outcome.rb +24 -0
  50. data/lib/canvas_sync/generators/templates/models/learning_outcome_result.rb +48 -0
  51. data/lib/canvas_sync/generators/templates/models/pseudonym.rb +2 -0
  52. data/lib/canvas_sync/generators/templates/models/role.rb +2 -0
  53. data/lib/canvas_sync/generators/templates/models/rubric.rb +29 -0
  54. data/lib/canvas_sync/generators/templates/models/rubric_assessment.rb +17 -0
  55. data/lib/canvas_sync/generators/templates/models/rubric_association.rb +14 -0
  56. data/lib/canvas_sync/generators/templates/models/section.rb +9 -0
  57. data/lib/canvas_sync/generators/templates/models/submission.rb +3 -0
  58. data/lib/canvas_sync/generators/templates/models/term.rb +3 -0
  59. data/lib/canvas_sync/generators/templates/models/user.rb +11 -0
  60. data/lib/canvas_sync/generators/templates/models/user_observer.rb +13 -0
  61. data/lib/canvas_sync/generators/templates/services/live_events/assignment_event.rb +1 -1
  62. data/lib/canvas_sync/generators/templates/services/live_events/assignment_group_event.rb +1 -1
  63. data/lib/canvas_sync/generators/templates/services/live_events/course_event.rb +1 -3
  64. data/lib/canvas_sync/generators/templates/services/live_events/course_section_event.rb +1 -1
  65. data/lib/canvas_sync/generators/templates/services/live_events/enrollment_event.rb +1 -1
  66. data/lib/canvas_sync/generators/templates/services/live_events/grade_event.rb +1 -1
  67. data/lib/canvas_sync/generators/templates/services/live_events/module_event.rb +1 -1
  68. data/lib/canvas_sync/generators/templates/services/live_events/module_item_event.rb +1 -1
  69. data/lib/canvas_sync/generators/templates/services/live_events/submission_event.rb +1 -1
  70. data/lib/canvas_sync/generators/templates/services/live_events/syllabus_event.rb +1 -1
  71. data/lib/canvas_sync/generators/templates/services/live_events/user_event.rb +1 -3
  72. data/lib/canvas_sync/importers/bulk_importer.rb +138 -31
  73. data/lib/canvas_sync/job.rb +7 -5
  74. data/lib/canvas_sync/job_batches/active_job.rb +108 -0
  75. data/lib/canvas_sync/job_batches/batch.rb +543 -0
  76. data/lib/canvas_sync/job_batches/callback.rb +149 -0
  77. data/lib/canvas_sync/job_batches/chain_builder.rb +249 -0
  78. data/lib/canvas_sync/job_batches/context_hash.rb +159 -0
  79. data/lib/canvas_sync/job_batches/hier_batch_ids.lua +25 -0
  80. data/lib/canvas_sync/job_batches/jobs/base_job.rb +7 -0
  81. data/lib/canvas_sync/job_batches/jobs/concurrent_batch_job.rb +22 -0
  82. data/lib/canvas_sync/job_batches/jobs/managed_batch_job.rb +170 -0
  83. data/lib/canvas_sync/job_batches/jobs/serial_batch_job.rb +22 -0
  84. data/lib/canvas_sync/job_batches/pool.rb +245 -0
  85. data/lib/canvas_sync/job_batches/pool_refill.lua +47 -0
  86. data/lib/canvas_sync/job_batches/redis_model.rb +69 -0
  87. data/lib/canvas_sync/job_batches/redis_script.rb +163 -0
  88. data/lib/canvas_sync/job_batches/schedule_callback.lua +14 -0
  89. data/lib/canvas_sync/job_batches/sidekiq/web/batches_assets/css/styles.less +182 -0
  90. data/lib/canvas_sync/job_batches/sidekiq/web/batches_assets/js/batch_tree.js +108 -0
  91. data/lib/canvas_sync/job_batches/sidekiq/web/batches_assets/js/util.js +2 -0
  92. data/lib/canvas_sync/job_batches/sidekiq/web/helpers.rb +41 -0
  93. data/lib/canvas_sync/job_batches/sidekiq/web/views/_batch_tree.erb +6 -0
  94. data/lib/canvas_sync/job_batches/sidekiq/web/views/_batches_table.erb +44 -0
  95. data/lib/canvas_sync/job_batches/sidekiq/web/views/_common.erb +13 -0
  96. data/lib/canvas_sync/job_batches/sidekiq/web/views/_jobs_table.erb +21 -0
  97. data/lib/canvas_sync/job_batches/sidekiq/web/views/_pagination.erb +26 -0
  98. data/lib/canvas_sync/job_batches/sidekiq/web/views/batch.erb +81 -0
  99. data/lib/canvas_sync/job_batches/sidekiq/web/views/batches.erb +23 -0
  100. data/lib/canvas_sync/job_batches/sidekiq/web/views/pool.erb +137 -0
  101. data/lib/canvas_sync/job_batches/sidekiq/web/views/pools.erb +47 -0
  102. data/lib/canvas_sync/job_batches/sidekiq/web.rb +218 -0
  103. data/lib/canvas_sync/job_batches/sidekiq.rb +136 -0
  104. data/lib/canvas_sync/job_batches/status.rb +91 -0
  105. data/lib/canvas_sync/jobs/begin_sync_chain_job.rb +99 -0
  106. data/lib/canvas_sync/jobs/canvas_process_waiter.rb +41 -0
  107. data/lib/canvas_sync/jobs/report_checker.rb +70 -8
  108. data/lib/canvas_sync/jobs/report_processor_job.rb +4 -7
  109. data/lib/canvas_sync/jobs/report_starter.rb +34 -20
  110. data/lib/canvas_sync/jobs/sync_accounts_job.rb +3 -5
  111. data/lib/canvas_sync/jobs/sync_admins_job.rb +2 -4
  112. data/lib/canvas_sync/jobs/sync_assignment_groups_job.rb +2 -4
  113. data/lib/canvas_sync/jobs/sync_assignments_job.rb +2 -4
  114. data/lib/canvas_sync/jobs/sync_content_migrations_job.rb +20 -0
  115. data/lib/canvas_sync/jobs/sync_context_module_items_job.rb +2 -4
  116. data/lib/canvas_sync/jobs/sync_context_modules_job.rb +2 -4
  117. data/lib/canvas_sync/jobs/sync_provisioning_report_job.rb +16 -50
  118. data/lib/canvas_sync/jobs/sync_roles_job.rb +2 -5
  119. data/lib/canvas_sync/jobs/sync_rubric_assessments_job.rb +15 -0
  120. data/lib/canvas_sync/jobs/sync_rubric_associations_job.rb +15 -0
  121. data/lib/canvas_sync/jobs/sync_rubrics_job.rb +15 -0
  122. data/lib/canvas_sync/jobs/sync_simple_table_job.rb +11 -32
  123. data/lib/canvas_sync/jobs/sync_submissions_job.rb +6 -4
  124. data/lib/canvas_sync/jobs/sync_terms_job.rb +9 -8
  125. data/lib/canvas_sync/jobs/term_batches_job.rb +50 -0
  126. data/lib/canvas_sync/{generators/templates/services/live_events/base_event.rb → live_events/base_handler.rb} +6 -10
  127. data/lib/canvas_sync/live_events/process_event_job.rb +26 -0
  128. data/lib/canvas_sync/live_events.rb +38 -0
  129. data/lib/canvas_sync/misc_helper.rb +63 -0
  130. data/lib/canvas_sync/processors/assignment_groups_processor.rb +3 -8
  131. data/lib/canvas_sync/processors/assignments_processor.rb +3 -8
  132. data/lib/canvas_sync/processors/content_migrations_processor.rb +19 -0
  133. data/lib/canvas_sync/processors/context_module_items_processor.rb +3 -8
  134. data/lib/canvas_sync/processors/context_modules_processor.rb +3 -8
  135. data/lib/canvas_sync/processors/model_mappings.yml +420 -0
  136. data/lib/canvas_sync/processors/normal_processor.rb +3 -3
  137. data/lib/canvas_sync/processors/provisioning_report_processor.rb +42 -55
  138. data/lib/canvas_sync/processors/report_processor.rb +15 -9
  139. data/lib/canvas_sync/processors/rubric_assessments_processor.rb +19 -0
  140. data/lib/canvas_sync/processors/rubric_associations_processor.rb +19 -0
  141. data/lib/canvas_sync/processors/rubrics_processor.rb +19 -0
  142. data/lib/canvas_sync/processors/submissions_processor.rb +3 -8
  143. data/lib/canvas_sync/record.rb +103 -0
  144. data/lib/canvas_sync/version.rb +1 -1
  145. data/lib/canvas_sync.rb +124 -125
  146. data/spec/canvas_sync/canvas_sync_spec.rb +224 -155
  147. data/spec/canvas_sync/jobs/canvas_process_waiter_spec.rb +34 -0
  148. data/spec/canvas_sync/jobs/job_spec.rb +9 -17
  149. data/spec/canvas_sync/jobs/report_checker_spec.rb +1 -3
  150. data/spec/canvas_sync/jobs/report_processor_job_spec.rb +0 -3
  151. data/spec/canvas_sync/jobs/report_starter_spec.rb +19 -28
  152. data/spec/canvas_sync/jobs/sync_admins_job_spec.rb +1 -4
  153. data/spec/canvas_sync/jobs/sync_assignment_groups_job_spec.rb +2 -1
  154. data/spec/canvas_sync/jobs/sync_assignments_job_spec.rb +3 -2
  155. data/spec/canvas_sync/jobs/sync_content_migrations_job_spec.rb +30 -0
  156. data/spec/canvas_sync/jobs/sync_context_module_items_job_spec.rb +3 -2
  157. data/spec/canvas_sync/jobs/sync_context_modules_job_spec.rb +3 -2
  158. data/spec/canvas_sync/jobs/sync_provisioning_report_job_spec.rb +7 -41
  159. data/spec/canvas_sync/jobs/sync_roles_job_spec.rb +1 -4
  160. data/spec/canvas_sync/jobs/sync_simple_table_job_spec.rb +5 -12
  161. data/spec/canvas_sync/jobs/sync_submissions_job_spec.rb +8 -2
  162. data/spec/canvas_sync/jobs/sync_terms_job_spec.rb +1 -4
  163. data/spec/canvas_sync/live_events/live_event_sync_spec.rb +27 -0
  164. data/spec/canvas_sync/live_events/live_events_controller_spec.rb +54 -0
  165. data/spec/canvas_sync/live_events/process_event_job_spec.rb +38 -0
  166. data/spec/canvas_sync/misc_helper_spec.rb +58 -0
  167. data/spec/canvas_sync/models/assignment_spec.rb +1 -1
  168. data/spec/canvas_sync/processors/content_migrations_processor_spec.rb +13 -0
  169. data/spec/canvas_sync/processors/provisioning_report_processor_spec.rb +101 -1
  170. data/spec/canvas_sync/processors/rubric_assessments_spec.rb +16 -0
  171. data/spec/canvas_sync/processors/rubric_associations_spec.rb +16 -0
  172. data/spec/canvas_sync/processors/rubrics_processor_spec.rb +17 -0
  173. data/spec/dummy/app/models/account.rb +6 -0
  174. data/spec/dummy/app/models/admin.rb +2 -0
  175. data/spec/dummy/app/models/assignment.rb +3 -0
  176. data/spec/dummy/app/models/assignment_group.rb +3 -0
  177. data/spec/dummy/app/models/content_migration.rb +18 -0
  178. data/spec/dummy/app/models/context_module.rb +3 -0
  179. data/spec/dummy/app/models/context_module_item.rb +3 -0
  180. data/spec/dummy/app/models/course.rb +11 -0
  181. data/spec/dummy/app/models/course_nickname.rb +19 -0
  182. data/spec/dummy/app/models/enrollment.rb +14 -0
  183. data/spec/dummy/app/models/grading_period.rb +16 -0
  184. data/spec/dummy/app/models/grading_period_group.rb +15 -0
  185. data/spec/dummy/app/models/group.rb +2 -0
  186. data/spec/dummy/app/models/group_membership.rb +2 -0
  187. data/spec/dummy/app/models/learning_outcome.rb +30 -0
  188. data/spec/dummy/app/models/learning_outcome_result.rb +54 -0
  189. data/spec/dummy/app/models/pseudonym.rb +16 -0
  190. data/spec/dummy/app/models/role.rb +2 -0
  191. data/spec/dummy/app/models/rubric.rb +35 -0
  192. data/spec/dummy/app/models/rubric_assessment.rb +22 -0
  193. data/spec/dummy/app/models/rubric_association.rb +20 -0
  194. data/spec/dummy/app/models/section.rb +9 -0
  195. data/spec/dummy/app/models/submission.rb +4 -0
  196. data/spec/dummy/app/models/term.rb +3 -0
  197. data/spec/dummy/app/models/user.rb +11 -0
  198. data/spec/dummy/app/models/user_observer.rb +19 -0
  199. data/spec/dummy/app/services/live_events/assignment_event.rb +1 -1
  200. data/spec/dummy/app/services/live_events/course_event.rb +1 -3
  201. data/spec/dummy/app/services/live_events/course_section_event.rb +1 -1
  202. data/spec/dummy/app/services/live_events/enrollment_event.rb +1 -1
  203. data/spec/dummy/app/services/live_events/grade_event.rb +1 -1
  204. data/spec/dummy/app/services/live_events/module_event.rb +1 -1
  205. data/spec/dummy/app/services/live_events/module_item_event.rb +1 -1
  206. data/spec/dummy/app/services/live_events/submission_event.rb +1 -1
  207. data/spec/dummy/app/services/live_events/syllabus_event.rb +1 -1
  208. data/spec/dummy/app/services/live_events/user_event.rb +1 -3
  209. data/spec/dummy/config/environments/test.rb +2 -0
  210. data/spec/dummy/config/routes.rb +1 -0
  211. data/spec/dummy/db/migrate/20201016181346_create_pseudonyms.rb +24 -0
  212. data/spec/dummy/db/migrate/20210907233329_create_user_observers.rb +23 -0
  213. data/spec/dummy/db/migrate/20210907233330_create_grading_periods.rb +28 -0
  214. data/spec/dummy/db/migrate/20211001184920_create_grading_period_groups.rb +24 -0
  215. data/spec/dummy/db/migrate/20220308072643_create_content_migrations.rb +30 -0
  216. data/spec/dummy/db/migrate/20220712210559_create_learning_outcomes.rb +36 -0
  217. data/spec/dummy/db/migrate/{20190702203620_create_users.rb → 20220926221926_create_users.rb} +0 -1
  218. data/spec/dummy/db/migrate/20240408223326_create_course_nicknames.rb +23 -0
  219. data/spec/dummy/db/migrate/20240509105100_create_rubrics.rb +44 -0
  220. data/spec/dummy/db/migrate/20240510094100_create_rubric_associations.rb +42 -0
  221. data/spec/dummy/db/migrate/20240510101100_create_rubric_assessments.rb +37 -0
  222. data/spec/dummy/db/migrate/20240523101010_create_learning_outcome_results.rb +52 -0
  223. data/spec/dummy/db/schema.rb +244 -5
  224. data/spec/factories/user_factory.rb +0 -1
  225. data/spec/job_batching/active_job_spec.rb +107 -0
  226. data/spec/job_batching/batch_spec.rb +489 -0
  227. data/spec/job_batching/callback_spec.rb +38 -0
  228. data/spec/job_batching/context_hash_spec.rb +54 -0
  229. data/spec/job_batching/flow_spec.rb +82 -0
  230. data/spec/job_batching/integration/fail_then_succeed.rb +42 -0
  231. data/spec/job_batching/integration/integration.rb +57 -0
  232. data/spec/job_batching/integration/nested.rb +88 -0
  233. data/spec/job_batching/integration/simple.rb +47 -0
  234. data/spec/job_batching/integration/workflow.rb +134 -0
  235. data/spec/job_batching/integration_helper.rb +50 -0
  236. data/spec/job_batching/pool_spec.rb +161 -0
  237. data/spec/job_batching/sidekiq_spec.rb +125 -0
  238. data/spec/job_batching/status_spec.rb +76 -0
  239. data/spec/job_batching/support/base_job.rb +14 -0
  240. data/spec/job_batching/support/sample_callback.rb +2 -0
  241. data/spec/spec_helper.rb +17 -0
  242. data/spec/support/fixtures/reports/content_migrations.csv +3 -0
  243. data/spec/support/fixtures/reports/course_nicknames.csv +3 -0
  244. data/spec/support/fixtures/reports/grading_period_groups.csv +2 -0
  245. data/spec/support/fixtures/reports/grading_periods.csv +3 -0
  246. data/spec/support/fixtures/reports/learning_outcome_results.csv +3 -0
  247. data/spec/support/fixtures/reports/learning_outcomes.csv +3 -0
  248. data/spec/support/fixtures/reports/provisioning_csv_unzipped/courses.csv +3 -0
  249. data/spec/support/fixtures/reports/provisioning_csv_unzipped/users.csv +4 -0
  250. data/spec/support/fixtures/reports/rubric_assessments.csv +3 -0
  251. data/spec/support/fixtures/reports/rubric_associations.csv +3 -0
  252. data/spec/support/fixtures/reports/rubrics.csv +3 -0
  253. data/spec/support/fixtures/reports/user_observers.csv +3 -0
  254. data/spec/support/fixtures/reports/users.csv +3 -2
  255. data/spec/support/fixtures/reports/xlist.csv +1 -1
  256. metadata +329 -27
  257. data/app/controllers/api/v1/live_events_controller.rb +0 -18
  258. data/lib/canvas_sync/job_chain.rb +0 -57
  259. data/lib/canvas_sync/jobs/fork_gather.rb +0 -59
  260. data/spec/canvas_sync/jobs/fork_gather_spec.rb +0 -73
@@ -1,9 +1,112 @@
1
+
2
+ require_relative 'concerns/sync_mapping'
3
+
1
4
  module CanvasSync
2
5
  module Record
3
6
  extend ActiveSupport::Concern
7
+ include CanvasSync::Concerns::SyncMapping
8
+
9
+ def self.registered_features
10
+ @canvas_sync_features ||= {}
11
+ end
12
+
13
+ def self.define_feature(modul, feature_ident = nil, models: nil, default: false, &blk)
14
+ feature_ident = modul.name.demodulize.underscore if feature_ident.nil?
15
+ if models == nil
16
+ pmod = _get_module_parent(modul)
17
+ models = pmod.name.demodulize if pmod && _get_module_parent(pmod) == CanvasSync::Concerns
18
+ end
19
+
20
+ @canvas_sync_features ||= {}
21
+ @canvas_sync_features[feature_ident] = {
22
+ name: feature_ident,
23
+ module: modul,
24
+ default: default,
25
+ models: Array.wrap(models).map(&:to_s),
26
+ setup: blk || ->{ include modul },
27
+ }
28
+ end
29
+
30
+ def self._get_module_parent(modul)
31
+ modul.respond_to?(:parent) ? modul.parent : modul.module_parent
32
+ end
33
+
34
+ class_methods do
35
+ def applicable_canvas_sync_features
36
+ @applicable_canvas_sync_features ||= begin
37
+ relev_feats = {}
38
+ CanvasSync::Record.registered_features.each do |k, cfg|
39
+ next if cfg[:models].present? && !cfg[:models].include?(self.name.demodulize)
40
+ relev_feats[k] = cfg
41
+ end
42
+ relev_feats
43
+ end
44
+ end
45
+
46
+ def canvas_sync_feature(feature, config = nil)
47
+ feat = applicable_canvas_sync_features[feature]
48
+
49
+ if !feat
50
+ raise "Unknown feature :#{feature} for model #{self}"
51
+ end
52
+
53
+ setup = feat[:setup]
54
+ if setup.arity != 0
55
+ instance_exec(config, &setup)
56
+ elsif config.present?
57
+ raise ArgumentError, "Feature #{feature} does not accept configuration"
58
+ else
59
+ instance_exec(&setup)
60
+ end
61
+ end
62
+
63
+ def canvas_sync_features(*args, skip: [], **kwargs)
64
+ features = {}
65
+
66
+ include_defaults = args.length == 0 || args.include?(:defaults)
67
+ if include_defaults
68
+ applicable_canvas_sync_features.each do |key, cfg|
69
+ next if skip.include?(key)
70
+
71
+ case cfg[:default]
72
+ when true
73
+ features[key] = {}
74
+ when Proc
75
+ features[key] = {} if instance_exec(&cfg[:default])
76
+ end
77
+ end
78
+ end
79
+
80
+ args.each do |a|
81
+ next if a == :defaults
82
+ features[a] ||= {}
83
+ end
84
+
85
+ kwargs.each do |key, opts|
86
+ if opts == false
87
+ features.delete(key)
88
+ elsif opts == true
89
+ features[key] ||= {}
90
+ else
91
+ features[key] = opts
92
+ end
93
+ end
94
+
95
+ features.each do |key, opts|
96
+ canvas_sync_feature(key, opts)
97
+ end
98
+ end
99
+ end
4
100
 
5
101
  included do
6
102
  define_model_callbacks :sync_import
103
+ define_model_callbacks :sync_batch_import
104
+
105
+ col_names = column_names rescue []
106
+
107
+ if col_names.include?("canvas_sync_batch_id")
108
+ belongs_to :canvas_sync_batch, foreign_key: :canvas_sync_batch_id, class_name: "CanvasSync::SyncBatch", optional: true
109
+ end
7
110
  end
8
111
  end
9
112
  end
@@ -1,3 +1,3 @@
1
1
  module CanvasSync
2
- VERSION = "0.16.4".freeze
2
+ VERSION = "0.21.0".freeze
3
3
  end
data/lib/canvas_sync.rb CHANGED
@@ -2,17 +2,21 @@ require "bearcat"
2
2
 
3
3
  require "canvas_sync/version"
4
4
  require "canvas_sync/engine"
5
+ require "canvas_sync/misc_helper"
5
6
  require "canvas_sync/class_callback_executor"
6
7
  require "canvas_sync/job"
7
- require "canvas_sync/job_chain"
8
8
  require "canvas_sync/sidekiq_job"
9
9
  require "canvas_sync/api_syncable"
10
10
  require "canvas_sync/record"
11
+ require "canvas_sync/live_events"
11
12
  require "canvas_sync/jobs/report_starter"
12
13
  require "canvas_sync/jobs/report_checker"
13
14
  require "canvas_sync/jobs/report_processor_job"
15
+ require "canvas_sync/batch_processor"
14
16
  require "canvas_sync/config"
15
17
 
18
+ require "canvas_sync/job_batches/batch"
19
+
16
20
  Dir[File.dirname(__FILE__) + "/canvas_sync/jobs/*.rb"].each { |file| require file }
17
21
  Dir[File.dirname(__FILE__) + "/canvas_sync/processors/*.rb"].each { |file| require file }
18
22
  Dir[File.dirname(__FILE__) + "/canvas_sync/importers/*.rb"].each { |file| require file }
@@ -38,6 +42,38 @@ module CanvasSync
38
42
  context_modules
39
43
  context_module_items
40
44
  xlist
45
+ user_observers
46
+ grading_periods
47
+ grading_period_groups
48
+ content_migrations
49
+ learning_outcomes
50
+ learning_outcome_results
51
+ course_nicknames
52
+ rubrics
53
+ rubric_associations
54
+ rubric_assessments
55
+ ].freeze
56
+
57
+ SUPPORTED_TERM_SCOPE_MODELS = %w[
58
+ assignments
59
+ submissions
60
+ assignment_groups
61
+ context_modules
62
+ context_module_items
63
+ rubrics
64
+ rubric_associations
65
+ rubric_assessments
66
+ ].freeze
67
+
68
+ DEFAULT_TERM_SCOPE_MODELS = %w[
69
+ assignments
70
+ submissions
71
+ assignment_groups
72
+ context_modules
73
+ context_module_items
74
+ rubrics
75
+ rubric_associations
76
+ rubric_assessments
41
77
  ].freeze
42
78
 
43
79
  SUPPORTED_LIVE_EVENTS = %w[
@@ -57,6 +93,10 @@ module CanvasSync
57
93
  graded_submissions
58
94
  ].freeze
59
95
 
96
+ JobBatches::ChainBuilder.register_chain_job(CanvasSync::Jobs::SyncTermsJob, :sub_jobs)
97
+ JobBatches::ChainBuilder.register_chain_job(CanvasSync::Jobs::TermBatchesJob, :sub_jobs)
98
+ JobBatches::ChainBuilder.register_chain_job(CanvasSync::Jobs::BeginSyncChainJob, 0)
99
+
60
100
  class << self
61
101
  # Runs a standard provisioning sync job with no extra report types.
62
102
  # Terms will be synced first using the API. If you are syncing users/roles/admins
@@ -72,72 +112,9 @@ module CanvasSync
72
112
  # and inserts it into the database. If an array of model names is provided then only those models will use legacy support.
73
113
  # @param account_id [Integer, nil] This optional parameter can be used if your Term creation and
74
114
  # canvas_sync_client methods require an account ID.
75
- def provisioning_sync(models, term_scope: nil, legacy_support: false, account_id: nil)
115
+ def provisioning_sync(models, **kwargs)
76
116
  validate_models!(models)
77
- invoke_next(default_provisioning_report_chain(models, term_scope, legacy_support, account_id))
78
- end
79
-
80
- # Runs a report different from provisioning sync job with no extra report types.
81
- #
82
- # @param reports_mapping [Array<Hash>] An Array of hash that list the model and params with their report you
83
- # want to import:
84
- # [{model: 'submissions', report_name: 'my_report_name_csv', params: { "parameters[include_deleted]" => true } }, ...]
85
- # @param term_scope [Symbol, nil] An optional symbol representing a scope that exists on the Term model.
86
- # The provisioning report will be run for each of the terms contained in that scope.
87
- # @param account_id [Integer, nil] This optional parameter can be used if your Term creation and
88
- # canvas_sync_client methods require an account ID.
89
- def simple_report_sync(reports_mapping, term_scope: nil, account_id: nil)
90
- invoke_next(simple_report_chain(reports_mapping, term_scope, account_id))
91
- end
92
-
93
- # Runs a chain of ordered jobs
94
- #
95
- # See the README for usage and examples
96
- #
97
- # @param job_chain [Hash]
98
- def process_jobs(job_chain)
99
- invoke_next(job_chain)
100
- end
101
-
102
- def duplicate_chain(job_chain)
103
- Marshal.load(Marshal.dump(job_chain))
104
- end
105
-
106
- # Invokes the next job in a chain of jobs.
107
- #
108
- # This should typically be called automatically by the gem where necessary.
109
- #
110
- # @param job_chain [Hash] A chain of jobs to execute
111
- def invoke_next(job_chain, extra_options: {})
112
- job_chain = job_chain.chain_data if job_chain.is_a?(JobChain)
113
-
114
- return if job_chain[:jobs].empty?
115
-
116
- # Make sure all job classes are serialized as strings
117
- job_chain[:jobs].each { |job| job[:job] = job[:job].to_s }
118
-
119
- duped_job_chain = Marshal.load(Marshal.dump(job_chain))
120
- jobs = duped_job_chain[:jobs]
121
- next_job = jobs.shift
122
- next_job_class = next_job[:job].constantize
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
- job_chain = job_chain.chain_data if job_chain.is_a?(JobChain)
130
-
131
- duped_job_chain = Marshal.load(Marshal.dump(job_chain))
132
- duped_job_chain[:global_options][:fork_path] ||= []
133
- duped_job_chain[:global_options][:fork_keys] ||= []
134
- duped_job_chain[:global_options][:fork_path] << job_log.job_id
135
- duped_job_chain[:global_options][:fork_keys] << keys.map(&:to_s)
136
- duped_job_chain[:global_options][:on_failure] ||= 'CanvasSync::Jobs::ForkGather.handle_branch_error'
137
- sub_items = yield duped_job_chain
138
- sub_count = sub_items.respond_to?(:count) ? sub_items.count : sub_items
139
- job_log.fork_count = sub_count
140
- sub_items
117
+ default_provisioning_report_chain(models, **kwargs).process!
141
118
  end
142
119
 
143
120
  # Given a Model or Relation, scope it down to items that should be synced
@@ -154,33 +131,6 @@ module CanvasSync
154
131
  scope
155
132
  end
156
133
 
157
- # Syn any report to an specific set of models
158
- #
159
- # @param reports_mapping [Array<Hash>] List of reports with their specific model and params
160
- # @param term_scope [String]
161
- # @param account_id [Integer, nil] This optional parameter can be used if your Term creation and
162
- # canvas_sync_client methods require an account ID.
163
- def simple_report_chain(reports_mapping, term_scope=nil, account_id=nil)
164
- jobs = reports_mapping.map do |report|
165
- {
166
- job: CanvasSync::Jobs::SyncSimpleTableJob.to_s,
167
- options: {
168
- report_name: report[:report_name],
169
- model: report[:model],
170
- mapping: report[:model],
171
- klass: report[:model].singularize.capitalize.to_s,
172
- term_scope: term_scope,
173
- params: report[:params]
174
- }
175
- }
176
- end
177
-
178
- global_options = {}
179
- global_options[:account_id] = account_id if account_id.present?
180
-
181
- JobChain.new(jobs: jobs, global_options: global_options)
182
- end
183
-
184
134
  # Syncs terms, users/roles/admins if necessary, then the rest of the specified models.
185
135
  #
186
136
  # @param models [Array<String>]
@@ -191,7 +141,13 @@ module CanvasSync
191
141
  # @param account_id [Integer, nil] This optional parameter can be used if your Term creation and
192
142
  # canvas_sync_client methods require an account ID.
193
143
  # @return [Hash]
194
- def default_provisioning_report_chain(models, term_scope=nil, legacy_support=false, account_id=nil, options: {}) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/LineLength
144
+ def default_provisioning_report_chain(
145
+ models,
146
+ term_scope: nil,
147
+ term_scoped_models: DEFAULT_TERM_SCOPE_MODELS,
148
+ options: {},
149
+ **kwargs
150
+ ) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/LineLength
195
151
  return unless models.present?
196
152
  models.map! &:to_s
197
153
  term_scope = term_scope.to_s if term_scope
@@ -208,66 +164,92 @@ module CanvasSync
208
164
  assignment_groups: CanvasSync::Jobs::SyncAssignmentGroupsJob,
209
165
  context_modules: CanvasSync::Jobs::SyncContextModulesJob,
210
166
  context_module_items: CanvasSync::Jobs::SyncContextModuleItemsJob,
167
+ content_migrations: CanvasSync::Jobs::SyncContentMigrationsJob,
168
+ rubrics: CanvasSync::Jobs::SyncRubricsJob,
169
+ rubric_associations: CanvasSync::Jobs::SyncRubricAssociationsJob,
170
+ rubric_assessments: CanvasSync::Jobs::SyncRubricAssessmentsJob,
211
171
  }.with_indifferent_access
212
172
 
213
- jobs = []
173
+ root_chain = base_canvas_sync_chain(**kwargs, globals: options[:global] || kwargs[:globals])
174
+ concurrent_root_chain = JobBatches::ChainBuilder.new(JobBatches::ConcurrentBatchJob)
175
+ root_chain << concurrent_root_chain
176
+ current_chain = concurrent_root_chain
177
+
214
178
  try_add_model_job = ->(model) {
215
179
  return unless models.include?(model)
216
- jobs.push(job: model_job_map[model].to_s, options: options[model.to_sym] || {})
180
+ current_chain << { job: model_job_map[model].to_s, options: options[model.to_sym] || {} }
217
181
  models -= [model]
218
182
  }
219
183
 
220
184
  ##############################
221
- # Pre provisioning report jobs
185
+ # General provisioning jobs (not term-scoped)
222
186
  ##############################
223
187
 
224
- # Always sync Terms first
225
- models.unshift('terms') unless models.include?('terms')
226
- try_add_model_job.call('terms')
227
-
228
- # Accounts, users, roles, and admins are synced before provisioning because they cannot be scoped to term
188
+ # Accounts, users, roles, and admins cannot be scoped to term
229
189
  try_add_model_job.call('accounts')
230
190
 
231
191
  # These Models use the provisioning report, but are not term-scoped,
232
- # so we sync them before to ensure work is not duplicated
192
+ # so we sync them outside of the term scoping to ensure work is not duplicated
233
193
  if term_scope.present?
234
- models -= (first_provisioning_models = models & ['users', 'pseudonyms'])
235
- jobs.concat(
236
- generate_provisioning_jobs(first_provisioning_models, options)
237
- )
194
+ models -= (first_provisioning_models = models & ['users', 'pseudonyms', 'user_observers', 'grading_periods', 'grading_period_groups'])
195
+ current_chain.insert(generate_provisioning_jobs(first_provisioning_models, options))
238
196
  end
239
197
 
240
198
  try_add_model_job.call('roles')
241
199
  try_add_model_job.call('admins')
242
- pre_provisioning_jobs = jobs
200
+
201
+ (SUPPORTED_TERM_SCOPE_MODELS - term_scoped_models).each do |mdl|
202
+ try_add_model_job.call(mdl)
203
+ end
243
204
 
244
205
  ###############################
245
- # Post provisioning report jobs
206
+ # Per-term provisioning jobs
246
207
  ###############################
247
208
 
248
- jobs = []
249
- try_add_model_job.call('assignments')
250
- try_add_model_job.call('submissions')
251
- try_add_model_job.call('assignment_groups')
252
- try_add_model_job.call('context_modules')
253
- try_add_model_job.call('context_module_items')
254
- post_provisioning_jobs = jobs
209
+ term_parent_chain = current_chain
210
+
211
+ per_term_chain = JobBatches::ChainBuilder.build(model_job_map[:terms], term_scope: term_scope)
212
+ current_chain = per_term_chain
213
+
214
+ term_scoped_models.each do |mdl|
215
+ try_add_model_job.call(mdl)
216
+ end
217
+
218
+ current_chain.insert(
219
+ generate_provisioning_jobs(models - ['terms'], options)
220
+ )
221
+
222
+ # Skip syncing terms if not required
223
+ if !current_chain.empty? || (models & ['terms']).present?
224
+ term_parent_chain << per_term_chain
225
+ end
255
226
 
256
227
  ###############################
257
- # Main provisioning job and queueing
228
+ # Wrap it all up
258
229
  ###############################
259
230
 
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
- ]
231
+ root_chain
232
+ end
265
233
 
266
- global_options = { legacy_support: legacy_support }
234
+ def base_canvas_sync_chain(
235
+ legacy_support: false, # Import records 1 by 1 instead of with bulk upserts
236
+ account_id: nil, # legacy/non PandaPal apps
237
+ updated_after: nil,
238
+ full_sync_every: nil,
239
+ batch_genre: nil,
240
+ globals: {},
241
+ &blk
242
+ )
243
+ global_options = {
244
+ legacy_support: legacy_support,
245
+ updated_after: updated_after,
246
+ full_sync_every: full_sync_every,
247
+ batch_genre: batch_genre,
248
+ }
267
249
  global_options[:account_id] = account_id if account_id.present?
268
- global_options.merge!(options[:global]) if options[:global].present?
250
+ global_options.merge!(globals) if globals
269
251
 
270
- JobChain.new(jobs: jobs, global_options: global_options)
252
+ JobBatches::ChainBuilder.build(CanvasSync::Jobs::BeginSyncChainJob, [], global_options, &blk)
271
253
  end
272
254
 
273
255
  def group_by_job_options(model_list, options_hash, only_split: nil, default_key: :provisioning)
@@ -352,5 +334,22 @@ module CanvasSync
352
334
  return if invalid.empty?
353
335
  raise "Invalid live event(s) specified: #{invalid.join(', ')}. Only #{SUPPORTED_LIVE_EVENTS.join(', ')} are supported."
354
336
  end
337
+
338
+ def logger
339
+ return @logger if defined? @logger
340
+ @logger = Logger.new(STDOUT)
341
+ @logger.level = Logger::DEBUG
342
+ @logger
343
+ end
344
+
345
+ def redis(*args, &blk)
346
+ JobBatches::Batch.redis(*args, &blk)
347
+ end
348
+
349
+ def redis_prefix
350
+ pfx = "cs"
351
+ pfx = "#{Apartment::Tenant.current}:#{pfx}" if defined?(Apartment)
352
+ pfx
353
+ end
355
354
  end
356
355
  end