good_job 1.2.1 → 1.2.6

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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +131 -1
  3. data/README.md +321 -160
  4. data/engine/app/controllers/good_job/active_jobs_controller.rb +8 -0
  5. data/engine/app/controllers/good_job/base_controller.rb +5 -0
  6. data/engine/app/controllers/good_job/dashboards_controller.rb +50 -0
  7. data/engine/app/helpers/good_job/application_helper.rb +4 -0
  8. data/engine/app/views/assets/_style.css.erb +16 -0
  9. data/engine/app/views/good_job/active_jobs/show.html.erb +1 -0
  10. data/engine/app/views/good_job/dashboards/index.html.erb +19 -0
  11. data/engine/app/views/layouts/good_job/base.html.erb +61 -0
  12. data/engine/app/views/shared/_chart.erb +51 -0
  13. data/engine/app/views/shared/_jobs_table.erb +26 -0
  14. data/engine/app/views/vendor/bootstrap/_bootstrap-native.js.erb +1662 -0
  15. data/engine/app/views/vendor/bootstrap/_bootstrap.css.erb +10258 -0
  16. data/engine/app/views/vendor/chartist/_chartist.css.erb +613 -0
  17. data/engine/app/views/vendor/chartist/_chartist.js.erb +4516 -0
  18. data/engine/config/routes.rb +4 -0
  19. data/engine/lib/good_job/engine.rb +5 -0
  20. data/lib/active_job/queue_adapters/good_job_adapter.rb +3 -2
  21. data/lib/generators/good_job/install_generator.rb +8 -0
  22. data/lib/good_job.rb +41 -25
  23. data/lib/good_job/adapter.rb +44 -4
  24. data/lib/good_job/cli.rb +66 -13
  25. data/lib/good_job/configuration.rb +61 -2
  26. data/lib/good_job/job.rb +128 -37
  27. data/lib/good_job/lockable.rb +125 -14
  28. data/lib/good_job/log_subscriber.rb +70 -6
  29. data/lib/good_job/multi_scheduler.rb +6 -0
  30. data/lib/good_job/notifier.rb +64 -27
  31. data/lib/good_job/performer.rb +38 -0
  32. data/lib/good_job/railtie.rb +1 -0
  33. data/lib/good_job/scheduler.rb +49 -40
  34. data/lib/good_job/version.rb +2 -1
  35. metadata +163 -7
  36. data/lib/good_job/pg_locks.rb +0 -21
@@ -1,23 +1,29 @@
1
1
  module GoodJob
2
+ # Delegates the interface of a single {Scheduler} to multiple Schedulers.
2
3
  class MultiScheduler
4
+ # @return [array<Scheduler>] List of the scheduler delegates
3
5
  attr_reader :schedulers
4
6
 
5
7
  def initialize(schedulers)
6
8
  @schedulers = schedulers
7
9
  end
8
10
 
11
+ # Delegates to {Scheduler#shutdown}.
9
12
  def shutdown(wait: true)
10
13
  schedulers.each { |s| s.shutdown(wait: wait) }
11
14
  end
12
15
 
16
+ # Delegates to {Scheduler#shutdown?}.
13
17
  def shutdown?
14
18
  schedulers.all?(&:shutdown?)
15
19
  end
16
20
 
21
+ # Delegates to {Scheduler#restart}.
17
22
  def restart(wait: true)
18
23
  schedulers.each { |s| s.restart(wait: wait) }
19
24
  end
20
25
 
26
+ # Delegates to {Scheduler#create_thread}.
21
27
  def create_thread(state = nil)
22
28
  results = []
23
29
  any_true = schedulers.any? do |scheduler|
@@ -2,11 +2,18 @@ require 'concurrent/atomic/atomic_boolean'
2
2
 
3
3
  module GoodJob # :nodoc:
4
4
  #
