canvas_sync 0.17.31 → 0.17.34

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '09cebb9b63254bcdf9e96ae0133d882fcf800ad405f6d53080793e8f3f0c052f'
4
- data.tar.gz: 40d27ea858ac931d006ccbab54f7ee1b725166ad3fec4ecb369f6e3f0fa611f1
3
+ metadata.gz: 8029fc55239810f8064f8d1c9fb4cd1f1756a7605581e7aced13e150e69c8b6b
4
+ data.tar.gz: 2deac9eb222ed8c636159c8100e8aa6cd59d2fa26d3b13b83c7be31cf7cbc0bd
5
5
  SHA512:
6
- metadata.gz: de38e11de998bef7cca15c875d63b29984d2cdf4dd90436854484e5686c5aca4eb249fd864e24b66d532afcba63b3f7ddb62c5e5abf55d1e2c621fa6cb566af7
7
- data.tar.gz: 8493bc7a6f100da697840f65aeaceacbacc95c224aa53c66129e19d5a88d61b1f59202252ac80b2e8d42fc4661f974b30bd1825b448e58b477ce9e33f71a9f2c
6
+ metadata.gz: b1d70b6a2493a49a9eeb48532e9438d02209d3de9609afa0a9bd09a5fe4c9816dcd9b3d7724ffa2385cd65f19b873901cce5fe5b733fa534e4c96ce785146f91
7
+ data.tar.gz: c985b3da6ca96c4120b852f0b4df938a6393a797dd8365ce3c9e61404b67623484930b9001e13172309399e745774bd63208097954d569e094f86c4a56e94e38
@@ -88,7 +88,7 @@ module CanvasSync::Concerns
88
88
  end
89
89
 
90
90
  def unlink_column(key)
91
- @map_def.delete(key)
91
+ @map_def[:report_columns].delete(key)
92
92
  end
93
93
 
94
94
  def link_column(m, type: nil, &blk)
@@ -32,24 +32,28 @@ module CanvasSync
32
32
  initializer :integrate_pandapal do
33
33
  require 'panda_pal'
34
34
 
35
- if PandaPal::Organization.respond_to?(:scheduled_task)
36
- if PandaPal::Organization.respond_to?(:define_setting)
37
- PandaPal::Organization.define_setting(:canvas_sync, {
38
- type: 'Hash',
39
- required: false,
40
- properties: {
41
- job_log_retention: { **RETENTION_TYPE },
42
- sync_batch_retention: { **RETENTION_TYPE },
43
- }
44
- })
45
- end
35
+ Rails.application.reloader.to_prepare do
36
+ if PandaPal::Organization.respond_to?(:scheduled_task)
37
+ if PandaPal::Organization.respond_to?(:define_setting)
38
+ PandaPal::Organization.define_setting(:canvas_sync, {
39
+ type: 'Hash',
40
+ required: false,
41
+ properties: {
42
+ job_log_retention: { **RETENTION_TYPE },
43
+ sync_batch_retention: { **RETENTION_TYPE },
44
+ }
45
+ })
46
+ end
46
47
 
47
- PandaPal::Organization.scheduled_task '0 0 3 * * *', :clean_canvas_sync_logs do
48
- job_log_retention = ChronicDuration.parse(settings.dig(:canvas_sync, :job_log_retention) || '3 months', keep_zero: true).seconds.ago
49
- JobLog.where('updated_at < ?', job_log_retention).delete_all
48
+ unless PandaPal::Organization.task_scheduled?(:clean_canvas_sync_logs)
49
+ PandaPal::Organization.scheduled_task '0 0 3 * * *', :clean_canvas_sync_logs do
50
+ job_log_retention = ChronicDuration.parse(settings.dig(:canvas_sync, :job_log_retention) || '3 months', keep_zero: true).seconds.ago
51
+ JobLog.where('updated_at < ?', job_log_retention).delete_all
50
52
 
51
- sync_batch_retention = ChronicDuration.parse(settings.dig(:canvas_sync, :sync_batch_retention) || '6 months', keep_zero: true).seconds.ago
52
- SyncBatch.where('updated_at < ?', sync_batch_retention).delete_all
53
+ sync_batch_retention = ChronicDuration.parse(settings.dig(:canvas_sync, :sync_batch_retention) || '6 months', keep_zero: true).seconds.ago
54
+ SyncBatch.where('updated_at < ?', sync_batch_retention).delete_all
55
+ end
56
+ end
53
57
  end
