canvas_sync 0.16.4 → 0.16.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +5 -5
  2. data/db/migrate/20170915210836_create_canvas_sync_job_log.rb +12 -31
  3. data/db/migrate/20180725155729_add_job_id_to_canvas_sync_job_logs.rb +4 -13
  4. data/db/migrate/20190916154829_add_fork_count_to_canvas_sync_job_logs.rb +3 -11
  5. data/lib/canvas_sync.rb +7 -27
  6. data/lib/canvas_sync/importers/bulk_importer.rb +7 -4
  7. data/lib/canvas_sync/job.rb +8 -2
  8. data/lib/canvas_sync/job_chain.rb +46 -1
  9. data/lib/canvas_sync/jobs/fork_gather.rb +27 -12
  10. data/lib/canvas_sync/jobs/report_starter.rb +1 -1
  11. data/lib/canvas_sync/jobs/sync_provisioning_report_job.rb +4 -4
  12. data/lib/canvas_sync/jobs/sync_simple_table_job.rb +4 -4
  13. data/lib/canvas_sync/misc_helper.rb +15 -0
  14. data/lib/canvas_sync/processors/assignment_groups_processor.rb +3 -2
  15. data/lib/canvas_sync/processors/assignments_processor.rb +3 -2
  16. data/lib/canvas_sync/processors/context_module_items_processor.rb +3 -2
  17. data/lib/canvas_sync/processors/context_modules_processor.rb +3 -2
  18. data/lib/canvas_sync/processors/normal_processor.rb +2 -1
  19. data/lib/canvas_sync/processors/provisioning_report_processor.rb +10 -2
  20. data/lib/canvas_sync/processors/submissions_processor.rb +3 -2
  21. data/lib/canvas_sync/version.rb +1 -1
  22. data/spec/canvas_sync/jobs/fork_gather_spec.rb +9 -9
  23. data/spec/canvas_sync/jobs/sync_provisioning_report_job_spec.rb +2 -2
  24. data/spec/canvas_sync/jobs/sync_simple_table_job_spec.rb +1 -1
  25. data/spec/dummy/app/models/account.rb +3 -0
  26. data/spec/dummy/app/models/pseudonym.rb +14 -0
  27. data/spec/dummy/app/models/submission.rb +1 -0
  28. data/spec/dummy/app/models/user.rb +1 -0
  29. data/spec/dummy/db/migrate/20201016181346_create_pseudonyms.rb +24 -0
  30. data/spec/dummy/db/schema.rb +16 -4
  31. data/spec/dummy/db/test.sqlite3 +0 -0
  32. data/spec/dummy/log/development.log +1248 -0
  33. data/spec/dummy/log/test.log +43258 -0
  34. data/spec/support/fixtures/reports/provisioning_csv_unzipped/courses.csv +3 -0
  35. data/spec/support/fixtures/reports/provisioning_csv_unzipped/users.csv +4 -0
  36. metadata +22 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: ddf6a257acf95c92ba99a6d03c9bcdd99ba1a8ff
4
- data.tar.gz: f10e8f91a225462545354a24daaf006a170bd90d
2
+ SHA256:
3
+ metadata.gz: 9be3b81c22d5b45a02d88fa29b5d57afa152ee7dec6ce0ecf3d81c041507c619
4
+ data.tar.gz: 0c42577064cbf018f8fd8d5f665e1adfbc542e38e704a675e735491543b3ab40
5
5
  SHA512:
