canvas_sync 0.16.5 → 0.17.0.beta5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +49 -137
  3. data/app/models/canvas_sync/sync_batch.rb +5 -0
  4. data/db/migrate/20201018210836_create_canvas_sync_sync_batches.rb +11 -0
  5. data/lib/canvas_sync.rb +35 -97
  6. data/lib/canvas_sync/importers/bulk_importer.rb +4 -7
  7. data/lib/canvas_sync/job.rb +4 -10
  8. data/lib/canvas_sync/job_batches/batch.rb +403 -0
  9. data/lib/canvas_sync/job_batches/batch_aware_job.rb +62 -0
  10. data/lib/canvas_sync/job_batches/callback.rb +152 -0
  11. data/lib/canvas_sync/job_batches/chain_builder.rb +220 -0
  12. data/lib/canvas_sync/job_batches/context_hash.rb +147 -0
  13. data/lib/canvas_sync/job_batches/jobs/base_job.rb +7 -0
  14. data/lib/canvas_sync/job_batches/jobs/concurrent_batch_job.rb +19 -0
  15. data/lib/canvas_sync/job_batches/jobs/serial_batch_job.rb +75 -0
  16. data/lib/canvas_sync/job_batches/sidekiq.rb +93 -0
  17. data/lib/canvas_sync/job_batches/status.rb +83 -0
  18. data/lib/canvas_sync/jobs/begin_sync_chain_job.rb +35 -0
  19. data/lib/canvas_sync/jobs/report_checker.rb +3 -6
  20. data/lib/canvas_sync/jobs/report_processor_job.rb +2 -5
  21. data/lib/canvas_sync/jobs/report_starter.rb +28 -20
  22. data/lib/canvas_sync/jobs/sync_accounts_job.rb +3 -5
  23. data/lib/canvas_sync/jobs/sync_admins_job.rb +2 -4
  24. data/lib/canvas_sync/jobs/sync_assignment_groups_job.rb +2 -4
  25. data/lib/canvas_sync/jobs/sync_assignments_job.rb +2 -4
  26. data/lib/canvas_sync/jobs/sync_context_module_items_job.rb +2 -4
  27. data/lib/canvas_sync/jobs/sync_context_modules_job.rb +2 -4
  28. data/lib/canvas_sync/jobs/sync_provisioning_report_job.rb +4 -34
  29. data/lib/canvas_sync/jobs/sync_roles_job.rb +2 -5
  30. data/lib/canvas_sync/jobs/sync_simple_table_job.rb +11 -32
  31. data/lib/canvas_sync/jobs/sync_submissions_job.rb +2 -4
  32. data/lib/canvas_sync/jobs/sync_terms_job.rb +25 -8
  33. data/lib/canvas_sync/processors/assignment_groups_processor.rb +2 -3
  34. data/lib/canvas_sync/processors/assignments_processor.rb +2 -3
  35. data/lib/canvas_sync/processors/context_module_items_processor.rb +2 -3
  36. data/lib/canvas_sync/processors/context_modules_processor.rb +2 -3
  37. data/lib/canvas_sync/processors/normal_processor.rb +1 -2
  38. data/lib/canvas_sync/processors/provisioning_report_processor.rb +2 -10
  39. data/lib/canvas_sync/processors/submissions_processor.rb +2 -3
  40. data/lib/canvas_sync/version.rb +1 -1
  41. data/spec/canvas_sync/canvas_sync_spec.rb +136 -153
  42. data/spec/canvas_sync/jobs/job_spec.rb +9 -17
  43. data/spec/canvas_sync/jobs/report_checker_spec.rb +1 -3
  44. data/spec/canvas_sync/jobs/report_processor_job_spec.rb +0 -3
  45. data/spec/canvas_sync/jobs/report_starter_spec.rb +19 -28
  46. data/spec/canvas_sync/jobs/sync_admins_job_spec.rb +1 -4
  47. data/spec/canvas_sync/jobs/sync_assignment_groups_job_spec.rb +2 -1
  48. data/spec/canvas_sync/jobs/sync_assignments_job_spec.rb +3 -2
  49. data/spec/canvas_sync/jobs/sync_context_module_items_job_spec.rb +3 -2
  50. data/spec/canvas_sync/jobs/sync_context_modules_job_spec.rb +3 -2
  51. data/spec/canvas_sync/jobs/sync_provisioning_report_job_spec.rb +3 -35
  52. data/spec/canvas_sync/jobs/sync_roles_job_spec.rb +1 -4
  53. data/spec/canvas_sync/jobs/sync_simple_table_job_spec.rb +5 -12
  54. data/spec/canvas_sync/jobs/sync_submissions_job_spec.rb +2 -1
  55. data/spec/canvas_sync/jobs/sync_terms_job_spec.rb +1 -4
  56. data/spec/dummy/config/environments/test.rb +2 -0
  57. data/spec/dummy/db/schema.rb +9 -1
  58. data/spec/job_batching/batch_aware_job_spec.rb +100 -0
  59. data/spec/job_batching/batch_spec.rb +372 -0
  60. data/spec/job_batching/callback_spec.rb +38 -0
  61. data/spec/job_batching/flow_spec.rb +88 -0
  62. data/spec/job_batching/integration/integration.rb +57 -0
  63. data/spec/job_batching/integration/nested.rb +88 -0
  64. data/spec/job_batching/integration/simple.rb +47 -0
  65. data/spec/job_batching/integration/workflow.rb +134 -0
  66. data/spec/job_batching/integration_helper.rb +48 -0
  67. data/spec/job_batching/sidekiq_spec.rb +124 -0
  68. data/spec/job_batching/status_spec.rb +92 -0
  69. data/spec/job_batching/support/base_job.rb +14 -0
  70. data/spec/job_batching/support/sample_callback.rb +2 -0
  71. data/spec/spec_helper.rb +17 -0
  72. metadata +85 -8
  73. data/lib/canvas_sync/job_chain.rb +0 -102
  74. data/lib/canvas_sync/jobs/fork_gather.rb +0 -74
  75. data/spec/canvas_sync/jobs/fork_gather_spec.rb +0 -73
