canvas_sync 0.22.8 → 0.22.11

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 (29) hide show
  1. checksums.yaml +4 -4
  2. data/lib/canvas_sync/concerns/auto_relations.rb +11 -0
  3. data/lib/canvas_sync/concerns/live_event_sync.rb +2 -2
  4. data/lib/canvas_sync/concerns/user/sharded.rb +18 -0
  5. data/lib/canvas_sync/generators/templates/migrations/create_assignment_overrides.rb +25 -0
  6. data/lib/canvas_sync/generators/templates/models/assignment_override.rb +28 -0
  7. data/lib/canvas_sync/jobs/sync_assignment_overrides_job.rb +20 -0
  8. data/lib/canvas_sync/processors/assignment_overrides_processor.rb +41 -0
  9. data/lib/canvas_sync/processors/model_mappings.yml +43 -0
  10. data/lib/canvas_sync/version.rb +1 -1
  11. data/lib/canvas_sync.rb +4 -0
  12. data/spec/canvas_sync/processors/assignment_overrides_processor_spec.rb +45 -0
  13. data/spec/dummy/app/models/assignment_override.rb +34 -0
  14. data/spec/dummy/app/models/learning_outcome_result.rb +4 -4
  15. data/spec/dummy/app/models/rubric.rb +4 -4
  16. data/spec/dummy/app/models/rubric_assessment.rb +1 -0
  17. data/spec/dummy/app/models/rubric_association.rb +3 -3
  18. data/spec/dummy/app/models/user.rb +1 -0
  19. data/spec/dummy/config/application.rb +2 -0
  20. data/spec/dummy/db/migrate/20241223080202_create_assignment_overrides.rb +25 -0
  21. data/spec/dummy/db/migrate/{20240408223326_create_course_nicknames.rb → 20250319194134_create_course_nicknames.rb} +1 -1
  22. data/spec/dummy/db/migrate/{20240509105100_create_rubrics.rb → 20250319194135_create_rubrics.rb} +4 -4
  23. data/spec/dummy/db/schema.rb +191 -178
  24. data/spec/dummy/tmp/local_secret.txt +1 -0
  25. data/spec/factories/assignment_override_factory.rb +10 -0
  26. data/spec/spec_helper.rb +2 -1
  27. data/spec/support/fixtures/reports/assignment_overrides.csv +3 -0
  28. data/spec/support/fixtures/reports/assignment_overrides_conflict.csv +2 -0
  29. metadata +26 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0882011929080b4e55e1798ae3639c4431a583dd2a31634b2b9c2ba5f450fcb4'
4
- data.tar.gz: 7a339eef193de9b640b3d68d5474a7771d363f7dee723be49da33cf319afe610
3
+ metadata.gz: 2bbd02c6460435a1bb3bc0c33c997d60c09c8f0fffbc8afe8292cbc855d51306
4
+ data.tar.gz: 99f488b171bb99dd51b1f20b4d39963f0f94624a5decf96b9e8d270355efe5b4
5
5
  SHA512:
6
- metadata.gz: a54b1779112dce3d106a503cc720b74d5b54ed2688c95c9d2c3523024813a0ac34d144417a8959bc81561dc4f78a7cf9914dd124037c85376a8a35b455bd5be1
7
- data.tar.gz: 74e8b3e7d9317bbc2bbd7f902cfbedeabfd826ddbfd70aa4c8bf57c6f05dc2f4176f7bf2c0cc0352adae4ecaa4978f57f02fc3dcbbed8d1d8130df58bbaea51d
6
+ metadata.gz: eed8224aa09c93c4d95db72db479edd40e3a75faa5f548e208b99afffb43b453de8b4a00202ffe01f1c7c8b2c6a2c11b978f203e5a406760e61236a0bfa7e372
7
+ data.tar.gz: 58e2541a97e2885bea180b30ad9ae8af85c074c635dc12ec309985685dd74de48ccadca81d87ec8791d0b4b5691b11eb198e47a2c9ad18e6b64c4f7fdf126320
@@ -0,0 +1,11 @@
1
+ module CanvasSync::Concerns
2
+ module AutoRelations
3
+ extend ActiveSupport::Concern
4
+
5
+ # CanvasSync::Record.define_feature self, default: true
6
+
7
+ included do
8
+
9
+ end
10
+ end
11
+ end
@@ -12,9 +12,9 @@ module CanvasSync::Concerns
12
12
  meta = event[:metadata]
13
13
  payload = event[:payload] || event[:body]