5
- # Wrapper for Postgres LISTEN/NOTIFY
5
+ # Notifiers hook into Postgres LISTEN/NOTIFY functionality to emit and listen for notifications across processes.
6
+ #
7
+ # Notifiers can emit NOTIFY messages through Postgres.
8
+ # A notifier will LISTEN for messages by creating a background thread that runs in an instance of +Concurrent::ThreadPoolExecutor+.
9
+ # When a message is received, the notifier passes the message to each of its recipients.
6
10
  #
7
11
  class Notifier
12
+ # Default Postgres channel for LISTEN/NOTIFY
8
13
  CHANNEL = 'good_job'.freeze
14
+ # Defaults for instance of Concurrent::ThreadPoolExecutor
9
15
  POOL_OPTIONS = {
16
+ name: name,
10
17
  min_threads: 0,
11
18
  max_threads: 1,
12
19
  auto_terminate: true,
@@ -14,13 +21,17 @@ module GoodJob # :nodoc:
14
21
  max_queue: 1,
15
22
  fallback_policy: :discard,
16
23
  }.freeze
24
+ # Seconds to block while LISTENing for a message
17
25
  WAIT_INTERVAL = 1
18
26
 
19
27
  # @!attribute [r] instances
20
28
  # @!scope class
21
- # @return [array<GoodJob:Adapter>] the instances of +GoodJob::Notifier+
29
+ # List of all instantiated Notifiers in the current process.
30
+ # @return [array<GoodJob:Adapter>]
22
31
  cattr_reader :instances, default: [], instance_reader: false
23
32
 
33
+ # Send a message via Postgres NOTIFY
34
+ # @param message [#to_json]
24
35
  def self.notify(message)
25
36
  connection = ActiveRecord::Base.connection
26
37
  connection.exec_query <<~SQL
@@ -28,8 +39,11 @@ module GoodJob # :nodoc:
28
39
  SQL
29
40
  end
30
41
 
42
+ # List of recipients that will receive notifications.
43
+ # @return [Array<#call, Array(Object, Symbol)>]
31
44
  attr_reader :recipients
32
45
 
46
+ # @param recipients [Array<#call, Array(Object, Symbol)>]
33
47
  def initialize(*recipients)
34
48
  @recipients = Concurrent::Array.new(recipients)
35
49
  @listening = Concurrent::AtomicBoolean.new(false)
@@ -40,16 +54,29 @@ module GoodJob # :nodoc:
40
54
  listen
41
55
  end
42
56
 
57
+ # Tests whether the notifier is active and listening for new messages.
58
+ # @return [true, false, nil]
43
59
  def listening?
44
60
  @listening.true?
45
61
  end
46
62
 
63
+ # Restart the notifier.
64
+ # When shutdown, start; or shutdown and start.
65
+ # @param wait [Boolean] Wait for background thread to finish
66
+ # @return [void]
47
67
  def restart(wait: true)
48
68
  shutdown(wait: wait)
49
69
  create_pool
50
70
  listen
51
71
  end
52
72
 
73
+ # Shut down the notifier.
74
+ # This stops the background LISTENing thread.
75
+ # If +wait+ is +true+, the notifier will wait for background thread to shutdown.
76
+ # If +wait+ is +false+, this method will return immediately even though threads may still be running.
77
+ # Use {#shutdown?} to determine whether threads have stopped.
78
+ # @param wait [Boolean] Wait for actively executing jobs to finish
79
+ # @return [void]
53
80
  def shutdown(wait: true)
54
81
  return unless @pool.running?
55
82
 
@@ -57,6 +84,8 @@ module GoodJob # :nodoc:
57
84
  @pool.wait_for_termination if wait
58
85
  end
59
86
 
87
+ # Tests whether the notifier is shutdown.
88
+ # @return [true, false, nil]
60
89
  def shutdown?
61
90
  !@pool.running?
62
91
  end
@@ -69,40 +98,37 @@ module GoodJob # :nodoc:
69
98
 
