canvas_sync 0.3.3 → 0.3.5
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 +5 -5
- data/app/controllers/api/v1/health_check_controller.rb +7 -0
- data/app/controllers/api/v1/live_events_controller.rb +16 -0
- data/lib/canvas_sync.rb +20 -5
- data/lib/canvas_sync/generators/install_generator.rb +1 -1
- data/lib/canvas_sync/generators/install_live_events_generator.rb +38 -0
- data/lib/canvas_sync/generators/templates/{course.rb → models/course.rb} +0 -0
- data/lib/canvas_sync/generators/templates/{create_courses.rb → models/create_courses.rb} +0 -0
- data/lib/canvas_sync/generators/templates/{create_enrollments.rb → models/create_enrollments.rb} +0 -0
- data/lib/canvas_sync/generators/templates/models/create_roles.rb +17 -0
- data/lib/canvas_sync/generators/templates/{create_sections.rb → models/create_sections.rb} +0 -0
- data/lib/canvas_sync/generators/templates/{create_terms.rb → models/create_terms.rb} +0 -0
- data/lib/canvas_sync/generators/templates/{create_users.rb → models/create_users.rb} +0 -0
- data/lib/canvas_sync/generators/templates/{enrollment.rb → models/enrollment.rb} +0 -0
- data/lib/canvas_sync/generators/templates/models/role.rb +18 -0
- data/lib/canvas_sync/generators/templates/{section.rb → models/section.rb} +0 -0
- data/lib/canvas_sync/generators/templates/{term.rb → models/term.rb} +0 -0
- data/lib/canvas_sync/generators/templates/{user.rb → models/user.rb} +0 -0
- data/lib/canvas_sync/generators/templates/services/live_events/assignment/assignment_created_event.rb +5 -0
- data/lib/canvas_sync/generators/templates/services/live_events/assignment/assignment_event.rb +63 -0
- data/lib/canvas_sync/generators/templates/services/live_events/assignment/assignment_updated_event.rb +4 -0
- data/lib/canvas_sync/generators/templates/services/live_events/base_event.rb +23 -0
- data/lib/canvas_sync/generators/templates/services/live_events/course/course_created_event.rb +4 -0
- data/lib/canvas_sync/generators/templates/services/live_events/course/course_event.rb +42 -0
- data/lib/canvas_sync/generators/templates/services/live_events/course/course_updated_event.rb +4 -0
- data/lib/canvas_sync/generators/templates/services/live_events/enrollment/enrollment_created_event.rb +4 -0
- data/lib/canvas_sync/generators/templates/services/live_events/enrollment/enrollment_event.rb +58 -0
- data/lib/canvas_sync/generators/templates/services/live_events/enrollment/enrollment_updated_event.rb +4 -0
- data/lib/canvas_sync/generators/templates/services/live_events/grade/grade_changed_event.rb +4 -0
- data/lib/canvas_sync/generators/templates/services/live_events/grade/grade_event.rb +56 -0
- data/lib/canvas_sync/generators/templates/services/live_events/submission/submission_created_event.rb +4 -0
- data/lib/canvas_sync/generators/templates/services/live_events/submission/submission_event.rb +57 -0
- data/lib/canvas_sync/generators/templates/services/live_events/submission/submission_updated_event.rb +4 -0
- data/lib/canvas_sync/generators/templates/services/live_events/syllabus/syllabus_event.rb +35 -0
- data/lib/canvas_sync/generators/templates/services/live_events/syllabus/syllabus_updated_event.rb +2 -0
- data/lib/canvas_sync/generators/templates/services/live_events/user/user_created_event.rb +4 -0
- data/lib/canvas_sync/generators/templates/services/live_events/user/user_event.rb +35 -0
- data/lib/canvas_sync/generators/templates/services/live_events/user/user_updated_event.rb +4 -0
- data/lib/canvas_sync/importers/bulk_importer.rb +1 -1
- data/lib/canvas_sync/jobs/sync_roles_job.rb +18 -0
- data/lib/canvas_sync/version.rb +1 -1
- data/spec/canvas_sync/canvas_sync_spec.rb +28 -0
- data/spec/canvas_sync/jobs/sync_roles_job_spec.rb +16 -0
- data/spec/canvas_sync/models/roles_spec.rb +46 -0
- data/spec/dummy/app/models/role.rb +24 -0
- data/spec/dummy/config/database.yml +4 -4
- data/spec/dummy/db/migrate/20180103162102_create_roles.rb +23 -0
- data/spec/dummy/db/schema.rb +16 -1
- data/spec/factories/role_factory.rb +8 -0
- data/spec/support/fake_canvas.rb +4 -0
- data/spec/support/fixtures/canvas_responses/roles.json +748 -0
- metadata +52 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5deb79781b2837c369b7dc299b0ced0082af7612027280eacdc63f4473dd9396
|
4
|
+
data.tar.gz: 9c1e5af6281752f7a467c6238c08e0810a108083c1e6b170f0fa81364e4d63ae
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 26045a4eb9f2f7c9f6cfb7bf7b978314322781d30945fe5ed4a60f138da3deea8fb407e31d19102003c55bb412ce1f10836ee076ddc6e2b8fef7a00db62e2eee
|
7
|
+
data.tar.gz: 57d54b949ad57b42fdd948886693af3a4c04361be9fbff6919ca6bd0fa4ce5cee50a7431f251ffbbd19e5b166b011ccd0ea150b272a0e665fa62276cd52907a9
|
@@ -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
|
File without changes
|
File without changes
|
data/lib/canvas_sync/generators/templates/{create_enrollments.rb → models/create_enrollments.rb}
RENAMED
File without changes
|
@@ -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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -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
|
File without changes
|
File without changes
|
File without changes
|
@@ -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,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,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,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
|