canvas_sync 0.20.5 → 0.21.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +64 -12
  3. data/app/controllers/{api → canvas_sync/api}/v1/health_check_controller.rb +1 -1
  4. data/app/controllers/canvas_sync/api/v1/live_events_controller.rb +122 -0
  5. data/config/routes.rb +7 -0
  6. data/lib/canvas_sync/concerns/live_event_sync.rb +46 -0
  7. data/lib/canvas_sync/generators/install_live_events_generator.rb +0 -1
  8. data/lib/canvas_sync/generators/templates/models/account.rb +1 -0
  9. data/lib/canvas_sync/generators/templates/models/assignment.rb +1 -0
  10. data/lib/canvas_sync/generators/templates/models/assignment_group.rb +1 -0
  11. data/lib/canvas_sync/generators/templates/models/context_module.rb +1 -0
  12. data/lib/canvas_sync/generators/templates/models/context_module_item.rb +1 -0
  13. data/lib/canvas_sync/generators/templates/models/course.rb +8 -0
  14. data/lib/canvas_sync/generators/templates/models/enrollment.rb +12 -0
  15. data/lib/canvas_sync/generators/templates/models/section.rb +7 -0
  16. data/lib/canvas_sync/generators/templates/models/submission.rb +1 -0
  17. data/lib/canvas_sync/generators/templates/models/user.rb +8 -0
  18. data/lib/canvas_sync/generators/templates/services/live_events/assignment_event.rb +1 -1
  19. data/lib/canvas_sync/generators/templates/services/live_events/assignment_group_event.rb +1 -1
  20. data/lib/canvas_sync/generators/templates/services/live_events/course_event.rb +1 -3
  21. data/lib/canvas_sync/generators/templates/services/live_events/course_section_event.rb +1 -1
  22. data/lib/canvas_sync/generators/templates/services/live_events/enrollment_event.rb +1 -1
  23. data/lib/canvas_sync/generators/templates/services/live_events/grade_event.rb +1 -1
  24. data/lib/canvas_sync/generators/templates/services/live_events/module_event.rb +1 -1
  25. data/lib/canvas_sync/generators/templates/services/live_events/module_item_event.rb +1 -1
  26. data/lib/canvas_sync/generators/templates/services/live_events/submission_event.rb +1 -1
  27. data/lib/canvas_sync/generators/templates/services/live_events/syllabus_event.rb +1 -1
  28. data/lib/canvas_sync/generators/templates/services/live_events/user_event.rb +1 -3
  29. data/lib/canvas_sync/{generators/templates/services/live_events/base_event.rb → live_events/base_handler.rb} +6 -10
  30. data/lib/canvas_sync/live_events/process_event_job.rb +26 -0
  31. data/lib/canvas_sync/live_events.rb +38 -0
  32. data/lib/canvas_sync/version.rb +1 -1
  33. data/lib/canvas_sync.rb +1 -0
  34. data/spec/canvas_sync/live_events/live_event_sync_spec.rb +27 -0
  35. data/spec/canvas_sync/live_events/live_events_controller_spec.rb +54 -0
  36. data/spec/canvas_sync/live_events/process_event_job_spec.rb +38 -0
  37. data/spec/dummy/app/models/account.rb +1 -0
  38. data/spec/dummy/app/models/assignment.rb +1 -0
  39. data/spec/dummy/app/models/assignment_group.rb +1 -0
  40. data/spec/dummy/app/models/context_module.rb +1 -0
  41. data/spec/dummy/app/models/context_module_item.rb +1 -0
  42. data/spec/dummy/app/models/course.rb +8 -0
  43. data/spec/dummy/app/models/enrollment.rb +12 -0
  44. data/spec/dummy/app/models/section.rb +7 -0
  45. data/spec/dummy/app/models/submission.rb +1 -0
  46. data/spec/dummy/app/models/user.rb +8 -0
  47. data/spec/dummy/app/services/live_events/assignment_event.rb +1 -1
  48. data/spec/dummy/app/services/live_events/course_event.rb +1 -3
  49. data/spec/dummy/app/services/live_events/course_section_event.rb +1 -1
  50. data/spec/dummy/app/services/live_events/enrollment_event.rb +1 -1
  51. data/spec/dummy/app/services/live_events/grade_event.rb +1 -1
  52. data/spec/dummy/app/services/live_events/module_event.rb +1 -1
  53. data/spec/dummy/app/services/live_events/module_item_event.rb +1 -1
  54. data/spec/dummy/app/services/live_events/submission_event.rb +1 -1
  55. data/spec/dummy/app/services/live_events/syllabus_event.rb +1 -1
  56. data/spec/dummy/app/services/live_events/user_event.rb +1 -3
  57. data/spec/dummy/config/routes.rb +1 -0
  58. metadata +218 -181
  59. data/app/controllers/api/v1/live_events_controller.rb +0 -18
  60. data/lib/canvas_sync/concerns/auto_relations.rb +0 -11