70
99
  def listen
71
100
  future = Concurrent::Future.new(args: [@recipients, @pool, @listening], executor: @pool) do |recipients, pool, listening|
72
- Rails.application.reloader.wrap do
73
- conn = ActiveRecord::Base.connection.raw_connection
101
+ with_listen_connection do |conn|
74
102
  ActiveSupport::Notifications.instrument("notifier_listen.good_job") do
75
103
  conn.async_exec "LISTEN #{CHANNEL}"
76
104
  end
77
105
 
78
- begin
79
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
80
- while pool.running?
81
- listening.make_true
82
- conn.wait_for_notify(WAIT_INTERVAL) do |channel, _pid, payload|
83
- listening.make_false
84
- next unless channel == CHANNEL
85
-
86
- ActiveSupport::Notifications.instrument("notifier_notified.good_job", { payload: payload })
87
- parsed_payload = JSON.parse(payload, symbolize_names: true)
88
- recipients.each do |recipient|
89
- target, method_name = recipient.is_a?(Array) ? recipient : [recipient, :call]
90
- target.send(method_name, parsed_payload)
91
- end
92
- end
106
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
107
+ while pool.running?
108
+ listening.make_true
109
+ conn.wait_for_notify(WAIT_INTERVAL) do |channel, _pid, payload|
93
110
  listening.make_false
111
+ next unless channel == CHANNEL
112
+
113
+ ActiveSupport::Notifications.instrument("notifier_notified.good_job", { payload: payload })
114
+ parsed_payload = JSON.parse(payload, symbolize_names: true)
115
+ recipients.each do |recipient|
116
+ target, method_name = recipient.is_a?(Array) ? recipient : [recipient, :call]
117
+ target.send(method_name, parsed_payload)
118
+ end
94
119
  end
95
- end
96
- rescue StandardError => e
97
- ActiveSupport::Notifications.instrument("notifier_notify_error.good_job", { error: e })
98
- raise
99
- ensure
100
- @listening.make_false
101
- ActiveSupport::Notifications.instrument("notifier_unlisten.good_job") do
102
- conn.async_exec "UNLISTEN *"
120
+ listening.make_false
103
121
  end
104
122
  end
105
123
  end
124
+ rescue StandardError => e
125
+ ActiveSupport::Notifications.instrument("notifier_notify_error.good_job", { error: e })
126
+ raise
127
+ ensure
128
+ @listening.make_false
129
+ ActiveSupport::Notifications.instrument("notifier_unlisten.good_job") do
130
+ conn.async_exec "UNLISTEN *"
131
+ end
106
132
  end
107
133
 
108
134
  future.add_observer(self, :listen_observer)
@@ -112,5 +138,16 @@ module GoodJob # :nodoc:
112
138
  def listen_observer(_time, _result, _thread_error)
113
139
  listen unless shutdown?
114
140
  end
141
+
142
+ def with_listen_connection
143
+ ar_conn = ActiveRecord::Base.connection_pool.checkout.tap do |conn|
144
+ ActiveRecord::Base.connection_pool.remove(conn)
145
+ end
146
+ pg_conn = ar_conn.raw_connection
147
+ pg_conn.exec("SET application_name = #{pg_conn.escape_identifier(self.class.name)}")
148
+ yield pg_conn
149
+ ensure
150
+ ar_conn.disconnect!
151
+ end
115
152
  end
116
153
  end
@@ -1,7 +1,33 @@
1
1
  module GoodJob
2
+ #
3
+ # Performer queries the database for jobs and performs them on behalf of a
4
+ # {Scheduler}. It mainly functions as glue between a {Scheduler} and the jobs
5
+ # it should be executing.
6
+ #
7
+ # The Performer enforces a callable that does not rely on scoped/closure
8
+ # variables because they might not be available when executed in a different
9
+ # thread.
10
+ #
2
11
  class Performer