14
14
 
15
- canvas_id = payload[:id] || payload[:"#{name.underscore}_id"]
16
- inst = self.find_or_initialize_by(canvas_id: canvas_id)
17
15
  model, _, subtype = meta[:event_name].rpartition('_')
16
+ canvas_id = payload[:id] || payload[:"#{model}_id"] || payload[:"#{name.underscore}_id"]
17
+ inst = self.find_or_initialize_by(canvas_id: canvas_id)
18
18
 
19
19
  result = inst.run_callbacks(:process_live_event) do
20
20
  inst.process_live_event(subtype.to_sym, payload, meta)
@@ -0,0 +1,18 @@
1
+ module CanvasSync::Concerns
2
+ module User
3
+ module CanvasSharded
4
+ extend ActiveSupport::Concern
5
+
6
+ CanvasSync::Record.define_feature self, default: false
7
+
8
+ included do
9
+
10
+ end
11
+
12
+ class_methods do
13
+
14
+ end
15
+
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,25 @@
1
+ class CreateAssignmentOverrides < ActiveRecord::Migration[5.1]
2
+ def change
3
+ create_table :assignment_overrides do |t|
4
+ t.bigint :canvas_id, null: false
5
+ t.bigint :canvas_assignment_id
6
+ t.bigint :canvas_group_id
7
+ t.bigint :canvas_course_id
8
+ t.bigint :canvas_section_id
9
+ t.bigint :student_ids, array: true
10
+ t.datetime :due_at
11
+ t.boolean :all_day
12
+ t.datetime :all_day_date
13
+ t.datetime :unlock_at
14
+ t.datetime :lock_at
15
+ t.string :title
16
+ t.string :workflow_state
17
+
18
+ t.timestamps
19
+ end
20
+
21
+ add_index :assignment_overrides, :canvas_id, unique: true
22
+ add_index :assignment_overrides, :canvas_assignment_id
23
+ add_index :assignment_overrides, :canvas_course_id
24
+ end
25
+ end
@@ -0,0 +1,28 @@
1
+ # <%= autogenerated_model_warning %>
2
+
3
+ class AssignmentOverride < ApplicationRecord
4
+ include CanvasSync::Record
5
+ include CanvasSync::Concerns::ApiSyncable
6
+
7
+ canvas_sync_features :defaults
8
+
9
+ validates :canvas_id, uniqueness: true, presence: true
10
+ belongs_to :assignment, primary_key: :canvas_id, foreign_key: :canvas_assignment_id, optional: true
11
+ belongs_to :course, primary_key: :canvas_id, foreign_key: :canvas_course_id, optional: true
12
+
13
+ api_syncable({
14
+ canvas_id: :id,
15
+ canvas_assignment_id: :assignment_id,
16
+ title: :title,
17
+ workflow_state: :workflow_state,
18
+ due_at: :due_at,
19
+ all_day: :all_day,
20
+ all_day_date: :all_day_date,
21
+ unlock_at: :unlock_at,
22
+ lock_at: :lock_at,
23
+ student_ids: ->(r) { JSON.parse(r[:student_ids]) if r[:student_ids].present? },
24
+ canvas_group_id: :group_id,
25
+ canvas_section_id: :course_section_id,
26
+ canvas_course_id: -> (r) { r.dig(:assignment, :course_id) }
27
+ }, -> (api) { api.assignment_override(canvas_assignment_id, canvas_id) })
28
+ end
@@ -0,0 +1,20 @@
1
+ module CanvasSync
2
+ module Jobs
3
+ class SyncAssignmentOverridesJob < ReportStarter
4
+ # Syncs SyncAssignmentOverrides
5
+ #
6
+ # Starts a report processor for the assignment overrides report
7
+ # (the proserv_assignment_overrides_csv report must be enabled)
8
+ #
9
+ # @param options [Hash]
10
+ def perform(options)
11
+ super(
12
+ "proserv_assignment_overrides_csv",
13
+ merge_report_params(options),
14
+ CanvasSync::Processors::AssignmentOverridesProcessor.to_s,
15
+ {},
16
+ )
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,41 @@
1
+ require_relative "./report_processor"
2
+
3
+ module CanvasSync
4
+ module Processors
5
+ # Processes a assignment overrides report using the bulk importer.
6
+ #
7
+ # @param report_file_path [String]
8
+ # @param options [Hash]
9
+ class AssignmentOverridesProcessor < ReportProcessor
10
+ # Class method that starts processing (for consistent interface with other processors).
11
+ def self.process(report_file_path, options, report_id)
12
+ new(report_file_path, options)
13
+ end
14
+
15
+ def initialize(report_file_path, options)
16
+ do_bulk_import(report_file_path, AssignmentOverride, options: options) do |row|
17
+ # Handle transforms here instead of in mappings
18
+ row[:student_ids] = parse_student_ids(row[:student_ids])
19
+
20
+ # Convert empty/null/'0' values to nil
21
+ # For Ruby 2.7
22
+ row[:group_id] = parse_nil_value(row[:group_id])
23
+ row[:course_section_id] = parse_nil_value(row[:course_section_id])
24
+ row
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def parse_student_ids(value)
31
+ value.present? ? JSON.parse(value) : nil
32
+ end
33
+
34
+ def parse_nil_value(value)
35
+ # Convert "0", "null", or empty string to nil
36
+ return nil if %w[0 null].include?(value) || value == ''
37
+ value
38
+ end
39
+ end
40
+ end
41
+ end
@@ -293,6 +293,49 @@ submissions:
293
293
  database_column_name: workflow_state
