good_job 4.1.0 → 4.1.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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +41 -0
  3. data/app/charts/good_job/performance_index_chart.rb +1 -1
  4. data/app/charts/good_job/performance_show_chart.rb +1 -1
  5. data/app/controllers/good_job/application_controller.rb +1 -1
  6. data/app/controllers/good_job/frontends_controller.rb +6 -2
  7. data/app/controllers/good_job/performance_controller.rb +1 -1
  8. data/app/frontend/good_job/icons.svg +79 -0
  9. data/app/frontend/good_job/style.css +5 -0
  10. data/app/helpers/good_job/icons_helper.rb +8 -5
  11. data/app/models/concerns/good_job/advisory_lockable.rb +17 -7
  12. data/app/models/concerns/good_job/reportable.rb +8 -12
  13. data/app/models/good_job/batch.rb +10 -5
  14. data/app/models/good_job/batch_record.rb +18 -15
  15. data/app/models/good_job/discrete_execution.rb +6 -59
  16. data/app/models/good_job/execution.rb +59 -4
  17. data/app/models/good_job/execution_result.rb +6 -6
  18. data/app/models/good_job/job.rb +567 -12
  19. data/app/views/good_job/batches/_jobs.erb +1 -1
  20. data/app/views/good_job/batches/_table.erb +1 -1
  21. data/app/views/good_job/jobs/index.html.erb +1 -1
  22. data/app/views/layouts/good_job/application.html.erb +7 -7
  23. data/config/brakeman.ignore +75 -0
  24. data/config/locales/de.yml +49 -49
  25. data/config/locales/es.yml +14 -14
  26. data/config/routes.rb +3 -3
  27. data/lib/good_job/active_job_extensions/concurrency.rb +105 -98
  28. data/lib/good_job/adapter/inline_buffer.rb +73 -0
  29. data/lib/good_job/adapter.rb +59 -53
  30. data/lib/good_job/configuration.rb +3 -4
  31. data/lib/good_job/cron_manager.rb +1 -3
  32. data/lib/good_job/current_thread.rb +4 -4
  33. data/lib/good_job/version.rb +1 -1
  34. data/lib/good_job.rb +6 -5
  35. metadata +6 -20
  36. data/app/models/good_job/base_execution.rb +0 -605
  37. data/app/views/good_job/shared/icons/_arrow_clockwise.html.erb +0 -5
  38. data/app/views/good_job/shared/icons/_check.html.erb +0 -5
  39. data/app/views/good_job/shared/icons/_circle_half.html.erb +0 -4
  40. data/app/views/good_job/shared/icons/_clock.html.erb +0 -5
  41. data/app/views/good_job/shared/icons/_dash_circle.html.erb +0 -5
  42. data/app/views/good_job/shared/icons/_dots.html.erb +0 -3
  43. data/app/views/good_job/shared/icons/_eject.html.erb +0 -4
  44. data/app/views/good_job/shared/icons/_exclamation.html.erb +0 -5
  45. data/app/views/good_job/shared/icons/_globe.html.erb +0 -3
  46. data/app/views/good_job/shared/icons/_info.html.erb +0 -4
  47. data/app/views/good_job/shared/icons/_moon_stars_fill.html.erb +0 -5
  48. data/app/views/good_job/shared/icons/_pause.html.erb +0 -4
  49. data/app/views/good_job/shared/icons/_play.html.erb +0 -4
  50. data/app/views/good_job/shared/icons/_skip_forward.html.erb +0 -4
  51. data/app/views/good_job/shared/icons/_stop.html.erb +0 -4
  52. data/app/views/good_job/shared/icons/_sun_fill.html.erb +0 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aead87131c66c247222ce6733d9fe579b6d61fa0cc166b23ae75c6011e2b52db
4
- data.tar.gz: 4158e3be14f1b93f46bb2eeefbeee246b4a2d98347519166b507811d343234ab
3
+ metadata.gz: 660e78528305c41b14c2926d9747037a091b57f952acd14246af99180991b434
4
+ data.tar.gz: 8bc407843b2d17fbeceb36507eb2e06177460f1bc4b8b4133560e1c4daa6ce3e
5
5
  SHA512:
6
- metadata.gz: f7c522339ad17886675ac8c97104087a1d132767fe9e90c5a7f62005fced53abc40f29488e431b18e1a3a1ce528202245448617123eabaef1bda478576a9bfde
7
- data.tar.gz: ce6fdd69c377f1e834b41640c5307e3bd030ac95d247b7037eab3fc5e806e6fb3030e57cc2de9d1f18a9e1f22e314e6b7e4a5aee709e2551b74b3d47f1e84388
6
+ metadata.gz: 8847a962731fec2c13683d3948e3ae51c0730bbcdd81853dfa3d6c2168bb05105ae2cf4c8f073a9c133d3f634473bdfaf58fefc08b07a0bbb8f51686e4b62ff0
7
+ data.tar.gz: c89b843399fe9bdc0d492c89ce1f481da62201689cc0f15ada8d73135308be1ee23295e54bae1fa586d1c9a50e2ca62a072784a4ac0fb9a5be3ee83ed374b1d7
data/CHANGELOG.md CHANGED
@@ -1,5 +1,46 @@
1
1
  # Changelog
