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