294
294
  type: string
295
295
 
296
+ assignment_overrides:
297
+ conflict_target: id
298
+ report_columns:
299
+ id:
300
+ database_column_name: canvas_id
301
+ type: integer
302
+ assignment_id:
303
+ database_column_name: canvas_assignment_id
304
+ type: integer
305
+ student_ids:
306
+ database_column_name: student_ids
307
+ type: array
308
+ group_id:
309
+ database_column_name: canvas_group_id
310
+ type: integer
311
+ course_section_id:
312
+ database_column_name: canvas_section_id
313
+ type: integer
314
+ course_id:
315
+ database_column_name: canvas_course_id
316
+ type: integer
317
+ title:
318
+ database_column_name: title
319
+ type: string
320
+ due_at:
321
+ database_column_name: due_at
322
+ type: datetime
323
+ unlock_at:
324
+ database_column_name: unlock_at
325
+ type: datetime
326
+ lock_at:
327
+ database_column_name: lock_at
328
+ type: datetime
329
+ all_day:
330
+ database_column_name: all_day
331
+ type: boolean
332
+ all_day_date:
333
+ database_column_name: all_day_date
334
+ type: datetime
335
+ workflow_state:
336
+ database_column_name: workflow_state
337
+ type: string
338
+
296
339
  assignment_groups:
297
340
  conflict_target: id
298
341
  report_columns:
@@ -1,3 +1,3 @@
1
1
  module CanvasSync
2
- VERSION = "0.22.8".freeze
2
+ VERSION = "0.22.11".freeze
3
3
  end
data/lib/canvas_sync.rb CHANGED
@@ -54,6 +54,7 @@ module CanvasSync
54
54
  rubric_associations
55
55
  rubric_assessments
56
56
  course_progresses
57
+ assignment_overrides
57
58
  ].freeze
58
59
 
59
60
  SUPPORTED_TERM_SCOPE_MODELS = %w[
@@ -66,6 +67,7 @@ module CanvasSync
66
67
  rubric_associations
67
68
  rubric_assessments
68
69
  course_progresses
70
+ assignment_overrides
69
71
  ].freeze
70
72
 
71
73
  DEFAULT_TERM_SCOPE_MODELS = %w[
@@ -78,6 +80,7 @@ module CanvasSync
78
80
  rubric_associations
79
81
  rubric_assessments
80
82
  course_progresses
83
+ assignment_overrides
81
84
  ].freeze
82
85
 