2
2
 
3
+ ## [v4.1.1](https://github.com/bensheldon/good_job/tree/v4.1.1) (2024-07-31)
4
+
5
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v1.99.2...v4.1.1)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - Convert Concurrency extension to use transaction-level \(xact\) advisory locks [\#1439](https://github.com/bensheldon/good_job/pull/1439) ([bensheldon](https://github.com/bensheldon))
10
+
11
+ **Fixed bugs:**
12
+
13
+ - Fix N+1 on dashboard batches index page [\#1442](https://github.com/bensheldon/good_job/pull/1442) ([bensheldon](https://github.com/bensheldon))
14
+ - Remove duplicate word on batches show page [\#1441](https://github.com/bensheldon/good_job/pull/1441) ([Earlopain](https://github.com/Earlopain))
15
+ - Ensure remaining inline jobs are unlocked when one job raises in Adapter\#enqueue\_all [\#1438](https://github.com/bensheldon/good_job/pull/1438) ([bensheldon](https://github.com/bensheldon))
16
+
17
+ **Closed issues:**
18
+
19
+ - Using Good Job to track all ActiveJob executions, even those with `perform_now` [\#1448](https://github.com/bensheldon/good_job/issues/1448)
20
+ - RuntimeError when running good\_job executable after updating v3 -\> v4 [\#1445](https://github.com/bensheldon/good_job/issues/1445)
21
+ - 2.99 -\> 3.0.2 migration not applying cleanly from a fresh DB [\#1435](https://github.com/bensheldon/good_job/issues/1435)
22
+ - \[Enhance\] Enhance performance via counter cache [\#1375](https://github.com/bensheldon/good_job/issues/1375)
23
+ - Change how svg images are inserted into partials [\#1364](https://github.com/bensheldon/good_job/issues/1364)
24
+
25
+ **Merged pull requests:**
26
+
27
+ - Improve some Spanish transcriptions [\#1452](https://github.com/bensheldon/good_job/pull/1452) ([sebastian-cloudnonic](https://github.com/sebastian-cloudnonic))
28
+ - All running jobs now have `performed_at` set so use that in status query; fix flaky test that took advisory lock in `before` block [\#1444](https://github.com/bensheldon/good_job/pull/1444) ([bensheldon](https://github.com/bensheldon))
29
+ - Handle empty asset format in Frontends controller [\#1443](https://github.com/bensheldon/good_job/pull/1443) ([bensheldon](https://github.com/bensheldon))
30
+ - Update development dependencies, Ruby 3.3.4 [\#1437](https://github.com/bensheldon/good_job/pull/1437) ([bensheldon](https://github.com/bensheldon))
31
+ - Refactor inline adapter to enable deferred execution after enqueue to allow batch-callbacks to use transaction-based advisory lock [\#1433](https://github.com/bensheldon/good_job/pull/1433) ([bensheldon](https://github.com/bensheldon))
32
+ - German translation pass [\#1432](https://github.com/bensheldon/good_job/pull/1432) ([Earlopain](https://github.com/Earlopain))
33
+ - Add Brakeman to linters [\#1431](https://github.com/bensheldon/good_job/pull/1431) ([bensheldon](https://github.com/bensheldon))
34
+ - Remove references to and ignore `good_jobs.retried_good_job_id` column [\#1430](https://github.com/bensheldon/good_job/pull/1430) ([bensheldon](https://github.com/bensheldon))
35
+ - Refactor Concurrency extension for Rails 6.1+ compatibility [\#1429](https://github.com/bensheldon/good_job/pull/1429) ([bensheldon](https://github.com/bensheldon))
36
+ - Use svg `use` for svg icons [\#1428](https://github.com/bensheldon/good_job/pull/1428) ([Earlopain](https://github.com/Earlopain))
37
+ - Replace references to "Discrete" executions with simply Executions; deprecate `GoodJob::DiscreteExecution` [\#1427](https://github.com/bensheldon/good_job/pull/1427) ([bensheldon](https://github.com/bensheldon))
38
+ - Refactor Adapter to reference jobs, not executions [\#1426](https://github.com/bensheldon/good_job/pull/1426) ([bensheldon](https://github.com/bensheldon))
39
+
40
+ ## [v1.99.2](https://github.com/bensheldon/good_job/tree/v1.99.2) (2024-07-18)
41
+
42
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v4.1.0...v1.99.2)
43
+
3
44
  ## [v4.1.0](https://github.com/bensheldon/good_job/tree/v4.1.0) (2024-07-16)
4
45
 
5
46
  [Full Changelog](https://github.com/bensheldon/good_job/compare/v3.99.1...v4.1.0)
@@ -3,7 +3,7 @@
3
3
  module GoodJob
4
4
  class PerformanceIndexChart < BaseChart
5
5
  def data
6
- table_name = GoodJob::DiscreteExecution.table_name
6
+ table_name = GoodJob::Execution.table_name
7
7
 
8
8
  sum_query = Arel.sql(GoodJob::Job.pg_or_jdbc_query(<<~SQL.squish))
9
9
  SELECT *
@@ -18,7 +18,7 @@ module GoodJob
18
18
  end
19
19
 
20
20
  def data
21
- table_name = GoodJob::DiscreteExecution.table_name
21
+ table_name = GoodJob::Execution.table_name
22
22
 
23
23
  interval_entries = BUCKET_INTERVALS.map { "interval '#{_1}'" }.join(",")
24
24
  sum_query = Arel.sql(GoodJob::Job.pg_or_jdbc_query(<<~SQL.squish))
@@ -7,7 +7,7 @@ module GoodJob
7
7
  around_action :use_good_job_locale
8
8
 
9
9
  content_security_policy do |policy|
10
- policy.default_src(:none) if policy.default_src(*policy.default_src).blank?
10
+ policy.default_src(:self) if policy.default_src(*policy.default_src).blank?
11
11
  policy.connect_src(:self) if policy.connect_src(*policy.connect_src).blank?
12
12
  policy.base_uri(:none) if policy.base_uri(*policy.base_uri).blank?
13
13
  policy.font_src(:self) if policy.font_src(*policy.font_src).blank?
@@ -2,6 +2,7 @@
2
2
 
3
3
  module GoodJob
4
4
  class FrontendsController < ActionController::Base # rubocop:disable Rails/ApplicationController
5
+ protect_from_forgery with: :exception
5
6
  skip_after_action :verify_same_origin_request, raise: false
6
7
 
7
8
  STATIC_ASSETS = {
@@ -15,6 +16,9 @@ module GoodJob
15
16
  es_module_shims: GoodJob::Engine.root.join("app", "frontend", "good_job", "vendor", "es_module_shims.js"),
16
17
  rails_ujs: GoodJob::Engine.root.join("app", "frontend", "good_job", "vendor", "rails_ujs.js"),
17
18
  },
19
+ svg: {
20
+ icons: GoodJob::Engine.root.join("app", "frontend", "good_job", "icons.svg"),
21
+ },
18
22
  }.freeze
19
23
 
20
24
  MODULE_OVERRIDES = {
@@ -34,13 +38,13 @@ module GoodJob
34
38
  end
35
39
 
36
40
  def static
37
- render file: STATIC_ASSETS.dig(params[:format].to_sym, params[:name].to_sym) || raise(ActionController::RoutingError, 'Not Found')
41
+ render file: STATIC_ASSETS.dig(params[:format]&.to_sym, params[:id]&.to_sym) || raise(ActionController::RoutingError, 'Not Found')
38
42
  end
39
43
 
40
44
  def module
41
45
  raise(ActionController::RoutingError, 'Not Found') if params[:format] != "js"
42
46
 
43
- render file: self.class.js_modules[params[:name].to_sym] || raise(ActionController::RoutingError, 'Not Found')
47
+ render file: self.class.js_modules[params[:id]&.to_sym] || raise(ActionController::RoutingError, 'Not Found')
44
48
  end
45
49
  end
46
50
  end
@@ -3,7 +3,7 @@
3
3
  module GoodJob
4
4
  class PerformanceController < ApplicationController
5
5
  def index
6
- @performances = GoodJob::DiscreteExecution
6
+ @performances = GoodJob::Execution
7
7
  .where.not(job_class: nil)
8
8
  .group(:job_class)
9
9
  .select("
@@ -0,0 +1,79 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg">
3
+ <!-- https://icons.getbootstrap.com/icons/arrow-clockwise/ -->
4
+ <symbol id="arrow_clockwise" viewBox="0 0 16 16">
5
+ <path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z" />
6
+ <path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z" />
7
+ </symbol>
8
+ <!-- https://icons.getbootstrap.com/icons/check-circle/ -->
9
+ <symbol id="check" viewBox="0 0 16 16">
10
+ <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" />
11
+ <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" />
12
+ </symbol>
13
+ <!-- https://icons.getbootstrap.com/icons/circle-half/ -->
14
+ <symbol id="circle_half" viewBox="0 0 16 16">
15
+ <path d="M8 15A7 7 0 1 0 8 1v14zm0 1A8 8 0 1 1 8 0a8 8 0 0 1 0 16z" />
16
+ </symbol>
17
+ <!-- https://icons.getbootstrap.com/icons/clock/ -->
18
+ <symbol id="clock" viewBox="0 0 16 16">
19
+ <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" />
20
+ <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" />
21
+ </symbol>
22
+ <!-- https://icons.getbootstrap.com/icons/dash-circle/ -->
23
+ <symbol id="dash_circle" viewBox="0 0 16 16">
24
+ <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" />
25
+ <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" />
26
+ </symbol>
27
+ <!-- https://icons.getbootstrap.com/icons/three-dots/ -->
28
+ <symbol id="dots" viewBox="0 0 16 16">
29
+ <path d="M3 9.5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z" />
30
+ </symbol>
31
+ <!-- https://icons.getbootstrap.com/icons/eject/ -->
32
+ <symbol id="eject" viewBox="0 0 16 16">
33
+ <path d="M7.27 1.047a1 1 0 0 1 1.46 0l6.345 6.77c.6.638.146 1.683-.73 1.683H1.656C.78 9.5.326 8.455.926 7.816L7.27 1.047zM14.346 8.5 8 1.731 1.654 8.5h12.692zM.5 11.5a1 1 0 0 1 1-1h13a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1h-13a1 1 0 0 1-1-1v-1zm14 0h-13v1h13v-1z" />
34
+ </symbol>
35
+ <!-- https://icons.getbootstrap.com/icons/exclamation-circle/ -->
36
+ <symbol id="exclamation" viewBox="0 0 16 16">
37
+ <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" />
38
+ <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" />
39
+ </symbol>
40
+ <!-- https://icons.getbootstrap.com/icons/globe/ -->
41
+ <symbol id="globe" viewBox="0 0 16 16">
42
+ <path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8m7.5-6.923c-.67.204-1.335.82-1.887 1.855A8 8 0 0 0 5.145 4H7.5zM4.09 4a9.3 9.3 0 0 1 .64-1.539 7 7 0 0 1 .597-.933A7.03 7.03 0 0 0 2.255 4zm-.582 3.5c.03-.877.138-1.718.312-2.5H1.674a7 7 0 0 0-.656 2.5zM4.847 5a12.5 12.5 0 0 0-.338 2.5H7.5V5zM8.5 5v2.5h2.99a12.5 12.5 0 0 0-.337-2.5zM4.51 8.5a12.5 12.5 0 0 0 .337 2.5H7.5V8.5zm3.99 0V11h2.653c.187-.765.306-1.608.338-2.5zM5.145 12q.208.58.468 1.068c.552 1.035 1.218 1.65 1.887 1.855V12zm.182 2.472a7 7 0 0 1-.597-.933A9.3 9.3 0 0 1 4.09 12H2.255a7 7 0 0 0 3.072 2.472M3.82 11a13.7 13.7 0 0 1-.312-2.5h-2.49c.062.89.291 1.733.656 2.5zm6.853 3.472A7 7 0 0 0 13.745 12H11.91a9.3 9.3 0 0 1-.64 1.539 7 7 0 0 1-.597.933M8.5 12v2.923c.67-.204 1.335-.82 1.887-1.855q.26-.487.468-1.068zm3.68-1h2.146c.365-.767.594-1.61.656-2.5h-2.49a13.7 13.7 0 0 1-.312 2.5m2.802-3.5a7 7 0 0 0-.656-2.5H12.18c.174.782.282 1.623.312 2.5zM11.27 2.461c.247.464.462.98.64 1.539h1.835a7 7 0 0 0-3.072-2.472c.218.284.418.598.597.933M10.855 4a8 8 0 0 0-.468-1.068C9.835 1.897 9.17 1.282 8.5 1.077V4z" />
43
+ </symbol>
44
+ <!-- https://icons.getbootstrap.com/icons/info-circle/ -->
45
+ <symbol id="info" viewBox="0 0 16 16">
46
+ <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" />
47
+ <path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z" />
48
+ </symbol>
49
+ <!-- https://icons.getbootstrap.com/icons/moon-stars-fill/ -->
50
+ <symbol id="moon_stars_fill" viewBox="0 0 16 16">
51
+ <path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z" />
52
+ <path d="M10.794 3.148a.217.217 0 0 1 .412 0l.387 1.162c.173.518.579.924 1.097 1.097l1.162.387a.217.217 0 0 1 0 .412l-1.162.387a1.734 1.734 0 0 0-1.097 1.097l-.387 1.162a.217.217 0 0 1-.412 0l-.387-1.162A1.734 1.734 0 0 0 9.31 6.593l-1.162-.387a.217.217 0 0 1 0-.412l1.162-.387a1.734 1.734 0 0 0 1.097-1.097l.387-1.162zM13.863.099a.145.145 0 0 1 .274 0l.258.774c.115.346.386.617.732.732l.774.258a.145.145 0 0 1 0 .274l-.774.258a1.156 1.156 0 0 0-.732.732l-.258.774a.145.145 0 0 1-.274 0l-.258-.774a1.156 1.156 0 0 0-.732-.732l-.774-.258a.145.145 0 0 1 0-.274l.774-.258c.346-.115.617-.386.732-.732L13.863.1z" />
53
+ </symbol>
54
+ <!-- https://icons.getbootstrap.com/icons/pause-btn/ -->
55
+ <symbol id="pause" viewBox="0 0 16 16">
56
+ <path d="M6 3.5a.5.5 0 0 1 .5.5v8a.5.5 0 0 1-1 0V4a.5.5 0 0 1 .5-.5zm4 0a.5.5 0 0 1 .5.5v8a.5.5 0 0 1-1 0V4a.5.5 0 0 1 .5-.5z" />
57
+ </symbol>
58
+ <!-- https://icons.getbootstrap.com/icons/play/ -->
59
+ <symbol id="play" viewBox="0 0 16 16">
60
+ <path d="M10.804 8 5 4.633v6.734L10.804 8zm.792-.696a.802.802 0 0 1 0 1.392l-6.363 3.692C4.713 12.69 4 12.345 4 11.692V4.308c0-.653.713-.998 1.233-.696l6.363 3.692z" />
61
+ </symbol>
62
+ <!-- https://icons.getbootstrap.com/icons/skip-forward/ -->
63
+ <symbol id="skip_forward" viewBox="0 0 16 16">
64
+ <path d="M15.5 3.5a.5.5 0 0 1 .5.5v8a.5.5 0 0 1-1 0V8.752l-6.267 3.636c-.52.302-1.233-.043-1.233-.696v-2.94l-6.267 3.636C.713 12.69 0 12.345 0 11.692V4.308c0-.653.713-.998 1.233-.696L7.5 7.248v-2.94c0-.653.713-.998 1.233-.696L15 7.248V4a.5.5 0 0 1 .5-.5zM1 4.633v6.734L6.804 8 1 4.633zm7.5 0v6.734L14.304 8 8.5 4.633z" />
65
+ </symbol>
66
+ <!-- https://icons.getbootstrap.com/icons/stop/ -->
67
+ <symbol id="stop" viewBox="0 0 16 16">
68
+ <path d="M3.5 5A1.5 1.5 0 0 1 5 3.5h6A1.5 1.5 0 0 1 12.5 5v6a1.5 1.5 0 0 1-1.5 1.5H5A1.5 1.5 0 0 1 3.5 11V5zM5 4.5a.5.5 0 0 0-.5.5v6a.5.5 0 0 0 .5.5h6a.5.5 0 0 0 .5-.5V5a.5.5 0 0 0-.5-.5H5z" />
69
+ </symbol>
70
+ <!-- https://icons.getbootstrap.com/icons/sun-fill/ -->
71
+ <symbol id="sun_fill" viewBox="0 0 16 16">
72
+ <path d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z" />
73
+ </symbol>
74
+ <!-- https://icons.getbootstrap.com/icons/trash/ -->
75
+ <symbol id="trash" viewBox="0 0 16 16">
76
+ <path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z" />
77
+ <path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z" />
78
+ </symbol>
79
+ </svg>
@@ -46,3 +46,8 @@
46
46
  .w-fit-content {
47
47
  width: fit-content
48
48
  }
49
+
50
+ .svg-icon {
51
+ height: 1rem;
52
+ width: 1rem;
53
+ }
@@ -31,11 +31,14 @@ module GoodJob
31
31
  content_tag :span, icon, **options
32
32
  end
33
33
 
34
- def render_icon(name, **options)
35
- # workaround to render svg icons without all of the log messages
36
- partial = lookup_context.find_template("good_job/shared/icons/#{name}", [], true)
37
- options[:class] = Array(options[:class]).join(" ")
38
- partial.render(self, { class: options[:class] })
34
+ def render_icon(name, class: nil, **options)
35
+ tag.svg(viewBox: "0 0 16 16", class: "svg-icon #{binding.local_variable_get(:class)}", **options) do
36
+ tag.use(fill: "currentColor", href: "#{icons_path}##{name}")
37
+ end
38
+ end
39
+
40
+ def icons_path
41
+ @_icons_path ||= frontend_static_path(:icons, format: :svg, locale: nil)
39
42
  end
40
43
  end
41
44
  end
@@ -49,8 +49,8 @@ module GoodJob
49
49
 
50
50
  lock_condition = "#{function}(('x' || substr(md5(#{connection.quote(table_name)} || '-' || #{connection.quote_table_name(cte_table.name)}.#{connection.quote_column_name(column)}::text), 1, 16))::bit(64)::bigint)"
51
51
  query = cte_table.project(cte_table[:id])
52
- .with(composed_cte)
53
- .where(defined?(Arel::Nodes::BoundSqlLiteral) ? Arel::Nodes::BoundSqlLiteral.new(lock_condition, [], {}) : Arel::Nodes::SqlLiteral.new(lock_condition))
52
+ .with(composed_cte)
53
+ .where(defined?(Arel::Nodes::BoundSqlLiteral) ? Arel::Nodes::BoundSqlLiteral.new(lock_condition, [], {}) : Arel::Nodes::SqlLiteral.new(lock_condition))
54
54
 
55
55
  limit = original_query.arel.ast.limit
56
56
  query.limit = limit.value if limit.present?
@@ -174,8 +174,11 @@ module GoodJob
174
174
  if unlock_session
175
175
  advisory_unlock_session
176
176
  else
177
- records.each do |record|
178
- record.advisory_unlock(key: record.lockable_column_key(column: column), function: advisory_unlockable_function(function))
177
+ unlock_function = advisory_unlockable_function(function)
178
+ if unlock_function
179
+ records.each do |record|
180
+ record.advisory_unlock(key: record.lockable_column_key(column: column), function: unlock_function)
181
+ end
179
182
  end
180
183
  end
181
184
  end
@@ -209,7 +212,8 @@ module GoodJob
209
212
  begin
210
213
  yield
211
214
  ensure
212
- advisory_unlock_key(key, function: advisory_unlockable_function(function))
215
+ unlock_function = advisory_unlockable_function(function)
216
+ advisory_unlock_key(key, function: unlock_function) if unlock_function
213
217
  end
214
218
  end
215
219
 
@@ -220,6 +224,9 @@ module GoodJob
220
224
  # @param function [String, Symbol] Postgres Advisory Lock function name to use
221
225
  # @return [Boolean] whether the lock was released.
222
226
  def advisory_unlock_key(key, function: advisory_unlockable_function)
227
+ raise ArgumentError, "Cannot unlock transactional locks" if function.include? "_xact_"
228
+ raise ArgumentError, "No unlock function provide" if function.blank?
229
+
223
230
  query = <<~SQL.squish
224
231
  SELECT #{function}(('x'||substr(md5($1::text), 1, 16))::bit(64)::bigint) AS unlocked
225
232
  SQL
@@ -284,6 +291,8 @@ module GoodJob
284
291
  # @param function [String, Symbol] name of advisory lock or unlock function
285
292
  # @return [Boolean]
286
293
  def advisory_unlockable_function(function = advisory_lockable_function)
294
+ return nil if function.include? "_xact_" # Cannot unlock transactional locks
295
+
287
296
  function.to_s.sub("_lock", "_unlock").sub("_try_", "_")
288
297
  end
289
298
 
@@ -358,7 +367,8 @@ module GoodJob
358
367
  begin
359
368
  yield
360
369
  ensure
361
- advisory_unlock(key: key, function: self.class.advisory_unlockable_function(function))
370
+ unlock_function = self.class.advisory_unlockable_function(function)
371
+ advisory_unlock(key: key, function: unlock_function) if unlock_function
362
372
  end
363
373
  end
364
374
 
@@ -403,7 +413,7 @@ module GoodJob
403
413
  # @param key [String, Symbol] Key to lock against
404
414
  # @param function [String, Symbol] Postgres Advisory Lock function name to use
405
415
  # @return [void]
406
- def advisory_unlock!(key: lockable_key, function: self.class.advisory_unlockable_function(advisory_lockable_function))
416
+ def advisory_unlock!(key: lockable_key, function: self.class.advisory_unlockable_function)
407
417
  advisory_unlock(key: key, function: function) while advisory_locked?
408
418
  end
409
419
 
@@ -16,27 +16,23 @@ module GoodJob
16
16
  # @return [Symbol]
17
17
  def status
18
18
  if finished_at.present?
19
- if error.present? && retried_good_job_id.present?
20
- :retried
21
- elsif error.present? && retried_good_job_id.nil?
19
+ if error.present?
22
20
  :discarded
23
21
  else
24
22
  :succeeded
25
23
  end
26
- elsif (scheduled_at || created_at) > DateTime.current
27
- if serialized_params.fetch('executions', 0) > 1
28
- :retried
29
- else
30
- :scheduled
31
- end
32
- elsif running?
24
+ elsif performed_at.present?
33
25
  :running
34
- else
26
+ elsif (scheduled_at || created_at) <= DateTime.current
35
27
  :queued
28
+ elsif error.present?
29
+ :retried
30
+ else
31
+ :scheduled
36
32
  end
37
33
  end
38
34
 
39
- # The last relevant timestamp for this execution
35
+ # The last relevant timestamp for this job
40
36
  def last_status_at
41
37
  finished_at || performed_at || scheduled_at || created_at
42
38
  end
@@ -102,12 +102,17 @@ module GoodJob
102
102
  active_jobs = add(active_jobs, &block)
103
103
 
104
104
  Rails.application.executor.wrap do
105
- record.with_advisory_lock(function: "pg_advisory_lock") do
106
- record.update!(enqueued_at: Time.current)
107
-
108
- # During inline execution, this could enqueue and execute further jobs
109
- record._continue_discard_or_finish(lock: false)
105
+ buffer = GoodJob::Adapter::InlineBuffer.capture do
106
+ record.transaction do
107
+ record.with_advisory_lock(function: "pg_advisory_xact_lock") do
108
+ record.update!(enqueued_at: Time.current)
109
+
110
+ # During inline execution, this could enqueue and execute further jobs
111
+ record._continue_discard_or_finish(lock: false)
112
+ end
113
+ end
110
114
  end
115
+ buffer.call
111
116
  end
112
117
 
113
118
  active_jobs
@@ -53,22 +53,25 @@ module GoodJob
53
53
  end
54
54
 
55
55
  def _continue_discard_or_finish(execution = nil, lock: true)
56
- execution_discarded = execution && execution.error.present? && execution.finished_at && execution.retried_good_job_id.nil?
57
- take_advisory_lock(lock) do
58
- Batch.within_thread(batch_id: nil, batch_callback_id: id) do
59
- reload
60
- if execution_discarded && !discarded_at
61
- update(discarded_at: Time.current)
62
- on_discard.constantize.set(priority: callback_priority, queue: callback_queue_name).perform_later(to_batch, { event: :discard }) if on_discard.present?
63
- end
64
-
65
- if enqueued_at && !finished_at && jobs.where(finished_at: nil).count.zero?
66
- update(finished_at: Time.current)
67
- on_success.constantize.set(priority: callback_priority, queue: callback_queue_name).perform_later(to_batch, { event: :success }) if !discarded_at && on_success.present?
68
- on_finish.constantize.set(priority: callback_priority, queue: callback_queue_name).perform_later(to_batch, { event: :finish }) if on_finish.present?
56
+ execution_discarded = execution && execution.finished_at.present? && execution.error.present?
57
+ buffer = GoodJob::Adapter::InlineBuffer.capture do
58
+ advisory_lock_maybe(lock) do
59
+ Batch.within_thread(batch_id: nil, batch_callback_id: id) do
60
+ reload
61
+ if execution_discarded && !discarded_at
62
+ update(discarded_at: Time.current)
63
+ on_discard.constantize.set(priority: callback_priority, queue: callback_queue_name).perform_later(to_batch, { event: :discard }) if on_discard.present?
64
+ end
65
+
66
+ if enqueued_at && !finished_at && jobs.where(finished_at: nil).count.zero?
67
+ update(finished_at: Time.current)
68
+ on_success.constantize.set(priority: callback_priority, queue: callback_queue_name).perform_later(to_batch, { event: :success }) if !discarded_at && on_success.present?
69
+ on_finish.constantize.set(priority: callback_priority, queue: callback_queue_name).perform_later(to_batch, { event: :finish }) if on_finish.present?
70
+ end
69
71
  end
70
72
  end
71
73
  end
74
+ buffer.call
72
75
  end
73
76
 
74
77
  class PropertySerializer
@@ -100,9 +103,9 @@ module GoodJob
100
103
 
101
104
  private
102
105
 
103
- def take_advisory_lock(value, &block)
106
+ def advisory_lock_maybe(value, &block)
104
107
  if value
105
- with_advisory_lock(function: "pg_advisory_lock", &block)
108
+ transaction { with_advisory_lock(function: "pg_advisory_xact_lock", &block) }
106
109
  else
107
110
  yield
108
111
  end
@@ -1,63 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module GoodJob # :nodoc:
4
- class DiscreteExecution < BaseRecord
5
- include ErrorEvents
6
-
7
- self.table_name = 'good_job_executions'
8
- self.implicit_order_column = 'created_at'
9
-
10
- belongs_to :job, class_name: 'GoodJob::Job', foreign_key: 'active_job_id', primary_key: 'active_job_id', inverse_of: :discrete_executions, optional: true
11
-
12
- scope :finished, -> { where.not(finished_at: nil) }
13
-
14
- alias_attribute :performed_at, :created_at
15
-
16
- # TODO: Remove when support for Rails 6.1 is dropped
17
- attribute :duration, :interval if ActiveJob.version.canonical_segments.take(2) == [6, 1]
18
-
19
- def number
20
- serialized_params.fetch('executions', 0) + 1
21
- end
22
-
23
- # Time between when this job was expected to run and when it started running
24
- def queue_latency
25
- created_at - scheduled_at
26
- end
27
-
28
- # Monotonic time between when this job started and finished
29
- def runtime_latency
30
- duration
31
- end
32
-
33
- def last_status_at
34
- finished_at || created_at
35
- end
36
-
37
- def status
38
- if finished_at.present?
39
- if error.present? && job.finished_at.present?
40
- :discarded
41
- elsif error.present?
42
- :retried
43
- else
44
- :succeeded
45
- end
46
- else
47
- :running
48
- end
49
- end
50
-
51
- def display_serialized_params
52
- serialized_params.merge({
53
- _good_job_execution: attributes.except('serialized_params'),
54
- })
55
- end
56
-
57
- def filtered_error_backtrace
58
- Rails.backtrace_cleaner.clean(error_backtrace || [])
59
- end
3
+ module GoodJob
4
+ # Deprecated, use +Execution+ instead.
5
+ class DiscreteExecution < Execution
60
6
  end
61
- end
62
7
 
63
- ActiveSupport.run_load_hooks(:good_job_execution, GoodJob::DiscreteExecution)
8
+ include ActiveSupport::Deprecation::DeprecatedConstantAccessor
9
+ deprecate_constant :DiscreteExecution, 'Execution', deprecator: GoodJob.deprecator
10
+ end
@@ -1,8 +1,63 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module GoodJob
4
- # Created at the time a Job begins executing.
5
- # Behavior from +DiscreteExecution+ will be merged into this class.
6
- class Execution < DiscreteExecution
3
+ module GoodJob # :nodoc:
4
+ class Execution < BaseRecord
5
+ include ErrorEvents
6
+
7
+ self.table_name = 'good_job_executions'
8
+ self.implicit_order_column = 'created_at'
9
+
10
+ belongs_to :job, class_name: 'GoodJob::Job', foreign_key: 'active_job_id', primary_key: 'id', inverse_of: :executions, optional: true
11
+
12
+ scope :finished, -> { where.not(finished_at: nil) }
13
+
14
+ alias_attribute :performed_at, :created_at
15
+
16
+ # TODO: Remove when support for Rails 6.1 is dropped
17
+ attribute :duration, :interval if ActiveJob.version.canonical_segments.take(2) == [6, 1]
18
+
19
+ def number
20
+ serialized_params.fetch('executions', 0) + 1
21
+ end
22
+
23
+ # Time between when this job was expected to run and when it started running
24
+ def queue_latency
25
+ created_at - scheduled_at
26
+ end
27
+
28
+ # Monotonic time between when this job started and finished
29
+ def runtime_latency
30
+ duration
31
+ end
32
+
33
+ def last_status_at
34
+ finished_at || created_at
35
+ end
36
+
37
+ def status
38
+ if finished_at.present?
39
+ if error.present? && job.finished_at.present?
40
+ :discarded
41
+ elsif error.present?
42
+ :retried
43
+ else
44
+ :succeeded
45
+ end
46
+ else
47
+ :running
48
+ end
49
+ end
50
+
51
+ def display_serialized_params
52
+ serialized_params.merge({
53
+ _good_job_execution: attributes.except('serialized_params'),
54
+ })
55
+ end
56
+
57
+ def filtered_error_backtrace
58
+ Rails.backtrace_cleaner.clean(error_backtrace || [])
59
+ end
7
60
  end
8
61
  end
62
+
63
+ ActiveSupport.run_load_hooks(:good_job_execution, GoodJob::Execution)
@@ -13,22 +13,22 @@ module GoodJob
13
13
  attr_reader :error_event
14
14
  # @return [Boolean, nil]
15
15
  attr_reader :unexecutable
16
- # @return [GoodJob::Execution, nil]
17
- attr_reader :retried
16
+ # @return [GoodJob::Job, nil]
17
+ attr_reader :retried_job
18
18
 
19
19
  # @param value [Object, nil]
20
20
  # @param handled_error [Exception, nil]
21
21
  # @param unhandled_error [Exception, nil]
22
22
  # @param error_event [String, nil]
23
23
  # @param unexecutable [Boolean, nil]
24
- # @param retried [Boolean, nil]
25
- def initialize(value:, handled_error: nil, unhandled_error: nil, error_event: nil, unexecutable: nil, retried: nil)
24
+ # @param retried_job [GoodJob::Job, nil]
25
+ def initialize(value:, handled_error: nil, unhandled_error: nil, error_event: nil, unexecutable: nil, retried_job: nil)
26
26
  @value = value
27
27
  @handled_error = handled_error
28
28
  @unhandled_error = unhandled_error
29
29
  @error_event = error_event
30
30
  @unexecutable = unexecutable
31
- @retried = retried
31
+ @retried_job = retried_job
32
32
  end
33
33
 
34
34
  # @return [Boolean]
@@ -38,7 +38,7 @@ module GoodJob
38
38
 
39
39
  # @return [Boolean]
40
40
  def retried?
41
- retried.present?
41
+ retried_job.present?
42
42
  end
43
43
  end
44
44
  end