canvas_sync 0.17.31 → 0.17.34

Sign up to get free protection for your applications and to get access to all the features.
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"