good_job 2.14.2 → 2.15.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +47 -0
  3. data/README.md +61 -24
  4. data/engine/app/controllers/good_job/jobs_controller.rb +10 -3
  5. data/engine/app/helpers/good_job/application_helper.rb +43 -13
  6. data/engine/app/views/good_job/cron_entries/index.html.erb +1 -1
  7. data/engine/app/views/good_job/jobs/_executions.erb +46 -0
  8. data/engine/app/views/good_job/jobs/_table.erb +13 -1
  9. data/engine/app/views/good_job/jobs/show.html.erb +56 -2
  10. data/engine/app/views/good_job/shared/_filter.erb +1 -1
  11. data/engine/app/views/good_job/shared/icons/_check.html.erb +4 -3
  12. data/engine/app/views/good_job/shared/icons/_clock.html.erb +5 -0
  13. data/engine/app/views/good_job/shared/icons/_dash_circle.html.erb +5 -0
  14. data/engine/app/views/good_job/shared/icons/_exclamation.html.erb +4 -3
  15. data/engine/config/locales/en.yml +13 -0
  16. data/engine/config/locales/es.yml +13 -0
  17. data/engine/config/locales/nl.yml +13 -0
  18. data/engine/config/locales/ru.yml +13 -0
  19. data/engine/config/routes.rb +1 -3
  20. data/lib/good_job/active_job_extensions/concurrency.rb +5 -0
  21. data/lib/good_job/active_job_job.rb +16 -32
  22. data/lib/good_job/cli.rb +5 -5
  23. data/lib/good_job/configuration.rb +10 -0
  24. data/lib/good_job/execution.rb +61 -2
  25. data/lib/good_job/job_performer.rb +1 -1
  26. data/lib/good_job/log_subscriber.rb +2 -2
  27. data/lib/good_job/probe_server.rb +1 -1
  28. data/lib/good_job/version.rb +1 -1
  29. data/lib/good_job.rb +12 -9
  30. metadata +6 -5
  31. data/engine/app/controllers/good_job/executions_controller.rb +0 -10
  32. data/engine/app/views/good_job/executions/_table.erb +0 -62
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 723ecfed33656e11fcabed4f807c7b9735e474cf15701d97faa0d95c84dd4280
4
- data.tar.gz: 210f4f8c8d420d46630292212c015d245c2035e8e79dc1b3148fcb40e1b8dbd0
3
+ metadata.gz: b978264b509be9bd83e1e5e67f291b2889deff86c8316f876ab9def8264a7dd0
4
+ data.tar.gz: 9b4172f97c1c7406d3c51ec81f926adaca801baa004aed743a8cac757bb2ce86
5
5
  SHA512:
6
- metadata.gz: 20f4b5844debd83589f2dc4f9cebe7be963f9dd7ec6749227db7fad00aca4366cdb87003776194695db84f2bc23739b65f866460d92622dbad8923ff690b9799
7
- data.tar.gz: 3528895af9ce6f1d4d2e33dab0b6a2fa60cb255d3211a0cfc5b0e97d0f2444e7270c696872c47562dd18d822a95a550345068073a50f80d7ebdd0c9eac782bf8
6
+ metadata.gz: a2696f821699ea080d2b77bb7704192222e6347d1b2be7fcf7a5e049d7b8979ceea3f5236c165fb27bb17fa5f9d8c7d2e5bb863b9c7298de9efb892facb6bca7
7
+ data.tar.gz: 0ab7a37d403e1bec9c0638835c903aff4a1168f33143c7c27acd87c07bf95a61c1475c3e61bea0a7590d167144761cc64b7bc81566746c854c2ae9698aa999fe
data/CHANGELOG.md CHANGED
@@ -1,5 +1,52 @@
1
1
  # Changelog
2
2
 