6
- metadata.gz: cde3a3a534316a0faa535347a6c71aa63ab46c5cc5d9c21b01512bb623bed7f6b34fc7da38de345fefcf238cc44f0423f849dd08aa8f57f292472838aa6f7d0c
7
- data.tar.gz: 2e1b8217af038166f60afe4032f0f1ec806761fb81d5727aec57540919146335f7a28292e8e81ba87220eb2d687325b8145be798ed6a29b07aaad0cd6e3b56f4
6
+ metadata.gz: 9ea295b8cc43aa6b27bf6067cf684d024430a9fd85608bdc0a243113920b1b65b72c257e6a99644afb5d20455ecf2265949ce941a9ae1d9bea160ab1bee803ba
7
+ data.tar.gz: e63b7f557e792cb850a5799753f9effe436ad391bc6e4442768683b8c4d7c8eea1d1e9bbd6419127f2f4401f6cce6129019fb73192496f7efc1d731d7ef48847
@@ -1,35 +1,16 @@
1
- if Rails.version.to_f >= 5.0
2
- class CreateCanvasSyncJobLog < ActiveRecord::Migration[Rails.version.to_f]
3
- def change
4
- create_table :canvas_sync_job_logs do |t|
5
- t.datetime :started_at
6
- t.datetime :completed_at
7
- t.string :exception
8
- t.text :backtrace
9
- t.string :job_class
10
- t.string :status
11
- t.text :metadata
12
- t.text :job_arguments
1
+ class CreateCanvasSyncJobLog < CanvasSync::MiscHelper::MigrationClass
2
+ def change
3
+ create_table :canvas_sync_job_logs do |t|
4
+ t.datetime :started_at
5
+ t.datetime :completed_at
6
+ t.string :exception
7
+ t.text :backtrace
8
+ t.string :job_class
9
+ t.string :status
10
+ t.text :metadata
11
+ t.text :job_arguments
13
12
 
14
- t.timestamps
15
- end
16
- end
17
- end
18
- else
19
- class CreateCanvasSyncJobLog < ActiveRecord::Migration
20
- def change
21
- create_table :canvas_sync_job_logs do |t|
22
- t.datetime :started_at
23
- t.datetime :completed_at
24
- t.string :exception
25
- t.text :backtrace
26
- t.string :job_class
27
- t.string :status
28
- t.text :metadata
29
- t.text :job_arguments
30
-
31
- t.timestamps
32
- end
13
+ t.timestamps
33
14
  end
34
15
  end
35
16
  end
@@ -1,15 +1,6 @@
1
- if Rails.version.to_f >= 5.0
2
- class AddJobIdToCanvasSyncJobLogs < ActiveRecord::Migration[Rails.version.to_f]
3
- def change
4
- add_column :canvas_sync_job_logs, :job_id, :string
5
- add_index :canvas_sync_job_logs, :job_id
6
- end
7
- end
8
- else
9
- class AddJobIdToCanvasSyncJobLogs < ActiveRecord::Migration
10
- def change
11
- add_column :canvas_sync_job_logs, :job_id, :string
12
- add_index :canvas_sync_job_logs, :job_id
13
- end
1
+ class AddJobIdToCanvasSyncJobLogs < CanvasSync::MiscHelper::MigrationClass
2
+ def change
3
+ add_column :canvas_sync_job_logs, :job_id, :string
4
+ add_index :canvas_sync_job_logs, :job_id
14
5
  end
15
6
  end
@@ -1,13 +1,5 @@
1
- if Rails.version.to_f >= 5.0
2
- class AddForkCountToCanvasSyncJobLogs < ActiveRecord::Migration[Rails.version.to_f]
3
- def change
4
- add_column :canvas_sync_job_logs, :fork_count, :integer
5
- end
6
- end
7
- else
8
- class AddForkCountToCanvasSyncJobLogs < ActiveRecord::Migration
9
- def change
10
- add_column :canvas_sync_job_logs, :fork_count, :integer
11
- end
1
+ class AddForkCountToCanvasSyncJobLogs < CanvasSync::MiscHelper::MigrationClass
2
+ def change
3
+ add_column :canvas_sync_job_logs, :fork_count, :integer
12
4
  end
13
5
  end
@@ -2,6 +2,7 @@ require "bearcat"
2
2
 
3
3
  require "canvas_sync/version"
4
4
  require "canvas_sync/engine"
5
+ require "canvas_sync/misc_helper"
5
6
  require "canvas_sync/class_callback_executor"
6
7
  require "canvas_sync/job"
7
8
  require "canvas_sync/job_chain"