12
+ # @!attribute [r] name
13
+ # @return [String]
14
+ # a meaningful name to identify the performer in logs and for debugging.
15
+ # This is usually set to the list of queues the performer will query,
16
+ # e.g. +"-transactional_messages,batch_processing"+.
3
17
  attr_reader :name
4
18
 
19
+ # @param target [Object]
20
+ # An object that can perform jobs. It must respond to +method_name+ by
21
+ # finding and performing jobs and is usually a {Job} query,
22
+ # e.g. +GoodJob::Job.where(queue_name: ['queue1', 'queue2'])+.
23
+ # @param method_name [Symbol]
24
+ # The name of a method on +target+ that finds and performs jobs.
25
+ # @param name [String]
26
+ # A name for the performer to be used in logs and for debugging.
27
+ # @param filter [#call]
28
+ # Used to determine whether the performer should be used in GoodJob's
29
+ # current state. GoodJob state is a +Hash+ that will be passed as the
30
+ # first argument to +filter+ and includes info like the current queue.
5
31
  def initialize(target, method_name, name: nil, filter: nil)
6
32
  @target = target
7
33
  @method_name = method_name
@@ -9,10 +35,22 @@ module GoodJob
9
35
  @filter = filter
10
36
  end
11
37
 
38
+ # Find and perform any eligible jobs.
12
39
  def next
13
40
  @target.public_send(@method_name)
14
41
  end
15
42
 
43
+ # Tests whether this performer should be used in GoodJob's current state by
44
+ # calling the +filter+ callable set in {#initialize}. Always returns +true+
45
+ # if there is no filter.
46
+ #
47
+ # For example, state will be a LISTEN/NOTIFY message that is passed down
48
+ # from the Notifier to the Scheduler. The Scheduler is able to ask
49
+ # its performer "does this message relate to you?", and if not, ignore it
50
+ # to minimize thread wake-ups, database queries, and thundering herds.
51
+ #
52
+ # @return [Boolean] whether the performer's {#next} method should be
53
+ # called in the current state.
16
54
  def next?(state = {})
17
55
  return true unless @filter.respond_to?(:call)
18
56
 
@@ -1,4 +1,5 @@
1
1
  module GoodJob
2
+ # Ruby on Rails integration.
2
3
  class Railtie < ::Rails::Railtie
3
4
  initializer "good_job.logger" do
4
5
  ActiveSupport.on_load(:good_job) { self.logger = ::Rails.logger }
@@ -4,38 +4,44 @@ require "concurrent/utility/processor_counter"
4
4
 
5
5
  module GoodJob # :nodoc:
6
6
  #
7
- # Schedulers are generic thread execution pools that are responsible for
8
- # periodically checking for available execution tasks, executing tasks in a
9
- # bounded thread-pool, and efficiently scaling execution threads.
7
+ # Schedulers are generic thread pools that are responsible for
8
+ # periodically checking for available tasks, executing tasks within a thread,
9
+ # and efficiently scaling active threads.
10
10
  #
11
- # Schedulers are "generic" in the sense that they delegate task execution
12
- # details to a "Performer" object that responds to #next.
11
+ # Every scheduler has a single {Performer} that will execute tasks.
12
+ # The scheduler is responsible for calling its performer efficiently across threads managed by an instance of +Concurrent::ThreadPoolExecutor+.
13
+ # If a performer does not have work, the thread will go to sleep.
14
+ # The scheduler maintains an instance of +Concurrent::TimerTask+, which wakes sleeping threads and causes them to check whether the performer has new work.
13
15
  #
14
16
  class Scheduler
15
- # Defaults for instance of Concurrent::TimerTask
16
- DEFAULT_TIMER_OPTIONS = {
17
- execution_interval: 1,
18
- timeout_interval: 1,
19
- run_now: true,
20
- }.freeze
21
-
22
17
  # Defaults for instance of Concurrent::ThreadPoolExecutor