@@ -0,0 +1,92 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe CanvasSync::JobBatches::Batch::Status do
4
+ let(:bid) { 'BID' }
5
+ let(:batch) { CanvasSync::JobBatches::Batch.new(bid) }
6
+ subject { described_class.new(bid) }
7
+
8
+ describe '#join' do
9
+ it 'raises info' do
10
+ expect { subject.join }.to raise_error('Not supported')
11
+ end
12
+ end
13
+
14
+ describe '#pending' do
15
+ context 'when not initalized' do
16
+ it 'returns 0 pending jobs' do
17
+ expect(subject.pending).to eq(0)
18
+ end
19
+ end
20
+
21
+ context 'when more than 0' do
22
+ before { batch.jobs do TestWorker.perform_async end }
23
+ it 'returns pending jobs' do
24
+ expect(subject.pending).to eq(1)
25
+ end
26
+ end
27
+ end
28
+
29
+ describe '#failures' do
30
+ context 'when not initalized' do
31
+ it 'returns 0 failed jobs' do
32
+ expect(subject.failures).to eq(0)
33
+ end
34
+ end
35
+
36
+ context 'when more than 0' do
37
+ before { batch.increment_job_queue(bid) }
38
+ before { CanvasSync::JobBatches::Batch.process_failed_job(bid, 'FAILEDID') }
39
+
40
+ it 'returns failed jobs' do
41
+ expect(subject.failures).to eq(1)
42
+ end
43
+ end
44
+ end
45
+
46
+ describe '#failure_info' do
47
+ context 'when not initalized' do
48
+ it 'returns empty array' do
49
+ expect(subject.failure_info).to eq([])
50
+ end
51
+ end
52
+
53
+ context 'when with error' do
54
+ before { CanvasSync::JobBatches::Batch.process_failed_job(bid, 'jid123') }
55
+
56
+ it 'returns array with failed jids' do
57
+ expect(subject.failure_info).to eq(['jid123'])
58
+ end
59
+ end
60
+ end
61
+
62
+ describe '#total' do
63
+ context 'when not initalized' do
64
+ it 'returns 0 failed jobs' do
65
+ expect(subject.total).to eq(0)
66
+ end
67
+ end
68
+
69
+ context 'when more than 0' do
70
+ before { batch.jobs do TestWorker.perform_async end }
71
+
72
+ it 'returns failed jobs' do
73
+ expect(subject.total).to eq(1)
74
+ end
75
+ end
76
+ end
77
+
78
+ describe '#data' do
79
+ it 'returns batch description' do
80
+ expect(subject.data).to include(total: 0, failures: 0, pending: 0, created_at: nil, complete: false, failure_info: [], parent_bid: nil)
81
+ end
82
+ end
83
+
84
+ describe '#created_at' do
85
+ it 'returns time' do
86
+ batch = CanvasSync::JobBatches::Batch.new
87
+ batch.jobs do TestWorker.perform_async end
88
+ status = described_class.new(batch.bid)
89
+ expect(status.created_at).not_to be_nil
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,14 @@
1
+ class BatchTestJobBase < ActiveJob::Base
2
+ def perform
3
+ end
4
+
5
+ def self.perform_async(*args)
6
+ perform_later(*args)
7
+ end
8
+ end
9
+
10
+ class FailingBatchTestJobBase < BatchTestJobBase
11
+ def perform
12
+ raise "Foo"
13
+ end
14
+ end
@@ -0,0 +1,2 @@
1
+ class SampleCallback; end
2
+ class SampleCallback2; end
@@ -13,6 +13,19 @@ require 'shoulda/matchers'
13
13
  require 'pry'
