good_job 2.4.2 → 2.6.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +79 -0
  3. data/README.md +12 -4
  4. data/engine/app/assets/scripts.js +1 -0
  5. data/engine/app/assets/style.css +5 -0
  6. data/engine/app/assets/vendor/chartjs/chart.min.js +13 -0
  7. data/engine/app/assets/vendor/rails_ujs.js +747 -0
  8. data/engine/app/charts/good_job/scheduled_by_queue_chart.rb +69 -0
  9. data/engine/app/controllers/good_job/assets_controller.rb +8 -4
  10. data/engine/app/controllers/good_job/cron_entries_controller.rb +19 -0
  11. data/engine/app/controllers/good_job/jobs_controller.rb +36 -0
  12. data/engine/app/filters/good_job/base_filter.rb +18 -56
  13. data/engine/app/filters/good_job/executions_filter.rb +9 -8
  14. data/engine/app/filters/good_job/jobs_filter.rb +12 -9
  15. data/engine/app/views/good_job/cron_entries/index.html.erb +51 -0
  16. data/engine/app/views/good_job/cron_entries/show.html.erb +4 -0
  17. data/engine/app/views/good_job/{shared/_executions_table.erb → executions/_table.erb} +1 -1
  18. data/engine/app/views/good_job/executions/index.html.erb +2 -2
  19. data/engine/app/views/good_job/{shared/_jobs_table.erb → jobs/_table.erb} +18 -6
  20. data/engine/app/views/good_job/jobs/index.html.erb +15 -2
  21. data/engine/app/views/good_job/jobs/show.html.erb +2 -2
  22. data/engine/app/views/good_job/shared/_chart.erb +19 -46
  23. data/engine/app/views/good_job/shared/_filter.erb +27 -13
  24. data/engine/app/views/good_job/shared/icons/_arrow_clockwise.html.erb +5 -0
  25. data/engine/app/views/good_job/shared/icons/_play.html.erb +4 -0
  26. data/engine/app/views/good_job/shared/icons/_skip_forward.html.erb +4 -0
  27. data/engine/app/views/good_job/shared/icons/_stop.html.erb +4 -0
  28. data/engine/app/views/layouts/good_job/base.html.erb +6 -4
  29. data/engine/config/routes.rb +17 -4
  30. data/lib/generators/good_job/templates/install/migrations/create_good_jobs.rb.erb +2 -0
  31. data/lib/generators/good_job/templates/update/migrations/02_add_cron_at_to_good_jobs.rb.erb +14 -0
  32. data/lib/generators/good_job/templates/update/migrations/03_add_cron_key_cron_at_index_to_good_jobs.rb.erb +20 -0
  33. data/lib/good_job/active_job_job.rb +228 -0
  34. data/lib/good_job/configuration.rb +1 -1
  35. data/lib/good_job/cron_entry.rb +78 -5
  36. data/lib/good_job/cron_manager.rb +4 -6
  37. data/lib/good_job/current_thread.rb +38 -5
  38. data/lib/good_job/execution.rb +53 -39
  39. data/lib/good_job/filterable.rb +42 -0
  40. data/lib/good_job/notifier.rb +17 -7
  41. data/lib/good_job/version.rb +1 -1
  42. metadata +31 -21
  43. data/engine/app/assets/vendor/chartist/chartist.css +0 -613
  44. data/engine/app/assets/vendor/chartist/chartist.js +0 -4516
  45. data/engine/app/controllers/good_job/cron_schedules_controller.rb +0 -9
  46. data/engine/app/models/good_job/active_job_job.rb +0 -127
  47. data/engine/app/views/good_job/cron_schedules/index.html.erb +0 -72
@@ -6,10 +6,14 @@ module GoodJob
6
6
  # class Execution < ActiveRecord::Base; end
7
7
  class Execution < Object.const_get(GoodJob.active_record_parent_class)
8
8
  include Lockable
9
+ include Filterable
9
10
 
10
11
  # Raised if something attempts to execute a previously completed Execution again.
11
12
  PreviouslyPerformedError = Class.new(StandardError)
12
13
 
14
+ # String separating Error Class from Error Message
15
+ ERROR_MESSAGE_SEPARATOR = ": "
16
+
13
17
  # ActiveJob jobs without a +queue_name+ attribute are placed on this queue.
14
18
  DEFAULT_QUEUE_NAME = 'default'
