canvas_sync 0.3.3 → 0.3.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +5 -5
  2. data/app/controllers/api/v1/health_check_controller.rb +7 -0
  3. data/app/controllers/api/v1/live_events_controller.rb +16 -0
  4. data/lib/canvas_sync.rb +20 -5
  5. data/lib/canvas_sync/generators/install_generator.rb +1 -1
  6. data/lib/canvas_sync/generators/install_live_events_generator.rb +38 -0
  7. data/lib/canvas_sync/generators/templates/{course.rb → models/course.rb} +0 -0
  8. data/lib/canvas_sync/generators/templates/{create_courses.rb → models/create_courses.rb} +0 -0
  9. data/lib/canvas_sync/generators/templates/{create_enrollments.rb → models/create_enrollments.rb} +0 -0
  10. data/lib/canvas_sync/generators/templates/models/create_roles.rb +17 -0
  11. data/lib/canvas_sync/generators/templates/{create_sections.rb → models/create_sections.rb} +0 -0
  12. data/lib/canvas_sync/generators/templates/{create_terms.rb → models/create_terms.rb} +0 -0
  13. data/lib/canvas_sync/generators/templates/{create_users.rb → models/create_users.rb} +0 -0
  14. data/lib/canvas_sync/generators/templates/{enrollment.rb → models/enrollment.rb} +0 -0
  15. data/lib/canvas_sync/generators/templates/models/role.rb +18 -0
  16. data/lib/canvas_sync/generators/templates/{section.rb → models/section.rb} +0 -0
  17. data/lib/canvas_sync/generators/templates/{term.rb → models/term.rb} +0 -0
  18. data/lib/canvas_sync/generators/templates/{user.rb → models/user.rb} +0 -0
  19. data/lib/canvas_sync/generators/templates/services/live_events/assignment/assignment_created_event.rb +5 -0
  20. data/lib/canvas_sync/generators/templates/services/live_events/assignment/assignment_event.rb +63 -0
  21. data/lib/canvas_sync/generators/templates/services/live_events/assignment/assignment_updated_event.rb +4 -0
  22. data/lib/canvas_sync/generators/templates/services/live_events/base_event.rb +23 -0
  23. data/lib/canvas_sync/generators/templates/services/live_events/course/course_created_event.rb +4 -0
  24. data/lib/canvas_sync/generators/templates/services/live_events/course/course_event.rb +42 -0
  25. data/lib/canvas_sync/generators/templates/services/live_events/course/course_updated_event.rb +4 -0
  26. data/lib/canvas_sync/generators/templates/services/live_events/enrollment/enrollment_created_event.rb +4 -0
  27. data/lib/canvas_sync/generators/templates/services/live_events/enrollment/enrollment_event.rb +58 -0
  28. data/lib/canvas_sync/generators/templates/services/live_events/enrollment/enrollment_updated_event.rb +4 -0
  29. data/lib/canvas_sync/generators/templates/services/live_events/grade/grade_changed_event.rb +4 -0
  30. data/lib/canvas_sync/generators/templates/services/live_events/grade/grade_event.rb +56 -0
  31. data/lib/canvas_sync/generators/templates/services/live_events/submission/submission_created_event.rb +4 -0
  32. data/lib/canvas_sync/generators/templates/services/live_events/submission/submission_event.rb +57 -0
  33. data/lib/canvas_sync/generators/templates/services/live_events/submission/submission_updated_event.rb +4 -0
  34. data/lib/canvas_sync/generators/templates/services/live_events/syllabus/syllabus_event.rb +35 -0
  35. data/lib/canvas_sync/generators/templates/services/live_events/syllabus/syllabus_updated_event.rb +2 -0
  36. data/lib/canvas_sync/generators/templates/services/live_events/user/user_created_event.rb +4 -0
  37. data/lib/canvas_sync/generators/templates/services/live_events/user/user_event.rb +35 -0
  38. data/lib/canvas_sync/generators/templates/services/live_events/user/user_updated_event.rb +4 -0
  39. data/lib/canvas_sync/importers/bulk_importer.rb +1 -1
  40. data/lib/canvas_sync/jobs/sync_roles_job.rb +18 -0
  41. data/lib/canvas_sync/version.rb +1 -1
  42. data/spec/canvas_sync/canvas_sync_spec.rb +28 -0
  43. data/spec/canvas_sync/jobs/sync_roles_job_spec.rb +16 -0
  44. data/spec/canvas_sync/models/roles_spec.rb +46 -0
  45. data/spec/dummy/app/models/role.rb +24 -0
  46. data/spec/dummy/config/database.yml +4 -4
  47. data/spec/dummy/db/migrate/20180103162102_create_roles.rb +23 -0
  48. data/spec/dummy/db/schema.rb +16 -1
  49. data/spec/factories/role_factory.rb +8 -0
  50. data/spec/support/fake_canvas.rb +4 -0
  51. data/spec/support/fixtures/canvas_responses/roles.json +748 -0
  52. metadata +52 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 4cb4e4d83e146469a152b7582f9dff23adbf57e5