14
14
  require 'pry-nav'
15
15
 
16
+ require 'sidekiq/testing'
17
+ Sidekiq::Testing.fake!
18
+
19
+ require 'fakeredis/rspec'
20
+ Dir[File.dirname(__FILE__) + "/job_batching/support/**/*.rb"].each {|f| require f }
21
+
22
+ # Fix an issue with fakeredis with Redis >=4.2
23
+ class Redis::Connection::Memory
24
+ def exists(*keys)
25
+ keys.count { |key| data.key?(key) }
26
+ end
27
+ end
28
+
16
29
  ActiveRecord::Migration.maintain_test_schema!
17
30
 
18
31
  RSpec.configure do |config|
@@ -51,3 +64,7 @@ end
51
64
  def canvas_sync_client
52
65
  Bearcat::Client.new(token: 'cool-token', prefix: 'http://test.instructure.com')
53
66
  end
67
+
68
+ def set_batch_context(ctx)
69
+ allow_any_instance_of(ActiveJob::Base).to receive(:batch_context).and_return(ctx.with_indifferent_access)
70
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: canvas_sync
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.5
4
+ version: 0.17.0.beta5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nate Collings
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-27 00:00:00.000000000 Z
11
+ date: 2020-10-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -234,6 +234,34 @@ dependencies:
234
234
  - - ">="
235
235
  - !ruby/object:Gem::Version
236
236
  version: '0'
237
+ - !ruby/object:Gem::Dependency
238
+ name: fakeredis
239
+ requirement: !ruby/object:Gem::Requirement
240
+ requirements:
241
+ - - ">="
242
+ - !ruby/object:Gem::Version
243
+ version: '0'
244
+ type: :development
245
+ prerelease: false
246
+ version_requirements: !ruby/object:Gem::Requirement
247
+ requirements:
248
+ - - ">="
249
+ - !ruby/object:Gem::Version
250
+ version: '0'
251
+ - !ruby/object:Gem::Dependency
252
+ name: sidekiq
253
+ requirement: !ruby/object:Gem::Requirement
254
+ requirements:
255
+ - - ">="
256
+ - !ruby/object:Gem::Version
257
+ version: '0'
258
+ type: :development
259
+ prerelease: false
260
+ version_requirements: !ruby/object:Gem::Requirement
261
+ requirements:
262
+ - - ">="
263
+ - !ruby/object:Gem::Version
264
+ version: '0'
237
265
  - !ruby/object:Gem::Dependency
238
266
  name: activejob
239
267
  requirement: !ruby/object:Gem::Requirement
@@ -248,6 +276,20 @@ dependencies:
248
276
  - - ">="
249
277
  - !ruby/object:Gem::Version
250
278
  version: '0'
279
+ - !ruby/object:Gem::Dependency
280
+ name: redis
281
+ requirement: !ruby/object:Gem::Requirement
282
+ requirements:
283
+ - - ">="
284
+ - !ruby/object:Gem::Version
285
+ version: '4.2'
286
+ type: :runtime
287
+ prerelease: false
288
+ version_requirements: !ruby/object:Gem::Requirement
289
+ requirements:
290
+ - - ">="
291
+ - !ruby/object:Gem::Version
292
+ version: '4.2'
251
293
  - !ruby/object:Gem::Dependency
252
294
  name: rails
253
295
  requirement: !ruby/object:Gem::Requirement
@@ -316,10 +358,12 @@ files:
316
358
  - app/controllers/api/v1/health_check_controller.rb
317
359
  - app/controllers/api/v1/live_events_controller.rb
318
360
  - app/models/canvas_sync/job_log.rb
361
+ - app/models/canvas_sync/sync_batch.rb
319
362
  - config/initializers/apartment.rb
320
363
  - db/migrate/20170915210836_create_canvas_sync_job_log.rb
321
364
  - db/migrate/20180725155729_add_job_id_to_canvas_sync_job_logs.rb
