good_job 4.9.0 → 4.9.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e647f9e6432f6972951bdf902f2de80d9cc0001ee37c9bb355fedbfaae2c0dd6
4
- data.tar.gz: 432488ed94165af462f1702aa08cdf26b771fa2815ba5f345d7acc48fa040a42
3
+ metadata.gz: 257efee85262d3d49be021082bc73cce8dfcf2bea117841a31862bb592158593
4
+ data.tar.gz: d5adc70dcd2894ce5190e07eab45e322c7724f74d96f5a4ccf96bee3c1b35798
5
5
  SHA512:
6
- metadata.gz: 68824597423a7149136168819e25d75f2ae427a574188352a793a0e8405eda879e032c4cfde00decab70e509c353dd3ea89752619099d1f4ddc80771ef3a7035
7
- data.tar.gz: 90c3b1c09665ca5c2cbb3dcda3067949e69e02edb68dfea4ffb42c3a128a9603bde4ffb8ee9082fddab98651b9eea0c7096af71f5653c9af1588df7377e05d6b
6
+ metadata.gz: 003cb3656a88bcd02e8354e315dff667a284d3cae0674ff8b5f73a7cbcfd0ed1b60faf27ff23b14a1d83e395209d4292cfb8e6d771835c4a9418c8ff1977b327
7
+ data.tar.gz: c13483d1ac9f15a720b161905cab041f2c52f7305568b885bfeb0c7fbc514662023cde9ff87fe09f99811536285265aa3d3a4aa31113f73c026f1d39a5521b88
data/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## [v4.9.1](https://github.com/bensheldon/good_job/tree/v4.9.1) (2025-03-09)
4
+
5
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v4.9.0...v4.9.1)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - Order Dashboard jobs in more "natural" order [\#1604](https://github.com/bensheldon/good_job/pull/1604) ([francois](https://github.com/francois))
10
+
11
+ **Fixed bugs:**
12
+
13
+ - \[dashboard\] Scheduled tasks are shown "backwards" [\#1580](https://github.com/bensheldon/good_job/issues/1580)
14
+ - Update `form_with` calls to be compatible with Rails 8 [\#1610](https://github.com/bensheldon/good_job/pull/1610) ([sallyhall](https://github.com/sallyhall))
15
+ - Add index on good\_jobs: \[:concurrency\_key, :created\_at\] to improve performance of throttling \(\#1603\) [\#1605](https://github.com/bensheldon/good_job/pull/1605) ([Intrepidd](https://github.com/Intrepidd))
16
+
17
+ **Closed issues:**
18
+
19
+ - Cron did not enqueue jobs [\#1600](https://github.com/bensheldon/good_job/issues/1600)
20
+ - Same job performed by all threads [\#1599](https://github.com/bensheldon/good_job/issues/1599)
21
+ - Option for bin/rails g good\_job:install to purge Solid gems [\#1593](https://github.com/bensheldon/good_job/issues/1593)
22
+ - Jobs are not being picked up at the expected rate [\#1578](https://github.com/bensheldon/good_job/issues/1578)
23
+ - Question about GoodJob Batches in tests [\#1479](https://github.com/bensheldon/good_job/issues/1479)
24
+
3
25
  ## [v4.9.0](https://github.com/bensheldon/good_job/tree/v4.9.0) (2025-02-07)
4
26
 
5
27
  [Full Changelog](https://github.com/bensheldon/good_job/compare/v4.8.2...v4.9.0)
data/README.md CHANGED
@@ -1032,7 +1032,7 @@ To detect the start of a graceful shutdown from within a performing job, for exa
1032
1032
  ```ruby
1033
1033
  def perform(lots_of_records)
1034
1034
  lots_of_records.each do |record|
1035
- break if GoodJob.current_thread_shutting_down? # or `unless GoodJob.current_thread.running?`
1035
+ break if GoodJob.current_thread_shutting_down? # or `unless GoodJob.current_thread_running?`
1036
1036
  # process record ...
1037
1037
  end
1038
1038
  end
@@ -13,12 +13,15 @@ module GoodJob
13
13
  end
14
14
 
15
15
  def records
16
- after_scheduled_at = params[:after_scheduled_at].present? ? Time.zone.parse(params[:after_scheduled_at]) : nil
16
+ after_at = params[:after_at].present? ? Time.zone.parse(params[:after_at]) : nil
17
+ after_id = params[:after_id] if after_at
18
+ limit = params.fetch(:limit, DEFAULT_LIMIT)
17
19
 
18
20
  query_for_records.display_all(
19
- after_scheduled_at: after_scheduled_at,
20
- after_id: params[:after_id]
21
- ).limit(params.fetch(:limit, DEFAULT_LIMIT))
21
+ ordered_by: ordered_by,
22
+ after_at: after_at,
23
+ after_id: after_id
24
+ ).limit(limit)
22
25
  end
23
26
 
24
27
  def last
@@ -66,6 +69,19 @@ module GoodJob
66
69
  filtered_query.count
67
70
  end
68
71
 
72
+ def ordered_by
73
+ %w[created_at desc]
74
+ end
75
+
76
+ def next_page_params
77
+ order_column = ordered_by.first
78
+
79
+ {
80
+ after_at: records.last&.send(order_column),
81
+ after_id: records.last&.id,
82
+ }.merge(to_params)
83
+ end
84
+
69
85
  private
70
86
 
71
87
  def query_for_records
@@ -2,19 +2,14 @@
2
2
 
3
3
  module GoodJob
4
4
  class BatchesFilter < BaseFilter
5
- def records
6
- after_created_at = params[:after_created_at].present? ? Time.zone.parse(params[:after_created_at]) : nil
7
-
8
- filtered_query.display_all(
9
- after_created_at: after_created_at,
10
- after_id: params[:after_id]
11
- ).limit(params.fetch(:limit, DEFAULT_LIMIT))
12
- end
13
-
14
5
  def filtered_query(_filtered_params = params)
15
6
  base_query
16
7
  end
17
8
 
9
+ def query_for_records
10
+ default_base_query
11
+ end
12
+
18
13
  def default_base_query
19
14
  GoodJob::BatchRecord.includes(:jobs)
20
15
  end
@@ -53,6 +53,19 @@ module GoodJob
53
53
  @_filtered_count ||= filtered_query.unscope(:select).count
54
54
  end
55
55
 
56
+ def ordered_by
57
+ case params[:state]
58
+ when "scheduled", "retried", "pending", "queued"
59
+ %w[scheduled_at asc]
60
+ when "running"
61
+ %w[performed_at desc]
62
+ when "finished", "discarded"
63
+ %w[finished_at desc]
64
+ else
65
+ %w[created_at desc]
66
+ end
67
+ end
68
+
56
69
  private
57
70
 
58
71
  def query_for_records
@@ -7,21 +7,27 @@ module GoodJob
7
7
 
8
8
  included do
9
9
  # Get records in display order with optional keyset pagination.
10
- # @!method display_all(after_scheduled_at: nil, after_id: nil)
10
+ # @!method display_all(ordered_by: ["created_at", "desc"], after_at: nil, after_id: nil)
11
11
  # @!scope class
12
- # @param after_scheduled_at [DateTime, String, nil]
13
- # Display records scheduled after this time for keyset pagination
12
+ # @param ordered_by [Array<String>]
13
+ # Order to display records, from Filter#ordered_by
14
+ # @param after_at [DateTime, String, nil]
15
+ # Display records after this time for keyset pagination
14
16
  # @param after_id [Numeric, String, nil]
15
17
  # Display records after this ID for keyset pagination
16
18
  # @return [ActiveRecord::Relation]
17
- scope :display_all, (lambda do |after_scheduled_at: nil, after_id: nil|
18
- query = order(Arel.sql('scheduled_at DESC, id DESC'))
19
- if after_scheduled_at.present? && after_id.present?
20
- query = query.where Arel::Nodes::Grouping.new([arel_table["scheduled_at"], arel_table["id"]]).lt(Arel::Nodes::Grouping.new([bind_value('scheduled_at', after_scheduled_at, ActiveRecord::Type::DateTime), bind_value('id', after_id, ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Uuid)]))
21
- elsif after_scheduled_at.present?
22
- query = query.where arel_table["scheduled_at"].lt(bind_value('scheduled_at', after_scheduled_at, ActiveRecord::Type::DateTime))
19
+ scope :display_all, (lambda do |ordered_by: %w[created_at desc], after_at: nil, after_id: nil|
20
+ order_column, order_direction = ordered_by
21
+ query = self
22
+
23
+ if after_at.present? && after_id.present?
24
+ query = query.where Arel::Nodes::Grouping.new([arel_table[order_column], arel_table[primary_key]]).send(
25
+ order_direction == 'asc' ? :gteq : :lt,
26
+ Arel::Nodes::Grouping.new([bind_value(order_column, after_at, ActiveRecord::Type::DateTime), bind_value(primary_key, after_id, ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Uuid)])
27
+ )
23
28
  end
24
- query
29
+
30
+ query.order Arel.sql("#{order_column} #{order_direction}, #{primary_key} #{order_direction}")
25
31
  end)
26
32
 
27
33
  # Search records by text query.
@@ -5,6 +5,7 @@ require 'active_job/arguments'
5
5
  module GoodJob
6
6
  class BatchRecord < BaseRecord
7
7
  include AdvisoryLockable
8
+ include Filterable
8
9
 
9
10
  self.table_name = 'good_job_batches'
10
11
  self.implicit_order_column = 'created_at'
@@ -24,20 +25,6 @@ module GoodJob
24
25
  alias_attribute :discarded?, :discarded_at
25
26
  alias_attribute :finished?, :finished_at
26
27
 
27
- scope :display_all, (lambda do |after_created_at: nil, after_id: nil|
28
- query = order(created_at: :desc, id: :desc)
29
- if after_created_at.present? && after_id.present?
30
- query = if Gem::Version.new(Rails.version) < Gem::Version.new('7.0.0.a') || Concurrent.on_jruby?
31
- query.where(Arel.sql('(created_at, id) < (:after_created_at, :after_id)'), after_created_at: after_created_at, after_id: after_id)
32
- else
33
- query.where Arel::Nodes::Grouping.new([arel_table["created_at"], arel_table["id"]]).lt(Arel::Nodes::Grouping.new([bind_value('created_at', after_created_at, ActiveRecord::Type::DateTime), bind_value('id', after_id, ActiveRecord::Type::String)]))
34
- end
35
- elsif after_created_at.present?
36
- query = query.where arel_table["created_at"].lt(bind_value('created_at', after_created_at, ActiveRecord::Type::DateTime))
37
- end
38
- query
39
- end)
40
-
41
28
  def self.jobs_finished_at_migrated?
42
29
  column_names.include?('jobs_finished_at')
43
30
  end
@@ -7,7 +7,7 @@
7
7
  <nav aria-label="Batch pagination" class="mt-3">
8
8
  <ul class="pagination">
9
9
  <li class="page-item">
10
- <%= link_to(@filter.to_params(after_created_at: @filter.last.created_at, after_id: @filter.last.id), class: "page-link") do %>
10
+ <%= link_to(@filter.next_page_params, class: "page-link") do %>
11
11
  <%= t ".older_batches" %> <span aria-hidden="true">&raquo;</span>
12
12
  <% end %>
13
13
  </li>
@@ -1,4 +1,4 @@
1
- <%= form_with(url: mass_update_jobs_path(filter.to_params), method: :put, local: true, data: { "checkbox-toggle": "job_ids" }) do |form| %>
1
+ <%= form_with(model: false, url: mass_update_jobs_path(filter.to_params), method: :put, local: true, data: { "checkbox-toggle": "job_ids" }) do |form| %>
2
2
  <div class="my-3 card" data-gj-poll-replace id="jobs-table">
3
3
  <div class="list-group list-group-flush text-nowrap table-jobs" role="table">
4
4
  <header class="list-group-item bg-body-tertiary">
@@ -7,7 +7,7 @@
7
7
  <nav aria-label="<%= t ".job_pagination" %>" class="mt-3">
8
8
  <ul class="pagination">
9
9
  <li class="page-item">
10
- <%= link_to(@filter.to_params(after_scheduled_at: @filter.last.scheduled_at || @filter.last.created_at, after_id: @filter.last.id), class: "page-link") do %>
10
+ <%= link_to(@filter.next_page_params, class: "page-link") do %>
11
11
  <%= t ".older_jobs" %> <span aria-hidden="true">&raquo;</span>
12
12
  <% end %>
13
13
  </li>
@@ -4,7 +4,7 @@
4
4
  <h2 class="pt-3 pb-2"><%= title %></h2>
5
5
  </div>
6
6
 
7
- <%= form_with(url: "", method: :get, local: true, id: "filter_form", class: "") do |form| %>
7
+ <%= form_with(model: false, url: "", method: :get, local: true, id: "filter_form", class: "") do |form| %>
8
8
  <%= hidden_field_tag :poll, params[:poll] %>
9
9
  <%= hidden_field_tag :state, params[:state] %>
10
10
  <%= hidden_field_tag :locale, params[:locale] if params[:locale] %>
@@ -82,6 +82,7 @@ class CreateGoodJobs < ActiveRecord::Migration<%= migration_version %>
82
82
  add_index :good_jobs, [:queue_name, :scheduled_at], where: "(finished_at IS NULL)", name: :index_good_jobs_on_queue_name_and_scheduled_at
83
83
  add_index :good_jobs, [:active_job_id, :created_at], name: :index_good_jobs_on_active_job_id_and_created_at
84
84
  add_index :good_jobs, :concurrency_key, where: "(finished_at IS NULL)", name: :index_good_jobs_on_concurrency_key_when_unfinished
85
+ add_index :good_jobs, [:concurrency_key, :created_at], name: :index_good_jobs_on_concurrency_key_and_created_at
85
86
  add_index :good_jobs, [:cron_key, :created_at], where: "(cron_key IS NOT NULL)", name: :index_good_jobs_on_cron_key_and_created_at_cond
86
87
  add_index :good_jobs, [:cron_key, :cron_at], where: "(cron_key IS NOT NULL)", unique: true, name: :index_good_jobs_on_cron_key_and_cron_at_cond
87
88
  add_index :good_jobs, [:finished_at], where: "retried_good_job_id IS NULL AND finished_at IS NOT NULL", name: :index_good_jobs_jobs_on_finished_at
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddIndexGoodJobsConcurrencyKeyCreatedAt < ActiveRecord::Migration<%= migration_version %>
4
+ disable_ddl_transaction!
5
+
6
+ def change
7
+ reversible do |dir|
8
+ dir.up do
9
+ # Ensure this incremental update migration is idempotent
10
+ # with monolithic install migration.
11
+ return if connection.index_exists? :good_jobs, [:concurrency_key, :created_at]
12
+ end
13
+ end
14
+
15
+ add_index :good_jobs, [:concurrency_key, :created_at], algorithm: :concurrently
16
+ end
17
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module GoodJob
4
4
  # GoodJob gem version.
5
- VERSION = '4.9.0'
5
+ VERSION = '4.9.1'
6
6
 
7
7
  # GoodJob version as Gem::Version object
8
8
  GEM_VERSION = Gem::Version.new(VERSION)
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: good_job
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.9.0
4
+ version: 4.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Sheldon
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-02-07 00:00:00.000000000 Z
10
+ date: 2025-03-09 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: activejob
@@ -348,6 +348,7 @@ files:
348
348
  - lib/generators/good_job/templates/install/migrations/create_good_jobs.rb.erb
349
349
  - lib/generators/good_job/templates/update/migrations/01_create_good_jobs.rb.erb
350
350
  - lib/generators/good_job/templates/update/migrations/02_add_jobs_finished_at_to_good_job_batches.rb.erb
351
+ - lib/generators/good_job/templates/update/migrations/03_add_index_good_jobs_concurrency_key_created_at.rb.erb
351
352
  - lib/generators/good_job/update_generator.rb
352
353
  - lib/good_job.rb
353
354
  - lib/good_job/active_job_extensions/batches.rb