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.
- checksums.yaml +5 -5
- data/README.md +235 -151
- data/app/controllers/{api → canvas_sync/api}/v1/health_check_controller.rb +1 -1
- data/app/controllers/canvas_sync/api/v1/live_events_controller.rb +122 -0
- data/app/models/canvas_sync/sync_batch.rb +5 -0
- data/config/initializers/apartment.rb +10 -1
- data/config/routes.rb +7 -0
- data/db/migrate/20170915210836_create_canvas_sync_job_log.rb +12 -31
- data/db/migrate/20180725155729_add_job_id_to_canvas_sync_job_logs.rb +4 -13
- data/db/migrate/20190916154829_add_fork_count_to_canvas_sync_job_logs.rb +3 -11
- data/db/migrate/20201018210836_create_canvas_sync_sync_batches.rb +11 -0
- data/db/migrate/20201030210836_add_full_sync_to_canvas_sync_sync_batch.rb +7 -0
- data/lib/canvas_sync/batch_processor.rb +41 -0
- data/lib/canvas_sync/concerns/ability_helper.rb +72 -0
- data/lib/canvas_sync/concerns/account/ancestry.rb +2 -0
- data/lib/canvas_sync/concerns/account/base.rb +15 -0
- data/lib/canvas_sync/concerns/api_syncable.rb +17 -10
- data/lib/canvas_sync/concerns/live_event_sync.rb +46 -0
- data/lib/canvas_sync/concerns/role/base.rb +57 -0
- data/lib/canvas_sync/concerns/sync_mapping.rb +120 -0
- data/lib/canvas_sync/engine.rb +80 -0
- data/lib/canvas_sync/generators/install_generator.rb +1 -0
- data/lib/canvas_sync/generators/install_live_events_generator.rb +0 -1
- data/lib/canvas_sync/generators/templates/migrations/create_content_migrations.rb +24 -0
- data/lib/canvas_sync/generators/templates/migrations/create_course_nicknames.rb +17 -0
- data/lib/canvas_sync/generators/templates/migrations/create_grading_period_groups.rb +18 -0
- data/lib/canvas_sync/generators/templates/migrations/create_grading_periods.rb +22 -0
- data/lib/canvas_sync/generators/templates/migrations/create_learning_outcome_results.rb +46 -0
- data/lib/canvas_sync/generators/templates/migrations/create_learning_outcomes.rb +30 -0
- data/lib/canvas_sync/generators/templates/migrations/create_rubric_assessments.rb +31 -0
- data/lib/canvas_sync/generators/templates/migrations/create_rubric_associations.rb +36 -0
- data/lib/canvas_sync/generators/templates/migrations/create_rubrics.rb +38 -0
- data/lib/canvas_sync/generators/templates/migrations/create_user_observers.rb +17 -0
- data/lib/canvas_sync/generators/templates/migrations/create_users.rb +0 -1
- data/lib/canvas_sync/generators/templates/models/account.rb +3 -0
- data/lib/canvas_sync/generators/templates/models/admin.rb +2 -0
- data/lib/canvas_sync/generators/templates/models/assignment.rb +3 -0
- data/lib/canvas_sync/generators/templates/models/assignment_group.rb +3 -0
- data/lib/canvas_sync/generators/templates/models/content_migration.rb +12 -0
- data/lib/canvas_sync/generators/templates/models/context_module.rb +3 -0
- data/lib/canvas_sync/generators/templates/models/context_module_item.rb +3 -0
- data/lib/canvas_sync/generators/templates/models/course.rb +11 -0
- data/lib/canvas_sync/generators/templates/models/course_nickname.rb +13 -0
- data/lib/canvas_sync/generators/templates/models/enrollment.rb +14 -0
- data/lib/canvas_sync/generators/templates/models/grading_period.rb +10 -0
- data/lib/canvas_sync/generators/templates/models/grading_period_group.rb +9 -0
- data/lib/canvas_sync/generators/templates/models/group.rb +2 -0
- data/lib/canvas_sync/generators/templates/models/group_membership.rb +2 -0
- data/lib/canvas_sync/generators/templates/models/learning_outcome.rb +24 -0
- data/lib/canvas_sync/generators/templates/models/learning_outcome_result.rb +48 -0
- data/lib/canvas_sync/generators/templates/models/pseudonym.rb +2 -0
- data/lib/canvas_sync/generators/templates/models/role.rb +2 -0
- data/lib/canvas_sync/generators/templates/models/rubric.rb +29 -0
- data/lib/canvas_sync/generators/templates/models/rubric_assessment.rb +17 -0
- data/lib/canvas_sync/generators/templates/models/rubric_association.rb +14 -0
- data/lib/canvas_sync/generators/templates/models/section.rb +9 -0
- data/lib/canvas_sync/generators/templates/models/submission.rb +3 -0
- data/lib/canvas_sync/generators/templates/models/term.rb +3 -0
- data/lib/canvas_sync/generators/templates/models/user.rb +11 -0
- data/lib/canvas_sync/generators/templates/models/user_observer.rb +13 -0
- data/lib/canvas_sync/generators/templates/services/live_events/assignment_event.rb +1 -1
- data/lib/canvas_sync/generators/templates/services/live_events/assignment_group_event.rb +1 -1
- data/lib/canvas_sync/generators/templates/services/live_events/course_event.rb +1 -3
- data/lib/canvas_sync/generators/templates/services/live_events/course_section_event.rb +1 -1
- data/lib/canvas_sync/generators/templates/services/live_events/enrollment_event.rb +1 -1
- data/lib/canvas_sync/generators/templates/services/live_events/grade_event.rb +1 -1
- data/lib/canvas_sync/generators/templates/services/live_events/module_event.rb +1 -1
- data/lib/canvas_sync/generators/templates/services/live_events/module_item_event.rb +1 -1
- data/lib/canvas_sync/generators/templates/services/live_events/submission_event.rb +1 -1
- data/lib/canvas_sync/generators/templates/services/live_events/syllabus_event.rb +1 -1
- data/lib/canvas_sync/generators/templates/services/live_events/user_event.rb +1 -3
- data/lib/canvas_sync/importers/bulk_importer.rb +138 -31
- data/lib/canvas_sync/job.rb +7 -5
- data/lib/canvas_sync/job_batches/active_job.rb +108 -0
- data/lib/canvas_sync/job_batches/batch.rb +543 -0
- data/lib/canvas_sync/job_batches/callback.rb +149 -0
- data/lib/canvas_sync/job_batches/chain_builder.rb +249 -0
- data/lib/canvas_sync/job_batches/context_hash.rb +159 -0
- data/lib/canvas_sync/job_batches/hier_batch_ids.lua +25 -0
- data/lib/canvas_sync/job_batches/jobs/base_job.rb +7 -0
- data/lib/canvas_sync/job_batches/jobs/concurrent_batch_job.rb +22 -0
- data/lib/canvas_sync/job_batches/jobs/managed_batch_job.rb +170 -0
- data/lib/canvas_sync/job_batches/jobs/serial_batch_job.rb +22 -0
- data/lib/canvas_sync/job_batches/pool.rb +245 -0
- data/lib/canvas_sync/job_batches/pool_refill.lua +47 -0
- data/lib/canvas_sync/job_batches/redis_model.rb +69 -0
- data/lib/canvas_sync/job_batches/redis_script.rb +163 -0
- data/lib/canvas_sync/job_batches/schedule_callback.lua +14 -0
- data/lib/canvas_sync/job_batches/sidekiq/web/batches_assets/css/styles.less +182 -0
- data/lib/canvas_sync/job_batches/sidekiq/web/batches_assets/js/batch_tree.js +108 -0
- data/lib/canvas_sync/job_batches/sidekiq/web/batches_assets/js/util.js +2 -0
- data/lib/canvas_sync/job_batches/sidekiq/web/helpers.rb +41 -0
- data/lib/canvas_sync/job_batches/sidekiq/web/views/_batch_tree.erb +6 -0
- data/lib/canvas_sync/job_batches/sidekiq/web/views/_batches_table.erb +44 -0
- data/lib/canvas_sync/job_batches/sidekiq/web/views/_common.erb +13 -0
- data/lib/canvas_sync/job_batches/sidekiq/web/views/_jobs_table.erb +21 -0
- data/lib/canvas_sync/job_batches/sidekiq/web/views/_pagination.erb +26 -0
- data/lib/canvas_sync/job_batches/sidekiq/web/views/batch.erb +81 -0
- data/lib/canvas_sync/job_batches/sidekiq/web/views/batches.erb +23 -0
- data/lib/canvas_sync/job_batches/sidekiq/web/views/pool.erb +137 -0
- data/lib/canvas_sync/job_batches/sidekiq/web/views/pools.erb +47 -0
- data/lib/canvas_sync/job_batches/sidekiq/web.rb +218 -0
- data/lib/canvas_sync/job_batches/sidekiq.rb +136 -0
- data/lib/canvas_sync/job_batches/status.rb +91 -0
- data/lib/canvas_sync/jobs/begin_sync_chain_job.rb +99 -0
- data/lib/canvas_sync/jobs/canvas_process_waiter.rb +41 -0
- data/lib/canvas_sync/jobs/report_checker.rb +70 -8
- data/lib/canvas_sync/jobs/report_processor_job.rb +4 -7
- data/lib/canvas_sync/jobs/report_starter.rb +34 -20
- data/lib/canvas_sync/jobs/sync_accounts_job.rb +3 -5
- data/lib/canvas_sync/jobs/sync_admins_job.rb +2 -4
- data/lib/canvas_sync/jobs/sync_assignment_groups_job.rb +2 -4
- data/lib/canvas_sync/jobs/sync_assignments_job.rb +2 -4
- data/lib/canvas_sync/jobs/sync_content_migrations_job.rb +20 -0
- data/lib/canvas_sync/jobs/sync_context_module_items_job.rb +2 -4
- data/lib/canvas_sync/jobs/sync_context_modules_job.rb +2 -4
- data/lib/canvas_sync/jobs/sync_provisioning_report_job.rb +16 -50
- data/lib/canvas_sync/jobs/sync_roles_job.rb +2 -5
- data/lib/canvas_sync/jobs/sync_rubric_assessments_job.rb +15 -0
- data/lib/canvas_sync/jobs/sync_rubric_associations_job.rb +15 -0
- data/lib/canvas_sync/jobs/sync_rubrics_job.rb +15 -0
- data/lib/canvas_sync/jobs/sync_simple_table_job.rb +11 -32
- data/lib/canvas_sync/jobs/sync_submissions_job.rb +6 -4
- data/lib/canvas_sync/jobs/sync_terms_job.rb +9 -8
- data/lib/canvas_sync/jobs/term_batches_job.rb +50 -0
- data/lib/canvas_sync/{generators/templates/services/live_events/base_event.rb → live_events/base_handler.rb} +6 -10
- data/lib/canvas_sync/live_events/process_event_job.rb +26 -0
- data/lib/canvas_sync/live_events.rb +38 -0
- data/lib/canvas_sync/misc_helper.rb +63 -0
- data/lib/canvas_sync/processors/assignment_groups_processor.rb +3 -8
- data/lib/canvas_sync/processors/assignments_processor.rb +3 -8
- data/lib/canvas_sync/processors/content_migrations_processor.rb +19 -0
- data/lib/canvas_sync/processors/context_module_items_processor.rb +3 -8
- data/lib/canvas_sync/processors/context_modules_processor.rb +3 -8
- data/lib/canvas_sync/processors/model_mappings.yml +420 -0
- data/lib/canvas_sync/processors/normal_processor.rb +3 -3
- data/lib/canvas_sync/processors/provisioning_report_processor.rb +42 -55
- data/lib/canvas_sync/processors/report_processor.rb +15 -9
- data/lib/canvas_sync/processors/rubric_assessments_processor.rb +19 -0
- data/lib/canvas_sync/processors/rubric_associations_processor.rb +19 -0
- data/lib/canvas_sync/processors/rubrics_processor.rb +19 -0
- data/lib/canvas_sync/processors/submissions_processor.rb +3 -8
- data/lib/canvas_sync/record.rb +103 -0
- data/lib/canvas_sync/version.rb +1 -1
- data/lib/canvas_sync.rb +124 -125
- data/spec/canvas_sync/canvas_sync_spec.rb +224 -155
- data/spec/canvas_sync/jobs/canvas_process_waiter_spec.rb +34 -0
- data/spec/canvas_sync/jobs/job_spec.rb +9 -17
- data/spec/canvas_sync/jobs/report_checker_spec.rb +1 -3
- data/spec/canvas_sync/jobs/report_processor_job_spec.rb +0 -3
- data/spec/canvas_sync/jobs/report_starter_spec.rb +19 -28
- data/spec/canvas_sync/jobs/sync_admins_job_spec.rb +1 -4
- data/spec/canvas_sync/jobs/sync_assignment_groups_job_spec.rb +2 -1
- data/spec/canvas_sync/jobs/sync_assignments_job_spec.rb +3 -2
- data/spec/canvas_sync/jobs/sync_content_migrations_job_spec.rb +30 -0
- data/spec/canvas_sync/jobs/sync_context_module_items_job_spec.rb +3 -2
- data/spec/canvas_sync/jobs/sync_context_modules_job_spec.rb +3 -2
- data/spec/canvas_sync/jobs/sync_provisioning_report_job_spec.rb +7 -41
- data/spec/canvas_sync/jobs/sync_roles_job_spec.rb +1 -4
- data/spec/canvas_sync/jobs/sync_simple_table_job_spec.rb +5 -12
- data/spec/canvas_sync/jobs/sync_submissions_job_spec.rb +8 -2
- data/spec/canvas_sync/jobs/sync_terms_job_spec.rb +1 -4
- data/spec/canvas_sync/live_events/live_event_sync_spec.rb +27 -0
- data/spec/canvas_sync/live_events/live_events_controller_spec.rb +54 -0
- data/spec/canvas_sync/live_events/process_event_job_spec.rb +38 -0
- data/spec/canvas_sync/misc_helper_spec.rb +58 -0
- data/spec/canvas_sync/models/assignment_spec.rb +1 -1
- data/spec/canvas_sync/processors/content_migrations_processor_spec.rb +13 -0
- data/spec/canvas_sync/processors/provisioning_report_processor_spec.rb +101 -1
- data/spec/canvas_sync/processors/rubric_assessments_spec.rb +16 -0
- data/spec/canvas_sync/processors/rubric_associations_spec.rb +16 -0
- data/spec/canvas_sync/processors/rubrics_processor_spec.rb +17 -0
- data/spec/dummy/app/models/account.rb +6 -0
- data/spec/dummy/app/models/admin.rb +2 -0
- data/spec/dummy/app/models/assignment.rb +3 -0
- data/spec/dummy/app/models/assignment_group.rb +3 -0
- data/spec/dummy/app/models/content_migration.rb +18 -0
- data/spec/dummy/app/models/context_module.rb +3 -0
- data/spec/dummy/app/models/context_module_item.rb +3 -0
- data/spec/dummy/app/models/course.rb +11 -0
- data/spec/dummy/app/models/course_nickname.rb +19 -0
- data/spec/dummy/app/models/enrollment.rb +14 -0
- data/spec/dummy/app/models/grading_period.rb +16 -0
- data/spec/dummy/app/models/grading_period_group.rb +15 -0
- data/spec/dummy/app/models/group.rb +2 -0
- data/spec/dummy/app/models/group_membership.rb +2 -0
- data/spec/dummy/app/models/learning_outcome.rb +30 -0
- data/spec/dummy/app/models/learning_outcome_result.rb +54 -0
- data/spec/dummy/app/models/pseudonym.rb +16 -0
- data/spec/dummy/app/models/role.rb +2 -0
- data/spec/dummy/app/models/rubric.rb +35 -0
- data/spec/dummy/app/models/rubric_assessment.rb +22 -0
- data/spec/dummy/app/models/rubric_association.rb +20 -0
- data/spec/dummy/app/models/section.rb +9 -0
- data/spec/dummy/app/models/submission.rb +4 -0
- data/spec/dummy/app/models/term.rb +3 -0
- data/spec/dummy/app/models/user.rb +11 -0
- data/spec/dummy/app/models/user_observer.rb +19 -0
- data/spec/dummy/app/services/live_events/assignment_event.rb +1 -1
- data/spec/dummy/app/services/live_events/course_event.rb +1 -3
- data/spec/dummy/app/services/live_events/course_section_event.rb +1 -1
- data/spec/dummy/app/services/live_events/enrollment_event.rb +1 -1
- data/spec/dummy/app/services/live_events/grade_event.rb +1 -1
- data/spec/dummy/app/services/live_events/module_event.rb +1 -1
- data/spec/dummy/app/services/live_events/module_item_event.rb +1 -1
- data/spec/dummy/app/services/live_events/submission_event.rb +1 -1
- data/spec/dummy/app/services/live_events/syllabus_event.rb +1 -1
- data/spec/dummy/app/services/live_events/user_event.rb +1 -3
- data/spec/dummy/config/environments/test.rb +2 -0
- data/spec/dummy/config/routes.rb +1 -0
- data/spec/dummy/db/migrate/20201016181346_create_pseudonyms.rb +24 -0
- data/spec/dummy/db/migrate/20210907233329_create_user_observers.rb +23 -0
- data/spec/dummy/db/migrate/20210907233330_create_grading_periods.rb +28 -0
- data/spec/dummy/db/migrate/20211001184920_create_grading_period_groups.rb +24 -0
- data/spec/dummy/db/migrate/20220308072643_create_content_migrations.rb +30 -0
- data/spec/dummy/db/migrate/20220712210559_create_learning_outcomes.rb +36 -0
- data/spec/dummy/db/migrate/{20190702203620_create_users.rb → 20220926221926_create_users.rb} +0 -1
- data/spec/dummy/db/migrate/20240408223326_create_course_nicknames.rb +23 -0
- data/spec/dummy/db/migrate/20240509105100_create_rubrics.rb +44 -0
- data/spec/dummy/db/migrate/20240510094100_create_rubric_associations.rb +42 -0
- data/spec/dummy/db/migrate/20240510101100_create_rubric_assessments.rb +37 -0
- data/spec/dummy/db/migrate/20240523101010_create_learning_outcome_results.rb +52 -0
- data/spec/dummy/db/schema.rb +244 -5
- data/spec/factories/user_factory.rb +0 -1
- data/spec/job_batching/active_job_spec.rb +107 -0
- data/spec/job_batching/batch_spec.rb +489 -0
- data/spec/job_batching/callback_spec.rb +38 -0
- data/spec/job_batching/context_hash_spec.rb +54 -0
- data/spec/job_batching/flow_spec.rb +82 -0
- data/spec/job_batching/integration/fail_then_succeed.rb +42 -0
- data/spec/job_batching/integration/integration.rb +57 -0
- data/spec/job_batching/integration/nested.rb +88 -0
- data/spec/job_batching/integration/simple.rb +47 -0
- data/spec/job_batching/integration/workflow.rb +134 -0
- data/spec/job_batching/integration_helper.rb +50 -0
- data/spec/job_batching/pool_spec.rb +161 -0
- data/spec/job_batching/sidekiq_spec.rb +125 -0
- data/spec/job_batching/status_spec.rb +76 -0
- data/spec/job_batching/support/base_job.rb +14 -0
- data/spec/job_batching/support/sample_callback.rb +2 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/fixtures/reports/content_migrations.csv +3 -0
- data/spec/support/fixtures/reports/course_nicknames.csv +3 -0
- data/spec/support/fixtures/reports/grading_period_groups.csv +2 -0
- data/spec/support/fixtures/reports/grading_periods.csv +3 -0
- data/spec/support/fixtures/reports/learning_outcome_results.csv +3 -0
- data/spec/support/fixtures/reports/learning_outcomes.csv +3 -0
- data/spec/support/fixtures/reports/provisioning_csv_unzipped/courses.csv +3 -0
- data/spec/support/fixtures/reports/provisioning_csv_unzipped/users.csv +4 -0
- data/spec/support/fixtures/reports/rubric_assessments.csv +3 -0
- data/spec/support/fixtures/reports/rubric_associations.csv +3 -0
- data/spec/support/fixtures/reports/rubrics.csv +3 -0
- data/spec/support/fixtures/reports/user_observers.csv +3 -0
- data/spec/support/fixtures/reports/users.csv +3 -2
- data/spec/support/fixtures/reports/xlist.csv +1 -1
- metadata +329 -27
- data/app/controllers/api/v1/live_events_controller.rb +0 -18
- data/lib/canvas_sync/job_chain.rb +0 -57
- data/lib/canvas_sync/jobs/fork_gather.rb +0 -59
- data/spec/canvas_sync/jobs/fork_gather_spec.rb +0 -73
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: aecd0f3989ecb1b42c9f8c1c83274333eabe316bf05407cc2de7113e14590cb6
|
4
|
+
data.tar.gz: 104cbae017ac24e83b5ad357ed381c6af24577917ec1a494ef9456478874eea0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8059b7b6fcadd5c0b8750b705881496dc63bb90a232e9d950618dff0682f5410ea9cc18a84b9a8a22ea4a051262fb2946c0c6c05a39e3a86871a6ee460f75f08
|
7
|
+
data.tar.gz: 2d1b85f6564c8916ae3b8a51290df8bf95d75156b2bb4b595466a502d78dd0f7f20260a14914ce482bd89af963b9b89e23207640c6df6a004616fcd9d0f62ff8
|
data/README.md
CHANGED
@@ -9,7 +9,7 @@ Add this line to your application's Gemfile:
|
|
9
9
|
```ruby
|
10
10
|
gem 'canvas_sync'
|
11
11
|
```
|
12
|
-
|
12
|
+
mount CanvasSync::Engine, at: '/canvas_sync'
|
13
13
|
Models and migrations can be installed using the following generator:
|
14
14
|
|
15
15
|
```
|
@@ -34,6 +34,7 @@ The following custom reports are required for the specified models:
|
|
34
34
|
- assignment_groups = "Assignment Group Export" (proserv_assignment_group_export_csv)
|
35
35
|
- context_modules = "Professional Services Context Modules Report" (proserv_context_modules_csv)
|
36
36
|
- context_module_items = "Professional Services Context Module Items Report" (proserv_context_module_items_csv)
|
37
|
+
- content_migrations = "Professional Services Content Migrations Report" (proserv_content_migrations_csv)
|
37
38
|
|
38
39
|
## Prerequisites
|
39
40
|
|
@@ -49,18 +50,14 @@ Make sure you've setup sidekiq to work properly with ActiveJob as [outlined here
|
|
49
50
|
|
50
51
|
If using apartment and sidekiq make sure you include the [apartment-sidekiq](https://github.com/influitive/apartment-sidekiq) gem so that the jobs are run in the correct tenant.
|
51
52
|
|
52
|
-
|
53
|
-
|
54
|
-
|
53
|
+
### Live Events
|
54
|
+
if enabling Live Events, the following additional dependencies are required:
|
55
|
+
- If using Core/Data Services events: `httparty`, `json-jwt`
|
56
|
+
- If using EventsManager/DejaVu events: `symmetric-encryption`
|
55
57
|
|
56
|
-
|
57
|
-
# config/initializers/canvas_sync.rb
|
58
|
-
def canvas_sync_client
|
59
|
-
Bearcat::Client.new(token: current_organization.settings[:api_token], prefix: current_organization.settings[:base_url])
|
60
|
-
end
|
61
|
-
```
|
58
|
+
## Basic Usage
|
62
59
|
|
63
|
-
|
60
|
+
Your tool must have an `ActiveJob` compatible job queue adapter configured, such as DelayedJob or Sidekiq.
|
64
61
|
|
65
62
|
Once that's done and you've used the generator to create your models and migrations you can run the standard provisioning sync:
|
66
63
|
|
@@ -82,6 +79,47 @@ If you pass in the optional `term_scope` the provisioning reports will be run fo
|
|
82
79
|
|
83
80
|
Imports are inserted in bulk with [activerecord-import](https://github.com/zdennis/activerecord-import) so they should be very fast.
|
84
81
|
|
82
|
+
### Live Events
|
83
|
+
|
84
|
+
Ensure that
|
85
|
+
```ruby
|
86
|
+
mount CanvasSync::Engine, at: '/canvas_sync'
|
87
|
+
```
|
88
|
+
is added to your `routes.rb`. Configure `DataServices` or `EventsManager` to send events to `https://YOUR_APP/canvas_sync/api/v1/live_event` (if using `DataServices`, event must be signed).
|
89
|
+
|
90
|
+
Uncomment `include CanvasSync::Concerns::LiveEventSync` and related lines in the appropriate models. (Some models provide some basic hooks to address a "typical" workflow).
|
91
|
+
|
92
|
+
When Live Events are received, the corresponding model (if present) instance will receive `process_live_event(subtype, payload, metadata)` (where `subtype` is the event name w/o the model name - eg `user_created` => `created`). The default logic is to call `ApiSyncable` and update the model from the Canvas API. `process_live_event` can be overridden directly, or hooked with the usual Rails callbacks system (eg `before_process_live_event`).
|
93
|
+
|
94
|
+
You can subscribe to Live Events outside of a model context using an intializer like so:
|
95
|
+
```ruby
|
96
|
+
CanvasSync::LiveEvents.subscribe(%w[Optional List of Events]) do |event|
|
97
|
+
# Your code here
|
98
|
+
# Note that this code is _not_ retried if it fails. If you need retries, use this block to trigger another Job.
|
99
|
+
end
|
100
|
+
```
|
101
|
+
|
102
|
+
#### Event Provenance
|
103
|
+
|
104
|
+
When using `EventsManager` events, events are verified as having come from a legitimate source by use of `SymmetricEncryption` (and thus `PRODUCTION_KEY1` will need to be set correctly when deployed).
|
105
|
+
|
106
|
+
When using `DataServices`, CanvasSync uses the `DataServices` JWK to authenticate incoming events. CanvasSync is coded to default to the Prod & Beta JWK URL at https://8axpcl50e4.execute-api.us-east-1.amazonaws.com/main/jwks, but this can be overridden with the `DATASERVICES_JWK_URL` ENV variable.
|
107
|
+
|
108
|
+
Additionally, when `PandaPal` is installed too, use `https://YOUR_APP/canvas_sync/api/v1/live_event?org=ORG_ID` instead. `CanvasSync` will automatically switch to the correct organization and will validate that the event was indeed from the correct Canvas instance. If you are not using `PandaPal`, you'll need to monkey-patch `CanvasSync::Api::V1::LiveEventsController#validate_tenant!`
|
109
|
+
|
110
|
+
#### Legacy-Style Event Jobs
|
111
|
+
|
112
|
+
CanvasSync also supports they legacy style of Event Handlers. In this design, properly-named classes are defined in the `::LiveEvents` module, such as (`class LiveEvents::UserCreatedEvent`). Any `ActiveJob` job is compatible, but CanvasSync also provides `CanvasSync::LiveEvents::BaseHandler` as a helpful base class.
|
113
|
+
|
114
|
+
When present, these jobs will, per event type (eg `user_created`), override the default behavior, meaning that `subscribe` blocks and `process_live_event` and related callbacks will _not_ be called unless you call them. In other words: If you define `class LiveEvents::UserCreatedEvent` and also
|
115
|
+
```ruby
|
116
|
+
subscribe(%w[user_created user_updated]) do |event|
|
117
|
+
# ...
|
118
|
+
end
|
119
|
+
```
|
120
|
+
the subscribe block (and User model) will receive `user_updated` events, but not `user_created` events.
|
121
|
+
|
122
|
+
These jobs can also be generated from template using `bin/rails generate canvas_sync:install_live_events --events users,courses,etc`
|
85
123
|
|
86
124
|
## Advanced Usage
|
87
125
|
|
@@ -91,27 +129,58 @@ This gem also helps with syncing and processing other reports if needed. In orde
|
|
91
129
|
- Integrate your reports with the `ReportStarter`
|
92
130
|
- Tell the gem what jobs to run
|
93
131
|
|
132
|
+
### `updated_after`
|
133
|
+
An `updated_after` param may be passed when triggering a provision or making a chain:
|
134
|
+
```ruby
|
135
|
+
CanvasSync.default_provisioning_report_chain(
|
136
|
+
%i[list of models to sync], updated_after: false
|
137
|
+
)
|
138
|
+
```
|
139
|
+
It may be one of the following values:
|
140
|
+
* `false` - Will not apply any `updated_after` filtering to the requested reports
|
141
|
+
* An ISO-8601 Date - Will pass the supplied date ad the `updated_after` param for the requested reports
|
142
|
+
* `true` (Default) - Will use the start date of the last successful sync
|
143
|
+
|
144
|
+
If `updated_after` is true, CanvasSync will, by default, perform a full sync every other Sunday.
|
145
|
+
This logic can be customized by passing `full_sync_every` parameter.
|
146
|
+
If you pass a date to `updated_after`, this logic will be disabled unless you explicitly pass a `full_sync_every` parameter.
|
147
|
+
`full_sync_every` accepts the following format strings:
|
148
|
+
- `15%` - Each sync will have a 15% chance of running a full sync
|
149
|
+
- `10 days` - A full sync will be run every 10 days
|
150
|
+
- `sunday` - A full sync will run every Sunday
|
151
|
+
- `saturday/4` - A full sync will run every fourth Saturday
|
152
|
+
|
153
|
+
#### Multiple Sync Chains
|
154
|
+
If your app uses multiple Sync Chains, you may run into issues with the automatic `updated_after` and `full_sync_every` logic.
|
155
|
+
You can fix this by using custom logic or by setting the `batch_genre` parameter when creating the Job Chain. Chains will only
|
156
|
+
use chains of the same genre when computing `updated_after` and `full_sync_every`.
|
157
|
+
|
94
158
|
### Extensible chain
|
95
159
|
It is sometimes desired to extend or customize the chain of jobs that are run with CanvasSync.
|
96
160
|
This can be achieved with the following pattern:
|
97
161
|
|
98
162
|
```ruby
|
99
|
-
|
163
|
+
chain = CanvasSync.default_provisioning_report_chain(
|
100
164
|
%i[list of models to sync]
|
101
165
|
)
|
102
166
|
|
103
|
-
#
|
104
|
-
|
105
|
-
# forks the chain to sync other models (eg Course, Sections, etc.) per-Term.
|
106
|
-
# ForkGather will wait until all the forked chains are complete before continuing.
|
107
|
-
# TL;DR: Jobs placed after SyncProvisioningReportJob and before ForkGather will run once per Term per Sync;
|
108
|
-
# Jobs placed before SyncProvisioningReportJob or after ForkGather will run once per Sync
|
109
|
-
job_chain[:jobs] << { job: CanvasSync::Jobs::ForkGather, options: {} }
|
167
|
+
# Add a custom job to the end of the chain.
|
168
|
+
chain << { job: CanvasSyncCompleteWorker, args: [job.id], kwargs: { job_id: job.id } }
|
110
169
|
|
111
|
-
|
112
|
-
job_chain[:jobs] << { job: CanvasSyncCompleteWorker, options: { job_id: job.id } }
|
170
|
+
chain.process!
|
113
171
|
|
114
|
-
|
172
|
+
# The chain object provides a fairly extensive API:
|
173
|
+
chain.insert({ job: SomeOtherJob, args: [], kwargs: {} }) # Adds the job to the end of the chain
|
174
|
+
chain.insert_at(0, { job: SomeOtherJob }) # Adds the job to the beginning of the chain
|
175
|
+
chain.insert({ job: SomeOtherJob }, after: 'CanvasSync::Jobs::SyncTermsJob') # Adds the job right after the SyncTermsJob
|
176
|
+
chain.insert({ job: SomeOtherJob }, before: 'CanvasSync::Jobs::SyncTermsJob') # Adds the job right before the SyncTermsJob
|
177
|
+
chain.insert({ job: SomeOtherJob }, with: 'CanvasSync::Jobs::SyncTermsJob') # Adds the job to be performed concurrently with the SyncTermsJob
|
178
|
+
|
179
|
+
# Some Jobs (such as the SyncTermsJob) have a sub-chain for, eg, Courses.
|
180
|
+
# chain.insert is aware of these sub-chains and will recurse into them when looking for a before:/after:/with: reference
|
181
|
+
chain.insert({ job: SomeOtherJob }, after: 'CanvasSync::Jobs::SyncCoursesJob') # Adds the job to be performed after SyncCoursesJob (which is a sub-job of the terms job and is duplicated for each term in the term_scope:)
|
182
|
+
# You can also retrieve the sub-chain like so:
|
183
|
+
chain.get_sub_chain('CanvasSync::Jobs::SyncTermsJob')
|
115
184
|
```
|
116
185
|
|
117
186
|
### Processor
|
@@ -130,13 +199,12 @@ end
|
|
130
199
|
|
131
200
|
You must implement a job that will enqueue a report starter for your report. (TODO: would be nice to make some sort of builder for this, so you just define the report and its params and then the gem runs it in a pre-defined job.)
|
132
201
|
|
133
|
-
Let's say we have a custom Canvas report called "my_really_cool_report_csv". First, we would need to create a job class that will enqueue a report starter.
|
202
|
+
Let's say we have a custom Canvas report called "my_really_cool_report_csv". First, we would need to create a job class that will enqueue a report starter.
|
134
203
|
|
135
204
|
```ruby
|
136
205
|
class MyReallyCoolReportJob < CanvasSync::Jobs::ReportStarter
|
137
|
-
def perform(
|
206
|
+
def perform(options)
|
138
207
|
super(
|
139
|
-
job_chain,
|
140
208
|
'my_really_cool_report_csv', # Report name
|
141
209
|
{ "parameters[param1]" => true }, # Report parameters
|
142
210
|
MyCoolProcessor.to_s, # Your processor class as a string
|
@@ -148,69 +216,41 @@ end
|
|
148
216
|
|
149
217
|
You can also see examples in `lib/canvas_sync/jobs/sync_users_job.rb` and `lib/canvas_sync/jobs/sync_provisioning_report.rb`.
|
150
218
|
|
151
|
-
###
|
219
|
+
### Batching
|
152
220
|
|
153
|
-
The `CanvasSync
|
221
|
+
The provisioning report uses the `CanvasSync::Importers::BulkImporter` class to bulk import rows with the activerecord-import gem. It inserts rows in batches of 10,000 by default. This can be customized by setting the `BULK_IMPORTER_BATCH_SIZE` environment variable.
|
154
222
|
|
155
|
-
|
156
|
-
{
|
157
|
-
jobs: [
|
158
|
-
{ job: JobClass, options: {} },
|
159
|
-
{ job: JobClass2, options: {} }
|
160
|
-
],
|
161
|
-
global_options: {}
|
162
|
-
}
|
163
|
-
```
|
223
|
+
### Mapping Overrides
|
164
224
|
|
165
|
-
|
225
|
+
Overrides are useful for two scenarios:
|
166
226
|
|
167
|
-
|
168
|
-
|
169
|
-
jobs: [
|
170
|
-
{ job: MyReallyCoolReportJob, options: {} },
|
171
|
-
{ job: CanvasSync::Jobs::SyncProvisioningReportJob, options: { models: ['users', 'courses'] } }
|
172
|
-
],
|
173
|
-
global_options: {}
|
174
|
-
}
|
227
|
+
- You have an existing application where the column names do not match up with what CanvasSync expects
|
228
|
+
- You want to sync some other column in the report that CanvasSync is not configured to sync
|
175
229
|
|
176
|
-
|
177
|
-
```
|
230
|
+
Mappings can be modified by editing the Model class like such:
|
231
|
+
```ruby
|
232
|
+
class User < ApplicationRecord
|
233
|
+
include CanvasSync::Record
|
178
234
|
|
179
|
-
|
235
|
+
sync_mapping(reset: false) do # `reset: false` is the default
|
236
|
+
# The mapping can be totally cleared with `reset: true` in the `sync_mapping` call, or like such:
|
237
|
+
reset_links
|
180
238
|
|
239
|
+
# Add a new column:
|
240
|
+
link_column :column_in_report => :column_in_database, type: :datetime
|
181
241
|
|
182
|
-
|
183
|
-
|
184
|
-
def perform(job_chain, options)
|
185
|
-
i_dunno_do_something!
|
242
|
+
# If the column name on the report and in the DB are the same, a shorthand can be used:
|
243
|
+
link_column :omit_from_final_grade, type: :datetime
|
186
244
|
|
187
|
-
|
245
|
+
# If the defaults define a column you don't want synced, you can remove it from the mapping:
|
246
|
+
unlink_column :column_in_database
|
188
247
|
end
|
189
|
-
end
|
190
248
|
|
191
|
-
|
192
|
-
|
193
|
-
{ job: SomeRandomJob, options: {} },
|
194
|
-
{ job: CanvasSync::Jobs::SyncProvisioningReportJob, options: { models: ['users', 'courses'] } }
|
195
|
-
],
|
196
|
-
global_options: {}
|
197
|
-
}
|
198
|
-
|
199
|
-
CanvasSync.process_jobs(job_chain)
|
249
|
+
# ...
|
250
|
+
end
|
200
251
|
```
|
201
252
|
|
202
|
-
|
203
|
-
|
204
|
-
The provisioning report uses the `CanvasSync::Importers::BulkImporter` class to bulk import rows with the activerecord-import gem. It inserts rows in batches of 10,000 by default. This can be customized by setting the `BULK_IMPORTER_BATCH_SIZE` environment variable.
|
205
|
-
|
206
|
-
### Mapping Overrides
|
207
|
-
|
208
|
-
Overrides are useful for two scenarios:
|
209
|
-
|
210
|
-
- You have an existing application where the column names do not match up with what CanvasSync expects
|
211
|
-
- You want to sync some other column in the report that CanvasSync is not configured to sync
|
212
|
-
|
213
|
-
In order to create an override, place a file called `canvas_sync_provisioning_mapping.yml` in your Rails `config` directory. Define the tables and columns you want to override using the following format:
|
253
|
+
You can also create a file called `canvas_sync_provisioning_mapping.yml` in your Rails `config` directory. However, this approach requires you to re-specify the complete table in order to modify a table. Define the tables and columns you want to override using the following format:
|
214
254
|
|
215
255
|
```ruby
|
216
256
|
users:
|
@@ -257,6 +297,87 @@ class CanvasSyncModel < ApplicationRecord
|
|
257
297
|
end
|
258
298
|
```
|
259
299
|
|
300
|
+
### Job Batching
|
301
|
+
CanvasSync adds a `CanvasSync::JobBatches` module. It adds Sidekiq/sidekiq-batch like support for Job Batches.
|
302
|
+
It integrates automatically with both Sidekiq and ActiveJob. The API is highly similar to the Sidekiq-batch implementation,
|
303
|
+
documentation for which can be found at https://github.com/mperham/sidekiq/wiki/Batches
|
304
|
+
|
305
|
+
A batch can be created using `Sidekiq::Batch` or `CanvasSync::JobBatching::Batch`.
|
306
|
+
|
307
|
+
Also see `canvas_sync/jobs/begin_sync_chain_job`, `canvas_sync/Job_batches/jobs/serial_batch_job`, or `canvas_sync/Job_batches/jobs/concurrent_batch_job` for example usage.
|
308
|
+
|
309
|
+
Example:
|
310
|
+
```ruby
|
311
|
+
batch = CanvasSync::JobBatches::Batch.new
|
312
|
+
batch.description = "Some Batch" # Optional, but can be useful for debugging
|
313
|
+
|
314
|
+
batch.on(:complete, "SomeClass.on_complete", kw_param: 1)
|
315
|
+
batch.on(:success, "SomeClass.on_success", some_param: 'foo')
|
316
|
+
|
317
|
+
# Add context to the batch. Can be accessed as batch_context on any jobs within the batch.
|
318
|
+
# Nested Batches will have their contexts merged
|
319
|
+
batch.context = {
|
320
|
+
some_value: 'blah',
|
321
|
+
}
|
322
|
+
|
323
|
+
batch.jobs do
|
324
|
+
# Enqueue jobs like normal
|
325
|
+
end
|
326
|
+
```
|
327
|
+
|
328
|
+
#### Job Pools
|
329
|
+
A job pool is like a custom Sidekiq Queue. You can add jobs to it and it will empty itself out into one of the actual queues.
|
330
|
+
However, it adds some options for tweaking the logic:
|
331
|
+
- `concurrency` (default: `nil`) - Define how many jobs from the pool can run at once.
|
332
|
+
- `order` (default: `fifo`) - Define how the pool will empty itself
|
333
|
+
- `fifo` - First-In First-Out, a traditional queue
|
334
|
+
- `lifo` - Last-In First-Out
|
335
|
+
- `random` - Pluck and run jobs in random order
|
336
|
+
- `priority` - Execute jobs in a priority order (NB: Due to Redis, this priority-random, meaning that items with the same priority will be run in random order, not fifo)
|
337
|
+
- `clean_when_empty` (default: `true`) - Automatically clean the pool when it is empty
|
338
|
+
- `on_failed_job` (default `:wait`) - If a Job fails, should the pool `:continue` and still enqueue the next job or `:wait` for the job to succeed
|
339
|
+
|
340
|
+
Example:
|
341
|
+
```ruby
|
342
|
+
pool = CanvasSync::JobBatches::Pool.new(concurrency: 4, order: :priority, clean_when_empty: false)
|
343
|
+
pool_id = pool.pid
|
344
|
+
|
345
|
+
# Add a job to the pool
|
346
|
+
pool << {
|
347
|
+
job: SomeJob, # The Class of a ActiveJob Job or Sidekiq Worker
|
348
|
+
args: [1, 2, 3], # Array of params to pass th e Job
|
349
|
+
kwargs: {},
|
350
|
+
priority: 100, # Only effective if order=:priority, higher is higher
|
351
|
+
}
|
352
|
+
|
353
|
+
# Add many jobs to the pool
|
354
|
+
pool.add_jobs([
|
355
|
+
{
|
356
|
+
job: SomeJob, # The Class of a ActiveJob Job or Sidekiq Worker
|
357
|
+
args: [1, 2, 3], # Array of params to pass th e Job
|
358
|
+
kwargs: {},
|
359
|
+
priority: 100, # Only effective if order=:priority, higher is higher
|
360
|
+
},
|
361
|
+
# ...
|
362
|
+
])
|
363
|
+
|
364
|
+
# ...Later
|
365
|
+
CanvasSync::JobBatches::Pool.from_pid(pool_id).cleanup_redis
|
366
|
+
```
|
367
|
+
|
368
|
+
### Custom Bearcat Instance
|
369
|
+
You can define a global `canvas_sync_client` method to return a Bearcat Client instance for CanvasSync to use:
|
370
|
+
```ruby
|
371
|
+
# config/initializers/canvas_sync.rb
|
372
|
+
def canvas_sync_client
|
373
|
+
Bearcat::Client.new(token: current_organization.settings[:api_token], prefix: current_organization.settings[:base_url])
|
374
|
+
end
|
375
|
+
```
|
376
|
+
|
377
|
+
(Having the client defined here means the sensitive API token doesn't have to be passed in plain text between jobs.)
|
378
|
+
|
379
|
+
This used to be required, but when both CanvasSync and PandaPal are up to date, this is defined automagically.
|
380
|
+
|
260
381
|
|
261
382
|
## Legacy Support
|
262
383
|
|
@@ -297,7 +418,7 @@ If you want your own jobs to also log to the table all you have to do is have yo
|
|
297
418
|
|
298
419
|
If you want to be able to utilize the `CanvasSync::JobLog` without `ActiveJob` (so you can get access to `Sidekiq` features that `ActiveJob` doesn't support), then add the following to an initializer in your Rails app:
|
299
420
|
|
300
|
-
```
|
421
|
+
```ruby
|
301
422
|
Sidekiq.configure_server do |config|
|
302
423
|
config.server_middleware do |chain|
|
303
424
|
chain.add CanvasSync::Sidekiq::Middleware
|
@@ -308,87 +429,26 @@ end
|
|
308
429
|
## Syncronize different reports
|
309
430
|
CanvasSync provides the functionality to import data from other reports into an specific table.
|
310
431
|
|
311
|
-
This can be
|
312
|
-
|
313
|
-
```ruby
|
314
|
-
CanvasSync.provisioning_sync(<array of models to sync>, term_scope: <optional term scope>)
|
315
|
-
CanvasSync
|
316
|
-
.simple_report_sync(
|
317
|
-
[
|
318
|
-
{
|
319
|
-
report_name: <report name>,
|
320
|
-
model: <model to sync>,
|
321
|
-
params: <hash with the require parameters the report needs to sync>
|
322
|
-
},
|
323
|
-
{
|
324
|
-
report_name: <report name>,
|
325
|
-
model: <model to sync>,
|
326
|
-
params: <hash with the require parameters the report needs to sync>
|
327
|
-
},
|
328
|
-
...
|
329
|
-
],
|
330
|
-
term_scope: <optional term scope>
|
331
|
-
)
|
332
|
-
```
|
333
|
-
|
334
|
-
Example:
|
335
|
-
|
336
|
-
```ruby
|
337
|
-
CanvasSync
|
338
|
-
.simple_report_sync(
|
339
|
-
[
|
340
|
-
{
|
341
|
-
report_name: 'proservices_provisioning_csv',
|
342
|
-
model: 'users',
|
343
|
-
params: {
|
344
|
-
"parameters[include_deleted]" => true,
|
345
|
-
"parameters[users]" => true
|
346
|
-
}
|
347
|
-
},
|
348
|
-
{
|
349
|
-
report_name: 'proservices_provisioning_csv',
|
350
|
-
model: 'accounts',
|
351
|
-
params: {
|
352
|
-
"parameters[include_deleted]" => true,
|
353
|
-
"parameters[accounts]" => true
|
354
|
-
}
|
355
|
-
}
|
356
|
-
]
|
357
|
-
)
|
358
|
-
```
|
359
|
-
|
360
|
-
Example with the term_scope active:
|
432
|
+
This can be achieved by using the following method
|
361
433
|
|
362
434
|
```ruby
|
363
|
-
CanvasSync
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
},
|
374
|
-
{
|
375
|
-
report_name: 'proservices_provisioning_csv',
|
376
|
-
model: 'courses',
|
377
|
-
params: {
|
378
|
-
"parameters[include_deleted]" => true,
|
379
|
-
"parameters[courses]" => true
|
380
|
-
}
|
381
|
-
}
|
382
|
-
],
|
383
|
-
term_scope: 'active'
|
384
|
-
)
|
435
|
+
chain = CanvasSync.default_provisioning_report_chain
|
436
|
+
chain << {
|
437
|
+
job: CanvasSync::Jobs::SyncSimpleTableJob,
|
438
|
+
options: {
|
439
|
+
report_name: <report name>,
|
440
|
+
model: <model to sync>,
|
441
|
+
params: <hash with the require parameters the report needs to sync>
|
442
|
+
},
|
443
|
+
}
|
444
|
+
chain.process!
|
385
445
|
```
|
386
446
|
|
387
447
|
## Configuration
|
388
448
|
|
389
449
|
You can configure CanvasSync settings by doing the following:
|
390
450
|
|
391
|
-
```
|
451
|
+
```ruby
|
392
452
|
CanvasSync.configure do |config|
|
393
453
|
config.classes_to_only_log_errors_on << "ClassToOnlyLogErrorsOn"
|
394
454
|
end
|
@@ -398,6 +458,29 @@ Available config options (if you add more, please update this!):
|
|
398
458
|
|
399
459
|
* `config.classes_to_only_log_errors_on` - use this if you are utilizing the `CanvasSync::JobLog` table, but want certain classes to only persist in the `job_logs` table if an error is encountered. This is useful if you've got a very frequently used job that's filling up your database, and only really care about tracking failures.
|
400
460
|
|
461
|
+
## Global Options
|
462
|
+
You can pass in global_options to a job chain. Global options are added to the batch_context and referenced by
|
463
|
+
various internal processes.
|
464
|
+
|
465
|
+
Pass global options into a job chain, using the options param nested in a :global key.
|
466
|
+
`options: { global: {...} }`
|
467
|
+
|
468
|
+
report_timeout (integer): Number of days until a Canvas report should timeout. Default is 1.
|
469
|
+
report_compilation_timeout (integer): Number of days until a Canvas report should timeout. Default is 1 hour.
|
470
|
+
You can likely pass a float to achieve sub-day timeouts, but not tested.
|
471
|
+
report_max_tries (integer): The number of times to attempt a report before giving up. A report is considered failed
|
472
|
+
if it has an 'error' status in Canvas or is deleted.
|
473
|
+
|
474
|
+
This is an example job chain with global options:
|
475
|
+
```ruby
|
476
|
+
job_chain = CanvasSync.default_provisioning_report_chain(
|
477
|
+
MODELS_TO_SYNC,
|
478
|
+
term_scope: :active,
|
479
|
+
full_sync_every: 'sunday',
|
480
|
+
options: { global: { report_timeout: 2 } }
|
481
|
+
)
|
482
|
+
```
|
483
|
+
|
401
484
|
## Handling Job errors
|
402
485
|
|
403
486
|
If you need custom handling for when a CanvasSync Job fails, you can add an `:on_failure` option to you Job Chain's `:global_options`.
|
@@ -421,7 +504,6 @@ class CanvasSyncStarterWorker
|
|
421
504
|
}
|
422
505
|
}
|
423
506
|
)
|
424
|
-
CanvasSync.invoke_next(job_chain)
|
425
507
|
end
|
426
508
|
|
427
509
|
def self.handle_canvas_sync_error(error, **options)
|
@@ -436,7 +518,9 @@ Re-running the generator when there's been a gem change will give you several ch
|
|
436
518
|
|
437
519
|
Additionally, if there have been schema changes to an existing model you may have to run your own migration to bring it up to speed.
|
438
520
|
|
439
|
-
|
521
|
+
Also see `CHANGELOG.md`.
|
522
|
+
|
523
|
+
If you make updates to the gem please add any upgrade instructions to `CHANGELOG.md`.
|
440
524
|
|
441
525
|
## Integrating with existing applications
|
442
526
|
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module CanvasSync::Api
|
2
|
+
module V1
|
3
|
+
class LiveEventsController < ActionController::Base
|
4
|
+
around_action :switch_tenant
|
5
|
+
|
6
|
+
def process_event
|
7
|
+
if params[:payload].present?
|
8
|
+
process_eventsmanager_event
|
9
|
+
else
|
10
|
+
process_dataservices_event
|
11
|
+
end
|
12
|
+
rescue => e
|
13
|
+
Rails.logger.error("Live Events Error: #{e.message} - #{e.backtrace}")
|
14
|
+
render json: { error: "Live Events Error: #{e.message}" }, status: 422
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def process_eventsmanager_event
|
20
|
+
event = SymmetricEncryption.decrypt(params[:payload])
|
21
|
+
event = JSON.parse(event).with_indifferent_access
|
22
|
+
|
23
|
+
event[:metadata] = event[:attributes]
|
24
|
+
event.delete(:attributes)
|
25
|
+
|
26
|
+
Rails.logger.debug("Processing event type: #{event['metadata']['event_name']}")
|
27
|
+
Rails.logger.debug("Payload: #{event}")
|
28
|
+
|
29
|
+
validate_tenant!(event.dig(:metadata, :root_account_uuid))
|
30
|
+
transform_ids!(event)
|
31
|
+
event[:via] = "eventsmanager"
|
32
|
+
|
33
|
+
dispatch_event(event)
|
34
|
+
|
35
|
+
head :ok
|
36
|
+
end
|
37
|
+
|
38
|
+
def process_dataservices_event
|
39
|
+
require "json/jwt"
|
40
|
+
require "httparty"
|
41
|
+
|
42
|
+
event = nil
|
43
|
+
|
44
|
+
# Only allow unsigned events during devleopment
|
45
|
+
if Rails.env.development?
|
46
|
+
event = JSON.parse(request.raw_post) rescue nil
|
47
|
+
end
|
48
|
+
|
49
|
+
event ||= JSON::JWT.decode(params[:_json], dataservices_jwks)
|
50
|
+
event = event.to_h.with_indifferent_access
|
51
|
+
|
52
|
+
Rails.logger.debug("Processing event type: #{event['metadata']['event_name']}")
|
53
|
+
Rails.logger.debug("Payload: #{event}")
|
54
|
+
|
55
|
+
validate_tenant!(event.dig(:metadata, :root_account_uuid))
|
56
|
+
transform_ids!(event)
|
57
|
+
event[:via] = "dataservices"
|
58
|
+
|
59
|
+
dispatch_event(event)
|
60
|
+
|
61
|
+
head :ok
|
62
|
+
end
|
63
|
+
|
64
|
+
def dispatch_event(event)
|
65
|
+
CanvasSync::LiveEvents::ProcessEventJob.perform_later(event)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Live events will use a canvas global ID (cross shard) for any ID's provided. This method will return the local ID.
|
69
|
+
def local_canvas_id(id)
|
70
|
+
# TODO: Don't apply this to cross-shard entries
|
71
|
+
id.to_i % 10_000_000_000_000
|
72
|
+
end
|
73
|
+
|
74
|
+
def transform_ids!(data)
|
75
|
+
transformed = {}
|
76
|
+
data.each do |k, v|
|
77
|
+
if k.ends_with?("_id") && (v.is_a?(String) || v.is_a?(Numeric))
|
78
|
+
transformed["sharded_#{k}"] = v
|
79
|
+
transformed[k] = local_canvas_id(v) rescue v
|
80
|
+
elsif v.is_a?(Hash)
|
81
|
+
transform_ids!(v)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
data.merge!(transformed)
|
85
|
+
end
|
86
|
+
|
87
|
+
def dataservices_jwks
|
88
|
+
require "httparty"
|
89
|
+
|
90
|
+
jwk_json = Rails.cache.fetch("canvas_sync/dataservices_jwks", expires_in: 24.hours) do
|
91
|
+
jkws_url = ENV["DATASERVICES_JWK_URL"].presence || "https://8axpcl50e4.execute-api.us-east-1.amazonaws.com/main/jwks"
|
92
|
+
response = HTTParty.get(jkws_url)
|
93
|
+
JSON.parse(response.body)
|
94
|
+
end
|
95
|
+
|
96
|
+
JSON::JWK::Set.new(jwk_json)
|
97
|
+
end
|
98
|
+
|
99
|
+
def validate_tenant!(event_uuid)
|
100
|
+
if defined?(PandaPal)
|
101
|
+
root_info = current_organization.root_account_info
|
102
|
+
root_uuid = root_info[:uuid]
|
103
|
+
|
104
|
+
if !root_uuid.present? || root_uuid != event_uuid
|
105
|
+
render json: { error: "Invalid Organization/UUID" }, status: 403
|
106
|
+
return
|
107
|
+
end
|
108
|
+
else
|
109
|
+
raise "No way to validate LiveEvent is genuinely from the correct Canvas instance! Monkey-patch CanvasSync::Api::V1::LiveEventsController#validate_tenant!"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def switch_tenant(&block)
|
114
|
+
if defined?(PandaPal) && (org = params[:organization] || params[:org]).present?
|
115
|
+
Apartment::Tenant.switch(PandaPal::Organization.find(org).name, &block)
|
116
|
+
else
|
117
|
+
yield
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|