322
365
  - db/migrate/20190916154829_add_fork_count_to_canvas_sync_job_logs.rb
366
+ - db/migrate/20201018210836_create_canvas_sync_sync_batches.rb
323
367
  - lib/canvas_sync.rb
324
368
  - lib/canvas_sync/api_syncable.rb
325
369
  - lib/canvas_sync/class_callback_executor.rb
@@ -377,8 +421,17 @@ files:
377
421
  - lib/canvas_sync/importers/bulk_importer.rb
378
422
  - lib/canvas_sync/importers/legacy_importer.rb
379
423
  - lib/canvas_sync/job.rb
380
- - lib/canvas_sync/job_chain.rb
381
- - lib/canvas_sync/jobs/fork_gather.rb
424
+ - lib/canvas_sync/job_batches/batch.rb
425
+ - lib/canvas_sync/job_batches/batch_aware_job.rb
426
+ - lib/canvas_sync/job_batches/callback.rb
427
+ - lib/canvas_sync/job_batches/chain_builder.rb
428
+ - lib/canvas_sync/job_batches/context_hash.rb
429
+ - lib/canvas_sync/job_batches/jobs/base_job.rb
430
+ - lib/canvas_sync/job_batches/jobs/concurrent_batch_job.rb
431
+ - lib/canvas_sync/job_batches/jobs/serial_batch_job.rb
432
+ - lib/canvas_sync/job_batches/sidekiq.rb
433
+ - lib/canvas_sync/job_batches/status.rb
434
+ - lib/canvas_sync/jobs/begin_sync_chain_job.rb
382
435
  - lib/canvas_sync/jobs/report_checker.rb
383
436
  - lib/canvas_sync/jobs/report_processor_job.rb
384
437
  - lib/canvas_sync/jobs/report_starter.rb
@@ -407,7 +460,6 @@ files:
407
460
  - lib/canvas_sync/sidekiq_job.rb
408
461
  - lib/canvas_sync/version.rb
409
462
  - spec/canvas_sync/canvas_sync_spec.rb
410
- - spec/canvas_sync/jobs/fork_gather_spec.rb
411
463
  - spec/canvas_sync/jobs/job_spec.rb
412
464
  - spec/canvas_sync/jobs/report_checker_spec.rb
413
465
  - spec/canvas_sync/jobs/report_processor_job_spec.rb
@@ -542,6 +594,19 @@ files:
542
594
  - spec/factories/submission_factory.rb
543
595
  - spec/factories/term_factory.rb
544
596
  - spec/factories/user_factory.rb
597
+ - spec/job_batching/batch_aware_job_spec.rb
598
+ - spec/job_batching/batch_spec.rb
599
+ - spec/job_batching/callback_spec.rb
600
+ - spec/job_batching/flow_spec.rb
601
+ - spec/job_batching/integration/integration.rb
602
+ - spec/job_batching/integration/nested.rb
603
+ - spec/job_batching/integration/simple.rb
604
+ - spec/job_batching/integration/workflow.rb
605
+ - spec/job_batching/integration_helper.rb
606
+ - spec/job_batching/sidekiq_spec.rb
607
+ - spec/job_batching/status_spec.rb
608
+ - spec/job_batching/support/base_job.rb
609
+ - spec/job_batching/support/sample_callback.rb
545
610
  - spec/spec_helper.rb
546
611
  - spec/support/fake_canvas.rb
547
612
  - spec/support/fixtures/canvas_responses/admins.json
@@ -578,9 +643,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
578
643
  version: '0'
579
644
  required_rubygems_version: !ruby/object:Gem::Requirement
580
645
  requirements:
581
- - - ">="
646
+ - - ">"
582
647
  - !ruby/object:Gem::Version
583
- version: '0'
648
+ version: 1.3.1
584
649
  requirements: []
585
650
  rubygems_version: 3.1.2
586
651
  signing_key:
@@ -588,7 +653,6 @@ specification_version: 4
588
653
  summary: Gem for generating Canvas models and migrations and syncing data from Canvas
589
654
  test_files:
590
655
  - spec/canvas_sync/canvas_sync_spec.rb
591
- - spec/canvas_sync/jobs/fork_gather_spec.rb
592
656
  - spec/canvas_sync/jobs/job_spec.rb
593
657
  - spec/canvas_sync/jobs/report_checker_spec.rb
594
658
  - spec/canvas_sync/jobs/report_processor_job_spec.rb
@@ -723,6 +787,19 @@ test_files:
723
787
  - spec/factories/submission_factory.rb