54
58
  end
55
59
  rescue LoadError
@@ -0,0 +1,30 @@
1
+ # <%= autogenerated_migration_warning %>
2
+
3
+ class CreateLearningOutcomes < ActiveRecord::Migration[5.1]
4
+ def change
5
+ create_table :learning_outcomes do |t|
6
+ t.bigint :canvas_id, null: false
7
+ t.integer :canvas_context_id
8
+ t.string :canvas_context_type
9
+ t.string :name
10
+ t.string :friendly_name
11
+ t.string :workflow_state
12
+ t.datetime :canvas_created_at
13
+ t.datetime :canvas_updated_at
14
+ t.string :migration_id
15
+ t.string :vendor_guid
16
+ t.string :low_grade
17
+ t.string :high_grade
18
+ t.string :calculation_method
19
+ t.string :calculation_int
20
+ t.integer :outcome_import_id
21
+ t.integer :root_account_ids, array: true, default: []
22
+ t.text :description
23
+
24
+ t.timestamps
25
+ end
26
+
27
+ add_index :learning_outcomes, :canvas_id, unique: true
28
+ add_index :learning_outcomes, [:canvas_context_id, :canvas_context_type], name: "index_learning_outcomes_on_context"
29
+ end
30
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ # # <%= autogenerated_migration_warning %>
4
+
5
+ class LearningOutcome < ApplicationRecord
6
+ include CanvasSync::Record
7
+ include CanvasSync::Concerns::ApiSyncable
8
+
9
+ belongs_to :context, polymorphic: true, optional: true, primary_key: :canvas_id, foreign_key: :canvas_context_id, foreign_type: :canvas_context_type
10
+
11
+ api_syncable({
12
+ canvas_id: :id,
13
+ canvas_context_id: :context_id,
14
+ canvas_context_type: :context_type,
15
+ name: :title,
16
+ friendly_name: :display_name,
17
+ vendor_guid: :vendor_guid,
18
+ calculation_method: :calculation_method,
19
+ calculation_int: :calculation_int,
20
+ description: :description
21
+ }, ->(api) { api.get("/api/v1/outcomes/#{canvas_id}") })
22
+ end
@@ -28,7 +28,7 @@ module CanvasSync
28
28
 
29
29
  delegate :redis, to: :class
30
30
 
31
- BID_EXPIRE_TTL = 2_592_000
31
+ BID_EXPIRE_TTL = 90.days.to_i
32
32
  SCHEDULE_CALLBACK = RedisScript.new(Pathname.new(__FILE__) + "../schedule_callback.lua")
33
33
  BID_HIERARCHY = RedisScript.new(Pathname.new(__FILE__) + "../hier_batch_ids.lua")
34
34
 
@@ -101,6 +101,8 @@ module CanvasSync
101
101
  r.hincrby("BID-#{parent_bid}", "children", 1)
102
102
  r.expire("BID-#{parent_bid}", BID_EXPIRE_TTL)
103
103
  r.zadd("BID-#{parent_bid}-bids", created_at, bid)
104
+ else
105
+ r.zadd("BID-ROOT-bids", created_at, bid)
104
106
  end
105
107
  end
106
108
 
@@ -369,6 +371,7 @@ module CanvasSync
369
371
  logger.debug {"Cleaning redis of batch #{bid}"}
370
372
  redis do |r|
371
373
  r.zrem("batches", bid)