15
19
  # ActiveJob jobs without a +priority+ attribute are given this priority.
@@ -50,6 +54,16 @@ module GoodJob
50
54
  end
51
55
  end
52
56
 
57
+ def self._migration_pending_warning
58
+ ActiveSupport::Deprecation.warn(<<~DEPRECATION)
59
+ GoodJob has pending database migrations. To create the migration files, run:
60
+ rails generate good_job:update
61
+ To apply the migration files, run:
62
+ rails db:migrate
63
+ DEPRECATION
64
+ nil
65
+ end
66
+
53
67
  # Get Jobs with given ActiveJob ID
54
68
  # @!method active_job_id
55
69
  # @!scope class
@@ -143,24 +157,6 @@ module GoodJob
143
157
  end
144
158
  end)
145
159
 
146
- # Get Jobs in display order with optional keyset pagination.
147
- # @!method display_all(after_scheduled_at: nil, after_id: nil)
148
- # @!scope class
149
- # @param after_scheduled_at [DateTime, String, nil]
150
- # Display records scheduled after this time for keyset pagination
151
- # @param after_id [Numeric, String, nil]
152
- # Display records after this ID for keyset pagination
153
- # @return [ActiveRecord::Relation]
154
- scope :display_all, (lambda do |after_scheduled_at: nil, after_id: nil|
155
- query = order(Arel.sql('COALESCE(scheduled_at, created_at) DESC, id DESC'))
156
- if after_scheduled_at.present? && after_id.present?
157
- query = query.where(Arel.sql('(COALESCE(scheduled_at, created_at), id) < (:after_scheduled_at, :after_id)'), after_scheduled_at: after_scheduled_at, after_id: after_id)
158
- elsif after_scheduled_at.present?
159
- query = query.where(Arel.sql('(COALESCE(scheduled_at, created_at)) < (:after_scheduled_at)'), after_scheduled_at: after_scheduled_at)
160
- end
161
- query
162
- end)
163
-
164
160
  # Finds the next eligible Execution, acquire an advisory lock related to it, and
165
161
  # executes the job.
166
162
  # @return [ExecutionResult, nil]
@@ -222,7 +218,15 @@ module GoodJob
222
218
 
223
219
  if CurrentThread.cron_key
224
220
  execution_args[:cron_key] = CurrentThread.cron_key
225
- elsif CurrentThread.active_job_id == active_job.job_id
221
+
222
+ @cron_at_index = column_names.include?('cron_at') && connection.index_name_exists?(:good_jobs, :index_good_jobs_on_cron_key_and_cron_at) unless instance_variable_defined?(:@cron_at_index)
223
+
224
+ if @cron_at_index
225
+ execution_args[:cron_at] = CurrentThread.cron_at
226
+ else
227
+ _migration_pending_warning
228
+ end
229
+ elsif CurrentThread.active_job_id && CurrentThread.active_job_id == active_job.job_id
226
230
  execution_args[:cron_key] = CurrentThread.execution.cron_key
227
231
  end
228
232
 
@@ -233,7 +237,7 @@ module GoodJob
233
237
  execution.save!
234
238
  active_job.provider_job_id = execution.id
235
239
 
236
- CurrentThread.execution.retried_good_job_id = execution.id if CurrentThread.execution && CurrentThread.execution.active_job_id == active_job.job_id
240
+ CurrentThread.execution.retried_good_job_id = execution.id if CurrentThread.active_job_id && CurrentThread.active_job_id == active_job.job_id
237
241
 
238
242
  execution
239
243
  end
@@ -253,7 +257,7 @@ module GoodJob
253
257
  result = execute
254
258
 
255
259
  job_error = result.handled_error || result.unhandled_error
256
- self.error = "#{job_error.class}: #{job_error.message}" if job_error
260
+ self.error = [job_error.class, ERROR_MESSAGE_SEPARATOR, job_error.message].join if job_error
257
261
 
258
262
  if result.unhandled_error && GoodJob.retry_on_unhandled_error
259
263
  save!
@@ -273,29 +277,39 @@ module GoodJob
273
277
  self.class.unscoped.unfinished.owns_advisory_locked.exists?(id: id)
274
278
  end
275
279
 
280
+ def active_job
281
+ ActiveJob::Base.deserialize(active_job_data)
282
+ end
283
+
276
284
  private
277
285
 
