canvas_sync 0.20.5 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +64 -12
- 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/config/routes.rb +7 -0
- data/lib/canvas_sync/concerns/live_event_sync.rb +46 -0
- data/lib/canvas_sync/generators/install_live_events_generator.rb +0 -1
- data/lib/canvas_sync/generators/templates/models/account.rb +1 -0
- data/lib/canvas_sync/generators/templates/models/assignment.rb +1 -0
- data/lib/canvas_sync/generators/templates/models/assignment_group.rb +1 -0
- data/lib/canvas_sync/generators/templates/models/context_module.rb +1 -0
- data/lib/canvas_sync/generators/templates/models/context_module_item.rb +1 -0
- data/lib/canvas_sync/generators/templates/models/course.rb +8 -0
- data/lib/canvas_sync/generators/templates/models/enrollment.rb +12 -0
- data/lib/canvas_sync/generators/templates/models/section.rb +7 -0
- data/lib/canvas_sync/generators/templates/models/submission.rb +1 -0
- data/lib/canvas_sync/generators/templates/models/user.rb +8 -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/{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/version.rb +1 -1
- data/lib/canvas_sync.rb +1 -0
- 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/dummy/app/models/account.rb +1 -0
- data/spec/dummy/app/models/assignment.rb +1 -0
- data/spec/dummy/app/models/assignment_group.rb +1 -0
- data/spec/dummy/app/models/context_module.rb +1 -0
- data/spec/dummy/app/models/context_module_item.rb +1 -0
- data/spec/dummy/app/models/course.rb +8 -0
- data/spec/dummy/app/models/enrollment.rb +12 -0
- data/spec/dummy/app/models/section.rb +7 -0
- data/spec/dummy/app/models/submission.rb +1 -0
- data/spec/dummy/app/models/user.rb +8 -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/routes.rb +1 -0
- metadata +218 -181
- data/app/controllers/api/v1/live_events_controller.rb +0 -18
- data/lib/canvas_sync/concerns/auto_relations.rb +0 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
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
|
```
|
@@ -50,18 +50,14 @@ Make sure you've setup sidekiq to work properly with ActiveJob as [outlined here
|
|
50
50
|
|
51
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.
|
52
52
|
|
53
|
-
|
54
|
-
|
55
|
-
|
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`
|
56
57
|
|
57
|
-
|
58
|
-
# config/initializers/canvas_sync.rb
|
59
|
-
def canvas_sync_client
|
60
|
-
Bearcat::Client.new(token: current_organization.settings[:api_token], prefix: current_organization.settings[:base_url])
|
61
|
-
end
|
62
|
-
```
|
58
|
+
## Basic Usage
|
63
59
|
|
64
|
-
|
60
|
+
Your tool must have an `ActiveJob` compatible job queue adapter configured, such as DelayedJob or Sidekiq.
|
65
61
|
|
66
62
|
Once that's done and you've used the generator to create your models and migrations you can run the standard provisioning sync:
|
67
63
|
|
@@ -83,6 +79,47 @@ If you pass in the optional `term_scope` the provisioning reports will be run fo
|
|
83
79
|
|
84
80
|
Imports are inserted in bulk with [activerecord-import](https://github.com/zdennis/activerecord-import) so they should be very fast.
|
85
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`
|
86
123
|
|
87
124
|
## Advanced Usage
|
88
125
|
|
@@ -326,9 +363,22 @@ pool.add_jobs([
|
|
326
363
|
|
327
364
|
# ...Later
|
328
365
|
CanvasSync::JobBatches::Pool.from_pid(pool_id).cleanup_redis
|
366
|
+
```
|
329
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
|
330
375
|
```
|
331
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
|
+
|
381
|
+
|
332
382
|
## Legacy Support
|
333
383
|
|
334
384
|
### Legacy Mappings
|
@@ -468,7 +518,9 @@ Re-running the generator when there's been a gem change will give you several ch
|
|
468
518
|
|
469
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.
|
470
520
|
|
471
|
-
|
521
|
+
Also see `CHANGELOG.md`.
|
522
|
+
|
523
|
+
If you make updates to the gem please add any upgrade instructions to `CHANGELOG.md`.
|
472
524
|
|
473
525
|
## Integrating with existing applications
|
474
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
|
data/config/routes.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
|
2
|
+
module CanvasSync::Concerns
|
3
|
+
module LiveEventSync
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
define_model_callbacks :process_live_event
|
8
|
+
end
|
9
|
+
|
10
|
+
class_methods do
|
11
|
+
def cs_internal_process_live_event(event)
|
12
|
+
meta = event[:metadata]
|
13
|
+
payload = event[:payload] || event[:body]
|
14
|
+
|
15
|
+
canvas_id = payload[:id] || payload[:"#{name.underscore}_id"]
|
16
|
+
inst = self.find_or_initialize_by(canvas_id: canvas_id)
|
17
|
+
model, _, subtype = meta[:event_name].rpartition('_')
|
18
|
+
|
19
|
+
result = inst.run_callbacks(:process_live_event) do
|
20
|
+
inst.process_live_event(subtype.to_sym, payload, meta)
|
21
|
+
end
|
22
|
+
|
23
|
+
inst.save! if result != false && inst.changed?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def process_live_event(event_type, payload, metadata)
|
28
|
+
api_response = request_from_api
|
29
|
+
assign_from_api_params(api_response)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
CanvasSync::LiveEvents.listen do |event|
|
34
|
+
meta = event[:metadata]
|
35
|
+
payload = event[:body]
|
36
|
+
|
37
|
+
event_type = meta[:event_name]
|
38
|
+
|
39
|
+
model, _, subtype = event_type.rpartition('_')
|
40
|
+
mcls = model.classify.constantize rescue nil
|
41
|
+
|
42
|
+
if mcls.present? && mcls < LiveEventSync
|
43
|
+
mcls.cs_internal_process_live_event(event)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -4,6 +4,7 @@ class Account < ApplicationRecord
|
|
4
4
|
include CanvasSync::Record
|
5
5
|
include CanvasSync::Concerns::ApiSyncable
|
6
6
|
# include CanvasSync::Concerns::Account::Ancestry # Add support for the ancestry Gem
|
7
|
+
# include CanvasSync::Concerns::LiveEventSync
|
7
8
|
|
8
9
|
canvas_sync_features :defaults
|
9
10
|
|
@@ -6,6 +6,14 @@ class Course < ApplicationRecord
|
|
6
6
|
|
7
7
|
canvas_sync_features :defaults
|
8
8
|
|
9
|
+
# include CanvasSync::Concerns::LiveEventSync
|
10
|
+
# after_process_live_event do
|
11
|
+
# if account.nil?
|
12
|
+
# acc = Account.new(canvas_id: canvas_account_id)
|
13
|
+
# acc.sync_from_api
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
|
9
17
|
validates :canvas_id, uniqueness: true, presence: true
|
10
18
|
belongs_to :term, foreign_key: :canvas_term_id, primary_key: :canvas_id, optional: true
|
11
19
|
has_many :enrollments, primary_key: :canvas_id, foreign_key: :canvas_course_id
|
@@ -6,6 +6,18 @@ class Enrollment < ApplicationRecord
|
|
6
6
|
|
7
7
|
canvas_sync_features :defaults
|
8
8
|
|
9
|
+
# include CanvasSync::Concerns::LiveEventSync
|
10
|
+
# after_process_live_event do
|
11
|
+
# if user.nil?
|
12
|
+
# u = User.new(canvas_id: canvas_user_id)
|
13
|
+
# u.sync_from_api
|
14
|
+
# end
|
15
|
+
# if course.nil?
|
16
|
+
# c = Course.new(canvas_id: canvas_course_id)
|
17
|
+
# c.sync_from_api
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
|
9
21
|
validates :canvas_id, uniqueness: true, presence: true
|
10
22
|
belongs_to :user, primary_key: :canvas_id, foreign_key: :canvas_user_id, optional: true
|
11
23
|
belongs_to :role, primary_key: :canvas_id, foreign_key: :canvas_role_id, optional: true
|
@@ -6,6 +6,13 @@ class Section < ApplicationRecord
|
|
6
6
|
|
7
7
|
canvas_sync_features :defaults
|
8
8
|
|
9
|
+
# include CanvasSync::Concerns::LiveEventSync
|
10
|
+
# after_process_live_event do
|
11
|
+
# # A section change could constitute a crosslisting change, which means
|
12
|
+
# # we need to make sure all our enrollments are pointing to the correct course
|
13
|
+
# enrollments.update_all(canvas_course_id: canvas_course_id)
|
14
|
+
# end
|
15
|
+
|
9
16
|
validates :canvas_id, uniqueness: true, presence: true
|
10
17
|
belongs_to :course, primary_key: :canvas_id, foreign_key: :canvas_course_id, optional: true
|
11
18
|
has_many :enrollments, primary_key: :canvas_id, foreign_key: :canvas_section_id
|
@@ -6,6 +6,14 @@ class User < ApplicationRecord
|
|
6
6
|
|
7
7
|
canvas_sync_features :defaults
|
8
8
|
|
9
|
+
# include CanvasSync::Concerns::LiveEventSync
|
10
|
+
# around_process_live_event do |user, blk|
|
11
|
+
# blk.call
|
12
|
+
# rescue Footrest::HttpError::Unauthorized => e
|
13
|
+
# # This can happen when a new user is created, but hasn't setup a login on Canvas yet.
|
14
|
+
# Rails.logger.info("Failed to fetch user #{canvas_user_id}: #{e.backtrace}")
|
15
|
+
# end
|
16
|
+
|
9
17
|
validates :canvas_id, uniqueness: true, presence: true
|
10
18
|
has_many :pseudonyms, primary_key: :canvas_id, foreign_key: :canvas_user_id
|
11
19
|
has_many :enrollments, primary_key: :canvas_id, foreign_key: :canvas_user_id
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# <%= autogenerated_event_warning %>
|
2
2
|
|
3
3
|
module LiveEvents
|
4
|
-
class AssignmentGroupEvent < LiveEvents::
|
4
|
+
class AssignmentGroupEvent < CanvasSync::LiveEvents::BaseHandler
|
5
5
|
|
6
6
|
def process
|
7
7
|
canvas_assignment_group_id = local_canvas_id(payload[:assignment_group_id])
|
@@ -1,8 +1,7 @@
|
|
1
1
|
# <%= autogenerated_event_warning %>
|
2
2
|
|
3
3
|
module LiveEvents
|
4
|
-
class CourseEvent < LiveEvents::
|
5
|
-
|
4
|
+
class CourseEvent < CanvasSync::LiveEvents::BaseHandler
|
6
5
|
def process
|
7
6
|
course = Course.where(canvas_id: local_canvas_id(payload[:course_id])).first_or_initialize
|
8
7
|
course.canvas_account_id = local_canvas_id(payload[:account_id])
|
@@ -12,7 +11,6 @@ module LiveEvents
|
|
12
11
|
end
|
13
12
|
course.sync_from_api
|
14
13
|
end
|
15
|
-
|
16
14
|
end
|
17
15
|
|
18
16
|
class CourseCreatedEvent < LiveEvents::CourseEvent; end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# <%= autogenerated_event_warning %>
|
2
2
|
|
3
3
|
module LiveEvents
|
4
|
-
class CourseSectionEvent < LiveEvents::
|
4
|
+
class CourseSectionEvent < CanvasSync::LiveEvents::BaseHandler
|
5
5
|
|
6
6
|
def process
|
7
7
|
section = Section.where(canvas_id: local_canvas_id(payload[:course_section_id])).first_or_initialize
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# <%= autogenerated_event_warning %>
|
2
2
|
|
3
3
|
module LiveEvents
|
4
|
-
class ModuleItemEvent < LiveEvents::
|
4
|
+
class ModuleItemEvent < CanvasSync::LiveEvents::BaseHandler
|
5
5
|
|
6
6
|
def process
|
7
7
|
context_module_item = ContextModuleItem.find_or_initialize_by(canvas_id: payload["module_item_id"])
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# <%= autogenerated_event_warning %>
|
2
2
|
|
3
3
|
module LiveEvents
|
4
|
-
class SubmissionEvent < LiveEvents::
|
4
|
+
class SubmissionEvent < CanvasSync::LiveEvents::BaseHandler
|
5
5
|
|
6
6
|
def process
|
7
7
|
submission = Submission.where(canvas_id: local_canvas_id(payload["submission_id"])).first_or_initialize
|
@@ -1,8 +1,7 @@
|
|
1
1
|
# <%= autogenerated_event_warning %>
|
2
2
|
|
3
3
|
module LiveEvents
|
4
|
-
class UserEvent < LiveEvents::
|
5
|
-
|
4
|
+
class UserEvent < CanvasSync::LiveEvents::BaseHandler
|
6
5
|
def process
|
7
6
|
canvas_user_id = local_canvas_id(payload[:user_id])
|
8
7
|
user = User.where(canvas_id: canvas_user_id).first_or_initialize
|
@@ -11,7 +10,6 @@ module LiveEvents
|
|
11
10
|
# This can happen when a new user is created, but hasn't setup a login on Canvas yet.
|
12
11
|
Rails.logger.info("Failed to fetch user #{canvas_user_id}: #{e.backtrace}")
|
13
12
|
end
|
14
|
-
|
15
13
|
end
|
16
14
|
|
17
15
|
class UserCreatedEvent < LiveEvents::UserEvent; end
|
@@ -1,29 +1,25 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
module LiveEvents
|
1
|
+
module CanvasSync::LiveEvents
|
4
2
|
class RetryLiveEventJob < StandardError; end
|
5
3
|
|
6
|
-
|
4
|
+
# Deprecated
|
5
|
+
class BaseHandler < CanvasSync::Job
|
7
6
|
attr_accessor :raw_payload
|
8
7
|
attr_accessor :payload
|
9
8
|
attr_accessor :metadata
|
10
9
|
|
11
10
|
def perform(event_payload)
|
12
11
|
@raw_payload = event_payload
|
13
|
-
@metadata = HashWithIndifferentAccess.new(event_payload["
|
12
|
+
@metadata = HashWithIndifferentAccess.new(event_payload["metadata"])
|
14
13
|
@payload = HashWithIndifferentAccess.new(event_payload["body"])
|
15
14
|
process_with_retry
|
16
15
|
end
|
17
16
|
|
17
|
+
protected
|
18
|
+
|
18
19
|
def process
|
19
20
|
raise "process must be implemented in your subclass"
|
20
21
|
end
|
21
22
|
|
22
|
-
# Live events will use a canvas global ID (cross shard) for any ID's provided. This method will return the local ID.
|
23
|
-
def local_canvas_id(id)
|
24
|
-
id.to_i % 10_000_000_000_000
|
25
|
-
end
|
26
|
-
|
27
23
|
private
|
28
24
|
|
29
25
|
# Sometimes a creation and update event get sent by Canvas at almost the exact same time. This means
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
module CanvasSync::LiveEvents
|
3
|
+
class ProcessEventJob < ::CanvasSync::Job
|
4
|
+
def perform(event)
|
5
|
+
event = event.with_indifferent_access
|
6
|
+
|
7
|
+
# Support Legacy-Style Event Handlers
|
8
|
+
begin
|
9
|
+
legacy_handler = "LiveEvents::#{event['metadata']['event_name'].camelcase}Event".constantize
|
10
|
+
legacy_handler.perform_later(event)
|
11
|
+
return
|
12
|
+
rescue LoadError, NameError => e
|
13
|
+
Rails.logger.error("Error accessing legacy-style LiveEvents handler #{e}, #{event}")
|
14
|
+
end
|
15
|
+
|
16
|
+
handlers = CanvasSync::LiveEvents.registered_handlers
|
17
|
+
handlers.each do |h|
|
18
|
+
h.call(event)
|
19
|
+
rescue => e
|
20
|
+
throw e if Rails.env.test?
|
21
|
+
Rails.logger.error("LiveEvent Handler raised Error: #{e}")
|
22
|
+
Raven.captureException(e) if defined? Raven
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|