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
@@ -0,0 +1,56 @@
|
|
1
|
+
<%= autogenerated_event_warning %>
|
2
|
+
|
3
|
+
class LiveEvents::GradeEvent < LiveEvents::BaseEvent
|
4
|
+
# The following is provided in the live events call:
|
5
|
+
# {
|
6
|
+
# submission_id: submission.global_id,
|
7
|
+
# assignment_id: submission.global_assignment_id,
|
8
|
+
# grade: submission.grade,
|
9
|
+
# old_grade: old_submission.try(:grade),
|
10
|
+
# score: submission.score,
|
11
|
+
# old_score: old_submission.try(:score),
|
12
|
+
# points_possible: submission.assignment.points_possible,
|
13
|
+
# old_points_possible: old_assignment.points_possible,
|
14
|
+
# grader_id: grader_id,
|
15
|
+
# student_id: submission.global_user_id,
|
16
|
+
# user_id: submission.global_user_id,
|
17
|
+
# grading_complete: submission.graded?,
|
18
|
+
# muted: submission.muted_assignment?
|
19
|
+
# }
|
20
|
+
|
21
|
+
def perform(event_payload)
|
22
|
+
super
|
23
|
+
attrs = {
|
24
|
+
submission_id: submission.try(:id),
|
25
|
+
assignment_id: assignment.try(:id),
|
26
|
+
score: payload[:score],
|
27
|
+
points_possible: payload[:points_possible],
|
28
|
+
grader_id: payload[:grader_id],
|
29
|
+
user_id: user.try(:id),
|
30
|
+
grading_complete: payload[:grading_complete],
|
31
|
+
muted: payload[:muted]
|
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
|
+
# Todo: add your code here
|
43
|
+
end
|
44
|
+
|
45
|
+
def submission
|
46
|
+
Submission.find_by(canvas_submission_id: local_canvas_id(payload[:submission_id]))
|
47
|
+
end
|
48
|
+
|
49
|
+
def assignment
|
50
|
+
Assignment.find_by(canvas_assignment_id: local_canvas_id(payload[:assignment_id]))
|
51
|
+
end
|
52
|
+
|
53
|
+
def user
|
54
|
+
User.find_by(canvas_user_id: local_canvas_id(payload[:user_id]))
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
<%= autogenerated_event_warning %>
|
2
|
+
|
3
|
+
class LiveEvents::SubmissionEvent < LiveEvents::BaseEvent
|
4
|
+
|
5
|
+
# The following is provided in the live events call:
|
6
|
+
# {
|
7
|
+
# submission_id: submission.global_id,
|
8
|
+
# assignment_id: submission.global_assignment_id,
|
9
|
+
# user_id: submission.global_user_id,
|
10
|
+
# submitted_at: submission.submitted_at,
|
11
|
+
# graded_at: submission.graded_at,
|
12
|
+
# updated_at: submission.updated_at,
|
13
|
+
# score: submission.score,
|
14
|
+
# grade: submission.grade,
|
15
|
+
# submission_type: submission.submission_type,
|
16
|
+
# body: LiveEvents.truncate(submission.body),
|
17
|
+
# url: submission.url,
|
18
|
+
# attempt: submission.attempt
|
19
|
+
# }
|
20
|
+
|
21
|
+
def perform(event_payload)
|
22
|
+
super
|
23
|
+
return if assignment.nil? || user.nil?
|
24
|
+
attrs = {
|
25
|
+
canvas_submission_id: local_canvas_id(payload[:submission_id]),
|
26
|
+
assignment_id: assignment.try(:id),
|
27
|
+
user_id: user.try(:id),
|
28
|
+
submitted_at: payload[:submitted_at],
|
29
|
+
graded_at: payload[:graded_at],
|
30
|
+
score: payload[:score],
|
31
|
+
grade: payload[:grade],
|
32
|
+
submission_type: payload[:submission_type]
|
33
|
+
}
|
34
|
+
create_or_update(attrs)
|
35
|
+
rescue => e
|
36
|
+
log_error e
|
37
|
+
ensure
|
38
|
+
true
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
def create_or_update(attrs)
|
44
|
+
submission = Submission.where(canvas_submission_id: attrs[:canvas_submission_id]).first_or_initialize
|
45
|
+
submission.assign_attributes(attrs)
|
46
|
+
submission.save!
|
47
|
+
submission
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
def assignment
|
52
|
+
@assignment ||= Assignment.find_by(canvas_assignment_id: local_canvas_id(payload[:assignment_id]))
|
53
|
+
end
|
54
|
+
|
55
|
+
def user
|
56
|
+
@user ||= User.find_by(canvas_user_id: local_canvas_id(payload[:user_id]))
|
57
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
<%= autogenerated_event_warning %>
|
2
|
+
|
3
|
+
class LiveEvents::SyllabusEvent < LiveEvents::BaseEvent
|
4
|
+
# The following is provided in the live events call:
|
5
|
+
# {
|
6
|
+
# course_id: course.global_id,
|
7
|
+
# syllabus_body: LiveEvents.truncate(course.syllabus_body),
|
8
|
+
# old_syllabus_body: LiveEvents.truncate(old_syllabus_body)
|
9
|
+
# }
|
10
|
+
#
|
11
|
+
|
12
|
+
def perform(event_payload)
|
13
|
+
super
|
14
|
+
attrs = {
|
15
|
+
course_id: course.try(:id),
|
16
|
+
syllabus_body: payload[:syllabus_body]
|
17
|
+
}
|
18
|
+
create_or_update(attrs)
|
19
|
+
rescue => e
|
20
|
+
log_error e
|
21
|
+
ensure
|
22
|
+
true
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def create_or_update(attrs)
|
27
|
+
syllabus = Syllabus.where(course_id: attrs[:course_id]).first_or_initialize
|
28
|
+
syllabus.body = attrs[:syllabus_body] # Syllabus Body can get truncated by live events
|
29
|
+
syllabus.save!
|
30
|
+
end
|
31
|
+
|
32
|
+
def course
|
33
|
+
Course.find_by(canvas_course_id: local_canvas_id(payload[:course_id]))
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
<%= autogenerated_event_warning %>
|
2
|
+
|
3
|
+
class LiveEvents::UserEvent < LiveEvents::BaseEvent
|
4
|
+
# The following is provided in the live events call:
|
5
|
+
# {
|
6
|
+
# user_id: user.global_id,
|
7
|
+
# uuid: user.uuid,
|
8
|
+
# name: user.name,
|
9
|
+
# short_name: user.short_name,
|
10
|
+
# workflow_state: user.workflow_state,
|
11
|
+
# created_at: user.created_at,
|
12
|
+
# updated_at: user.updated_at
|
13
|
+
# }
|
14
|
+
|
15
|
+
def perform(event_payload)
|
16
|
+
super
|
17
|
+
attrs = {
|
18
|
+
canvas_user_id: local_canvas_id(payload[:user_id]),
|
19
|
+
name: payload[:name],
|
20
|
+
workflow_state: payload[:workflow_state]
|
21
|
+
}
|
22
|
+
create_or_update(attrs)
|
23
|
+
rescue => e
|
24
|
+
log_error e
|
25
|
+
ensure
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
def create_or_update(attrs)
|
31
|
+
user = User.where(canvas_user_id: attrs[:canvas_user_id]).first_or_initialize
|
32
|
+
user.assign_attributes(attrs)
|
33
|
+
user.save!
|
34
|
+
end
|
35
|
+
end
|
@@ -31,7 +31,7 @@ module CanvasSync
|
|
31
31
|
row_ids[row[conflict_target]] = true
|
32
32
|
|
33
33
|
rows << csv_column_names.map do |column|
|
34
|
-
if mapping[column][:type] == :datetime
|
34
|
+
if mapping[column][:type].to_sym == :datetime
|
35
35
|
# todo - add some timezone config to the mapping.
|
36
36
|
# In cases where the timestamp or date doesn't include a timezone, you should be able to specify one
|
37
37
|
DateTime.parse(row[column]).utc rescue ''
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module CanvasSync
|
2
|
+
module Jobs
|
3
|
+
class SyncRolesJob < CanvasSync::Job
|
4
|
+
# Syncs Roles using the Canvas API
|
5
|
+
#
|
6
|
+
#
|
7
|
+
# @param job_chain [Hash]
|
8
|
+
# @param options [Hash]
|
9
|
+
def perform(job_chain, options)
|
10
|
+
CanvasSync.get_canvas_sync_client(job_chain[:global_options]).list_roles('self').all_pages!.each do |role_params|
|
11
|
+
Role.create_or_update(role_params)
|
12
|
+
end
|
13
|
+
|
14
|
+
CanvasSync.invoke_next(job_chain)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/canvas_sync/version.rb
CHANGED
@@ -50,6 +50,34 @@ RSpec.describe CanvasSync do
|
|
50
50
|
})
|
51
51
|
end
|
52
52
|
end
|
53
|
+
|
54
|
+
context 'we are syncing roles with a term scope' do
|
55
|
+
it 'syncs the roles in a separate job that runs first' do
|
56
|
+
chain = CanvasSync.default_provisioning_report_chain(['roles', 'courses'], :active)
|
57
|
+
expect(chain).to eq({
|
58
|
+
jobs: [
|
59
|
+
{ job: CanvasSync::Jobs::SyncTermsJob.to_s, options: {} },
|
60
|
+
{ job: CanvasSync::Jobs::SyncRolesJob.to_s, options: {} },
|
61
|
+
{ job: CanvasSync::Jobs::SyncProvisioningReportJob.to_s, options: { term_scope: 'active', models: ['courses'] } }
|
62
|
+
],
|
63
|
+
global_options: { legacy_support: false }
|
64
|
+
})
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'we are syncing roles without a term scope' do
|
69
|
+
it 'syncs roles separately even with no term scope' do
|
70
|
+
chain = CanvasSync.default_provisioning_report_chain(['roles', 'courses'])
|
71
|
+
expect(chain).to eq({
|
72
|
+
jobs: [
|
73
|
+
{ job: CanvasSync::Jobs::SyncTermsJob.to_s, options: {} },
|
74
|
+
{ job: CanvasSync::Jobs::SyncRolesJob.to_s, options: {} },
|
75
|
+
{ job: CanvasSync::Jobs::SyncProvisioningReportJob.to_s, options: { term_scope: nil, models: ['courses'] } }
|
76
|
+
],
|
77
|
+
global_options: { legacy_support: false }
|
78
|
+
})
|
79
|
+
end
|
80
|
+
end
|
53
81
|
end
|
54
82
|
end
|
55
83
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe CanvasSync::Jobs::SyncRolesJob do
|
4
|
+
describe '#perform' do
|
5
|
+
let(:role_params) { open_canvas_fixture('roles') }
|
6
|
+
let(:job_chain) { { jobs: [], global_options: {}} }
|
7
|
+
|
8
|
+
it 'retrieves all terms from the Canvas API and then invokes the next job' do
|
9
|
+
expect(CanvasSync).to receive(:invoke_next).with(job_chain)
|
10
|
+
|
11
|
+
expect {
|
12
|
+
CanvasSync::Jobs::SyncRolesJob.perform_now(job_chain, {})
|
13
|
+
}.to change { Role.count }.by(role_params.length)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Role, type: :model do
|
4
|
+
let(:subject) { FactoryGirl.create(:role) }
|
5
|
+
|
6
|
+
describe 'validations' do
|
7
|
+
it { should validate_presence_of(:canvas_role_id) }
|
8
|
+
it { should validate_uniqueness_of(:canvas_role_id) }
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '.create_or_update' do
|
12
|
+
let(:role_params) { open_canvas_fixture('roles')[0] }
|
13
|
+
|
14
|
+
context 'the role does not already exist' do
|
15
|
+
it 'creates it' do
|
16
|
+
expect {
|
17
|
+
Role.create_or_update(role_params)
|
18
|
+
}.to change { Role.count }.by(1)
|
19
|
+
|
20
|
+
role = Role.last
|
21
|
+
expect(role.label).to eq(role_params['label'])
|
22
|
+
expect(role.base_role_type).to eq(role_params['base_role_type'])
|
23
|
+
expect(role.account).to eq(role_params['account'])
|
24
|
+
expect(role.permissions).to eq(role_params['permissions'])
|
25
|
+
expect(role.workflow_state).to eq(role_params['workflow_state'])
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'the role already exists' do
|
30
|
+
let!(:existing_role) { FactoryGirl.create(:role, canvas_role_id: role_params['id']) }
|
31
|
+
|
32
|
+
it 'updates it' do
|
33
|
+
expect {
|
34
|
+
Role.create_or_update(role_params)
|
35
|
+
}.to_not change { Role.count }
|
36
|
+
|
37
|
+
existing_role.reload
|
38
|
+
expect(existing_role.label).to eq(role_params['label'])
|
39
|
+
expect(existing_role.base_role_type).to eq(role_params['base_role_type'])
|
40
|
+
expect(existing_role.account).to eq(role_params['account'])
|
41
|
+
expect(existing_role.permissions).to eq(role_params['permissions'])
|
42
|
+
expect(existing_role.workflow_state).to eq(role_params['workflow_state'])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
#
|
2
|
+
# AUTO GENERATED MODEL
|
3
|
+
# This model was auto generated by the CanvasSync Gem.
|
4
|
+
# You can customize it as needed, but make sure you test
|
5
|
+
# any changes you make to the auto generated methods.
|
6
|
+
#
|
7
|
+
|
8
|
+
|
9
|
+
class Role < ApplicationRecord
|
10
|
+
validates :canvas_role_id, uniqueness: true, presence: true
|
11
|
+
|
12
|
+
def self.create_or_update(role_params)
|
13
|
+
role = Role.find_or_initialize_by(canvas_role_id: role_params['id'])
|
14
|
+
|
15
|
+
role.assign_attributes(label: role_params['label'],
|
16
|
+
base_role_type: role_params['base_role_type'],
|
17
|
+
account: role_params['account'],
|
18
|
+
permissions: role_params['permissions'],
|
19
|
+
workflow_state: role_params['workflow_state'])
|
20
|
+
|
21
|
+
role.save! if role.changed?
|
22
|
+
role
|
23
|
+
end
|
24
|
+
end
|
@@ -5,21 +5,21 @@
|
|
5
5
|
# gem 'sqlite3'
|
6
6
|
#
|
7
7
|
default: &default
|
8
|
-
adapter:
|
8
|
+
adapter: postgresql
|
9
9
|
pool: 5
|
10
10
|
timeout: 5000
|
11
11
|
|
12
12
|
development:
|
13
13
|
<<: *default
|
14
|
-
database:
|
14
|
+
database: canvas_sync_development
|
15
15
|
|
16
16
|
# Warning: The database defined as "test" will be erased and
|
17
17
|
# re-generated from your development database when you run "rake".
|
18
18
|
# Do not set this db to the same as development or production.
|
19
19
|
test:
|
20
20
|
<<: *default
|
21
|
-
database:
|
21
|
+
database: canvas_sync_test
|
22
22
|
|
23
23
|
production:
|
24
24
|
<<: *default
|
25
|
-
database:
|
25
|
+
database: canvas_sync_production
|
@@ -0,0 +1,23 @@
|
|
1
|
+
#
|
2
|
+
# AUTO GENERATED MIGRATION
|
3
|
+
# This migration was auto generated by the CanvasSync Gem.
|
4
|
+
# You can add new columns to this table, but removing or
|
5
|
+
# re-naming ones created here may break Canvas Syncing.
|
6
|
+
#
|
7
|
+
|
8
|
+
|
9
|
+
class CreateRoles < ActiveRecord::Migration[5.1]
|
10
|
+
def change
|
11
|
+
create_table :roles do |t|
|
12
|
+
t.integer :canvas_role_id, null: false
|
13
|
+
t.string :label, null: false
|
14
|
+
t.string :base_role_type, null: false
|
15
|
+
t.json :account
|
16
|
+
t.string :workflow_state, null: false
|
17
|
+
t.json :permissions
|
18
|
+
|
19
|
+
t.timestamps
|
20
|
+
end
|
21
|
+
add_index :roles, :canvas_role_id, unique: true
|
22
|
+
end
|
23
|
+
end
|
data/spec/dummy/db/schema.rb
CHANGED
@@ -10,7 +10,10 @@
|
|
10
10
|
#
|
11
11
|
# It's strongly recommended that you check this file into your version control system.
|
12
12
|
|
13
|
-
ActiveRecord::Schema.define(version:
|
13
|
+
ActiveRecord::Schema.define(version: 20180103162102) do
|
14
|
+
|
15
|
+
# These are extensions that must be enabled in order to support this database
|
16
|
+
enable_extension "plpgsql"
|
14
17
|
|
15
18
|
create_table "canvas_sync_job_logs", force: :cascade do |t|
|
16
19
|
t.datetime "started_at"
|
@@ -60,6 +63,18 @@ ActiveRecord::Schema.define(version: 20171107213207) do
|
|
60
63
|
t.index ["canvas_user_id"], name: "index_enrollments_on_canvas_user_id"
|
61
64
|
end
|
62
65
|
|
66
|
+
create_table "roles", force: :cascade do |t|
|
67
|
+
t.integer "canvas_role_id", null: false
|
68
|
+
t.string "label", null: false
|
69
|
+
t.string "base_role_type", null: false
|
70
|
+
t.json "account"
|
71
|
+
t.string "workflow_state", null: false
|
72
|
+
t.json "permissions"
|
73
|
+
t.datetime "created_at", null: false
|
74
|
+
t.datetime "updated_at", null: false
|
75
|
+
t.index ["canvas_role_id"], name: "index_roles_on_canvas_role_id", unique: true
|
76
|
+
end
|
77
|
+
|
63
78
|
create_table "sections", force: :cascade do |t|
|
64
79
|
t.bigint "canvas_section_id", null: false
|
65
80
|
t.string "sis_id"
|