@@ -99,6 +100,7 @@ module CanvasSync
99
100
  invoke_next(job_chain)
100
101
  end
101
102
 
103
+ # @deprecated
102
104
  def duplicate_chain(job_chain)
103
105
  Marshal.load(Marshal.dump(job_chain))
104
106
  end
@@ -109,35 +111,13 @@ module CanvasSync
109
111
  #
110
112
  # @param job_chain [Hash] A chain of jobs to execute
111
113
  def invoke_next(job_chain, extra_options: {})
112
- job_chain = job_chain.chain_data if job_chain.is_a?(JobChain)
113
-
114
- return if job_chain[:jobs].empty?
115
-
116
- # Make sure all job classes are serialized as strings
117
- job_chain[:jobs].each { |job| job[:job] = job[:job].to_s }
118
-
119
- duped_job_chain = Marshal.load(Marshal.dump(job_chain))
120
- jobs = duped_job_chain[:jobs]
121
- next_job = jobs.shift
122
- next_job_class = next_job[:job].constantize
123
- next_options = next_job[:options] || {}
124
- next_options.merge!(extra_options)
125
- next_job_class.perform_later(duped_job_chain, next_options)
114
+ job_chain = JobChain.new(job_chain) unless job_chain.is_a?(JobChain)
115
+ job_chain.perform_next(extra_options)
126
116
  end
127
117
 
128
- def fork(job_log, job_chain, keys: [])
129
- job_chain = job_chain.chain_data if job_chain.is_a?(JobChain)
130
-
131
- duped_job_chain = Marshal.load(Marshal.dump(job_chain))
132
- duped_job_chain[:global_options][:fork_path] ||= []
133
- duped_job_chain[:global_options][:fork_keys] ||= []
134
- duped_job_chain[:global_options][:fork_path] << job_log.job_id
135
- duped_job_chain[:global_options][:fork_keys] << keys.map(&:to_s)
136
- duped_job_chain[:global_options][:on_failure] ||= 'CanvasSync::Jobs::ForkGather.handle_branch_error'
137
- sub_items = yield duped_job_chain
138
- sub_count = sub_items.respond_to?(:count) ? sub_items.count : sub_items
139
- job_log.fork_count = sub_count
140
- sub_items
118
+ def fork(job_log, job_chain, keys: [], &blk)
119
+ job_chain = JobChain.new(job_chain) unless job_chain.is_a?(JobChain)
120
+ job_chain.fork(job_log, keys: keys, &blk)
141
121
  end
142
122
 
143
123
  # Given a Model or Relation, scope it down to items that should be synced
@@ -64,13 +64,12 @@ module CanvasSync
64
64
  columns = columns.dup
65
65
 
66
66
  update_conditions = {
67
- condition: condition_sql(klass, columns),
67
+ condition: condition_sql(klass, columns, import_args[:sync_start_time]),
68
68
  columns: columns
69
69
  }
70
70
  update_conditions[:conflict_target] = conflict_target if conflict_target
71
71
 
72
72
  options = { validate: false, on_duplicate_key_update: update_conditions }.merge(import_args)
73
-
74
73
  options.delete(:on_duplicate_key_update) if options.key?(:on_duplicate_key_ignore)
75
74
  klass.import(columns, rows, options)
76
75
  end
@@ -85,10 +84,14 @@ module CanvasSync
85
84
  # started_at = Time.now
86
85
  # run_the_users_sync!
87
86
  # changed = User.where("updated_at >= ?", started_at)
88
- def self.condition_sql(klass, columns)
87
+ def self.condition_sql(klass, columns, report_start)
89
88
  columns_str = columns.map { |c| "#{klass.quoted_table_name}.#{c}" }.join(", ")
90
89
  excluded_str = columns.map { |c| "EXCLUDED.#{c}" }.join(", ")