@@ -0,0 +1,38 @@
1
+ require_relative './live_events/base_handler.rb'
2
+ require_relative './live_events/process_event_job.rb'
3
+
4
+ module CanvasSync
5
+ module LiveEvents
6
+
7
+ @@registered_handlers = []
8
+
9
+ class << self
10
+ def listen(event_types = nil, &blk)
11
+ if event_types != nil
12
+ blk = wrap_method(blk) do |inner, event|
13
+ meta = event[:metadata]
14
+ payload = event[:payload]
15
+
16
+ if event_types.include?(meta[:event_name])
17
+ inner.call(*args)
18
+ end
19
+ end
20
+ end
21
+
22
+ @@registered_handlers << blk
23
+ end
24
+
25
+ def registered_handlers
26
+ @@registered_handlers
27
+ end
28
+
29
+ private
30
+
31
+ def wrap_method(inner, &outer)
32
+ ->(*args) {
33
+ outer.call(inner, *args)
34
+ }
35
+ end
36
+ end
37
+ end
38
+ end
@@ -1,3 +1,3 @@
1
1
  module CanvasSync
2
- VERSION = "0.20.5".freeze
2
+ VERSION = "0.21.0".freeze
3
3
  end
data/lib/canvas_sync.rb CHANGED
@@ -8,6 +8,7 @@ require "canvas_sync/job"
8
8
  require "canvas_sync/sidekiq_job"
9
9
  require "canvas_sync/api_syncable"
10
10
  require "canvas_sync/record"
11
+ require "canvas_sync/live_events"
11
12
  require "canvas_sync/jobs/report_starter"
12
13
  require "canvas_sync/jobs/report_checker"
13
14
  require "canvas_sync/jobs/report_processor_job"