286
+ def active_job_data
287
+ serialized_params.deep_dup
288
+ .tap do |job_data|
289
+ job_data["provider_job_id"] = id
290
+ end
291
+ end
292
+
278
293
  # @return [ExecutionResult]
279
294
  def execute
280
- GoodJob::CurrentThread.reset
281
- GoodJob::CurrentThread.execution = self
282
-
283
- job_data = serialized_params.deep_dup
284
- job_data["provider_job_id"] = id
285
-
286
- # DEPRECATION: Remove deprecated `good_job:` parameter in GoodJob v3
287
- ActiveSupport::Notifications.instrument("perform_job.good_job", { good_job: self, execution: self, process_id: GoodJob::CurrentThread.process_id, thread_name: GoodJob::CurrentThread.thread_name }) do
288
- value = ActiveJob::Base.execute(job_data)
289
-
290
- if value.is_a?(Exception)
291
- handled_error = value
292
- value = nil
295
+ GoodJob::CurrentThread.within do |current_thread|
296
+ current_thread.reset
297
+ current_thread.execution = self
298
+
299
+ # DEPRECATION: Remove deprecated `good_job:` parameter in GoodJob v3
300
+ ActiveSupport::Notifications.instrument("perform_job.good_job", { good_job: self, execution: self, process_id: current_thread.process_id, thread_name: current_thread.thread_name }) do
301
+ value = ActiveJob::Base.execute(active_job_data)
302
+
303
+ if value.is_a?(Exception)
304
+ handled_error = value
305
+ value = nil
306
+ end
307
+ handled_error ||= current_thread.error_on_retry || current_thread.error_on_discard
308
+
309
+ ExecutionResult.new(value: value, handled_error: handled_error)
310
+ rescue StandardError => e
311
+ ExecutionResult.new(value: nil, unhandled_error: e)
293
312
  end
294
- handled_error ||= GoodJob::CurrentThread.error_on_retry || GoodJob::CurrentThread.error_on_discard
295
-
296
- ExecutionResult.new(value: value, handled_error: handled_error)
297
- rescue StandardError => e
298
- ExecutionResult.new(value: nil, unhandled_error: e)
299
313
  end
300
314
  end
301
315
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+ module GoodJob
3
+ # Shared methods for filtering Execution/Job records from the +good_jobs+ table.
4
+ module Filterable
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ # Get records in display order with optional keyset pagination.
9
+ # @!method display_all(after_scheduled_at: nil, after_id: nil)
10
+ # @!scope class
11
+ # @param after_scheduled_at [DateTime, String, nil]
12
+ # Display records scheduled after this time for keyset pagination
13
+ # @param after_id [Numeric, String, nil]
14
+ # Display records after this ID for keyset pagination
15
+ # @return [ActiveRecord::Relation]
16
+ scope :display_all, (lambda do |after_scheduled_at: nil, after_id: nil|
17
+ query = order(Arel.sql('COALESCE(scheduled_at, created_at) DESC, id DESC'))
18
+ if after_scheduled_at.present? && after_id.present?
19
+ query = query.where(Arel.sql('(COALESCE(scheduled_at, created_at), id) < (:after_scheduled_at, :after_id)'), after_scheduled_at: after_scheduled_at, after_id: after_id)
20
+ elsif after_scheduled_at.present?
21
+ query = query.where(Arel.sql('(COALESCE(scheduled_at, created_at)) < (:after_scheduled_at)'), after_scheduled_at: after_scheduled_at)
22
+ end
23
+ query
24
+ end)
25
+
26
+ # Search records by text query.
27
+ # @!method search_text(query)
28
+ # @!scope class
29
+ # @param query [String]
30
+ # Search Query
31
+ # @return [ActiveRecord::Relation]
32
+ scope :search_text, (lambda do |query|
33
+ query = query.to_s.strip
34
+ next if query.blank?
35
+
36
+ tsvector = "(to_tsvector('english', serialized_params) || to_tsvector('english', id::text) || to_tsvector('english', COALESCE(error, '')::text))"
37
+ where("#{tsvector} @@ to_tsquery(?)", query)
38
+ .order(sanitize_sql_for_order([Arel.sql("ts_rank(#{tsvector}, to_tsquery(?))"), query]) => 'DESC')
39
+ end)
40
+ end
41
+ end
42
+ end
@@ -25,10 +25,17 @@ module GoodJob # :nodoc:
25
25
  max_queue: 1,
26
26
  fallback_policy: :discard,