91
- "(#{columns_str}) IS DISTINCT FROM (#{excluded_str})"
90
+ condition_sql = "(#{columns_str}) IS DISTINCT FROM (#{excluded_str})"
91
+ if klass.column_names.include?("updated_at") && report_start
92
+ condition_sql += " AND #{klass.quoted_table_name}.updated_at < '#{report_start}'"
93
+ end
94
+ condition_sql
92
95
  end
93
96
 
94
97
  def self.batch_size
@@ -3,6 +3,8 @@ require "active_job"
3
3
  module CanvasSync
4
4
  # Inherit from this class to build a Job that will log to the canvas_sync_job_logs table
5
5
  class Job < ActiveJob::Base
6
+ attr_reader :job_chain, :job_log
7
+
6
8
  before_enqueue do |job|
7
9
  create_job_log(job)
8
10
  end
@@ -13,7 +15,11 @@ module CanvasSync
13
15
  @job_log.started_at = Time.now
14
16
  @job_log.save
15
17
 
16
- @job_chain = job.arguments[0] if job.arguments[0].is_a?(Hash) && job.arguments[0].include?(:jobs)
18
+ if job.arguments[0].is_a?(Hash) && job.arguments[0].include?(:jobs)
19
+ # @job_chain = JobChain.new(job.arguments[0])
20
+ @job_chain = job.arguments[0]
21
+ job.arguments[0] = @job_chain
22
+ end
17
23
 
18
24
  begin
19
25
  block.call
@@ -22,7 +28,7 @@ module CanvasSync
22
28
  @job_log.exception = "#{e.class}: #{e.message}"
23
29
  @job_log.backtrace = e.backtrace.join('\n')
24
30
  @job_log.status = JobLog::ERROR_STATUS
25
- if @job_chain&.[](:global_options)&.[](:on_failure)&.present?
31
+ if @job_chain&.dig(:global_options, :on_failure)&.present?
26
32
  begin
27
33
  class_name, method = @job_chain[:global_options][:on_failure].split('.')
28
34
  klass = class_name.constantize
@@ -45,7 +45,52 @@ module CanvasSync
45
45
  end
46
46
 
47
47
  def process!(extra_options: {})
48
- CanvasSync::invoke_next(self, extra_options: extra_options)
48
+ perform_next(extra_options)
49
+ end
50
+
51
+ def duplicate
52
+ self.class.new(Marshal.load(Marshal.dump(chain_data)))
53
+ end
54
+
55
+ def normalize!
56
+ @chain_data[:global_options] ||= {}
57
+ end
58
+
59
+ def serialize
60
+ normalize!
61
+ chain_data
62
+ end
63
+
64
+ def perform_next(extra_options = {})
65
+ return if jobs.empty?
66
+
67
+ # Make sure all job classes are serialized as strings
68
+ jobs.each { |job| job[:job] = job[:job].to_s }
69
+
70
+ duped_job_chain = duplicate
71
+
72
+ jobs = duped_job_chain[:jobs]
73
+ next_job = jobs.shift
74
+ next_job_class = next_job[:job].constantize
75
+ next_options = next_job[:options] || {}
76
+ next_options.merge!(extra_options)
77
+ next_job_class.perform_later(duped_job_chain.serialize, next_options)
78
+ end
79
+
80
+ def fork(job_log, keys: [])
81
+ duped_job_chain = duplicate
82
+ duped_job_chain[:fork_state] ||= {}
83
+ duped_job_chain[:fork_state][:forking_path] ||= []
84
+ duped_job_chain[:fork_state][:pre_fork_globals] ||= []
85
+
86
+ duped_job_chain[:fork_state][:forking_path] << job_log.job_id
87
+ duped_job_chain[:fork_state][:pre_fork_globals] << global_options
88
+ # duped_job_chain[:global_options][:on_failure] ||= ['CanvasSync::Jobs::ForkGather.handle_branch_error']
89
+
90
+ sub_items = yield duped_job_chain
91
+ sub_count = sub_items.respond_to?(:count) ? sub_items.count : sub_items
92
+ job_log.update!(fork_count: sub_count)
93
+ sub_items
49
94
  end
50
95
 
51
96
  private