@@ -0,0 +1,27 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe CanvasSync::Concerns::LiveEventSync do
4
+ before(:all) do
5
+ User.include(CanvasSync::Concerns::LiveEventSync)
6
+ end
7
+
8
+ let(:event) {
9
+ {
10
+ "metadata" => {
11
+ event_name: "user_created",
12
+ },
13
+ "payload" => {
14
+ user_id: 124,
15
+ },
16
+ }
17
+ }
18
+
19
+ describe "#perform" do
20
+ it "determines ID from payload" do
21
+ expect_any_instance_of(User).to receive(:process_live_event).with(:created, event["payload"], event["metadata"]) do |user, *args|
22
+ expect(user.canvas_id).to eql 124
23
+ end
24
+ User.cs_internal_process_live_event(event.with_indifferent_access)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,54 @@
1
+ require "spec_helper"
2
+ require "json/jwt"
3
+
4
+ RSpec.describe CanvasSync::Api::V1::LiveEventsController, type: :controller do
5
+ routes { CanvasSync::Engine.routes }
6
+
7
+ let(:event) {
8
+ {
9
+ "metadata" => {
10
+ event_name: "user_created",
11
+ },
12
+ "payload" => {
13
+ user_id: 42660000000000001,
14
+ },
15
+ }.with_indifferent_access
16
+ }
17
+
18
+ describe "#transform_ids!" do
19
+ it "transforms sharded IDs to local IDs" do
20
+ controller.send(:transform_ids!, event)
21
+ expect(event[:payload][:user_id]).to eql 1
22
+ expect(event[:payload][:sharded_user_id]).to eql 42660000000000001
23
+ end
24
+
25
+ xit "it does not transform cross-shard IDs" do
26
+ # TODO
27
+ end
28
+ end
29
+
30
+ describe "#process_dataservices_event" do
31
+ let(:private_key) { OpenSSL::PKey::RSA.new(2048) }
32
+ let(:jwk) { JSON::JWK.new(private_key) }
33
+ let(:jwks) { JSON::JWK::Set.new([jwk]) }
34
+
35
+ before :each do
36
+ allow(controller).to receive(:dataservices_jwks).and_return(jwks)
37
+ end
38
+
39
+ let(:event_data) { event.to_json }
40
+
41
+ it "triggers a background job" do
42
+ expect(controller).to receive(:dispatch_event)
43
+ expect(controller).to receive(:validate_tenant!)
44
+ post :process_event, params: { "_json" => JSON::JWT.new(event).sign(jwk).to_s }
45
+ expect(response).to be_successful
46
+ end
47
+
48
+ it "does not allow unsigned events" do
49
+ expect(controller).to_not receive(:validate_tenant!)
50
+ post :process_event, params: { "_json" => event.to_json }
51
+ expect(response).to_not be_successful
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,38 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe CanvasSync::LiveEvents::ProcessEventJob do
4
+ before(:all) do
5
+ User.include(CanvasSync::Concerns::LiveEventSync)
6
+ end
7
+
8
+ let(:event) {
9
+ {
10
+ "metadata" => {
11
+ event_name: "user_created",
12
+ },
13
+ "payload" => {
14
+ user_id: 1,
15
+ },
16
+ }
17
+ }
18
+
19
+ describe "#perform" do
20
+ it "invokes a legacy handler if present" do
21
+ expect(LiveEvents::UserCreatedEvent).to receive(:perform_later)
22
+ described_class.perform_now(event)
23
+ end
24
+
25
+ it "does not invoke new handlers if legacy is present" do
26
+ expect(CanvasSync::LiveEvents).to_not receive(:registered_handlers)
27
+ described_class.perform_now(event)
28
+ end
29
+
30
+ it "invokes new handlers" do
31
+ hide_const("LiveEvents::UserCreatedEvent")
32
+ expect(CanvasSync::LiveEvents).to receive(:registered_handlers).and_call_original
33
+ expect(User).to receive(:cs_internal_process_live_event).and_call_original
34
+ expect_any_instance_of(User).to receive(:process_live_event).with(:created, event["payload"], event["metadata"])
35
+ described_class.perform_now(event)
36
+ end
37
+ end
38
+ end
@@ -10,6 +10,7 @@ class Account < ApplicationRecord
10
10
  include CanvasSync::Record
11
11
  include CanvasSync::Concerns::ApiSyncable
12
12
  # include CanvasSync::Concerns::Account::Ancestry # Add support for the ancestry Gem
13
+ # include CanvasSync::Concerns::LiveEventSync
13
14
 
14
15
  canvas_sync_features :defaults
15
16
 
@@ -9,6 +9,7 @@
9
9
  class Assignment < ApplicationRecord
10
10
  include CanvasSync::Record
11
11
  include CanvasSync::Concerns::ApiSyncable
12
+ # include CanvasSync::Concerns::LiveEventSync
12
13
 
13
14
  canvas_sync_features :defaults
14
15
 
@@ -9,6 +9,7 @@
9
9
  class AssignmentGroup < ApplicationRecord
10
10
  include CanvasSync::Record
11
11
  include CanvasSync::Concerns::ApiSyncable
12
+ # include CanvasSync::Concerns::LiveEventSync
12
13
 
13
14
  canvas_sync_features :defaults
14
15
 
@@ -12,6 +12,7 @@
12
12
  class ContextModule < ApplicationRecord
13
13
  include CanvasSync::Record
14
14
  include CanvasSync::Concerns::ApiSyncable
15
+ # include CanvasSync::Concerns::LiveEventSync
15
16
 
16
17
  canvas_sync_features :defaults
17
18
 
@@ -9,6 +9,7 @@
9
9
  class ContextModuleItem < ApplicationRecord
10
10
  include CanvasSync::Record
11
11
  include CanvasSync::Concerns::ApiSyncable
12
+ # include CanvasSync::Concerns::LiveEventSync
12
13
 