83
86
  SUPPORTED_LIVE_EVENTS = %w[
@@ -166,6 +169,7 @@ module CanvasSync
166
169
  assignments: CanvasSync::Jobs::SyncAssignmentsJob,
167
170
  submissions: CanvasSync::Jobs::SyncSubmissionsJob,
168
171
  assignment_groups: CanvasSync::Jobs::SyncAssignmentGroupsJob,
172
+ assignment_overrides: CanvasSync::Jobs::SyncAssignmentOverridesJob,
169
173
  context_modules: CanvasSync::Jobs::SyncContextModulesJob,
170
174
  context_module_items: CanvasSync::Jobs::SyncContextModuleItemsJob,
171
175
  content_migrations: CanvasSync::Jobs::SyncContentMigrationsJob,
@@ -0,0 +1,45 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe CanvasSync::Processors::AssignmentOverridesProcessor do
4
+ describe '#process' do
5
+ it 'inserts assignment overrides' do
6
+ expect {
7
+ described_class.process('spec/support/fixtures/reports/assignment_overrides.csv', {}, 1)
8
+ }.to change { AssignmentOverride.count }.by(2)
9
+
10
+ override = AssignmentOverride.first
11
+ expect(override).to have_attributes(
12
+ canvas_id: 1,
13
+ canvas_assignment_id: 1,
14
+ student_ids: [1, 2],
15
+ canvas_group_id: nil,
16
+ canvas_section_id: nil,
17
+ canvas_course_id: 1,
18
+ title: "Override 1",
19
+ workflow_state: "active"
20
+ )
21
+ expect(override.due_at.strftime('%Y-%m-%d %H:%M:%S')).to eq('2024-05-01 09:00:00')
22
+ end
23
+
24
+ context 'record already present in the database' do
25
+ before do
26
+ described_class.process('spec/support/fixtures/reports/assignment_overrides.csv', {}, 1)
27
+ end
28
+
29
+ it 'updates the override' do
30
+ expect {
31
+ described_class.process('spec/support/fixtures/reports/assignment_overrides_conflict.csv', {}, 1)
32
+ }.to change { AssignmentOverride.count }.by(0)
33
+
34
+ override = AssignmentOverride.first
35
+ expect(override).to have_attributes(
36
+ canvas_id: 1,
37
+ student_ids: [1, 2, 3],
38
+ title: "Override 1 Updated",
39
+ workflow_state: "active"
40
+ )
41
+ expect(override.due_at.strftime('%Y-%m-%d %H:%M:%S')).to eq('2024-06-01 09:00:00')
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,34 @@
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 AssignmentOverride < ApplicationRecord
10
+ include CanvasSync::Record
11
+ include CanvasSync::Concerns::ApiSyncable
12
+
13
+ canvas_sync_features :defaults
14
+
15
+ validates :canvas_id, uniqueness: true, presence: true
16
+ belongs_to :assignment, primary_key: :canvas_id, foreign_key: :canvas_assignment_id, optional: true
17
+ belongs_to :course, primary_key: :canvas_id, foreign_key: :canvas_course_id, optional: true
18
+
19
+ api_syncable({
20
+ canvas_id: :id,
21
+ canvas_assignment_id: :assignment_id,
22
+ title: :title,
23
+ workflow_state: :workflow_state,
24
+ due_at: :due_at,
25
+ all_day: :all_day,
26
+ all_day_date: :all_day_date,
27
+ unlock_at: :unlock_at,
28
+ lock_at: :lock_at,
29
+ student_ids: ->(r) { JSON.parse(r[:student_ids]) if r[:student_ids].present? },
30
+ canvas_group_id: :group_id,
31
+ canvas_section_id: :course_section_id,
32
+ canvas_course_id: -> (r) { r.dig(:assignment, :course_id) }
33
+ }, -> (api) { api.assignment_override(canvas_assignment_id, canvas_id) })
34
+ end
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # # #
4
- # AUTO GENERATED MIGRATION
5
- # This migration was auto generated by the CanvasSync Gem.
6
- # You can add new columns to this table, but removing or
7
- # re-naming ones created here may break Canvas Syncing.
4
+ # AUTO GENERATED MODEL
5
+ # This model was auto generated by the CanvasSync Gem.
6
+ # You can customize it as needed, but make sure you test
7
+ # any changes you make to the auto generated methods.
8
8
  #
9
9
 
10
10
 
@@ -13,9 +13,9 @@ class Rubric < ApplicationRecord
13
13
  canvas_sync_features :defaults
14
14
 
15
15
  validates :canvas_id, uniqueness: true, presence: true
16
- belongs_to :user, primary_key: :canvas_id, foreign_key: :canvas_user_id, optional: true
17
- belongs_to :rubric, primary_key: :canvas_id, foreign_key: :canvas_rubric_id , optional: true # based on another rubric
18
- belongs_to :context, polymorphic: true, primary_key: :canvas_id, foreign_key: :canvas_context_id, foreign_type: :canvas_context_type, optional: true
16
+ belongs_to :user, primary_key: :canvas_id, foreign_key: :canvas_user_id
17
+ belongs_to :rubric, primary_key: :canvas_id, foreign_key: :canvas_rubric_id # based on another rubric
18
+ belongs_to :context, polymorphic: true, primary_key: :canvas_id, foreign_key: :canvas_context_id, foreign_type: :canvas_context_type
19
19
 
20
20
  has_many :rubric_assessments, through: :rubric_associations, dependent: :destroy
21
21
  has_many :rubric_associations, primary_key: :canvas_id, foreign_key: :canvas_rubric_id
@@ -32,5 +32,5 @@ class Rubric < ApplicationRecord
32
32
  free_form_criterion_comments: :free_form_criterion_comments,
33
33
  hide_score_total: :hide_score_total,
34
34
  data: :data,
35
- }, -> (api) { api.get("api/v1/accounts/#{canvas_root_account_id}/rubrics/#{canvas_id}") })
35
+ }, -> (api) { api.rubrics("self", canvas_id) })
36
36
  end