374
+ r.zrem("BID-ROOT-bids", bid)
372
375
  r.unlink(
373
376
  "BID-#{bid}",
374
377
  "BID-#{bid}-callbacks-complete",
@@ -450,6 +453,23 @@ module CanvasSync
450
453
  end
451
454
  end
452
455
 
456
+ def uget(key)
457
+ Batch.redis do |r|
458
+ case r.type(key)
459
+ when 'string'
460
+ r.get(key)
461
+ when 'list'
462
+ r.lrange(key, 0, -1)
463
+ when 'hash'
464
+ r.hgetall(key)
465
+ when 'set'
466
+ r.smembers(key)
467
+ when 'zset'
468
+ r.smembers(key, 0, -1)
469
+ end
470
+ end
471
+ end
472
+
453
473
  def method_missing(method_name, *arguments, &block)
454
474
  Batch.redis do |r|
455
475
  r.send(method_name, *arguments, &block)
@@ -27,7 +27,7 @@ module CanvasSync
27
27
 
28
28
  if clazz && object = Object.const_get(clazz)
29
29
  target = target == :instance ? object.new : object
30
- if target.respond_to?(method)
30
+ if target.respond_to?(method, true)
31
31
  target.send(method, status, opts)
32
32
  else
33
33
  Batch.logger.warn("Invalid callback method #{definition} - #{target.to_s} does not respond to #{method}")
@@ -44,7 +44,7 @@ module CanvasSync
44
44
  wrapper.on(checkin_event, "#{self.class.to_s}.job_checked_in", pool_id: pid)
45
45
  wrapper.jobs {}
46
46
 
47
- job_desc = job_desc.with_indifferent_access
47
+ job_desc = job_desc.symbolize_keys
48
48
  job_desc = job_desc.merge!(
49
49
  job: job_desc[:job].to_s,
50
50
  pool_wrapper_batch: wrapper.bid,
@@ -149,7 +149,7 @@ module CanvasSync
149
149
  if current_count < limit
150
150
  job_desc = pop_job_from_pool
151
151
  if job_desc.present?
152
- Batch.new(job_desc['pool_wrapper_batch']).jobs do
152
+ Batch.new(job_desc[:pool_wrapper_batch]).jobs do
153
153
  ChainBuilder.enqueue_job(job_desc)
154
154
  end
155
155
  jobs_added += 1
@@ -170,7 +170,7 @@ module CanvasSync
170
170
  def push_job_to_pool(job_desc)
171
171
  jobs_key = "#{redis_key}-jobs"
172
172
  # This allows duplicate jobs when a Redis Set is used
173
- job_desc['_pool_random_key_'] = SecureRandom.urlsafe_base64(10)
173
+ job_desc[:_pool_random_key_] = SecureRandom.urlsafe_base64(10)
174
174
  job_json = JSON.unparse(ActiveJob::Arguments.serialize([job_desc]))
175
175
  order = self.order
176
176
 
@@ -204,7 +204,7 @@ module CanvasSync
204
204
 
205
205
  return nil unless job_json.present?
206
206
 
207
- ActiveJob::Arguments.deserialize(JSON.parse(job_json))[0]
207
+ ActiveJob::Arguments.deserialize(JSON.parse(job_json))[0]&.symbolize_keys
208
208
  end
209
209
 
210
210
  def self.redis(&blk)
@@ -12,7 +12,7 @@ module CanvasSync::JobBatches::Sidekiq
12
12
  end
13
13
 
14
14
  def drain_zset(key)
15
- items, _ = Batch.redis do |r|
15
+ items, _ = CanvasSync::JobBatches::Batch.redis do |r|
16
16
  r.multi do |r|
17
17
  r.zrange(key, 0, -1)
18
18
  r.zremrangebyrank(key, 0, -1)
@@ -10,6 +10,16 @@
10
10
  <th colspan="2" scope=row><%= t('Batch') %></td>
11
11
  <td><%= @batch.bid %></td>
12
12
  </tr>
13
+ <tr>
14
+ <th colspan="2" scope=row><%= t('Parent') %></td>
15
+ <td>
16
+ <% if @batch.parent_bid.present? %>
17
+ <a href="<%= root_path %>batches/<%= @batch.parent_bid %>"><%= @batch.parent_bid %></a>
18
+ <% else %>
19
+ ROOT
20
+ <% end %>
21
+ </td>
22
+ </tr>
13
23
  <tr>
14
24
  <th colspan="2" scope=row><%= t('Started') %></td>
15
25
  <td><%= safe_relative_time(@batch.created_at.to_f) %></td>
@@ -10,6 +10,8 @@ require_relative "web/helpers"
10
10
  module CanvasSync::JobBatches::Sidekiq
11
11
  module Web
12
12
  DEV_MODE = (defined?(Rails) && !Rails.env.production?) || !!ENV["SIDEKIQ_WEB_TESTING"]
13
+ Sidekiq::WebHelpers::SAFE_QPARAMS << 'all_batches'
14
+ Sidekiq::WebHelpers::SAFE_QPARAMS << 'count'
13
15
 
14
16
  def self.registered(app) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
15
17
  app.helpers do
@@ -24,7 +26,9 @@ module CanvasSync::JobBatches::Sidekiq
24
26
 
25
27
  app.get "/batches" do
26
28
  @count = (params['count'] || 25).to_i
27
- @current_page, @total_size, @batches = page('batches', params['page'], @count)
29
+
30
+ source_key = params['all_batches'] ? "batches" : "BID-ROOT-bids"
31
+ @current_page, @total_size, @batches = page(source_key, params['page'], @count)
28
32
  @batches = @batches.map {|b, score| CanvasSync::JobBatches::Batch.new(b) }
29
33
 
30
34
  erb(get_template(:batches))
@@ -509,3 +509,58 @@ content_migrations:
509
509
  canvas_root_account_id:
510
510
  database_column_name: canvas_root_account_id
511
511
  type: integer
512
+
513
+ learning_outcomes:
514
+ conflict_target: learning_outcome_id
515
+ report_columns:
516
+ learning_outcome_id:
517
+ database_column_name: canvas_id
518
+ type: integer
519
+ context_id:
520
+ database_column_name: canvas_context_id
521
+ type: integer
522
+ context_type:
523
+ database_column_name: canvas_context_type
524
+ type: string
525
+ name:
526
+ database_column_name: name
527
+ type: string
528
+ friendly_name:
529
+ database_column_name: friendly_name
530
+ type: string
531
+ workflow_state:
532
+ database_column_name: workflow_state
533
+ type: string
534
+ created_at:
535
+ database_column_name: canvas_created_at
536
+ type: datetime
537
+ updated_at:
538
+ database_column_name: canvas_updated_at
539
+ type: datetime
540
+ migration_id:
541
+ database_column_name: migration_id
542
+ type: string
543
+ vendor_guid:
544
+ database_column_name: vendor_guid
545
+ type: string
546
+ low_grade:
547
+ database_column_name: low_grade
548
+ type: string
549
+ high_grade:
550
+ database_column_name: high_grade
551
+ type: string
552
+ calculation_method:
553
+ database_column_name: calculation_method
554
+ type: string
555
+ calculation_int:
556
+ database_column_name: calculation_int
557
+ type: integer
558
+ outcome_import_id:
559
+ database_column_name: outcome_import_id
560
+ type: integer
561
+ root_account_ids:
562
+ database_column_name: root_account_ids
563
+ type: integer
564
+ description:
565
+ database_column_name: description
566
+ type: string
@@ -116,6 +116,13 @@ module CanvasSync
116
116
  def bulk_process_group_membership(report_file_path)
117
117
  do_bulk_import(report_file_path, GroupMembership, options: @options)
118
118
  end
119
+
120
+ def bulk_process_learning_outcomes(report_file_path)
121
+ do_bulk_import(report_file_path, LearningOutcome, options: @options) do |row|
122
+ row[:root_account_ids] = JSON.parse row[:root_account_ids]
123
+ row
124
+ end
125
+ end
119
126
  end
120
127
  end
121
128
  end
@@ -12,14 +12,15 @@ module CanvasSync
12
12
  model.try(:get_sync_mapping, key) || mapping[key || CanvasSync::Concerns::SyncMapping::Mapping.normalize_model_name(model)]
13
13
  end
14
14
 
15
- def do_bulk_import(report_file_path, model, options: {}, mapping_key: nil)
15
+ def do_bulk_import(report_file_path, model, options: {}, mapping_key: nil, &blk)
16
16
  m = mapping_for(model, mapping_key)
17
17
  CanvasSync::Importers::BulkImporter.import(
18
18
  report_file_path,
19
19
  m[:report_columns],
20
20
  model,
21
21
  m[:conflict_target],
22
- import_args: options
22
+ import_args: options,
23
+ &blk
23
24
  )
24
25
  end
25
26
  end
@@ -1,3 +1,3 @@
1
1
  module CanvasSync
2
- VERSION = "0.17.31".freeze
2
+ VERSION = "0.17.34".freeze
3
3
  end
data/lib/canvas_sync.rb CHANGED
@@ -45,6 +45,7 @@ module CanvasSync
45
45
  grading_periods
46
46
  grading_period_groups
47
47
  content_migrations
48
+ learning_outcomes
48
49
  ].freeze
49
50
 
50
51
  SUPPORTED_TERM_SCOPE_MODELS = %w[
@@ -104,6 +104,12 @@ RSpec.describe CanvasSync::Processors::ProvisioningReportProcessor do
104
104
  expect(obj.workflow_state).to eq 'active'
105
105
  end
106
106
 
107
+ it 'processes learning_outcomes' do
108
+ expect {
109
+ subject.process('spec/support/fixtures/reports/learning_outcomes.csv', { models: ['learning_outcomes'] }, 1)
110
+ }.to change { LearningOutcome.count }.by(2)
111
+ end
112
+
107
113
  it 'model with composite key behaves as expected' do
108
114
  expect {
109
115
  subject.process('spec/support/fixtures/reports/user_observers.csv', { models: ['user_observers'] }, 1)
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
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.
8
+ #
9
+
10
+
11
+ class LearningOutcome < ApplicationRecord
12
+ include CanvasSync::Record
13
+ include CanvasSync::Concerns::ApiSyncable
14
+
15
+ belongs_to :context, polymorphic: true, optional: true, primary_key: :canvas_id, foreign_key: :canvas_context_id, foreign_type: :canvas_context_type
16
+
17
+ api_syncable({
18
+ canvas_id: :id,
19
+ canvas_context_id: :context_id,
20
+ canvas_context_type: :context_type,
21
+ name: :title,
22
+ friendly_name: :display_name,
23
+ vendor_guid: :vendor_guid,
24
+ calculation_method: :calculation_method,
25
+ calculation_int: :calculation_int,
26
+ description: :description
27
+ }, ->(api) { api.get("/api/v1/outcomes/#{canvas_id}") })
28
+ end
@@ -0,0 +1,36 @@
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 CreateLearningOutcomes < ActiveRecord::Migration[5.1]
10
+ def change
11
+ create_table :learning_outcomes do |t|
12
+ t.bigint :canvas_id, null: false
13
+ t.integer :canvas_context_id
14
+ t.string :canvas_context_type
15
+ t.string :name
16
+ t.string :friendly_name
17
+ t.string :workflow_state
18
+ t.datetime :canvas_created_at
19
+ t.datetime :canvas_updated_at
20
+ t.string :migration_id
21
+ t.string :vendor_guid
22
+ t.string :low_grade
23
+ t.string :high_grade
24
+ t.string :calculation_method
25
+ t.string :calculation_int
26
+ t.integer :outcome_import_id
27
+ t.integer :root_account_ids, array: true, default: []
28
+ t.text :description
29
+
30
+ t.timestamps
31
+ end
32
+
33
+ add_index :learning_outcomes, :canvas_id, unique: true
34
+ add_index :learning_outcomes, [:canvas_context_id, :canvas_context_type], name: "index_learning_outcomes_on_context"
35
+ end
36
+ end
@@ -10,7 +10,7 @@
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: 2022_03_08_072643) do
13
+ ActiveRecord::Schema.define(version: 2022_07_12_210559) do
14
14
 
15
15
  # These are extensions that must be enabled in order to support this database
16
16
  enable_extension "plpgsql"
@@ -246,6 +246,30 @@ ActiveRecord::Schema.define(version: 2022_03_08_072643) do
246
246
  t.index ["canvas_id"], name: "index_groups_on_canvas_id", unique: true
247
247
  end
248
248
 
249
+ create_table "learning_outcomes", force: :cascade do |t|
250
+ t.bigint "canvas_id", null: false
251
+ t.integer "canvas_context_id"
252
+ t.string "canvas_context_type"
253
+ t.string "name"
254
+ t.string "friendly_name"
255
+ t.string "workflow_state"
256
+ t.datetime "canvas_created_at"
257
+ t.datetime "canvas_updated_at"
258
+ t.string "migration_id"
259
+ t.string "vendor_guid"
260
+ t.string "low_grade"
261
+ t.string "high_grade"
262
+ t.string "calculation_method"
263
+ t.string "calculation_int"
264
+ t.integer "outcome_import_id"
265
+ t.integer "root_account_ids", default: [], array: true
266
+ t.text "description"
267
+ t.datetime "created_at", null: false
268
+ t.datetime "updated_at", null: false
269
+ t.index ["canvas_context_id", "canvas_context_type"], name: "index_learning_outcomes_on_context"
270
+ t.index ["canvas_id"], name: "index_learning_outcomes_on_canvas_id", unique: true
271
+ end
272
+
249
273
  create_table "pseudonyms", force: :cascade do |t|
250
274
  t.bigint "canvas_id", null: false
251
275
  t.bigint "canvas_user_id"