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
@@ -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"
|