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.
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
@@ -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,4 @@
1
+ <%= autogenerated_event_warning %>
2
+
3
+ class LiveEvents::SubmissionCreatedEvent < LiveEvents::SubmissionEvent
4
+ 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,4 @@
1
+ <%= autogenerated_event_warning %>
2
+
3
+ class LiveEvents::SubmissionUpdatedEvent < LiveEvents::SubmissionEvent
4
+ 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,2 @@
1
+ class LiveEvents::SyllabusUpdatedEvent < LiveEvents::SyllabusEvent
2
+ end
@@ -0,0 +1,4 @@
1
+ <%= autogenerated_event_warning %>
2
+
3
+ class LiveEvents::UserChangedEvent < LiveEvents::UserEvent
4
+ 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
@@ -0,0 +1,4 @@
1
+ <%= autogenerated_event_warning %>
2
+
3
+ class LiveEvents::UserUpdatedEvent < LiveEvents::UserEvent
4
+ 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
@@ -1,3 +1,3 @@
1
1
  module CanvasSync
2
- VERSION = '0.3.3'
2
+ VERSION = '0.3.5'
3
3
  end
@@ -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: sqlite3
8
+ adapter: postgresql
9
9
  pool: 5
10
10
  timeout: 5000
11
11
 
12
12
  development:
13
13
  <<: *default
14
- database: db/development.sqlite3
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: db/test.sqlite3
21
+ database: canvas_sync_test
22
22
 
23
23
  production:
24
24
  <<: *default
25
- database: db/production.sqlite3
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
@@ -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: 20171107213207) do
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"