canvas_sync 0.20.5 → 0.21.1.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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 +51 -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: 4084b7cb347bac36ddd3ac2a0731af45c6798671d6804b1d2e4d137f378c36e1
|
4
|
+
data.tar.gz: 59525fd3e1ca60683e28ee7c406182954b6c95bc3489fcff47975cf77a7502bd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ec138d7bb268418a14f99f4746f59f3bbf69cad3ffe97323555225e7356f0ba92576e62c073c692b017c34a87da92f3a42021eea0c89cf270143a96863fd5207
|
7
|
+
data.tar.gz: 8b12e3b29bca0998845aa1ee88c4928b304e3e077a008dd6e71ae17a0d056239472d4936c3a00410d8841cf264824e1072907e6e1eddb628c96b4a2891e0f075
|
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,51 @@
|
|
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
|
+
if self.has_attribute?(:workflow_state) && payload['workflow_state'].present? && payload['workflow_state'] == 'deleted'
|
29
|
+
# API will respond with 404, so just update the workflow_state
|
30
|
+
update(workflow_state: payload['workflow_state'])
|
31
|
+
else
|
32
|
+
api_response = request_from_api
|
33
|
+
assign_from_api_params(api_response)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
CanvasSync::LiveEvents.listen do |event|
|
39
|
+
meta = event[:metadata]
|
40
|
+
payload = event[:body]
|
41
|
+
|
42
|
+
event_type = meta[:event_name]
|
43
|
+
|
44
|
+
model, _, subtype = event_type.rpartition('_')
|
45
|
+
mcls = model.classify.constantize rescue nil
|
46
|
+
|
47
|
+
if mcls.present? && mcls < LiveEventSync
|
48
|
+
mcls.cs_internal_process_live_event(event)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
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
|