27
27
  }.freeze
28
- # Seconds to wait if database cannot be connected to
29
- RECONNECT_INTERVAL = 5
30
28
  # Seconds to block while LISTENing for a message
31
29
  WAIT_INTERVAL = 1
30
+ # Seconds to wait if database cannot be connected to
31
+ RECONNECT_INTERVAL = 5
32
+ # Connection errors that will wait {RECONNECT_INTERVAL} before reconnecting
33
+ CONNECTION_ERRORS = %w[
34
+ ActiveRecord::ConnectionNotEstablished
35
+ ActiveRecord::StatementInvalid
36
+ PG::UnableToSend
37
+ PG::Error
38
+ ].freeze
32
39
 
33
40
  # @!attribute [r] instances
34
41
  # @!scope class
@@ -115,15 +122,18 @@ module GoodJob # :nodoc:
115
122
  if thread_error
116
123
  GoodJob.on_thread_error.call(thread_error) if GoodJob.on_thread_error.respond_to?(:call)
117
124
  ActiveSupport::Notifications.instrument("notifier_notify_error.good_job", { error: thread_error })
125
+
126
+ connection_error = CONNECTION_ERRORS.any? do |error_string|
127
+ error_class = error_string.safe_constantize
128
+ next unless error_class
129
+
130
+ thread_error.is_a? error_class
131
+ end
118
132
  end
119
133
 
120
134
  return if shutdown?
121
135
 
122
- if thread_error.is_a?(ActiveRecord::ConnectionNotEstablished) || thread_error.is_a?(ActiveRecord::StatementInvalid)
123
- listen(delay: RECONNECT_INTERVAL)
124
- else
125
- listen
126
- end
136
+ listen(delay: connection_error ? RECONNECT_INTERVAL : 0)
127
137
  end
128
138
 
129
139
  private
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  module GoodJob
3
3
  # GoodJob gem version.
4
- VERSION = '2.4.2'
4
+ VERSION = '2.6.2'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: good_job
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.2
4
+ version: 2.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Sheldon
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-10-19 00:00:00.000000000 Z
11
+ date: 2021-11-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob
@@ -126,16 +126,16 @@ dependencies:
126
126
  name: capybara
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
- - - ">="
129
+ - - "~>"
130
130
  - !ruby/object:Gem::Version
131
- version: '0'
131
+ version: 3.35.0
132
132
  type: :development
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
- - - ">="
136
+ - - "~>"
137
137
  - !ruby/object:Gem::Version
138
- version: '0'
138
+ version: 3.35.0
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: database_cleaner
141
141
  requirement: !ruby/object:Gem::Requirement
@@ -280,16 +280,16 @@ dependencies:
280
280
  name: selenium-webdriver
281
281
  requirement: !ruby/object:Gem::Requirement
282
282
  requirements:
283
- - - ">="
283
+ - - "~>"
284
284
  - !ruby/object:Gem::Version
285
- version: '0'
285
+ version: '3.142'
286
286
  type: :development
287
287
  prerelease: false
288
288
  version_requirements: !ruby/object:Gem::Requirement
289
289
  requirements:
290
- - - ">="
290
+ - - "~>"
291
291
  - !ruby/object:Gem::Version
292
- version: '0'
292
+ version: '3.142'
293
293
  - !ruby/object:Gem::Dependency
294
294
  name: sigdump
295
295
  requirement: !ruby/object:Gem::Requirement
@@ -346,31 +346,37 @@ files:
346
346
  - CHANGELOG.md
347
347
  - LICENSE.txt
348
348
  - README.md
349
+ - engine/app/assets/scripts.js
349
350
  - engine/app/assets/style.css
350
351
  - engine/app/assets/vendor/bootstrap/bootstrap.bundle.min.js
351
352
  - engine/app/assets/vendor/bootstrap/bootstrap.min.css
352
- - engine/app/assets/vendor/chartist/chartist.css
353
- - engine/app/assets/vendor/chartist/chartist.js
353
+ - engine/app/assets/vendor/chartjs/chart.min.js
354
+ - engine/app/assets/vendor/rails_ujs.js
355
+ - engine/app/charts/good_job/scheduled_by_queue_chart.rb
354
356
  - engine/app/controllers/good_job/assets_controller.rb
355
357
  - engine/app/controllers/good_job/base_controller.rb