18
+ # The thread pool is where work is performed.
23
19
  DEFAULT_POOL_OPTIONS = {
20
+ name: name,
24
21
  min_threads: 0,
25
- max_threads: Concurrent.processor_count,
22
+ max_threads: Configuration::DEFAULT_MAX_THREADS,
26
23
  auto_terminate: true,
27
24
  idletime: 60,
28
25
  max_queue: -1,
29
26
  fallback_policy: :discard,
30
27
  }.freeze
31
28
 
29
+ # Defaults for instance of Concurrent::TimerTask.
30
+ # The timer controls how and when sleeping threads check for new work.
31
+ DEFAULT_TIMER_OPTIONS = {
32
+ execution_interval: Configuration::DEFAULT_POLL_INTERVAL,
33
+ timeout_interval: 1,
34
+ run_now: true,
35
+ }.freeze
36
+
32
37
  # @!attribute [r] instances
33
38
  # @!scope class
34
- # All instantiated Schedulers in the current process.
39
+ # List of all instantiated Schedulers in the current process.
35
40
  # @return [array<GoodJob:Scheduler>]
36
41
  cattr_reader :instances, default: [], instance_reader: false
37
42
 
38
43
  # Creates GoodJob::Scheduler(s) and Performers from a GoodJob::Configuration instance.
44
+ # TODO: move this to GoodJob::Configuration
39
45
  # @param configuration [GoodJob::Configuration]
40
46
  # @return [GoodJob::Scheduler, GoodJob::MultiScheduler]
41
47
  def self.from_configuration(configuration)
@@ -56,14 +62,7 @@ module GoodJob # :nodoc:
56
62
  end
57
63
  job_performer = GoodJob::Performer.new(job_query, :perform_with_advisory_lock, name: queue_string, filter: job_filter)
58
64
 
59
- timer_options = {}
60
- timer_options[:execution_interval] = configuration.poll_interval
61
-
62
- pool_options = {
63
- max_threads: max_threads,
64
- }
65
-
66
- GoodJob::Scheduler.new(job_performer, timer_options: timer_options, pool_options: pool_options)
65
+ GoodJob::Scheduler.new(job_performer, max_threads: max_threads, poll_interval: configuration.poll_interval)
67
66
  end
68
67
 
69
68
  if schedulers.size > 1
@@ -74,23 +73,30 @@ module GoodJob # :nodoc:
74
73
  end
75
74
 
76
75
  # @param performer [GoodJob::Performer]
77
- # @param timer_options [Hash] Options to instantiate a Concurrent::TimerTask
78
- # @param pool_options [Hash] Options to instantiate a Concurrent::ThreadPoolExecutor
79
- def initialize(performer, timer_options: {}, pool_options: {})
76
+ # @param max_threads [Numeric, nil] the number of execution threads to use
77
+ # @param poll_interval [Numeric, nil] the number of seconds between polls for jobs
78
+ def initialize(performer, max_threads: nil, poll_interval: nil)
80
79
  raise ArgumentError, "Performer argument must implement #next" unless performer.respond_to?(:next)
81
80
 
82
81
  self.class.instances << self
83
82
 
84
83
  @performer = performer
85
- @pool_options = DEFAULT_POOL_OPTIONS.merge(pool_options)
86
- @timer_options = DEFAULT_TIMER_OPTIONS.merge(timer_options)
87
84
 
85
+ @timer_options = DEFAULT_TIMER_OPTIONS.dup
86
+ @timer_options[:execution_interval] = poll_interval if poll_interval.present?
87
+
88
+ @pool_options = DEFAULT_POOL_OPTIONS.dup
89
+ @pool_options[:max_threads] = max_threads if max_threads.present?
88
90
  @pool_options[:name] = "GoodJob::Scheduler(queues=#{@performer.name} max_threads=#{@pool_options[:max_threads]} poll_interval=#{@timer_options[:execution_interval]})"