@@ -4,25 +4,40 @@ module CanvasSync
4
4
  def perform(job_chain, options)
5
5
  forked_job = self.class.forked_at_job(job_chain)
6
6
 
7
- if forked_job.present?
8
- forked_job.with_lock do
9
- forked_job.fork_count -= 1
10
- forked_job.save!
11
- end
7
+ while true
8
+ if forked_job.present?
9
+ forked_job.with_lock do
10
+ forked_job.fork_count -= 1
11
+ forked_job.save!
12
+ end
13
+
14
+ if forked_job.fork_count <= 0
15
+ pfgs = job_chain[:fork_state][:pre_fork_globals].pop
16
+ job_chain[:global_options] = pfgs
12
17
 
13
- if forked_job.fork_count <= 0
14
- (job_chain[:global_options][:fork_keys] || []).pop&.each do |k|
15
- job_chain[:global_options].delete(k.to_sym)
18
+ if options[:gather_all]
19
+ # If we want to gather all, repeat for the next level fork
20
+ forked_job = self.class.forked_at_job(job_chain)
21
+ else
22
+ forked_job = nil
23
+ end
24
+ else
25
+ # If a fork was found and it isn't complete, break the loop before continuing the chain
26
+ break
16
27
  end
17
- CanvasSync.invoke_next(job_chain)
28
+
29
+ # Repeat this logic for [if gather_all] the next fork up, or [if not gather_all] nil
30
+ next
18
31
  end
19
- else
32
+
33
+ # If there is no current fork (either not in a fork, or all forks were closed), continue the chain
20
34
  CanvasSync.invoke_next(job_chain)
35
+ break
21
36
  end
22
37
  end
23
38
 
24
39
  def self.handle_branch_error(e, job_chain:, skip_invoke: false, **kwargs)
25
- return nil unless job_chain&.[](:global_options)&.[](:fork_path).present?
40
+ return nil unless job_chain&.dig(:fork_state, :forking_path).present?
26
41
 
27
42
  duped_chain = CanvasSync.duplicate_chain(job_chain)
28
43
  job_list = duped_chain[:jobs]
@@ -46,7 +61,7 @@ module CanvasSync
46
61
  protected
47
62
 
48
63
  def self.forked_at_job(job_chain)
49
- fork_item = (job_chain[:global_options][:fork_path] || []).pop
64
+ fork_item = (job_chain.dig(:fork_state, :forking_path) || []).pop
50
65
 
51
66
  if fork_item.present?
52
67
  CanvasSync::JobLog.find_by(job_id: fork_item)
@@ -12,7 +12,7 @@ module CanvasSync
12
12
  # @return [nil]
13
13
  def perform(job_chain, report_name, report_params, processor, options, allow_redownloads: false)
14
14
  account_id = options[:account_id] || job_chain[:global_options][:account_id] || "self"
15
-
15
+ options[:sync_start_time] = DateTime.now.utc.iso8601
16
16
  report_id = if allow_redownloads
17
17
  get_cached_report(job_chain, account_id, report_name, report_params)
18
18
  else
@@ -8,15 +8,15 @@ module CanvasSync
8
8
  # models to sync.
9
9
  def perform(job_chain, options)
10
10
  if options[:term_scope]
11
- sub_reports = CanvasSync.fork(@job_log, job_chain, keys: [:canvas_term_id]) do |job_chain|
11
+ sub_reports = CanvasSync.fork(@job_log, job_chain, keys: [:canvas_term_id]) do |fork_template|
12
12
  Term.send(options[:term_scope]).find_each.map do |term|
13
+ fork = fork_template.duplicate
13
14
  # Deep copy the job_chain so each report gets the correct term id passed into
14
15
  # its options with no side effects
15
16
  term_id = get_term_id(term)
16
- duped_job_chain = Marshal.load(Marshal.dump(job_chain))
17
- duped_job_chain[:global_options][:canvas_term_id] = term_id
17
+ fork[:global_options][:canvas_term_id] = term_id
18
18
  {
19
- job_chain: duped_job_chain,
19
+ job_chain: fork.serialize,
20
20
  params: report_params(options, term_id),
21
21
  options: options,
22
22
  }
