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.
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