3
+ ## [v2.15.0](https://github.com/bensheldon/good_job/tree/v2.15.0) (2022-05-18)
4
+
5
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v2.14.4...v2.15.0)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - Adds the ability to delete jobs on the dashboard; add `cleanup_discarded_jobs` option to retain discarded jobs during cleanup [\#597](https://github.com/bensheldon/good_job/pull/597) ([TAGraves](https://github.com/TAGraves))
10
+ - Dashboard: show more details about jobs [\#575](https://github.com/bensheldon/good_job/pull/575) ([bkeepers](https://github.com/bkeepers))
11
+
12
+ **Closed issues:**
13
+
14
+ - Show status on jobs\#show page [\#547](https://github.com/bensheldon/good_job/issues/547)
15
+
16
+ **Merged pull requests:**
17
+
18
+ - Remove ability to destroy individual Executions from Dashboard; rename "Toggle" to "Inspect" everywhere [\#601](https://github.com/bensheldon/good_job/pull/601) ([bensheldon](https://github.com/bensheldon))
19
+ - Disable ActiveRecord Connection Reaper in test [\#600](https://github.com/bensheldon/good_job/pull/600) ([bensheldon](https://github.com/bensheldon))
20
+ - Update README dashboard screenshot [\#599](https://github.com/bensheldon/good_job/pull/599) ([aried3r](https://github.com/aried3r))
21
+
22
+ ## [v2.14.4](https://github.com/bensheldon/good_job/tree/v2.14.4) (2022-05-15)
23
+
24
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v2.14.3...v2.14.4)
25
+
26
+ **Fixed bugs:**
27
+
28
+ - Fix Concurrency extension to not break `perform_now` [\#593](https://github.com/bensheldon/good_job/pull/593) ([bensheldon](https://github.com/bensheldon))
29
+
30
+ ## [v2.14.3](https://github.com/bensheldon/good_job/tree/v2.14.3) (2022-05-13)
31
+
32
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v2.14.2...v2.14.3)
33
+
34
+ **Fixed bugs:**
35
+
36
+ - Bind probe server to all interfaces [\#598](https://github.com/bensheldon/good_job/pull/598) ([Timmitry](https://github.com/Timmitry))
37
+
38
+ **Closed issues:**
39
+
40
+ - NoMethodError: undefined method `current\_tags' for nil:NilClass [\#596](https://github.com/bensheldon/good_job/issues/596)
41
+ - When running rspec, I get: current transaction is aborted, commands ignored until end of transaction block [\#595](https://github.com/bensheldon/good_job/issues/595)
42
+ - CLI healtheck only listening on localhost, not reachable for Kubernetes [\#592](https://github.com/bensheldon/good_job/issues/592)
43
+
44
+ **Merged pull requests:**
45
+
46
+ - Improve development instructions and tooling \(rename bin/rails, add bin/appraisal\) [\#590](https://github.com/bensheldon/good_job/pull/590) ([bensheldon](https://github.com/bensheldon))
47
+ - Replace test Instrumentation mocking with temporary subscriptions [\#589](https://github.com/bensheldon/good_job/pull/589) ([bensheldon](https://github.com/bensheldon))
48
+ - Update to development to Ruby 3.0.4, include `matrix` gem in development Gemfile [\#588](https://github.com/bensheldon/good_job/pull/588) ([bensheldon](https://github.com/bensheldon))
49
+
3
50
  ## [v2.14.2](https://github.com/bensheldon/good_job/tree/v2.14.2) (2022-05-01)
4
51
 
5
52
  [Full Changelog](https://github.com/bensheldon/good_job/compare/v2.14.1...v2.14.2)
data/README.md CHANGED
@@ -60,6 +60,9 @@ For more of the story of GoodJob, read the [introductory blog post](https://isla
60
60
  - [CLI HTTP health check probes](#cli-http-health-check-probes)
61
61
  - [Contribute](#contribute)
62
62
  - [Gem development](#gem-development)
63
+ - [Development setup](#development-setup)
64
+ - [Rails development harness](#rails-development-harness)
65
+ - [Running tests](#running-tests)
63
66
  - [Release](#release)
64
67
  - [License](#license)
65
68
 
@@ -182,7 +185,7 @@ separate isolated execution pools with semicolons and threads with colons.
182
185
 
183
186
  #### `good_job cleanup_preserved_jobs`
184
187
 
185
- `good_job cleanup_preserved_jobs` deletes preserved job records. See `GoodJob.preserve_job_records` for when this command is useful.
188
+ `good_job cleanup_preserved_jobs` destroys preserved job records. See `GoodJob.preserve_job_records` for when this command is useful.
186
189
 
187
190
  ```bash
188
191
  $ bundle exec good_job help cleanup_preserved_jobs
@@ -191,11 +194,11 @@ Usage:
191
194
  good_job cleanup_preserved_jobs
192
195
 
193
196
  Options:
194
- [--before-seconds-ago=SECONDS] # Delete records finished more than this many seconds ago (env var: GOOD_JOB_CLEANUP_PRESERVED_JOBS_BEFORE_SECONDS_AGO, default: 86400)
197
+ [--before-seconds-ago=SECONDS] # Destroy records finished more than this many seconds ago (env var: GOOD_JOB_CLEANUP_PRESERVED_JOBS_BEFORE_SECONDS_AGO, default: 86400)
195
198
 
196
- Deletes preserved job records.
199
+ Destroys preserved job records.
197
200
 
198
- By default, GoodJob deletes job records when the job is performed and this
201
+ By default, GoodJob destroys job records when the job is performed and this
199
202
  command is not necessary.
200
203
 
201
204
  However, when `GoodJob.preserve_job_records = true`, the jobs will be
@@ -203,7 +206,7 @@ preserved in the database. This is useful when wanting to analyze or
203
206
  inspect job performance.
204
207
 
205
208
  If you are preserving job records this way, use this command regularly
206
- to delete old records and preserve space in your database.
209
+ to destroy old records and preserve space in your database.
207
210
  ```
208
211
 
209
212
  ### Configuration options
@@ -266,6 +269,7 @@ Available configuration options are:
266
269
  - `shutdown_timeout` (float) number of seconds to wait for jobs to finish when shutting down before stopping the thread. Defaults to forever: `-1`. You can also set this with the environment variable `GOOD_JOB_SHUTDOWN_TIMEOUT`.
267
270
  - `enable_cron` (boolean) whether to run cron process. Defaults to `false`. You can also set this with the environment variable `GOOD_JOB_ENABLE_CRON`.
268
271
  - `cron` (hash) cron configuration. Defaults to `{}`. You can also set this as a JSON string with the environment variable `GOOD_JOB_CRON`
272
+ - `cleanup_discarded_jobs` (boolean) whether to destroy discarded jobs when cleaning up preserved jobs using the `$ good_job cleanup_preserved_jobs` CLI command or calling `GoodJob.cleanup_preserved_jobs`. Defaults to `true`. Can also be set with the environment variable `GOOD_JOB_CLEANUP_DISCARDED_JOBS`. _This configuration is only used when {GoodJob.preserve_job_records} is `true`._
269
273
  - `cleanup_preserved_jobs_before_seconds_ago` (integer) number of seconds to preserve jobs when using the `$ good_job cleanup_preserved_jobs` CLI command or calling `GoodJob.cleanup_preserved_jobs`. Defaults to `86400` (1 day). Can also be set with the environment variable `GOOD_JOB_CLEANUP_PRESERVED_JOBS_BEFORE_SECONDS_AGO`. _This configuration is only used when {GoodJob.preserve_job_records} is `true`._
270
274
  - `cleanup_interval_jobs` (integer) Number of jobs a Scheduler will execute before cleaning up preserved jobs. Defaults to `nil`. Can also be set with the environment variable `GOOD_JOB_CLEANUP_INTERVAL_JOBS`.
271
275
  - `cleanup_interval_seconds` (integer) Number of seconds a Scheduler will wait before cleaning up preserved jobs. Defaults to `nil`. Can also be set with the environment variable `GOOD_JOB_CLEANUP_INTERVAL_SECONDS`.
@@ -823,7 +827,7 @@ If your application is already using an ActiveJob backend, you will need to inst
823
827
 
824
828
  GoodJob is fully instrumented with [`ActiveSupport::Notifications`](https://edgeguides.rubyonrails.org/active_support_instrumentation.html#introduction-to-instrumentation).
825
829
 
826
- By default, GoodJob will delete job records after they are run, regardless of whether they succeed or not (raising a kind of `StandardError`), unless they are interrupted (raising a kind of `Exception`).
830
+ By default, GoodJob will destroy job records after they are run, regardless of whether they succeed or not (raising a kind of `StandardError`), unless they are interrupted (raising a kind of `Exception`).
827
831
 
828
832
  To preserve job records for later inspection, set an initializer:
829
833
 
@@ -832,7 +836,7 @@ To preserve job records for later inspection, set an initializer:
832
836
  GoodJob.preserve_job_records = true
833
837
  ```
834
838
 
835
- It is also necessary to delete these preserved jobs from the database after a certain time period:
839
+ It is also necessary to destroy these preserved jobs from the database after a certain time period:
836
840
 
837
841
  - For example, in a Rake task:
838
842
 
@@ -941,49 +945,82 @@ spec:
941
945
 
942
946
  ## Contribute
943
947
 
944
- Contributions are welcomed and appreciated 🙏
948
+ <!-- Please keep this section in sync with CONTRIBUTING.md -->
949
+
950
+ All contributions, from feedback to code and beyond, are welcomed and appreciated 🙏
945
951
 
946
952
  - Review the [Prioritized Project Backlog](https://github.com/bensheldon/good_job/projects/1).
947
953
  - Open a new Issue or contribute to an [existing Issue](https://github.com/bensheldon/good_job/issues). Questions or suggestions are fantastic.
948
- - Participate according to our [Code of Conduct](https://github.com/bensheldon/good_job/projects/1).
954
+ - Participate according to our [Code of Conduct](/CODE_OF_CONDUCT.md).
955
+ - Financially support the project via [Sponsorship](https://github.com/sponsors/bensheldon).
956
+
957
+ For gem development and debugging information, please review the [README's Gem Development section](/README.md#gem-development).
949
958
 
950
959
  ### Gem development
951
960
 
952
- To run tests:
961
+ #### Development setup
953
962
 
954
963
  ```bash
955
964
  # Clone the repository locally
956
- $ git clone git@github.com:bensheldon/good_job.git
965
+ git clone git@github.com:bensheldon/good_job.git
957
966
 
958
967
  # Set up the local environment
959
- $ bin/setup
960
-
961
- # Run the tests
962
- $ bin/rspec
968
+ bin/setup
963
969
  ```
964
970
 
965
- This gem uses Appraisal to run tests against multiple versions of Rails:
971
+ #### Rails development harness
972
+
973
+ A Rails application exists within `spec/test_app` that is used for development, test, and GoodJob Demo environments.
966
974
 
967
975
  ```bash
968
- # Install Appraisal(s) gemfiles
969
- $ bundle exec appraisal
976
+ # Run a local development webserver
977
+ bin/rails s
978
+
979
+ # Disable job execution and cron for cleaner console output
980
+ GOOD_JOB_ENABLE_CRON=0 GOOD_JOB_EXECUTION_MODE=external bin/rails s
970
981
 
971
- # Run tests
972
- $ bundle exec appraisal bin/rspec
982
+ # Open the Rails console
983
+ bin/rails c
973
984
  ```
974
985
 
975
986
  For developing locally within another Ruby on Rails project:
976
987
 
977
988
  ```bash
978
- # Within Ruby on Rails directory...
979
- $ bundle config local.good_job /path/to/local/git/repository
989
+ # Within Ruby on Rails project directory
990
+ # Ensure that the Gemfile is set to git with a branch e.g.
991
+ # gem "good_job", git: "https://github.com/bensheldon/good_job.git", branch: "main"
992
+ # Then, override the Bundle config to point to the local filesystem's good_job repository
993
+ bundle config local.good_job /path/to/local/good_job/repository
980
994
 
981
995
  # Confirm that the local copy is used
982
- $ bundle install
996
+ bundle install
983
997
 
984
998
  # => Using good_job 0.1.0 from https://github.com/bensheldon/good_job.git (at /Users/You/Projects/good_job@dc57fb0)
985
999
  ```
986
1000
 
1001
+ #### Running tests
1002
+
1003
+ Tests can be run against the primary development environment:
1004
+
1005
+ ```bash
1006
+ bin/rspec
1007
+ ```
1008
+
1009
+ Environment variables that may help with debugging:
1010
+
1011
+ - `LOUD=1`: display all stdout/stderr output from all sources. This is helpful because GoodJob wraps some tests with `quiet { }` for cleaner test output, but it can hinder debugging.
1012
+ - `SHOW_BROWSER=1`: Run system tests headfully with Chrome/Chromedriver. Use `binding.irb` in the system tests to pause.
1013
+
1014
+ Appraisal can be used to run a test matrix of multiple versions of Rails:
1015
+
1016
+ ```bash
1017
+ # Install Appraisal matrix of gemfiles
1018
+ bin/appraisal
1019
+
1020
+ # Run tests against matrix
1021
+ bin/appraisal bin/rspec
1022
+ ```
1023
+
987
1024
  ### Release
988
1025
 
989
1026
  Package maintainers can release this gem by running:
@@ -996,7 +1033,7 @@ $ gem signin
996
1033
  # CHANGELOG_GITHUB_TOKEN= # Github Personal Access Token
997
1034
 
998
1035
  # Update version number, changelog, and create git commit:
999
- $ bundle exec rake release[minor] # major,minor,patch
1036
+ $ bundle exec rake release_good_job[minor] # major,minor,patch
1000
1037
 
1001
1038
  # ..and follow subsequent directions.
1002
1039
  ```
@@ -7,6 +7,7 @@ module GoodJob
7
7
  discard: "discarded",
8
8
  reschedule: "rescheduled",
9
9
  retry: "retried",
10
+ destroy: "destroyed",
10
11
  }.freeze
11
12
 
12
13
  rescue_from GoodJob::ActiveJobJob::AdapterNotGoodJobError,
@@ -36,6 +37,8 @@ module GoodJob
36
37
  job.reschedule_job
37
38
  when :retry
38
39
  job.retry_job
40
+ when :destroy
41
+ job.destroy_job
39
42
  end
40
43
 
41
44
  job
@@ -53,9 +56,7 @@ module GoodJob
53
56
  end
54
57
 
55
58
  def show
56
- @executions = GoodJob::Execution.active_job_id(params[:id])
57
- .order(Arel.sql("COALESCE(scheduled_at, created_at) DESC"))
58
- redirect_to jobs_path, alert: "Executions for Active Job #{params[:id]} not found" if @executions.empty?
59
+ @job = ActiveJobJob.find(params[:id])
59
60
  end
60
61
 
61
62
  def discard
@@ -76,6 +77,12 @@ module GoodJob
76
77
  redirect_back(fallback_location: jobs_path, notice: "Job has been retried")
77
78
  end
78
79
 
80
+ def destroy
81
+ @job = ActiveJobJob.find(params[:id])
82
+ @job.destroy_job
83
+ redirect_back(fallback_location: jobs_path, notice: "Job has been destroyed")
84
+ end
85
+
79
86
  private
80
87
 
81
88
  def redirect_on_error(exception)
@@ -1,24 +1,54 @@
1
1
  # frozen_string_literal: true
2
2
  module GoodJob
3
3
  module ApplicationHelper
4
- def relative_time(timestamp)
5
- text = timestamp.future? ? "in #{time_ago_in_words(timestamp)}" : "#{time_ago_in_words(timestamp)} ago"
4
+ def format_duration(sec)
5
+ return unless sec
6
+
7
+ if sec < 1
8
+ t 'duration.milliseconds', ms: (sec * 1000).floor
9
+ elsif sec < 10
10
+ t 'duration.less_than_10_seconds', sec: sec.floor
11
+ elsif sec < 60
12
+ t 'duration.seconds', sec: sec.floor
13
+ elsif sec < 3600
14
+ t 'duration.minutes', min: (sec / 60).floor, sec: (sec % 60).floor
15
+ else
16
+ t 'duration.hours', hour: (sec / 3600).floor, min: ((sec % 3600) / 60).floor
17
+ end
18
+ end
19
+
20
+ def relative_time(timestamp, **args)
21
+ text = timestamp.future? ? "in #{time_ago_in_words(timestamp, **args)}" : "#{time_ago_in_words(timestamp, **args)} ago"
6
22
  tag.time(text, datetime: timestamp, title: timestamp)
7
23
  end
8
24
 
25
+ STATUS_ICONS = {
26
+ discarded: "exclamation",
27
+ finished: "check",
28
+ queued: "dash_circle",
29
+ retried: "arrow_clockwise",
30
+ running: "play",
31
+ scheduled: "clock",
32
+ }.freeze
33
+
34
+ STATUS_COLOR = {
35
+ discarded: "danger",
36
+ finished: "success",
37
+ queued: "warning",
38
+ retried: "secondary",
39
+ running: "primary",
40
+ scheduled: "secondary",
41
+ }.freeze
42
+
9
43
  def status_badge(status)
10
- classes = case status
11
- when :finished
12
- "badge rounded-pill bg-success"
13
- when :queued, :scheduled, :retried
14
- "badge rounded-pill bg-secondary"
15
- when :running
16
- "badge rounded-pill bg-primary"
17
- when :discarded
18
- "badge rounded-pill bg-danger"
19
- end
44
+ content_tag :span, status_icon(status, class: "text-white") + t(status, scope: '.status'),
45
+ class: "badge rounded-pill bg-#{STATUS_COLOR.fetch(status)} d-inline-flex gap-2 ps-1 pe-3 align-items-center"
46
+ end
20
47
 
21
- content_tag :span, status.to_s, class: classes
48
+ def status_icon(status, **options)
49
+ options[:class] ||= "text-#{STATUS_COLOR.fetch(status)}"
50
+ icon = render_icon STATUS_ICONS.fetch(status)
51
+ content_tag :span, icon, **options
22
52
  end
23
53
 
24
54
  def render_icon(name)
@@ -27,7 +27,7 @@
27
27
  <td class="font-monospace"><%= cron_entry.key %></td>
28
28
  <td class="font-monospace"><%= cron_entry.schedule %></td>
29
29
  <td>
30
- <%= tag.button("Preview", type: "button", class: "btn btn-sm btn-outline-primary", role: "button",
30
+ <%= tag.button("Inspect", type: "button", class: "btn btn-sm btn-outline-primary", role: "button",
31
31
  data: { bs_toggle: "collapse", bs_target: "##{dom_id(cron_entry, 'properties')}" },
32
32
  aria: { expanded: false, controls: dom_id(cron_entry, 'properties') }) %>
33
33
  <%= tag.pre(JSON.pretty_generate(cron_entry.display_properties), id: dom_id(cron_entry, 'properties'), class: "collapse cron-entry-properties") %>
@@ -0,0 +1,46 @@
1
+ <h5>Executions</h5>
2
+ <div class="card mb-4" data-live-poll-region="executions-table">
3
+ <div class="list-group list-group-flush">
4
+ <% executions.each do |execution| %>
5
+ <%= tag.div id: dom_id(execution), class: "list-group-item py-3" do %>
6
+ <div class="d-md-flex">
7
+ <div class="flex-fill">
8
+ <div class="small text-muted">
9
+ #<%= execution.number %>:
10
+ <%= tag.code link_to(execution.id, "##{dom_id(execution)}", class: "text-muted text-decoration-none") %>
11
+ </div>
12
+ <div class="d-flex gap-2 align-items-center text-muted mt-1">
13
+ <%= status_badge execution.status %>
14
+ <%= relative_time execution.last_status_at, include_seconds: true %>
15
+
16
+ <% if execution.runtime_latency %>
17
+ • <div><%= format_duration execution.runtime_latency %> runtime</div>
18
+ <% end %>
19
+ <% if execution.queue_latency %>
20
+ • <div><%= format_duration execution.queue_latency %> in queue</div>
21
+ <% end %>
22
+ </div>
23
+ </div>
24
+ <div>
25
+ <div class="mt-4 d-flex gap-2">
26
+ <%= tag.button type: "button", class: "btn btn-sm btn-outline-primary", role: "button",
27
+ data: { bs_toggle: "collapse", bs_target: "##{dom_id(execution, 'params')}" },
28
+ aria: { expanded: false, controls: dom_id(execution, "params") } do %>
29
+ Inspect
30
+ <% end %>
31
+ </div>
32
+ </div>
33
+ </div>
34
+ <% if execution.error %>
35
+ <div class="mt-3">
36
+ <strong class="small">Error:</strong>
37
+ <pre class="text-wrap text-break m-0"><%= execution.error %></pre>
38
+ </div>
39
+ <% end %>
40
+ <div>
41
+ <%= tag.pre JSON.pretty_generate(execution.serialized_params), id: dom_id(execution, "params"), class: "collapse bg-light card card-body p-3 my-3" %>
42
+ </div>
43
+ <% end %>
44
+ <% end %>
45
+ </div>
46
+ </div>
@@ -34,6 +34,10 @@
34
34
  <%= form.button type: 'submit', name: 'mass_action', value: 'retry', class: 'btn btn-sm btn-outline-primary', title: "Retry all", data: { confirm: "Confirm retry all", disable: true } do %>
35
35
  <%= render_icon "arrow_clockwise" %> All
36
36
  <% end %>
37
+
38
+ <%= form.button type: 'submit', name: 'mass_action', value: 'destroy', class: 'btn btn-sm btn-outline-primary', title: "Destroy all", data: { confirm: "Confirm destroy all", disable: true } do %>
39
+ <%= render_icon "trash" %> All
40
+ <% end %>
37
41
  </div>
38
42
  </th>
39
43
  </tr>
@@ -63,7 +67,7 @@
63
67
  <td><%= job.executions_count %></td>
64
68
  <td class="text-break"><%= truncate(job.recent_error, length: 1_000) %></td>
65
69
  <td>
66
- <%= tag.button "Preview", type: "button", class: "btn btn-sm btn-outline-primary", role: "button",
70
+ <%= tag.button "Inspect", type: "button", class: "btn btn-sm btn-outline-primary", role: "button",
67
71
  data: { bs_toggle: "collapse", bs_target: "##{dom_id(job, 'params')}" },
68
72
  aria: { expanded: false, controls: dom_id(job, "params") }
69
73
  %>
@@ -94,6 +98,14 @@
94
98
  <% else %>
95
99
  <button class="btn btn-sm btn-outline-secondary" disabled><%= render_icon "arrow_clockwise" %></button>
96
100
  <% end %>
101
+
102
+ <% if job.status.in? [:discarded, :finished] %>
103
+ <%= link_to job_path(job.id), method: :delete, class: "btn btn-sm btn-outline-primary", title: "Destroy job", data: { confirm: "Confirm destroy", disable: true } do %>
104
+ <%= render_icon "trash" %>
105
+ <% end %>
106
+ <% else %>
107
+ <button class="btn btn-sm btn-outline-secondary" disabled><%= render_icon "trash" %></button>
108
+ <% end %>
97
109
  </div>
98
110
  </td>
99
111
  </tr>
@@ -1,3 +1,57 @@
1
- <h1 class="mb-3">ActiveJob ID: <code><%= @executions.first.id %></code></h1>
1
+ <div class="break-out bg-light border-bottom py-2 mb-3">
2
+ <div class="container-fluid pt-2">
3
+ <div class="d-flex align-items-center">
4
+ <div class="flex-fill">
5
+ <nav aria-label="breadcrumb">
6
+ <ol class="breadcrumb small mb-0">
7
+ <li class="breadcrumb-item"><%= link_to "Jobs", jobs_path %></li>
8
+ <li class="breadcrumb-item active" aria-current="page">ActiveJob ID: <%= tag.code @job.id %></li>
9
+ </ol>
10
+ </nav>
11
+ <h2 class="mb-1"><%= tag.code @job.job_class %></h2>
12
+ <div class="text-muted small d-flex gap-2">
13
+ <div>Queue: <%= tag.strong @job.queue_name %></div>
14
+
15
+ <div>Priority: <%= tag.strong @job.priority %></div>
16
+ </div>
17
+ </div>
18
+ <div>
19
+ <% job_reschedulable = @job.status.in? [:scheduled, :retried, :queued] %>
20
+ <%= button_to reschedule_job_path(@job.id), method: :put,
21
+ class: "btn btn-sm #{job_reschedulable ? 'btn-outline-primary' : 'btn-outline-secondary'}",
22
+ form_class: "d-inline-block",
23
+ disabled: !job_reschedulable,
24
+ aria: { label: "Reschedule job" },
25
+ title: "Reschedule job",
26
+ data: { confirm: "Confirm reschedule" } do %>
27
+ <%= render_icon "skip_forward" %>
28
+ Reschedule
29
+ <% end %>
2
30
 
3
- <%= render 'good_job/executions/table', executions: @executions %>
31
+ <% job_discardable = @job.status.in? [:scheduled, :retried, :queued] %>
32
+ <%= button_to discard_job_path(@job.id), method: :put, class: "btn btn-sm #{job_discardable ? 'btn-outline-primary' : 'btn-outline-secondary'}", form_class: "d-inline-block", disabled: !job_discardable, aria: { label: "Discard job" }, title: "Discard job", data: { confirm: "Confirm discard" } do %>
33
+ <%= render_icon "stop" %>
34
+ Discard
35
+ <% end %>
36
+
37
+ <%= button_to retry_job_path(@job.id), method: :put, class: "btn btn-sm #{@job.status == :discarded ? 'btn-outline-primary' : 'btn-outline-secondary'}", form_class: "d-inline-block", disabled: @job.status != :discarded, aria: { label: "Retry job" }, title: "Retry job", data: { confirm: "Confirm retry" } do %>
38
+ <%= render_icon "arrow_clockwise" %>
39
+ Retry
40
+ <% end %>
41
+
42
+ <% job_destroyable = @job.status.in? [:discarded, :finished] %>
43
+ <%= button_to job_path(@job.id), method: :delete, class: "btn btn-sm #{job_destroyable ? 'btn-outline-primary' : 'btn-outline-secondary'}", form_class: "d-inline-block", disabled: !job_destroyable, aria: { label: "Destroy job" }, title: "Destroy job", data: { confirm: "Confirm destroy" } do %>
44
+ <%= render_icon "trash" %>
45
+ Destroy
46
+ <% end %>
47
+ </div>
48
+ </div>
49
+ </div>
50
+ </div>
51
+
52
+ <div class="my-4">
53
+ <h5>Arguments</h5>
54
+ <%= tag.pre @job.serialized_params["arguments"].map(&:inspect).join(', ') %>
55
+ </div>
56
+
57
+ <%= render 'executions', executions: @job.executions.reverse %>
@@ -10,7 +10,7 @@
10
10
  <% filter.states.each do |name, count| %>
11
11
  <li class="nav-item">
12
12
  <%= link_to url_for({state: name}), class: "nav-link #{"active" if params[:state] == name}" do %>
13
- <%= name.titleize %>
13
+ <%= t(name, scope: '.status') %>
14
14
  <span class="badge bg-primary rounded-pill <%= "bg-secondary" if count == 0 %>"><%= count %></span>
15
15
  <% end %>
16
16
  </li>
@@ -1,4 +1,5 @@
1
- <!-- https://icons.getbootstrap.com/icons/check-circle-fill/ -->
2
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-check-circle-fill <%= local_assigns[:class] %>" viewBox="0 0 16 16">
3
- <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z" />
1
+ <!-- https://icons.getbootstrap.com/icons/check-circle/ -->
2
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-check-circle" viewBox="0 0 16 16">
3
+ <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
4
+ <path d="M10.97 4.97a.235.235 0 0 0-.02.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-1.071-1.05z" />
4
5
  </svg>
@@ -0,0 +1,5 @@
1
+ <!-- https://icons.getbootstrap.com/icons/clock/ -->
2
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-clock" viewBox="0 0 16 16">
3
+ <path d="M8 3.5a.5.5 0 0 0-1 0V9a.5.5 0 0 0 .252.434l3.5 2a.5.5 0 0 0 .496-.868L8 8.71V3.5z" />
4
+ <path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm7-8A7 7 0 1 1 1 8a7 7 0 0 1 14 0z" />
5
+ </svg>
@@ -0,0 +1,5 @@
1
+ <!-- https://icons.getbootstrap.com/icons/dash-circle/ -->
2
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-dash-circle" viewBox="0 0 16 16">
3
+ <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
4
+ <path d="M4 8a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 4 8z" />
5
+ </svg>
@@ -1,4 +1,5 @@
1
- <!-- https://icons.getbootstrap.com/icons/exclamation-triangle-fill/ -->
2
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-exclamation-triangle-fill <%= local_assigns[:class] %>" viewBox="0 0 16 16">
3
- <path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z" />
1
+ <!-- https://icons.getbootstrap.com/icons/exclamation-circle/ -->
2
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-exclamation-circle" viewBox="0 0 16 16">
3
+ <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
4
+ <path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z" />
4
5
  </svg>
@@ -39,6 +39,12 @@ en:
39
39
  x_years:
40
40
  one: 1 year
41
41
  other: "%{count} years"
42
+ duration:
43
+ hours: "%{hour}h %{min}m"
44
+ less_than_10_seconds: "%{sec}s"
45
+ milliseconds: "%{ms}ms"
46
+ minutes: "%{min}m %{sec}s"
47
+ seconds: "%{sec}s"
42
48
  good_job:
43
49
  shared:
44
50
  footer:
@@ -50,3 +56,10 @@ en:
50
56
  live_poll: Live Poll
51
57
  name: "GoodJob 👍"
52
58
  processes: Processes
59
+ status:
60
+ discarded: Discarded
61
+ finished: Finished
62
+ queued: Queued
63
+ retried: Retried
64
+ running: Running
65
+ scheduled: Scheduled
@@ -39,6 +39,12 @@ es:
39
39
  x_years:
40
40
  one: 1 año
41
41
  other: "%{count} años"
42
+ duration:
43
+ hours: "%{hour}h %{min}m"
44
+ less_than_10_seconds: "%{sec}s"
45
+ milliseconds: "%{ms}ms"
46
+ minutes: "%{min}m %{sec}s"
47
+ seconds: "%{sec}s"
42
48
  good_job:
43
49
  shared:
44
50
  footer:
@@ -50,3 +56,10 @@ es:
50
56
  live_poll: En vivo
51
57
  name: "GoodJob 👍"
52
58
  processes: Procesos
59
+ status:
60
+ discarded: Descartado
61
+ finished: Acabado
62
+ queued: Puesto en cola
63
+ retried: reintentado
64
+ running: Corriendo
65
+ scheduled: Programado
@@ -39,6 +39,12 @@ nl:
39
39
  x_years:
40
40
  one: 1 jaar
41
41
  other: "%{count} jaren"
42
+ duration:
43
+ hours: "%{hour}h %{min}m"
44
+ less_than_10_seconds: "%{sec}s"
45
+ milliseconds: "%{ms}ms"
46
+ minutes: "%{min}m %{sec}s"
47
+ seconds: "%{sec}s"
42
48
  good_job:
43
49
  shared:
44
50
  footer:
@@ -50,3 +56,10 @@ nl:
50
56
  live_poll: Live Poll
51
57
  name: "GoodJob 👍"
52
58
  processes: Processen
59
+ status:
60
+ discarded: weggegooid
61
+ finished: Afgewerkt
62
+ queued: In de wachtrij
63
+ retried: Opnieuw geprobeerd
64
+ running: Rennen
65
+ scheduled: Gepland
@@ -63,6 +63,12 @@ ru:
63
63
  many: "%{count} лет"
64
64
  one: 1 год
65
65
  other: "%{count} года"
66
+ duration:
67
+ hours: "%{hour}h %{min}m"
68
+ less_than_10_seconds: "%{sec}s"
69
+ milliseconds: "%{ms}мс"
70
+ minutes: "%{min}м %{sec}с"
71
+ seconds: "%{sec}s"
66
72
  good_job:
67
73
  shared:
68
74
  footer:
@@ -74,3 +80,10 @@ ru:
74
80
  live_poll: Живой Опрос
75
81
  name: "GoodJob 👍"
76
82
  processes: Процессы
83
+ status:
84
+ discarded: Отброшено
85
+ finished: Законченный
86
+ queued: В очереди
87
+ retried: Повторная попытка
88
+ running: Бег
89
+ scheduled: по расписанию
@@ -2,9 +2,7 @@
2
2
  GoodJob::Engine.routes.draw do
3
3
  root to: redirect(path: 'jobs')
4
4
 
5
- resources :executions, only: %i[destroy]
6
-
7
- resources :jobs, only: %i[index show] do
5
+ resources :jobs, only: %i[index show destroy] do
8
6
  collection do
9
7
  get :mass_update, to: redirect(path: 'jobs')
10
8
  put :mass_update
@@ -61,6 +61,11 @@ module GoodJob
61
61
  key = job.good_job_concurrency_key
62
62
  next if key.blank?
63
63
 
64
+ if CurrentThread.execution.blank?
65
+ logger.debug("Ignoring concurrency limits because the job is executed with `perform_now`.")
66
+ next
67
+ end
68
+
64
69
  GoodJob::Execution.advisory_lock_key(key, function: "pg_advisory_lock") do
65
70
  allowed_active_job_ids = GoodJob::Execution.unfinished.where(concurrency_key: key).advisory_locked.order(Arel.sql("COALESCE(performed_at, scheduled_at, created_at) ASC")).limit(perform_limit).pluck(:active_job_id)
66
71
  # The current job has already been locked and will appear in the previous query
@@ -54,9 +54,11 @@ module GoodJob
54
54
  # Advisory locked and executing
55
55
  scope :running, -> { where(finished_at: nil).joins_advisory_locks.where.not(pg_locks: { locktype: nil }) }
56
56
  # Completed executing successfully
57
- scope :finished, -> { where.not(finished_at: nil).where(error: nil) }
57
+ scope :finished, -> { not_discarded.where.not(finished_at: nil) }
58
58
  # Errored but will not be retried
59
59
  scope :discarded, -> { where.not(finished_at: nil).where.not(error: nil) }
60
+ # Not errored
61
+ scope :not_discarded, -> { where(error: nil) }
60
62
 
61
63
  # The job's ActiveJob UUID
62
64
  # @return [String]
@@ -71,38 +73,8 @@ module GoodJob
71
73
  end
72
74
 
73
75
  # The status of the Job, based on the state of its most recent execution.
74
- # There are 3 buckets of non-overlapping statuses:
75
- # 1. The job will be executed
76
- # - queued: The job will execute immediately when an execution thread becomes available.
77
- # - scheduled: The job is scheduled to execute in the future.
78
- # - retried: The job previously errored on execution and will be re-executed in the future.
79
- # 2. The job is being executed
80
- # - running: the job is actively being executed by an execution thread
81
- # 3. The job will not execute
82
- # - finished: The job executed successfully
83
- # - discarded: The job previously errored on execution and will not be re-executed in the future.
84
- #
85
76
  # @return [Symbol]
86
- def status
87
- execution = head_execution
88
- if execution.finished_at.present?
89
- if execution.error.present?
90
- :discarded
91
- else
92
- :finished
93
- end
94
- elsif (execution.scheduled_at || execution.created_at) > DateTime.current
95
- if execution.serialized_params.fetch('executions', 0) > 1
96
- :retried
97
- else
98
- :scheduled
99
- end
100
- elsif running?
101
- :running
102
- else
103
- :queued
104
- end
105
- end
77
+ delegate :status, :last_status_at, to: :head_execution
106
78
 
107
79
  # This job's most recent {Execution}
108
80
  # @param reload [Booelan] whether to reload executions
@@ -225,6 +197,18 @@ module GoodJob
225
197
  end
226
198
  end
227
199
 
200
+ # Destroy all of a discarded or finished job's executions from the database so that it will no longer appear on the dashboard.
201
+ # @return [void]
202
+ def destroy_job
203
+ with_advisory_lock do
204
+ execution = head_execution(reload: true)
205
+
206
+ raise ActionForStateMismatchError if execution.finished_at.blank?
207
+
208
+ destroy
209
+ end
210
+ end
211
+
228
212
  # Utility method to determine which execution record is used to represent this job
229
213
  # @return [String]
230
214
  def _execution_id
data/lib/good_job/cli.rb CHANGED
@@ -120,11 +120,11 @@ module GoodJob
120
120
  default_task :start
121
121
 
122
122
  # @!macro thor.desc
123
- desc :cleanup_preserved_jobs, "Deletes preserved job records."
123
+ desc :cleanup_preserved_jobs, "Destroys preserved job records."
124
124
  long_desc <<~DESCRIPTION
125
- Deletes preserved job records.
125
+ Destroys preserved job records.
126
126
 
127
- By default, GoodJob deletes job records when the job is performed and this
127
+ By default, GoodJob destroys job records when the job is performed and this
128
128
  command is not necessary.
129
129
 
130
130
  However, when `GoodJob.preserve_job_records = true`, the jobs will be
@@ -132,13 +132,13 @@ module GoodJob
132
132
  inspect job performance.
133
133
 
134
134
  If you are preserving job records this way, use this command regularly
135
- to delete old records and preserve space in your database.
135
+ to destroy old records and preserve space in your database.
136
136
 
137
137
  DESCRIPTION
138
138
  method_option :before_seconds_ago,
139
139
  type: :numeric,
140
140
  banner: 'SECONDS',
141
- desc: "Delete records finished more than this many seconds ago (env var: GOOD_JOB_CLEANUP_PRESERVED_JOBS_BEFORE_SECONDS_AGO, default: 86400)"
141
+ desc: "Destroy records finished more than this many seconds ago (env var: GOOD_JOB_CLEANUP_PRESERVED_JOBS_BEFORE_SECONDS_AGO, default: 86400)"
142
142
 
143
143
  def cleanup_preserved_jobs
144
144
  set_up_application!
@@ -171,6 +171,16 @@ module GoodJob
171
171
  cron.map { |cron_key, params| GoodJob::CronEntry.new(params.merge(key: cron_key)) }
172
172
  end
173
173
 
174
+ # Whether to destroy discarded jobs when cleaning up preserved jobs.
175
+ # This configuration is only used when {GoodJob.preserve_job_records} is +true+.
176
+ # @return [Boolean]
177
+ def cleanup_discarded_jobs?
178
+ return rails_config[:cleanup_discarded_jobs] unless rails_config[:cleanup_discarded_jobs].nil?
179
+ return ActiveModel::Type::Boolean.new.cast(env['GOOD_JOB_CLEANUP_DISCARDED_JOBS']) unless env['GOOD_JOB_CLEANUP_DISCARDED_JOBS'].nil?
180
+
181
+ true
182
+ end
183
+
174
184
  # Number of seconds to preserve jobs when using the +good_job cleanup_preserved_jobs+ CLI command.
175
185
  # This configuration is only used when {GoodJob.preserve_job_records} is +true+.
176
186
  # @return [Integer]
@@ -88,7 +88,7 @@ module GoodJob
88
88
  # @return [ActiveRecord::Relation]
89
89
  scope :priority_ordered, -> { order('priority DESC NULLS LAST') }
90
90
 
91
- # Order jobs by scheduled (unscheduled or soonest first).
91
+ # Order jobs by scheduled or created (oldest first).
92
92
  # @!method schedule_ordered
93
93
  # @!scope class
94
94
  # @return [ActiveRecord::Relation]
@@ -96,7 +96,7 @@ module GoodJob
96
96
 
97
97
  # Get Jobs were completed before the given timestamp. If no timestamp is
98
98
  # provided, get all jobs that have been completed. By default, GoodJob
99
- # deletes jobs after they are completed and this will find no jobs.
99
+ # destroys jobs after they are completed and this will find no jobs.
100
100
  # However, if you have changed {GoodJob.preserve_job_records}, this may
101
101
  # find completed Jobs.
102
102
  # @!method finished(timestamp = nil)
@@ -272,6 +272,65 @@ module GoodJob
272
272
  end
273
273
  end
274
274
 
275
+ # There are 3 buckets of non-overlapping statuses:
276
+ # 1. The job will be executed
277
+ # - queued: The job will execute immediately when an execution thread becomes available.
278
+ # - scheduled: The job is scheduled to execute in the future.
279
+ # - retried: The job previously errored on execution and will be re-executed in the future.
280
+ # 2. The job is being executed
281
+ # - running: the job is actively being executed by an execution thread
282
+ # 3. The job will not execute
283
+ # - finished: The job executed successfully
284
+ # - discarded: The job previously errored on execution and will not be re-executed in the future.
285
+ #
286
+ # @return [Symbol]
287
+ def status
288
+ if finished_at.present?
289
+ if error.present?
290
+ :discarded
291
+ else
292
+ :finished
293
+ end
294
+ elsif (scheduled_at || created_at) > DateTime.current
295
+ if serialized_params.fetch('executions', 0) > 1
296
+ :retried
297
+ else
298
+ :scheduled
299
+ end
300
+ elsif running?
301
+ :running
302
+ else
303
+ :queued
304
+ end
305
+ end
306
+
307
+ def running?
308
+ performed_at? && !finished_at?
309
+ end
310
+
311
+ def number
312
+ serialized_params.fetch('executions', 0) + 1
313
+ end
314
+
315
+ # The last relevant timestamp for this execution
316
+ def last_status_at
317
+ finished_at || performed_at || scheduled_at || created_at
318
+ end
319
+
320
+ # Time between when this job was expected to run and when it started running
321
+ def queue_latency
322
+ now = Time.zone.now
323
+ expected_start = scheduled_at || created_at
324
+ actual_start = performed_at || now
325
+
326
+ actual_start - expected_start unless expected_start >= now
327
+ end
328
+
329
+ # Time between when this job started and finished
330
+ def runtime_latency
331
+ (finished_at || Time.zone.now) - performed_at if performed_at
332
+ end
333
+
275
334
  private
276
335
 
277
336
  def active_job_data
@@ -57,7 +57,7 @@ module GoodJob
57
57
  job_query.next_scheduled_at(after: after, limit: limit, now_limit: now_limit)
58
58
  end
59
59
 
60
- # Delete expired preserved jobs
60
+ # Destroy expired preserved jobs
61
61
  # @return [void]
62
62
  def cleanup
63
63
  GoodJob.cleanup_preserved_jobs
@@ -140,10 +140,10 @@ module GoodJob
140
140
  # @!macro notification_responder
141
141
  def cleanup_preserved_jobs(event)
142
142
  timestamp = event.payload[:timestamp]
143
- deleted_records_count = event.payload[:deleted_records_count]
143
+ destroyed_records_count = event.payload[:destroyed_records_count]
144
144
 
145
145
  info do
146
- "GoodJob deleted #{deleted_records_count} preserved #{'job'.pluralize(deleted_records_count)} finished before #{timestamp}."
146
+ "GoodJob destroyed #{destroyed_records_count} preserved #{'job'.pluralize(destroyed_records_count)} finished before #{timestamp}."
147
147
  end
148
148
  end
149
149
 
@@ -17,7 +17,7 @@ module GoodJob
17
17
  def start
18
18
  @handler = Rack::Handler.get(RACK_SERVER)
19
19
  @future = Concurrent::Future.new(args: [@handler, @port, GoodJob.logger]) do |thr_handler, thr_port, thr_logger|
20
- thr_handler.run(self, Port: thr_port, Logger: thr_logger, AccessLog: [])
20
+ thr_handler.run(self, Port: thr_port, Host: '0.0.0.0', Logger: thr_logger, AccessLog: [])
21
21
  end
22
22
  @future.add_observer(self.class, :task_observer)
23
23
  @future.execute
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  module GoodJob
3
3
  # GoodJob gem version.
4
- VERSION = '2.14.2'
4
+ VERSION = '2.15.0'
5
5
  end
data/lib/good_job.rb CHANGED
@@ -43,7 +43,7 @@ module GoodJob
43
43
  # @!attribute [rw] preserve_job_records
44
44
  # @!scope class
45
45
  # Whether to preserve job records in the database after they have finished (default: +false+).
46
- # By default, GoodJob deletes job records after the job is completed successfully.
46
+ # By default, GoodJob destroys job records after the job is completed successfully.
47
47
  # If you want to preserve jobs for latter inspection, set this to +true+.
48
48
  # If you want to preserve only jobs that finished with error for latter inspection, set this to +:on_unhandled_error+.
49
49
  # If +true+, you will need to clean out jobs using the +good_job cleanup_preserved_jobs+ CLI command or
@@ -126,25 +126,28 @@ module GoodJob
126
126
  end
127
127
  end
128
128
 
129
- # Deletes preserved job records.
130
- # By default, GoodJob deletes job records when the job is performed and this
129
+ # Destroys preserved job records.
130
+ # By default, GoodJob destroys job records when the job is performed and this
131
131
  # method is not necessary. However, when `GoodJob.preserve_job_records = true`,
132
132
  # the jobs will be preserved in the database. This is useful when wanting to
133
133
  # analyze or inspect job performance.
134
134
  # If you are preserving job records this way, use this method regularly to
135
- # delete old records and preserve space in your database.
136
- # @params older_than [nil,Numeric,ActiveSupport::Duration] Jobs older than this will be deleted (default: +86400+).
137
- # @return [Integer] Number of jobs that were deleted.
135
+ # destroy old records and preserve space in your database.
136
+ # @params older_than [nil,Numeric,ActiveSupport::Duration] Jobs older than this will be destroyed (default: +86400+).
137
+ # @return [Integer] Number of jobs that were destroyed.
138
138
  def self.cleanup_preserved_jobs(older_than: nil)
139
- older_than ||= GoodJob::Configuration.new({}).cleanup_preserved_jobs_before_seconds_ago
139
+ configuration = GoodJob::Configuration.new({})
140
+ older_than ||= configuration.cleanup_preserved_jobs_before_seconds_ago
140
141
  timestamp = Time.current - older_than
142
+ include_discarded = configuration.cleanup_discarded_jobs?
141
143
 
142
144
  ActiveSupport::Notifications.instrument("cleanup_preserved_jobs.good_job", { older_than: older_than, timestamp: timestamp }) do |payload|
143
145
  old_jobs = GoodJob::ActiveJobJob.where('finished_at <= ?', timestamp)
146
+ old_jobs = old_jobs.not_discarded unless include_discarded
144
147
  old_jobs_count = old_jobs.count
145
148
 
146
- GoodJob::Execution.where(job: old_jobs).delete_all
147
- payload[:deleted_records_count] = old_jobs_count
149
+ GoodJob::Execution.where(job: old_jobs).destroy_all
150
+ payload[:destroyed_records_count] = old_jobs_count
148
151
  end
149
152
  end
150
153
 
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.14.2
4
+ version: 2.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Sheldon
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-05-01 00:00:00.000000000 Z
11
+ date: 2022-05-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob
@@ -377,7 +377,6 @@ files:
377
377
  - engine/app/controllers/good_job/application_controller.rb
378
378
  - engine/app/controllers/good_job/assets_controller.rb
379
379
  - engine/app/controllers/good_job/cron_entries_controller.rb
380
- - engine/app/controllers/good_job/executions_controller.rb
381
380
  - engine/app/controllers/good_job/jobs_controller.rb
382
381
  - engine/app/controllers/good_job/processes_controller.rb
383
382
  - engine/app/filters/good_job/base_filter.rb
@@ -385,7 +384,7 @@ files:
385
384
  - engine/app/helpers/good_job/application_helper.rb
386
385
  - engine/app/views/good_job/cron_entries/index.html.erb
387
386
  - engine/app/views/good_job/cron_entries/show.html.erb
388
- - engine/app/views/good_job/executions/_table.erb
387
+ - engine/app/views/good_job/jobs/_executions.erb
389
388
  - engine/app/views/good_job/jobs/_table.erb
390
389
  - engine/app/views/good_job/jobs/index.html.erb
391
390
  - engine/app/views/good_job/jobs/show.html.erb
@@ -397,6 +396,8 @@ files:
397
396
  - engine/app/views/good_job/shared/_navbar.erb
398
397
  - engine/app/views/good_job/shared/icons/_arrow_clockwise.html.erb
399
398
  - engine/app/views/good_job/shared/icons/_check.html.erb
399
+ - engine/app/views/good_job/shared/icons/_clock.html.erb
400
+ - engine/app/views/good_job/shared/icons/_dash_circle.html.erb
400
401
  - engine/app/views/good_job/shared/icons/_exclamation.html.erb
401
402
  - engine/app/views/good_job/shared/icons/_play.html.erb
402
403
  - engine/app/views/good_job/shared/icons/_skip_forward.html.erb
@@ -483,7 +484,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
483
484
  - !ruby/object:Gem::Version
484
485
  version: '0'
485
486
  requirements: []
486
- rubygems_version: 3.3.7
487
+ rubygems_version: 3.2.33
487
488
  signing_key:
488
489
  specification_version: 4
489
490
  summary: A multithreaded, Postgres-based ActiveJob backend for Ruby on Rails
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
- module GoodJob
3
- class ExecutionsController < GoodJob::ApplicationController
4
- def destroy
5
- deleted_count = GoodJob::Execution.where(id: params[:id]).delete_all
6
- message = deleted_count.positive? ? { notice: "Job execution deleted" } : { alert: "Job execution not deleted" }
7
- redirect_back fallback_location: jobs_path, **message
8
- end
9
- end
10
- end
@@ -1,62 +0,0 @@
1
- <div class="my-3" data-live-poll-region="executions-table">
2
- <div class="table-responsive">
3
- <table class="table table-hover table-sm mb-0" id="executions_index_table">
4
- <thead>
5
- <tr>
6
- <th>ActiveJob ID</th>
7
- <th>Execution ID</th>
8
- <th>Job Class</th>
9
- <th>Queue</th>
10
- <th>Scheduled At</th>
11
- <th>Error</th>
12
- <th>
13
- ActiveJob Params&nbsp;
14
- <%= tag.button "Toggle", type: "button", class: "btn btn-sm btn-outline-primary", role: "button",
15
- data: { bs_toggle: "collapse", bs_target: ".job-params" },
16
- aria: { expanded: false, controls: executions.map { |execution| "##{dom_id(execution, "params")}" }.join(" ") }
17
- %>
18
- </th>
19
- <th>Actions</th>
20
- </tr>
21
- </thead>
22
- <tbody>
23
- <% if executions.present? %>
24
- <% executions.each do |execution| %>
25
- <tr id="<%= dom_id(execution) %>">
26
- <td>
27
- <%= link_to job_path(execution.serialized_params['job_id']) do %>
28
- <code><%= execution.active_job_id %></code>
29
- <% end %>
30
- </td>
31
- <td>
32
- <%= link_to job_path(execution.active_job_id, anchor: dom_id(execution)) do %>
33
- <code><%= execution.id %></code>
34
- <% end %>
35
- </td>
36
- <td><%= execution.serialized_params['job_class'] %></td>
37
- <td><%= execution.queue_name %></td>
38
- <td><%= relative_time(execution.scheduled_at || execution.created_at) %></td>
39
- <td class="text-break"><%= truncate(execution.error, length: 1_000) %></td>
40
- <td>
41
- <%= tag.button "Preview", type: "button", class: "btn btn-sm btn-outline-primary", role: "button",
42
- data: { bs_toggle: "collapse", bs_target: "##{dom_id(execution, 'params')}" },
43
- aria: { expanded: false, controls: dom_id(execution, "params") }
44
- %>
45
- <%= tag.pre JSON.pretty_generate(execution.serialized_params), id: dom_id(execution, "params"), class: "collapse job-params" %>
46
- </td>
47
- <td>
48
- <%= button_to execution_path(execution.id), method: :delete, class: "btn btn-sm btn-outline-danger", title: "Delete execution", data: { confirm: "Confirm delete" } do %>
49
- <%= render_icon "trash" %>
50
- <% end %>
51
- </td>
52
- </tr>
53
- <% end %>
54
- <% else %>
55
- <tr>
56
- <td colspan="8" class="py-2 text-center text-muted">No executions found.</td>
57
- </tr>
58
- <% end %>
59
- </tbody>
60
- </table>
61
- </div>
62
- </div>