13
14
  canvas_sync_features :defaults
14
15
 
@@ -12,6 +12,14 @@ class Course < ApplicationRecord
12
12
 
13
13
  canvas_sync_features :defaults
14
14
 
15
+ # include CanvasSync::Concerns::LiveEventSync
16
+ # after_process_live_event do
17
+ # if account.nil?
18
+ # acc = Account.new(canvas_id: canvas_account_id)
19
+ # acc.sync_from_api
20
+ # end
21
+ # end
22
+
15
23
  validates :canvas_id, uniqueness: true, presence: true
16
24
  belongs_to :term, foreign_key: :canvas_term_id, primary_key: :canvas_id, optional: true
17
25
  has_many :enrollments, primary_key: :canvas_id, foreign_key: :canvas_course_id
@@ -12,6 +12,18 @@ class Enrollment < ApplicationRecord
12
12
 
13
13
  canvas_sync_features :defaults
14
14
 
15
+ # include CanvasSync::Concerns::LiveEventSync
16
+ # after_process_live_event do
17
+ # if user.nil?
18
+ # u = User.new(canvas_id: canvas_user_id)
19
+ # u.sync_from_api
20
+ # end
21
+ # if course.nil?
22
+ # c = Course.new(canvas_id: canvas_course_id)
23
+ # c.sync_from_api
24
+ # end
25
+ # end
26
+
15
27
  validates :canvas_id, uniqueness: true, presence: true
16
28
  belongs_to :user, primary_key: :canvas_id, foreign_key: :canvas_user_id, optional: true
17
29
  belongs_to :role, primary_key: :canvas_id, foreign_key: :canvas_role_id, optional: true
@@ -12,6 +12,13 @@ class Section < ApplicationRecord
12
12
 
13
13
  canvas_sync_features :defaults
14
14
 
15
+ # include CanvasSync::Concerns::LiveEventSync
16
+ # after_process_live_event do
17
+ # # A section change could constitute a crosslisting change, which means
18
+ # # we need to make sure all our enrollments are pointing to the correct course
19
+ # enrollments.update_all(canvas_course_id: canvas_course_id)
20
+ # end
21
+
15
22
  validates :canvas_id, uniqueness: true, presence: true
16
23
  belongs_to :course, primary_key: :canvas_id, foreign_key: :canvas_course_id, optional: true
17
24
  has_many :enrollments, primary_key: :canvas_id, foreign_key: :canvas_section_id
@@ -9,6 +9,7 @@
9
9
  class Submission < ApplicationRecord
10
10
  include CanvasSync::Record
11
11
  include CanvasSync::Concerns::ApiSyncable
12
+ # include CanvasSync::Concerns::LiveEventSync
12
13
 
13
14
  canvas_sync_features :defaults
14
15
 
@@ -12,6 +12,14 @@ class User < ApplicationRecord
12
12
 
13
13
  canvas_sync_features :defaults
14
14
 
15
+ # include CanvasSync::Concerns::LiveEventSync
16
+ # around_process_live_event do |user, blk|
17
+ # blk.call
18
+ # rescue Footrest::HttpError::Unauthorized => e
19
+ # # This can happen when a new user is created, but hasn't setup a login on Canvas yet.
20
+ # Rails.logger.info("Failed to fetch user #{canvas_user_id}: #{e.backtrace}")
21
+ # end
22
+
15
23
  validates :canvas_id, uniqueness: true, presence: true
16
24
  has_many :pseudonyms, primary_key: :canvas_id, foreign_key: :canvas_user_id
17
25
  has_many :enrollments, primary_key: :canvas_id, foreign_key: :canvas_user_id
@@ -11,7 +11,7 @@
11
11
 
12
12
 
13
13
  module LiveEvents
14
- class AssignmentEvent < LiveEvents::BaseEvent
14
+ class AssignmentEvent < CanvasSync::LiveEvents::BaseHandler
15
15
 
16
16
  def process
17
17
  canvas_assignment_id = local_canvas_id(payload[:assignment_id])
@@ -11,8 +11,7 @@
11
11
 
12
12
 
13
13
  module LiveEvents
14
- class CourseEvent < LiveEvents::BaseEvent
15
-
14
+ class CourseEvent < CanvasSync::LiveEvents::BaseHandler
16
15
  def process