89
91
 
90
92
  create_pools
91
93
  end
92
94
 
93
- # Shut down the Scheduler.
95
+ # Shut down the scheduler.
96
+ # This stops all threads in the pool.
97
+ # If +wait+ is +true+, the scheduler will wait for any active tasks to finish.
98
+ # If +wait+ is +false+, this method will return immediately even though threads may still be running.
99
+ # Use {#shutdown?} to determine whether threads have stopped.
94
100
  # @param wait [Boolean] Wait for actively executing jobs to finish
95
101
  # @return [void]
96
102
  def shutdown(wait: true)
@@ -101,22 +107,25 @@ module GoodJob # :nodoc:
101
107
  if @timer&.running?
102
108
  @timer.shutdown
103
109
  @timer.wait_for_termination if wait
110
+ # TODO: Should be killed if wait is not true
104
111
  end
105
112
 
106
113
  if @pool&.running?
107
114
  @pool.shutdown
108
115
  @pool.wait_for_termination if wait
116
+ # TODO: Should be killed if wait is not true
109
117
  end
110
118
  end
111
119
  end
112
120
 
113
- # True when the Scheduler is shutdown.
121
+ # Tests whether the scheduler is shutdown.
114
122
  # @return [true, false, nil]
115
123
  def shutdown?
116
124
  @_shutdown
117
125
  end
118
126
 
119
- # Restart the Scheduler. When shutdown, start; or shutdown and start.
127
+ # Restart the Scheduler.
128
+ # When shutdown, start; or shutdown and start.
120
129
  # @param wait [Boolean] Wait for actively executing jobs to finish
121
130
  # @return [void]
122
131
  def restart(wait: true)
@@ -127,15 +136,15 @@ module GoodJob # :nodoc:
127
136
  end
128
137
  end
129
138
 
130
- # Triggers a Performer execution, if an execution thread is available.
131
- # @param state [nil, Object] Allows Performer#next? to accept or reject the execution
132
- # @return [nil, Boolean] if the thread was created
139
+ # Wakes a thread to allow the performer to execute a task.
140
+ # @param state [nil, Object] Contextual information for the performer. See {Performer#next?}.
141
+ # @return [nil, Boolean] Whether work was started.
142
+ # Returns +nil+ if the scheduler is unable to take new work, for example if the thread pool is shut down or at capacity.
143
+ # Returns +true+ if the performer started executing work.
144
+ # Returns +false+ if the performer decides not to attempt to execute a task based on the +state+ that is passed to it.
133
145
  def create_thread(state = nil)
134
146
  return nil unless @pool.running? && @pool.ready_worker_count.positive?
135
-
136
- if state
137
- return false unless @performer.next?(state)
138
- end
147
+ return false if state && !@performer.next?(state)
139
148
 
140
149
  future = Concurrent::Future.new(args: [@performer], executor: @pool) do |performer|
141
150
  output = nil
@@ -167,7 +176,6 @@ module GoodJob # :nodoc:
167
176
 
168
177
  private
169
178
 
170
- # @return [void]
171
179
  def create_pools
172
180
  instrument("scheduler_create_pools", { performer_name: @performer.name, max_threads: @pool_options[:max_threads], poll_interval: @timer_options[:execution_interval] }) do
173
181
  @pool = ThreadPoolExecutor.new(@pool_options)
@@ -190,9 +198,10 @@ module GoodJob # :nodoc:
190
198
  end
191
199
  end
192
200
 
193
- # Slightly customized sub-class of Concurrent::ThreadPoolExecutor
201
+ # Custom sub-class of +Concurrent::ThreadPoolExecutor+ to add additional worker status.
202
+ # @private
194
203
  class ThreadPoolExecutor < Concurrent::ThreadPoolExecutor