4
- data.tar.gz: 7ac7ec7146259a6099093c63134a070af1656a07
2
+ SHA256:
3
+ metadata.gz: 5deb79781b2837c369b7dc299b0ced0082af7612027280eacdc63f4473dd9396
4
+ data.tar.gz: 9c1e5af6281752f7a467c6238c08e0810a108083c1e6b170f0fa81364e4d63ae
5
5
  SHA512:
6
- metadata.gz: 1b98e4574ce7bce06cfba710c003fe8e8b4bf48b3f84326307d099bc9ab80136384c367ef4913e85a80331e4f0a855e41c40ea89e5cb4f30dec7af9e81c7585e
7
- data.tar.gz: d572b58ae50eab9287e04972f79a72f0619cb25623a8615d4c2e55f93f58d54c9232e5371ffce4d844405380fb8c7e4e4d5706909f88f730d75878ddab10280a
6
+ metadata.gz: 26045a4eb9f2f7c9f6cfb7bf7b978314322781d30945fe5ed4a60f138da3deea8fb407e31d19102003c55bb412ce1f10836ee076ddc6e2b8fef7a00db62e2eee
7
+ data.tar.gz: 57d54b949ad57b42fdd948886693af3a4c04361be9fbff6919ca6bd0fa4ce5cee50a7431f251ffbbd19e5b166b011ccd0ea150b272a0e665fa62276cd52907a9
@@ -0,0 +1,7 @@
1
+ class Api::V1::HealthCheckController < ActionController::Base
2
+
3
+ def ping
4
+ head :ok
5
+ end
6
+
7
+ end
@@ -0,0 +1,16 @@
1
+ class Api::V1::LiveEventsController < ActionController::Base
2
+
3
+ def process_event
4
+ payload = SymmetricEncryption.decrypt(params[:payload])
5
+ payload = JSON.parse(payload)
6
+ Rails.logger.debug("Processing event type: #{payload['attributes']['event_name']}")
7
+ Rails.logger.debug("Payload: #{payload}")
8
+ event = "LiveEvents::#{payload['attributes']['event_name'].camelcase}Event".constantize
9
+ event.perform_later(payload)
10
+ head :ok
11
+ rescue => e
12
+ Rails.logger.error("Live Events Error: #{e.message} - #{e.backtrace}")
13
+ render json: {error: "Live Events Error: #{e.message}"}, status: 422
14
+ end
15
+
16
+ end
data/lib/canvas_sync.rb CHANGED
@@ -9,18 +9,20 @@ require "canvas_sync/jobs/report_processor_job"
9
9
  require "canvas_sync/jobs/sync_provisioning_report_job"
10
10
  require "canvas_sync/jobs/sync_terms_job"
11
11
  require "canvas_sync/jobs/sync_users_job"
12
+ require "canvas_sync/jobs/sync_roles_job"
12
13
 
13
14
  Dir[File.dirname(__FILE__) + "/canvas_sync/processors/*.rb"].each {|file| require file }
14
15
  Dir[File.dirname(__FILE__) + "/canvas_sync/importers/*.rb"].each {|file| require file }
15
16
  Dir[File.dirname(__FILE__) + "/canvas_sync/generators/*.rb"].each {|file| require file }
16
17
 
17
18
  module CanvasSync
18
- SUPPORTED_MODELS = %w(users courses terms enrollments sections)
19
+ SUPPORTED_MODELS = %w(users courses terms enrollments sections roles)
20
+ SUPPORTED_LIVE_EVENTS = %w(course enrollment submission assignment user syllabus grade)
19
21
 
20
22
  # Runs a standard provisioning sync job with no extra report types.
21
- # Terms will be synced first using the API. If you are syncing users
22
- # and have also specified a Term scope, Users will by synced first, before
23
- # every other model (as Users are never scoped to Term).
23
+ # Terms will be synced first using the API. If you are syncing users/roles
24
+ # and have also specified a Term scope, Users/Roles will by synced first, before
25
+ # every other model (as Users/Roles are never scoped to Term).
24
26
  #