17
16
  course = Course.where(canvas_id: local_canvas_id(payload[:course_id])).first_or_initialize
18
17
  course.canvas_account_id = local_canvas_id(payload[:account_id])
@@ -22,7 +21,6 @@ module LiveEvents
22
21
  end
23
22
  course.sync_from_api
24
23
  end
25
-
26
24
  end
27
25
 
28
26
  class CourseCreatedEvent < LiveEvents::CourseEvent; end
@@ -11,7 +11,7 @@
11
11
 
12
12
 
13
13
  module LiveEvents
14
- class CourseSectionEvent < LiveEvents::BaseEvent
14
+ class CourseSectionEvent < CanvasSync::LiveEvents::BaseHandler
15
15
 
16
16
  def process
17
17
  section = Section.where(canvas_id: local_canvas_id(payload[:course_section_id])).first_or_initialize
@@ -11,7 +11,7 @@
11
11
 
12
12
 
13
13
  module LiveEvents
14
- class EnrollmentEvent < LiveEvents::BaseEvent
14
+ class EnrollmentEvent < CanvasSync::LiveEvents::BaseHandler
15
15
  attr_accessor :enrollment
16
16
 
17
17
  def process()
@@ -11,7 +11,7 @@
11
11
 
12
12
 
13
13
  module LiveEvents
14
- class GradeEvent < LiveEvents::BaseEvent
14
+ class GradeEvent < CanvasSync::LiveEvents::BaseHandler
15
15
 
16
16
  def process
17
17
  raise "process must be implemented in your subclass"
@@ -11,7 +11,7 @@
11
11
 
12
12
 
13
13
  module LiveEvents
14
- class ModuleEvent < LiveEvents::BaseEvent
14
+ class ModuleEvent < CanvasSync::LiveEvents::BaseHandler
15
15
 
16
16
  def process
17
17
  return unless payload["context_type"] == "Course"
@@ -11,7 +11,7 @@
11
11
 
12
12
 
13
13
  module LiveEvents
14
- class ModuleItemEvent < LiveEvents::BaseEvent
14
+ class ModuleItemEvent < CanvasSync::LiveEvents::BaseHandler
15
15
 
16
16
  def process
17
17
  context_module_item = ContextModuleItem.find_or_initialize_by(canvas_id: payload["module_item_id"])
@@ -11,7 +11,7 @@
11
11
 
12
12
 
13
13
  module LiveEvents
14
- class SubmissionEvent < LiveEvents::BaseEvent
14
+ class SubmissionEvent < CanvasSync::LiveEvents::BaseHandler
15
15
 
16
16
  def process
17
17
  submission = Submission.where(canvas_id: local_canvas_id(payload["submission_id"])).first_or_initialize
@@ -11,7 +11,7 @@
11
11
 
12
12
 
13
13
  module LiveEvents
14
- class SyllabusEvent < LiveEvents::BaseEvent
14
+ class SyllabusEvent < CanvasSync::LiveEvents::BaseHandler
15
15
 
16
16
  def process
17
17
  # syllabus = Syllabus.where(course_id: attrs[:course_id]).first_or_initialize
@@ -11,8 +11,7 @@
11
11
 
12
12
 
13
13
  module LiveEvents
14
- class UserEvent < LiveEvents::BaseEvent
15
-
14
+ class UserEvent < CanvasSync::LiveEvents::BaseHandler
16
15
  def process
17
16
  canvas_user_id = local_canvas_id(payload[:user_id])
18
17
  user = User.where(canvas_id: canvas_user_id).first_or_initialize
@@ -21,7 +20,6 @@ module LiveEvents
21
20
  # This can happen when a new user is created, but hasn't setup a login on Canvas yet.
22
21
  Rails.logger.info("Failed to fetch user #{canvas_user_id}: #{e.backtrace}")
23
22
  end
24
-
25
23
  end
26
24
 
27
25
  class UserCreatedEvent < LiveEvents::UserEvent; end
@@ -1,2 +1,3 @@
1
1
  Rails.application.routes.draw do
2
+ mount CanvasSync::Engine, at: '/canvas_sync'
2
3
  end