724
788
  - spec/factories/term_factory.rb
725
789
  - spec/factories/user_factory.rb
790
+ - spec/job_batching/batch_aware_job_spec.rb
791
+ - spec/job_batching/batch_spec.rb
792
+ - spec/job_batching/callback_spec.rb
793
+ - spec/job_batching/flow_spec.rb
794
+ - spec/job_batching/integration/integration.rb
795
+ - spec/job_batching/integration/nested.rb
796
+ - spec/job_batching/integration/simple.rb
797
+ - spec/job_batching/integration/workflow.rb
798
+ - spec/job_batching/integration_helper.rb
799
+ - spec/job_batching/sidekiq_spec.rb
800
+ - spec/job_batching/status_spec.rb
801
+ - spec/job_batching/support/base_job.rb
802
+ - spec/job_batching/support/sample_callback.rb
726
803
  - spec/spec_helper.rb
727
804
  - spec/support/fake_canvas.rb
728
805
  - spec/support/fixtures/canvas_responses/admins.json
@@ -1,102 +0,0 @@
1
- module CanvasSync
2
- class JobChain
3
- attr_reader :chain_data
4
-
5
- delegate_missing_to :chain_data
6
-
7
- VALID_PLACEMENT_PARAMETERS = %i[before after].freeze
8
-
9
- def initialize(chain_data)
10
- @chain_data = chain_data
11
- end
12
-
13
- def jobs
14
- chain_data[:jobs]
15
- end
16
-
17
- def global_options
18
- chain_data[:global_options]
19
- end
20
-
21
- def merge_options(job, options)
22
- jobs.each do |j|
23
- j[:options].deep_merge!(options) if job_matches_pattern(j, job)
24
- end
25
- end
26
-
27
- def insert(new_job, **kwargs)
28
- invalid_params = kwargs.keys - VALID_PLACEMENT_PARAMETERS
29
- raise "Invalid placement parameters: #{invalid_params.map(&:to_s).join(', ')}" if invalid_params.present?
30
- raise "Exactly one placement parameter may be provided" if kwargs.values.compact!.length > 1
31
-
32
- if !kwargs.present?
33
- jobs << new_job
34
- else
35
- placement = kwargs.keys[0]
36
- relative_to = kwargs.values[0]
37
-
38
- index = jobs.index { |job| job_matches_pattern(job, relative_to) }
39
- raise "Could not find a \"#{relative_to}\" job in the chain" if index.nil?
40
-
41
- index += 1 if placement == :after
42
- new_job[:job] = new_job[:job].to_s
43
- jobs.insert(index, new_job)
44
- end
45
- end
46
-
47
- def process!(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
94
- end
95
-
96
- private
97
-
98
- def job_matches_pattern(job_entry, pattern)
99
- job_entry[:job].to_s == pattern.to_s
100
- end
101
- end
102
- end
@@ -1,74 +0,0 @@
1
- module CanvasSync
2
- module Jobs
3
- class ForkGather < CanvasSync::Job
4
- def perform(job_chain, options)
5
- forked_job = self.class.forked_at_job(job_chain)
6
-
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
17
-
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
27
- end
28
-
29
- # Repeat this logic for [if gather_all] the next fork up, or [if not gather_all] nil
30
- next
31
- end
32
-
33
- # If there is no current fork (either not in a fork, or all forks were closed), continue the chain
34
- CanvasSync.invoke_next(job_chain)
35
- break
36
- end
37
- end
38
-
39
- def self.handle_branch_error(e, job_chain:, skip_invoke: false, **kwargs)
40
- return nil unless job_chain&.dig(:fork_state, :forking_path).present?
41
-
42
- duped_chain = CanvasSync.duplicate_chain(job_chain)
43
- job_list = duped_chain[:jobs]
44
- while job_list.count > 0
45
- job_class_name = job_list[0][:job]
46
- job_class = job_class_name.constantize
47
- break if job_class <= CanvasSync::Jobs::ForkGather
48
- job_list.shift
49
- end
50
-
51
- return nil unless job_list.present?
52
-
53
- if skip_invoke
54
- duped_chain
55
- else
56
- CanvasSync.invoke_next(duped_chain)
57
- true
58
- end
59
- end
60
-
61
- protected
62
-
63
- def self.forked_at_job(job_chain)
64
- fork_item = (job_chain.dig(:fork_state, :forking_path) || []).pop
65
-
66
- if fork_item.present?
67
- CanvasSync::JobLog.find_by(job_id: fork_item)
68
- else
69
- nil
70
- end
71
- end
72
- end
73
- end
74
- end