@@ -9,15 +9,15 @@ module CanvasSync
9
9
  # @param options [Hash]
10
10
  def perform(job_chain, options)
11
11
  if options[:term_scope]
12
- sub_reports = CanvasSync.fork(@job_log, job_chain, keys: [:canvas_term_id]) do |job_chain|
12
+ sub_reports = CanvasSync.fork(@job_log, job_chain, keys: [:canvas_term_id]) do |fork_template|
13
13
  Term.send(options[:term_scope]).find_each.map do |term|
14
+ fork = fork_template.duplicate
14
15
  # Deep copy the job_chain so each report gets the correct term id passed into
15
16
  # its options with no side effects
16
17
  term_id = get_term_id(term)
17
- duped_job_chain = Marshal.load(Marshal.dump(job_chain))
18
- duped_job_chain[:global_options][:canvas_term_id] = term_id
18
+ fork[:global_options][:canvas_term_id] = term_id
19
19
  {
20
- job_chain: duped_job_chain,
20
+ job_chain: fork.serialize,
21
21
  params: report_params(options, term_id),
22
22
  options: options,
23
23
  }
@@ -0,0 +1,15 @@
1
+ require 'active_record'
2
+
3
+ module CanvasSync
4
+ module MiscHelper
5
+ MigrationClass = Rails.version < '5.0' ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
6
+
7
+ def self.to_boolean(v)
8
+ if Rails.version < '5.0'
9
+ ActiveRecord::Type::Boolean.new.type_cast_from_user(v)
10
+ else
11
+ ActiveRecord::Type::Boolean.new.deserialize(v)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -8,15 +8,16 @@ module CanvasSync
8
8
  # @param options [Hash]
9
9
  class AssignmentGroupsProcessor < ReportProcessor
10
10
  def self.process(report_file_path, _options, report_id)
11
- new(report_file_path)
11
+ new(report_file_path, _options)
12
12
  end
13
13
 
14
- def initialize(report_file_path)
14
+ def initialize(report_file_path, options)
15
15
  CanvasSync::Importers::BulkImporter.import(
16
16
  report_file_path,
17
17
  mapping[:assignment_groups][:report_columns],
18
18
  AssignmentGroup,
19
19
  mapping[:assignment_groups][:conflict_target].to_sym,
20
+ import_args: options
20
21
  )
21
22
  end
22
23
  end
@@ -8,15 +8,16 @@ module CanvasSync
8
8
  # @param options [Hash]
9
9
  class AssignmentsProcessor < ReportProcessor
10
10
  def self.process(report_file_path, _options, report_id)
11
- new(report_file_path)
11
+ new(report_file_path, _options)
12
12
  end
13
13
 
14
- def initialize(report_file_path)
14
+ def initialize(report_file_path, options)
15
15
  CanvasSync::Importers::BulkImporter.import(
16
16
  report_file_path,
17
17
  mapping[:assignments][:report_columns],
18
18
  Assignment,
19
19
  mapping[:assignments][:conflict_target].to_sym,
20
+ import_args: options
20
21
  )
21
22
  end
22
23
  end
@@ -8,15 +8,16 @@ module CanvasSync
8
8
  # @param options [Hash]
9
9
  class ContextModuleItemsProcessor < ReportProcessor
10
10
  def self.process(report_file_path, _options, report_id)
11
- new(report_file_path)
11
+ new(report_file_path, _options)
12
12
  end
13
13
 
14
- def initialize(report_file_path)
14
+ def initialize(report_file_path, options)
15
15
  CanvasSync::Importers::BulkImporter.import(
16
16
  report_file_path,
17
17
  mapping[:context_module_items][:report_columns],
18
18
  ContextModuleItem,
19
19
  mapping[:context_module_items][:conflict_target].to_sym,
20
+ import_args: options
20
21
  )
21
22
  end
22
23
  end