25
27
  # @param models [Array<String>] A list of models to sync. e.g., ['users', 'courses'].
26
28
  # must be one of SUPPORTED_MODELS
@@ -63,7 +65,7 @@ module CanvasSync
63
65
  next_job_class.perform_later(duped_job_chain, next_job[:options])
64
66
  end
65
67
 
66
- # Syncs terms, users if necessary, then the rest of the specified models.
68
+ # Syncs terms, users/roles if necessary, then the rest of the specified models.
67
69
  #
68
70
  # @param models [Array<String>]
69
71
  # @param term_scope [String]
@@ -86,6 +88,12 @@ module CanvasSync
86
88
  models = models - ['users']
87
89
  end
88
90
 
91
+ if models.include?('roles')
92
+ # Sync all roles first when scoping by term, because roles cannot be scoped to term
93
+ jobs.push({ job: CanvasSync::Jobs::SyncRolesJob.to_s, options: {} })
94
+ models = models - ['roles']
95
+ end
96
+
89
97
  jobs.push({ job: CanvasSync::Jobs::SyncProvisioningReportJob.to_s, options: { term_scope: term_scope, models: models } })
90
98
 
91
99
  global_options = { legacy_support: legacy_support }
@@ -114,4 +122,11 @@ module CanvasSync
114
122
  raise "Invalid model(s) specified: #{invalid.join(', ')}. Only #{SUPPORTED_MODELS.join(', ')} are supported."
115
123
  end
116
124
  end
125
+
126
+ def self.validate_live_events!(events)
127
+ invalid = events - SUPPORTED_LIVE_EVENTS
128
+ if invalid.length > 0
129
+ raise "Invalid live event(s) specified: #{invalid.join(', ')}. Only #{SUPPORTED_LIVE_EVENTS.join(', ')} are supported."
130
+ end
131
+ end
117
132
  end
@@ -4,7 +4,7 @@ require "rails/generators/migration"
4
4
  module CanvasSync
5
5
  class InstallGenerator < Rails::Generators::Base
6
6
  include Rails::Generators::Migration
7
- source_root File.expand_path('../templates', __FILE__)
7
+ source_root File.expand_path('../templates/models', __FILE__)
8
8
  class_option :models, type: :string, required: true
9
9
 
10
10
  def self.next_migration_number(path)