356
- - engine/app/controllers/good_job/cron_schedules_controller.rb
358
+ - engine/app/controllers/good_job/cron_entries_controller.rb
357
359
  - engine/app/controllers/good_job/executions_controller.rb
358
360
  - engine/app/controllers/good_job/jobs_controller.rb
359
361
  - engine/app/filters/good_job/base_filter.rb
360
362
  - engine/app/filters/good_job/executions_filter.rb
361
363
  - engine/app/filters/good_job/jobs_filter.rb
362
364
  - engine/app/helpers/good_job/application_helper.rb
363
- - engine/app/models/good_job/active_job_job.rb
364
- - engine/app/views/good_job/cron_schedules/index.html.erb
365
+ - engine/app/views/good_job/cron_entries/index.html.erb
366
+ - engine/app/views/good_job/cron_entries/show.html.erb
367
+ - engine/app/views/good_job/executions/_table.erb
365
368
  - engine/app/views/good_job/executions/index.html.erb
369
+ - engine/app/views/good_job/jobs/_table.erb
366
370
  - engine/app/views/good_job/jobs/index.html.erb
367
371
  - engine/app/views/good_job/jobs/show.html.erb
368
372
  - engine/app/views/good_job/shared/_chart.erb
369
- - engine/app/views/good_job/shared/_executions_table.erb
370
373
  - engine/app/views/good_job/shared/_filter.erb
371
- - engine/app/views/good_job/shared/_jobs_table.erb
374
+ - engine/app/views/good_job/shared/icons/_arrow_clockwise.html.erb
372
375
  - engine/app/views/good_job/shared/icons/_check.html.erb
373
376
  - engine/app/views/good_job/shared/icons/_exclamation.html.erb
377
+ - engine/app/views/good_job/shared/icons/_play.html.erb
378
+ - engine/app/views/good_job/shared/icons/_skip_forward.html.erb
379
+ - engine/app/views/good_job/shared/icons/_stop.html.erb
374
380
  - engine/app/views/good_job/shared/icons/_trash.html.erb
375
381
  - engine/app/views/layouts/good_job/base.html.erb
376
382
  - engine/config/routes.rb
@@ -380,10 +386,13 @@ files:
380
386
  - lib/generators/good_job/install_generator.rb
381
387
  - lib/generators/good_job/templates/install/migrations/create_good_jobs.rb.erb
382
388
  - lib/generators/good_job/templates/update/migrations/01_create_good_jobs.rb.erb
389
+ - lib/generators/good_job/templates/update/migrations/02_add_cron_at_to_good_jobs.rb.erb
390
+ - lib/generators/good_job/templates/update/migrations/03_add_cron_key_cron_at_index_to_good_jobs.rb.erb
383
391
  - lib/generators/good_job/update_generator.rb
384
392
  - lib/good_job.rb
385
393
  - lib/good_job/active_job_extensions.rb
386
394
  - lib/good_job/active_job_extensions/concurrency.rb
395
+ - lib/good_job/active_job_job.rb
387
396
  - lib/good_job/adapter.rb
388
397
  - lib/good_job/cli.rb
389
398
  - lib/good_job/configuration.rb
@@ -393,6 +402,7 @@ files:
393
402
  - lib/good_job/daemon.rb
394
403
  - lib/good_job/execution.rb
395
404
  - lib/good_job/execution_result.rb
405
+ - lib/good_job/filterable.rb
396
406
  - lib/good_job/job.rb
397
407
  - lib/good_job/job_performer.rb
398
408
  - lib/good_job/lockable.rb
@@ -412,7 +422,7 @@ metadata:
412
422
  documentation_uri: https://rdoc.info/github/bensheldon/good_job
413
423
  homepage_uri: https://github.com/bensheldon/good_job
414
424
  source_code_uri: https://github.com/bensheldon/good_job
415
- post_install_message:
425
+ post_install_message:
416
426
  rdoc_options:
417
427
  - "--title"
418
428
  - GoodJob - a multithreaded, Postgres-based ActiveJob backend for Ruby on Rails
@@ -435,8 +445,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
435
445
  - !ruby/object:Gem::Version
436
446
  version: '0'
437
447
  requirements: []
438
- rubygems_version: 3.1.6
439
- signing_key:
448
+ rubygems_version: 3.2.30
449
+ signing_key:
440
450
  specification_version: 4
441
451
  summary: A multithreaded, Postgres-based ActiveJob backend for Ruby on Rails
442
452
  test_files: []