195
- # Number of idle or potential threads available to execute tasks
204
+ # Number of inactive threads available to execute tasks.
196
205
  # https://github.com/ruby-concurrency/concurrent-ruby/issues/684#issuecomment-427594437
197
206
  # @return [Integer]
198
207
  def ready_worker_count
@@ -1,3 +1,4 @@
1
1
  module GoodJob
2
- VERSION = '1.2.1'.freeze
2
+ # GoodJob gem version.
3
+ VERSION = '1.2.6'.freeze
3
4
  end
metadata CHANGED
@@ -1,15 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: good_job
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ version: 1.2.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Sheldon
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-08-21 00:00:00.000000000 Z
11
+ date: 2020-09-29 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activejob
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 5.2.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 5.2.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: activerecord
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 5.2.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 5.2.0
13
41
  - !ruby/object:Gem::Dependency
14
42
  name: concurrent-ruby
15
43
  requirement: !ruby/object:Gem::Requirement
@@ -39,19 +67,19 @@ dependencies:
39
67
  - !ruby/object:Gem::Version
40
68
  version: 1.0.0
41
69
  - !ruby/object:Gem::Dependency
42
- name: rails
70
+ name: railties
43
71
  requirement: !ruby/object:Gem::Requirement
44
72
  requirements:
45
73
  - - ">="
46
74
  - !ruby/object:Gem::Version
47
- version: 5.1.0
75
+ version: 5.2.0
48
76
  type: :runtime
49
77
  prerelease: false
50
78
  version_requirements: !ruby/object:Gem::Requirement
51
79
  requirements:
52
80
  - - ">="
53
81
  - !ruby/object:Gem::Version
54
- version: 5.1.0
82
+ version: 5.2.0
55
83
  - !ruby/object:Gem::Dependency
56
84
  name: thor
57
85
  requirement: !ruby/object:Gem::Requirement
@@ -66,6 +94,20 @@ dependencies:
66
94
  - - ">="
67
95
  - !ruby/object:Gem::Version
68
96
  version: 0.14.1
97
+ - !ruby/object:Gem::Dependency
98
+ name: zeitwerk
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '2.0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '2.0'
69
111
  - !ruby/object:Gem::Dependency
70
112
  name: appraisal
71
113
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +122,20 @@ dependencies:
80
122
  - - ">="
81
123
  - !ruby/object:Gem::Version
82
124
  version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: capybara
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
83
139
  - !ruby/object:Gem::Dependency
84
140
  name: database_cleaner
85
141
  requirement: !ruby/object:Gem::Requirement
@@ -108,6 +164,20 @@ dependencies:
108
164
  - - ">="
109
165
  - !ruby/object:Gem::Version
110
166
  version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: erb_lint
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
111
181
  - !ruby/object:Gem::Dependency
112
182
  name: foreman
113
183
  requirement: !ruby/object:Gem::Requirement
@@ -150,6 +220,48 @@ dependencies:
150
220
  - - ">="
151
221
  - !ruby/object:Gem::Version
152
222
  version: '0'
223
+ - !ruby/object:Gem::Dependency
224
+ name: kramdown
225
+ requirement: !ruby/object:Gem::Requirement
226
+ requirements:
227
+ - - ">="
228
+ - !ruby/object:Gem::Version
229
+ version: '0'
230
+ type: :development
231
+ prerelease: false
232
+ version_requirements: !ruby/object:Gem::Requirement
233
+ requirements:
234
+ - - ">="
235
+ - !ruby/object:Gem::Version
236
+ version: '0'
237
+ - !ruby/object:Gem::Dependency
238
+ name: kramdown-parser-gfm
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: mdl
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'
153
265
  - !ruby/object:Gem::Dependency
154
266
  name: pry-rails
155
267
  requirement: !ruby/object:Gem::Requirement
@@ -262,6 +374,20 @@ dependencies:
262
374
  - - ">="
263
375
  - !ruby/object:Gem::Version