@@ -0,0 +1,38 @@
1
+ require "rails/generators"
2
+
3
+ module CanvasSync
4
+ class InstallLiveEventsGenerator < Rails::Generators::Base
5
+ source_root File.expand_path('../templates/services/live_events', __FILE__)
6
+ class_option :events, type: :string, required: true
7
+
8
+ def autogenerated_event_warning
9
+ <<-HERE.strip_heredoc
10
+ #
11
+ # AUTO GENERATED LIVE EVENT
12
+ # This was auto generated by the CanvasSync Gem.
13
+ # You can customize it as needed, but make sure you test
14
+ # any changes you make to the auto generated methods.
15
+ #
16
+ HERE
17
+ end
18
+
19
+ # Generates the specified live events. Invoke with:
20
+ #
21
+ # bin/rails generate canvas_sync:install_live_events --events users,courses
22
+ #
23
+ # Install all live events with:
24
+ #
25
+ # bin/rails generate canvas_sync:install_live_events --events all
26
+ def generate_live_events
27
+ events = options['events'] == 'all' ? CanvasSync::SUPPORTED_LIVE_EVENTS : options['events'].split(',')
28
+ CanvasSync.validate_live_events!(events)
29
+
30
+ events.each do |event|
31
+ Dir.glob("#{File.dirname(__FILE__)}/templates/services/live_events/#{event}/*.rb") do |rb_file|
32
+ template "#{rb_file}", "app/services/live_events/#{File.basename(rb_file)}"
33
+ end
34
+ end
35
+ template 'base_event.rb', 'app/services/live_events/base_event.rb'
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,17 @@
1
+ <%= autogenerated_migration_warning %>
2
+
3
+ class CreateRoles < ActiveRecord::Migration[5.1]
4
+ def change
5
+ create_table :roles do |t|
6
+ t.integer :canvas_role_id, null: false
7
+ t.string :label, null: false
8
+ t.string :base_role_type, null: false
9
+ t.json :account
10
+ t.string :workflow_state, null: false
11
+ t.json :permissions
12
+
13
+ t.timestamps
14
+ end
15
+ add_index :roles, :canvas_role_id, unique: true
16
+ end
17
+ end
@@ -0,0 +1,18 @@
1
+ <%= autogenerated_model_warning %>
2
+
3
+ class Role < ApplicationRecord
4
+ validates :canvas_role_id, uniqueness: true, presence: true
5
+
6
+ def self.create_or_update(role_params)
7
+ role = Role.find_or_initialize_by(canvas_role_id: role_params['id'])
8
+
9
+ role.assign_attributes(label: role_params['label'],
10
+ base_role_type: role_params['base_role_type'],
11
+ account: role_params['account'],
12
+ permissions: role_params['permissions'],
13
+ workflow_state: role_params['workflow_state'])
14
+
15
+ role.save! if role.changed?
16
+ role
17
+ end
18
+ end
@@ -0,0 +1,5 @@
1
+ <%= autogenerated_event_warning %>
2
+
3
+ class LiveEvents::AssignmentCreatedEvent < LiveEvents::AssignmentEvent
4
+
5
+ end
@@ -0,0 +1,63 @@
1
+ <%= autogenerated_event_warning %>
2
+
3
+ class LiveEvents::AssignmentEvent < LiveEvents::BaseEvent
4
+ # The following is provided in the live events call:
5
+ # {
6
+ # assignment_id: assignment.global_id,
7
+ # context_id: assignment.global_context_id,
8
+ # context_type: assignment.context_type,
9
+ # workflow_state: assignment.workflow_state,
10
+ # title: LiveEvents.truncate(assignment.title),
11
+ # description: LiveEvents.truncate(assignment.description),
12
+ # due_at: assignment.due_at,
13
+ # unlock_at: assignment.unlock_at,
14
+ # lock_at: assignment.lock_at,
15
+ # updated_at: assignment.updated_at,
16
+ # points_possible: assignment.points_possible
17
+ # }
18
+
19
+ def perform(event_payload)
20
+ super
21
+ attrs = {
22
+ canvas_assignment_id: local_canvas_id(payload[:assignment_id]),
23
+ course_id: course.try(:id),
24
+ name: payload[:title],
25
+ workflow_state: payload[:workflow_state],
26
+ description: payload[:description],
27
+ due_at: payload[:due_at],
28
+ unlock_at: payload[:unlock_at],
29
+ lock_at: payload[:lock_at],
30
+ points_possible: payload[:points_possible],
31
+ canvas_updated_at: payload[:updated_at]
32
+ }
33
+ create_or_update(attrs)
34
+ rescue => e
35
+ log_error e
36
+ ensure
37
+ true
38
+ end
39
+
40
+ private
41
+ def create_or_update(attrs)
42
+ assignment = Assignment.where(canvas_assignment_id: attrs[:canvas_assignment_id]).first_or_initialize
43
+ assignment.assign_attributes(attrs)
44
+ assignment.save!
45
+ assignment
46
+ end
47
+
48
+ # This could be nil if the context type for this assignment is not 'Course' of if a local course was not found
49
+ def canvas_course_id
50
+ payload[:context_type] == 'Course' ? local_canvas_id(payload[:context_id]) : nil
51
+ end
52
+
53
+ # This could be nil if the context type for this assignment is not 'Course' of if a local course was not found
54
+ def course
55
+ unless @course
56
+ if canvas_course_id.present?
57
+ @course = Course.find_by(canvas_course_id: canvas_course_id)
58
+ end
59
+ end
60
+ @course
61
+ end
62
+
63
+ end
@@ -0,0 +1,4 @@
1
+ <%= autogenerated_event_warning %>
2
+
3
+ class LiveEvents::AssignmentUpdatedEvent < LiveEvents::AssignmentEvent
4
+ end
@@ -0,0 +1,23 @@
1
+ <%= autogenerated_event_warning %>
2
+
3
+ class LiveEvents::BaseEvent
4
+
5
+ attr_accessor :payload
6
+
7
+ def perform(event_payload)
8
+ @payload = HashWithIndifferentAccess.new(event_payload['body'])
9
+ end
10
+
11
+ # Live events will use a canvas global ID (cross shard) for any ID's provided. This method will return the local ID.
12
+ def local_canvas_id(id)
13
+ id.to_i % 10_000_000_000_000
14
+ end
15
+
16
+ def log_error(e)
17
+ Rails.logger.error "Error processing Live Event: #{self.class.name}"
18
+ Rails.logger.error "#{payload}"
19
+ Rails.logger.error e.message
20
+ Rails.logger.error e.backtrace.join("\n")
21
+ end
22
+
23
+ end
@@ -0,0 +1,4 @@
1
+ <%= autogenerated_event_warning %>
2
+
3
+ class LiveEvents::CourseCreatedEvent < LiveEvents::CourseEvent
4
+ end
@@ -0,0 +1,42 @@
1
+ <%= autogenerated_event_warning %>
2
+
3
+ class LiveEvents::CourseEvent < LiveEvents::BaseEvent
4
+
5
+ # The following is provided in the live events call:
6
+ # {
7
+ # course_id: course.global_id,
8
+ # uuid: course.uuid,
9
+ # account_id: course.global_account_id,
10
+ # name: course.name,
11
+ # created_at: course.created_at,
12
+ # updated_at: course.updated_at,
13
+ # workflow_state: course.workflow_state
14
+ # }
15
+
16
+
17
+ def perform(event_payload)
18
+ super
19
+ attrs = {
20
+ canvas_course_id: local_canvas_id(payload[:course_id]),
21
+ canvas_account_id: local_canvas_id(payload[:account_id]),
22
+ name: payload[:name],
23
+ workflow_state: payload[:workflow_state]
24
+ }
25
+ create_or_update(attrs)
26
+ rescue => e
27
+ log_error e
28
+ ensure
29
+ true
30
+ end
31
+
32
+ private
33
+
34
+ def create_or_update(attrs)
35
+ course = Course.where(canva_course_id: attrs[:canva_course_id]).first_or_initialize
36
+ course.assign_attributes(attrs)
37
+ course.save!
38
+ course
39
+ end
40
+
41
+ end
42
+
@@ -0,0 +1,4 @@
1
+ <%= autogenerated_event_warning %>
2
+
3
+ class LiveEvents::CourseUpdatedEvent < LiveEvents::CourseEvent
4
+ end
@@ -0,0 +1,4 @@
1
+ <%= autogenerated_event_warning %>
2
+
3
+ class LiveEvents::EnrollmentCreatedEvent < LiveEvents::EnrollmentEvent
4
+ end
@@ -0,0 +1,58 @@
1
+ <%= autogenerated_event_warning %>
2
+
3
+ class LiveEvents::EnrollmentEvent < LiveEvents::BaseEvent
4
+
5
+ # The following is provided in the live events call:
6
+ # {
7
+ #
8
+ # enrollment_id: enrollment.global_id,
9
+ # course_id: enrollment.global_course_id,
10
+ # user_id: enrollment.global_user_id,
11
+ # user_name: enrollment.user_name,
12
+ # type: enrollment.type,
13
+ # created_at: enrollment.created_at,
14
+ # updated_at: enrollment.updated_at,
15
+ # limit_privileges_to_course_section: enrollment.limit_privileges_to_course_section,
16
+ # course_section_id: enrollment.global_course_section_id,
17
+ # workflow_state: enrollment.workflow_state
18
+ # }
19
+
20
+ def perform(paload)
21
+ super
22
+ return if course.nil? || section.nil? || user.nil?
23
+ attrs = {
24
+
25
+ canvas_enrollment_id: local_canvas_id(payload[:enrollment_id]),
26
+ course_id: course.try(:id),
27
+ user_id: user.try(:id),
28
+ type: payload[:type],
29
+ section_id: section.try(:id),
30
+ workflow_state: payload[:workflow_state]
31
+ }
32
+ create_or_update(attrs)
33
+ rescue => e
34
+ log_error e
35
+ ensure
36
+ true
37
+ end
38
+
39
+ private
40
+ def create_or_update(attrs)
41
+ enrollment = Enrollment.where(canvas_enrollment_id: attrs[:canvas_enrollment_id]).first_or_initialize
42
+ enrollment.assign_attributes(attrs)
43
+ enrollment.save!
44
+ enrollment
45
+ end
46
+
47
+ def course
48
+ @course ||= Course.find_by(canvas_course_id: local_canvas_id(payload[:course_id]))
49
+ end
50
+
51
+ def section
52
+ @section ||= Section.find_by(canvas_section_id: local_canvas_id(payload[:section_id]))
53
+ end
54
+
55
+ def user
56
+ @user ||= User.find_by(canvas_user_id: local_canvas_id(payload[:user_id]))
57
+ end
58
+ end
@@ -0,0 +1,4 @@
1
+ <%= autogenerated_event_warning %>
2
+
3
+ class LiveEvents::EnrollmentUpdatedEvent < LiveEvents::EnrollmentEvent
4
+ end
@@ -0,0 +1,4 @@
1
+ <%= autogenerated_event_warning %>
2
+
3
+ class LiveEvents::GradeChangedEvent < LiveEvents::GradeEvent
4
+ end