@@ -8,6 +8,7 @@
8
8
 
9
9
  class RubricAssessment < ApplicationRecord
10
10
  include CanvasSync::Record
11
+ include CanvasSync::Concerns::ApiSyncable
11
12
 
12
13
  canvas_sync_features :defaults
13
14
 
@@ -8,13 +8,13 @@
8
8
 
9
9
  class RubricAssociation < ApplicationRecord
10
10
  include CanvasSync::Record
11
+ include CanvasSync::Concerns::ApiSyncable
11
12
 
12
13
  canvas_sync_features :defaults
13
14
 
14
15
  validates :canvas_id, uniqueness: true, presence: true
15
16
  belongs_to :rubric, primary_key: :canvas_id, foreign_key: :canvas_rubric_id, optional: true
16
- belongs_to :association_object, polymorphic: true, primary_key: :canvas_id, foreign_type: :canvas_association_type, foreign_key: :canvas_association_id, optional: true
17
- belongs_to :context, polymorphic: true, primary_key: :canvas_id, foreign_key: :canvas_context_id, foreign_type: :canvas_context_type, optional: true
18
-
17
+ belongs_to :association_object, polymorphic: true, primary_key: :canvas_id, foreign_type: :canvas_association_type, optional: true
18
+ belongs_to :context, polymorphic: true, primary_key: :canvas_id, foreign_key: :canvas_context_id, optional: true
19
19
  has_many :rubric_assessments
20
20
  end
@@ -27,6 +27,7 @@ class User < ApplicationRecord
27
27
  has_many :admin_roles, through: :admins, source: :role
28
28
  has_many :submissions, primary_key: :canvas_id, foreign_key: :canvas_user_id
29
29
  has_many :group_memberships, primary_key: :canvas_id, foreign_key: :canvas_user_id
30
+ has_many :rubrics, primary_key: :canvas_id, foreign_key: :canvas_user_id
30
31
 
31
32
  api_syncable({
32
33
  sis_id: :sis_user_id,
@@ -1,5 +1,7 @@
1
1
  require File.expand_path('../boot', __FILE__)
2
2
 
3
+ require 'logger'
4
+
3
5
  require 'rails'
4
6
  require 'active_record/railtie'
5
7
  # require 'active_storage/engine'
@@ -0,0 +1,25 @@
1
+ class CreateAssignmentOverrides < ActiveRecord::Migration[5.1]
2
+ def change
3
+ create_table :assignment_overrides do |t|
4
+ t.bigint :canvas_id, null: false
5
+ t.bigint :canvas_assignment_id
6
+ t.bigint :canvas_group_id
7
+ t.bigint :canvas_course_id
8
+ t.bigint :canvas_section_id
9
+ t.bigint :student_ids, array: true
10
+ t.datetime :due_at
11
+ t.boolean :all_day
12
+ t.datetime :all_day_date
13
+ t.datetime :unlock_at
14
+ t.datetime :lock_at
15
+ t.string :title
16
+ t.string :workflow_state
17
+
18
+ t.timestamps
19
+ end
20
+
21
+ add_index :assignment_overrides, :canvas_id, unique: true
22
+ add_index :assignment_overrides, :canvas_assignment_id
23
+ add_index :assignment_overrides, :canvas_course_id
24
+ end
25
+ end
@@ -9,7 +9,7 @@
9
9
  class CreateCourseNicknames < ActiveRecord::Migration[5.1]
10
10
  def change
11
11
  create_table :course_nicknames do |t|
12
- t.bigint :canvas_user_preference_value_id, null: false
12
+ t.bigint :canvas_user_preference_value_id, null: true
13
13
  t.integer :canvas_user_id
14
14
  t.integer :canvas_course_id
15
15
  t.string :nickname
@@ -1,8 +1,8 @@
1
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.
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
6
  #
7
7
 
8
8