264
376
  version: '0'
377
+ - !ruby/object:Gem::Dependency
378
+ name: selenium-webdriver
379
+ requirement: !ruby/object:Gem::Requirement
380
+ requirements:
381
+ - - ">="
382
+ - !ruby/object:Gem::Version
383
+ version: '0'
384
+ type: :development
385
+ prerelease: false
386
+ version_requirements: !ruby/object:Gem::Requirement
387
+ requirements:
388
+ - - ">="
389
+ - !ruby/object:Gem::Version
390
+ version: '0'
265
391
  - !ruby/object:Gem::Dependency
266
392
  name: sigdump
267
393
  requirement: !ruby/object:Gem::Requirement
@@ -290,6 +416,20 @@ dependencies:
290
416
  - - ">="
291
417
  - !ruby/object:Gem::Version
292
418
  version: '0'
419
+ - !ruby/object:Gem::Dependency
420
+ name: yard-activesupport-concern
421
+ requirement: !ruby/object:Gem::Requirement
422
+ requirements:
423
+ - - ">="
424
+ - !ruby/object:Gem::Version
425
+ version: '0'
426
+ type: :development
427
+ prerelease: false
428
+ version_requirements: !ruby/object:Gem::Requirement
429
+ requirements:
430
+ - - ">="
431
+ - !ruby/object:Gem::Version
432
+ version: '0'
293
433
  description: A multithreaded, Postgres-based ActiveJob backend for Ruby on Rails
294
434
  email:
295
435
  - bensheldon@gmail.com
@@ -304,6 +444,22 @@ files:
304
444
  - CHANGELOG.md
305
445
  - LICENSE.txt
306
446
  - README.md
447
+ - engine/app/controllers/good_job/active_jobs_controller.rb
448
+ - engine/app/controllers/good_job/base_controller.rb
449
+ - engine/app/controllers/good_job/dashboards_controller.rb
450
+ - engine/app/helpers/good_job/application_helper.rb
451
+ - engine/app/views/assets/_style.css.erb
452
+ - engine/app/views/good_job/active_jobs/show.html.erb
453
+ - engine/app/views/good_job/dashboards/index.html.erb
454
+ - engine/app/views/layouts/good_job/base.html.erb
455
+ - engine/app/views/shared/_chart.erb
456
+ - engine/app/views/shared/_jobs_table.erb
457
+ - engine/app/views/vendor/bootstrap/_bootstrap-native.js.erb
458
+ - engine/app/views/vendor/bootstrap/_bootstrap.css.erb
459
+ - engine/app/views/vendor/chartist/_chartist.css.erb
460
+ - engine/app/views/vendor/chartist/_chartist.js.erb
461
+ - engine/config/routes.rb
462
+ - engine/lib/good_job/engine.rb
307
463
  - exe/good_job
308
464
  - lib/active_job/queue_adapters/good_job_adapter.rb
309
465
  - lib/generators/good_job/install_generator.rb
@@ -319,7 +475,6 @@ files:
319
475
  - lib/good_job/multi_scheduler.rb
320
476
  - lib/good_job/notifier.rb
321
477
  - lib/good_job/performer.rb
322
- - lib/good_job/pg_locks.rb
323
478
  - lib/good_job/railtie.rb
324
479
  - lib/good_job/scheduler.rb
325
480
  - lib/good_job/version.rb
@@ -343,11 +498,12 @@ rdoc_options:
343
498
  - "--quiet"
344
499
  require_paths:
345
500
  - lib
501
+ - engine/lib
346
502
  required_ruby_version: !ruby/object:Gem::Requirement
347
503
  requirements:
348
504
  - - ">="
349
505
  - !ruby/object:Gem::Version
350
- version: 2.4.0
506
+ version: 2.5.0
351
507
  required_rubygems_version: !ruby/object:Gem::Requirement
352
508
  requirements:
353
509
  - - ">="