good_job 4.0.3 → 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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +71 -0
  3. data/README.md +1 -1
  4. data/app/charts/good_job/base_chart.rb +25 -0
  5. data/app/charts/good_job/performance_index_chart.rb +69 -0
  6. data/app/charts/good_job/performance_show_chart.rb +71 -0
  7. data/app/charts/good_job/scheduled_by_queue_chart.rb +23 -28
  8. data/app/controllers/good_job/application_controller.rb +1 -1
  9. data/app/controllers/good_job/frontends_controller.rb +6 -2
  10. data/app/controllers/good_job/metrics_controller.rb +5 -15
  11. data/app/controllers/good_job/performance_controller.rb +6 -1
  12. data/app/frontend/good_job/icons.svg +79 -0
  13. data/app/frontend/good_job/modules/charts.js +5 -17
  14. data/app/frontend/good_job/style.css +5 -0
  15. data/app/helpers/good_job/application_helper.rb +9 -1
  16. data/app/helpers/good_job/icons_helper.rb +8 -5
  17. data/app/models/concerns/good_job/advisory_lockable.rb +17 -7
  18. data/app/models/concerns/good_job/error_events.rb +14 -35
  19. data/app/models/concerns/good_job/reportable.rb +8 -12
  20. data/app/models/good_job/batch.rb +10 -5
  21. data/app/models/good_job/batch_record.rb +18 -15
  22. data/app/models/good_job/discrete_execution.rb +6 -60
  23. data/app/models/good_job/execution.rb +59 -4
  24. data/app/models/good_job/execution_result.rb +6 -6
  25. data/app/models/good_job/job.rb +569 -14
  26. data/app/models/good_job/process.rb +13 -29
  27. data/app/views/good_job/batches/_jobs.erb +1 -1
  28. data/app/views/good_job/batches/_table.erb +1 -1
  29. data/app/views/good_job/jobs/index.html.erb +1 -1
  30. data/app/views/good_job/performance/index.html.erb +3 -1
  31. data/app/views/good_job/performance/show.html.erb +5 -0
  32. data/app/views/good_job/shared/_filter.erb +2 -2
  33. data/app/views/layouts/good_job/application.html.erb +7 -7
  34. data/config/brakeman.ignore +75 -0
  35. data/config/locales/de.yml +52 -48
  36. data/config/locales/en.yml +4 -0
  37. data/config/locales/es.yml +16 -12
  38. data/config/locales/fr.yml +4 -0
  39. data/config/locales/it.yml +4 -0
  40. data/config/locales/ja.yml +4 -0
  41. data/config/locales/ko.yml +4 -0
  42. data/config/locales/nl.yml +4 -0
  43. data/config/locales/pt-BR.yml +4 -0
  44. data/config/locales/ru.yml +4 -0
  45. data/config/locales/tr.yml +4 -0
  46. data/config/locales/uk.yml +4 -0
  47. data/config/routes.rb +4 -4
  48. data/lib/good_job/active_job_extensions/concurrency.rb +105 -98
  49. data/lib/good_job/adapter/inline_buffer.rb +73 -0
  50. data/lib/good_job/adapter.rb +59 -53
  51. data/lib/good_job/capsule_tracker.rb +1 -1
  52. data/lib/good_job/configuration.rb +3 -4
  53. data/lib/good_job/cron_manager.rb +1 -3
  54. data/lib/good_job/current_thread.rb +4 -4
  55. data/lib/good_job/notifier.rb +7 -0
  56. data/lib/good_job/version.rb +1 -1
  57. data/lib/good_job.rb +6 -5
  58. metadata +10 -20
  59. data/app/models/good_job/base_execution.rb +0 -609
  60. data/app/views/good_job/shared/icons/_arrow_clockwise.html.erb +0 -5
  61. data/app/views/good_job/shared/icons/_check.html.erb +0 -5
  62. data/app/views/good_job/shared/icons/_circle_half.html.erb +0 -4
  63. data/app/views/good_job/shared/icons/_clock.html.erb +0 -5
  64. data/app/views/good_job/shared/icons/_dash_circle.html.erb +0 -5
  65. data/app/views/good_job/shared/icons/_dots.html.erb +0 -3
  66. data/app/views/good_job/shared/icons/_eject.html.erb +0 -4
  67. data/app/views/good_job/shared/icons/_exclamation.html.erb +0 -5
  68. data/app/views/good_job/shared/icons/_globe.html.erb +0 -3
  69. data/app/views/good_job/shared/icons/_info.html.erb +0 -4
  70. data/app/views/good_job/shared/icons/_moon_stars_fill.html.erb +0 -5
  71. data/app/views/good_job/shared/icons/_pause.html.erb +0 -4
  72. data/app/views/good_job/shared/icons/_play.html.erb +0 -4
  73. data/app/views/good_job/shared/icons/_skip_forward.html.erb +0 -4
  74. data/app/views/good_job/shared/icons/_stop.html.erb +0 -4
  75. 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: 41adaae5b9a9c016ba000f7610efa23d533a177374e61039f135a1d152be146c
4
- data.tar.gz: 92443b132e11697ae1ba31758852a818e5fbaf201cf080649f1c948d927265d4
3
+ metadata.gz: 660e78528305c41b14c2926d9747037a091b57f952acd14246af99180991b434
4
+ data.tar.gz: 8bc407843b2d17fbeceb36507eb2e06177460f1bc4b8b4133560e1c4daa6ce3e
5
5
  SHA512:
6
- metadata.gz: 155174df9c9d4daf50f4e898585e2cf40898de46eb5974592acdf32cc435cb4cb13b6d4953862a89a3b8223d7bbd2c9e79c60d4a974642f180092d836d04ebb2
7
- data.tar.gz: 84b922a4853ffb85da2380ab80c20b3d7392f403218838c3fdcb8872726d2df469d81edeaa28cb2f5a76401280460839c1d3f6ca1f2c9a98c4a1b275ff99cee4
6
+ metadata.gz: 8847a962731fec2c13683d3948e3ae51c0730bbcdd81853dfa3d6c2168bb05105ae2cf4c8f073a9c133d3f634473bdfaf58fefc08b07a0bbb8f51686e4b62ff0
7
+ data.tar.gz: c89b843399fe9bdc0d492c89ce1f481da62201689cc0f15ada8d73135308be1ee23295e54bae1fa586d1c9a50e2ca62a072784a4ac0fb9a5be3ee83ed374b1d7
data/CHANGELOG.md CHANGED
@@ -1,5 +1,76 @@
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
+
44
+ ## [v4.1.0](https://github.com/bensheldon/good_job/tree/v4.1.0) (2024-07-16)
45
+
46
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v3.99.1...v4.1.0)
47
+
48
+ **Implemented enhancements:**
49
+
50
+ - Add keepalive SQL query to Notifier [\#1423](https://github.com/bensheldon/good_job/pull/1423) ([bensheldon](https://github.com/bensheldon))
51
+ - Latency charts and histograms for individual job classes [\#1411](https://github.com/bensheldon/good_job/pull/1411) ([Earlopain](https://github.com/Earlopain))
52
+
53
+ **Fixed bugs:**
54
+
55
+ - Fix nonexistant association error between DiscreteExecution and Execution [\#1425](https://github.com/bensheldon/good_job/pull/1425) ([bensheldon](https://github.com/bensheldon))
56
+
57
+ **Closed issues:**
58
+
59
+ - Could not find the inverse association for execution \(:discrete\_executions in GoodJob::Execution\) [\#1424](https://github.com/bensheldon/good_job/issues/1424)
60
+ - 3.99.1 is marked as the latest version, not 4.0.3 [\#1422](https://github.com/bensheldon/good_job/issues/1422)
61
+ - How to maximise amount of jobs executed in parallel [\#1418](https://github.com/bensheldon/good_job/issues/1418)
62
+ - Performance Metrics for individual jobs [\#1397](https://github.com/bensheldon/good_job/issues/1397)
63
+
64
+ **Merged pull requests:**
65
+
66
+ - Remove some now unnecessary checks against `locked_by_id` existence [\#1421](https://github.com/bensheldon/good_job/pull/1421) ([Earlopain](https://github.com/Earlopain))
67
+ - Use rails enum for `error_event` and `lock_type` [\#1420](https://github.com/bensheldon/good_job/pull/1420) ([Earlopain](https://github.com/Earlopain))
68
+ - Add a little more wording to the v4 "ready to upgrade" instructions [\#1415](https://github.com/bensheldon/good_job/pull/1415) ([bensheldon](https://github.com/bensheldon))
69
+
70
+ ## [v3.99.1](https://github.com/bensheldon/good_job/tree/v3.99.1) (2024-07-10)
71
+
72
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v4.0.3...v3.99.1)
73
+
3
74
  ## [v4.0.3](https://github.com/bensheldon/good_job/tree/v4.0.3) (2024-07-10)
4
75
 
5
76
  [Full Changelog](https://github.com/bensheldon/good_job/compare/v4.0.2...v4.0.3)
data/README.md CHANGED
@@ -876,7 +876,7 @@ To perform upgrades to the GoodJob database tables:
876
876
 
877
877
  #### Upgrading v3 to v4
878
878
 
879
- GoodJob v4 changes how job and job execution records are stored in the database; moving from job and executions being commingled in the `good_jobs` table to separately and discretely storing job executions in `good_job_executions`. To safely upgrade, all unfinished jobs must use the new format. This change was introduced in GoodJob [v3.15.4 (April 2023)](https://github.com/bensheldon/good_job/releases/tag/v3.15.4), so your application is likely ready-to-upgrade already if you have kept up with GoodJob updates.
879
+ GoodJob v4 changes how job and job execution records are stored in the database; moving from job and executions being commingled in the `good_jobs` table to separately and discretely storing job executions in `good_job_executions`. To safely upgrade, all unfinished jobs must use the new format. This change was introduced in GoodJob [v3.15.4 (April 2023)](https://github.com/bensheldon/good_job/releases/tag/v3.15.4), so your application is likely ready-to-upgrade in this respect if you have kept up with GoodJob updates and applied migrations (`bin/rails g good_job:update`). _Please be sure to doublecheck you are not missing subsequent migrations or deprecations too by following the instructions below._
880
880
 
881
881
  To upgrade:
882
882
 
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GoodJob
4
+ class BaseChart
5
+ def start_end_binds
6
+ end_time = Time.current
7
+ start_time = end_time - 1.day
8
+
9
+ [
10
+ ActiveRecord::Relation::QueryAttribute.new('start_time', start_time, ActiveRecord::Type::DateTime.new),
11
+ ActiveRecord::Relation::QueryAttribute.new('end_time', end_time, ActiveRecord::Type::DateTime.new),
12
+ ]
13
+ end
14
+
15
+ def string_to_hsl(string)
16
+ hash_value = string.sum
17
+
18
+ hue = hash_value % 360
19
+ saturation = (hash_value % 50) + 50
20
+ lightness = '50'
21
+
22
+ "hsl(#{hue}, #{saturation}%, #{lightness}%)"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GoodJob
4
+ class PerformanceIndexChart < BaseChart
5
+ def data
6
+ table_name = GoodJob::Execution.table_name
7
+
8
+ sum_query = Arel.sql(GoodJob::Job.pg_or_jdbc_query(<<~SQL.squish))
9
+ SELECT *
10
+ FROM generate_series(
11
+ date_trunc('hour', $1::timestamp),
12
+ date_trunc('hour', $2::timestamp),
13
+ '1 hour'
14
+ ) timestamp
15
+ LEFT JOIN (
16
+ SELECT
17
+ date_trunc('hour', scheduled_at) AS scheduled_at,
18
+ job_class,
19
+ SUM(duration) AS sum
20
+ FROM #{table_name} sources
21
+ GROUP BY date_trunc('hour', scheduled_at), job_class
22
+ ) sources ON sources.scheduled_at = timestamp
23
+ ORDER BY timestamp ASC
24
+ SQL
25
+
26
+ executions_data = GoodJob::Job.connection.exec_query(GoodJob::Job.pg_or_jdbc_query(sum_query), "GoodJob Performance Chart", start_end_binds)
27
+
28
+ job_names = executions_data.reject { |d| d['sum'].nil? }.map { |d| d['job_class'] || BaseFilter::EMPTY }.uniq
29
+ labels = []
30
+ jobs_data = executions_data.to_a.group_by { |d| d['timestamp'] }.each_with_object({}) do |(timestamp, values), hash|
31
+ labels << timestamp.in_time_zone.strftime('%H:%M')
32
+ job_names.each do |job_class|
33
+ sum = values.find { |d| d['job_class'] == job_class }&.[]('sum')
34
+ duration = sum ? ActiveSupport::Duration.parse(sum).to_f : 0
35
+ (hash[job_class] ||= []) << duration
36
+ end
37
+ end
38
+
39
+ {
40
+ type: "line",
41
+ data: {
42
+ labels: labels,
43
+ datasets: jobs_data.map do |job_class, data|
44
+ label = job_class || '(none)'
45
+ {
46
+ label: label,
47
+ data: data,
48
+ backgroundColor: string_to_hsl(label),
49
+ borderColor: string_to_hsl(label),
50
+ }
51
+ end,
52
+ },
53
+ options: {
54
+ plugins: {
55
+ title: {
56
+ display: true,
57
+ text: I18n.t("good_job.performance.index.chart_title"),
58
+ },
59
+ },
60
+ scales: {
61
+ y: {
62
+ beginAtZero: true,
63
+ },
64
+ },
65
+ },
66
+ }
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GoodJob
4
+ class PerformanceShowChart < BaseChart
5
+ # These numbers are lifted from Sidekiq
6
+ BUCKET_INTERVALS = [
7
+ 0.02, 0.03, 0.045, 0.065, 0.1,
8
+ 0.15, 0.225, 0.335, 0.5, 0.75,
9
+ 1.1, 1.7, 2.5, 3.8, 5.75,
10
+ 8.5, 13, 20, 30, 45,
11
+ 65, 100, 150, 225, 335,
12
+ 10**8 # About 3 years
13
+ ].freeze
14
+
15
+ def initialize(job_class)
16
+ super()
17
+ @job_class = job_class
18
+ end
19
+
20
+ def data
21
+ table_name = GoodJob::Execution.table_name
22
+
23
+ interval_entries = BUCKET_INTERVALS.map { "interval '#{_1}'" }.join(",")
24
+ sum_query = Arel.sql(GoodJob::Job.pg_or_jdbc_query(<<~SQL.squish))
25
+ SELECT
26
+ WIDTH_BUCKET(duration, ARRAY[#{interval_entries}]) as bucket_index,
27
+ COUNT(WIDTH_BUCKET(duration, ARRAY[#{interval_entries}])) AS count
28
+ FROM #{table_name} sources
29
+ WHERE
30
+ scheduled_at > $1::timestamp
31
+ AND scheduled_at < $2::timestamp
32
+ AND job_class = $3
33
+ AND duration IS NOT NULL
34
+ GROUP BY bucket_index
35
+ SQL
36
+
37
+ binds = [
38
+ *start_end_binds,
39
+ @job_class,
40
+ ]
41
+ labels = BUCKET_INTERVALS.map { |interval| GoodJob::ApplicationController.helpers.format_duration(interval) }
42
+ labels[-1] = I18n.t("good_job.performance.show.slow")
43
+ executions_data = GoodJob::Job.connection.exec_query(GoodJob::Job.pg_or_jdbc_query(sum_query), "GoodJob Performance Job Chart", binds)
44
+ executions_data = executions_data.to_a.index_by { |data| data["bucket_index"] }
45
+
46
+ bucket_data = 0.upto(BUCKET_INTERVALS.count).map do |bucket_index|
47
+ executions_data.dig(bucket_index, "count") || 0
48
+ end
49
+
50
+ {
51
+ type: "bar",
52
+ data: {
53
+ labels: labels,
54
+ datasets: [{
55
+ label: @job_class,
56
+ data: bucket_data,
57
+ backgroundColor: string_to_hsl(@job_class),
58
+ borderColor: string_to_hsl(@job_class),
59
+ }],
60
+ },
61
+ options: {
62
+ scales: {
63
+ y: {
64
+ beginAtZero: true,
65
+ },
66
+ },
67
+ },
68
+ }
69
+ end
70
+ end
71
+ end
@@ -1,14 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GoodJob
4
- class ScheduledByQueueChart
4
+ class ScheduledByQueueChart < BaseChart
5
5
  def initialize(filter)
6
+ super()
6
7
  @filter = filter
7
8
  end
8
9
 
9
10
  def data
10
- end_time = Time.current
11
- start_time = end_time - 1.day
12
11
  table_name = GoodJob::Job.table_name
13
12
 
14
13
  count_query = Arel.sql(GoodJob::Job.pg_or_jdbc_query(<<~SQL.squish))
@@ -31,11 +30,7 @@ module GoodJob
31
30
  ORDER BY timestamp ASC
32
31
  SQL
33
32
 
34
- binds = [
35
- ActiveRecord::Relation::QueryAttribute.new('start_time', start_time, ActiveRecord::Type::DateTime.new),
36
- ActiveRecord::Relation::QueryAttribute.new('end_time', end_time, ActiveRecord::Type::DateTime.new),
37
- ]
38
- executions_data = GoodJob::Job.connection.exec_query(GoodJob::Job.pg_or_jdbc_query(count_query), "GoodJob Dashboard Chart", binds)
33
+ executions_data = GoodJob::Job.connection.exec_query(GoodJob::Job.pg_or_jdbc_query(count_query), "GoodJob Dashboard Chart", start_end_binds)
39
34
 
40
35
  queue_names = executions_data.reject { |d| d['count'].nil? }.map { |d| d['queue_name'] || BaseFilter::EMPTY }.uniq
41
36
  labels = []
@@ -47,27 +42,27 @@ module GoodJob
47
42
  end
48
43
 
49
44
  {
50
- labels: labels,
51
- datasets: queues_data.map do |queue, data|
52
- label = queue || '(none)'
53
- {
54
- label: label,
55
- data: data,
56
- backgroundColor: string_to_hsl(label),
57
- borderColor: string_to_hsl(label),
58
- }
59
- end,
45
+ type: "line",
46
+ data: {
47
+ labels: labels,
48
+ datasets: queues_data.map do |queue, data|
49
+ label = queue || '(none)'
50
+ {
51
+ label: label,
52
+ data: data,
53
+ backgroundColor: string_to_hsl(label),
54
+ borderColor: string_to_hsl(label),
55
+ }
56
+ end,
57
+ },
58
+ options: {
59
+ scales: {
60
+ y: {
61
+ beginAtZero: true,
62
+ },
63
+ },
64
+ },
60
65
  }
61
66
  end
62
-
63
- def string_to_hsl(string)
64
- hash_value = string.sum
65
-
66
- hue = hash_value % 360
67
- saturation = (hash_value % 50) + 50
68
- lightness = '50'
69
-
70
- "hsl(#{hue}, #{saturation}%, #{lightness}%)"
71
- end
72
67
  end
73
68
  end
@@ -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
@@ -9,27 +9,17 @@ module GoodJob
9
9
  processes_count = GoodJob::Process.active.count
10
10
 
11
11
  render json: {
12
- jobs_count: number_to_human(jobs_count),
13
- batches_count: number_to_human(batches_count),
14
- cron_entries_count: number_to_human(cron_entries_count),
15
- processes_count: number_to_human(processes_count),
12
+ jobs_count: helpers.number_to_human(jobs_count),
13
+ batches_count: helpers.number_to_human(batches_count),
14
+ cron_entries_count: helpers.number_to_human(cron_entries_count),
15
+ processes_count: helpers.number_to_human(processes_count),
16
16
  }
17
17
  end
18
18
 
19
19
  def job_status
20
20
  @filter = JobsFilter.new(params)
21
21
 
22
- render json: @filter.states.transform_values { |count| number_with_delimiter(count) }
23
- end
24
-
25
- private
26
-
27
- def number_to_human(count)
28
- helpers.number_to_human(count, **helpers.translate_hash("good_job.number.human.decimal_units"))
29
- end
30
-
31
- def number_with_delimiter(count)
32
- helpers.number_with_delimiter(count, **helpers.translate_hash('good_job.number.format'))
22
+ render json: @filter.states.transform_values { |count| helpers.number_with_delimiter(count) }
33
23
  end
34
24
  end
35
25
  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("
@@ -15,5 +15,10 @@ module GoodJob
15
15
  ")
16
16
  .order("job_class")
17
17
  end
18
+
19
+ def show
20
+ representative_job = GoodJob::Job.find_by!(job_class: params[:id])
21
+ @job_class = representative_job.job_class
22
+ end
18
23
  end
19
24
  end
@@ -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>
@@ -4,25 +4,13 @@ function renderCharts(animate) {
4
4
  for (let i = 0; i < charts.length; i++) {
5
5
  const chartEl = charts[i];
6
6
  const chartData = JSON.parse(chartEl.dataset.json);
7
+ chartData.options ||= {};
8
+ chartData.options.animation = animate;
9
+ chartData.options.responsive = true;
10
+ chartData.options.maintainAspectRatio = false;
7
11
 
8
12
  const ctx = chartEl.getContext('2d');
9
- const chart = new Chart(ctx, {
10
- type: 'line',
11
- data: {
12
- labels: chartData.labels,
13
- datasets: chartData.datasets
14
- },
15
- options: {
16
- animation: animate,
17
- responsive: true,
18
- maintainAspectRatio: false,
19
- scales: {
20
- y: {
21
- beginAtZero: true
22
- }
23
- }
24
- }
25
- });
13
+ const chart = new Chart(ctx, chartData);
26
14
  }
27
15
  }
28
16
 
@@ -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
+ }
@@ -14,7 +14,7 @@ module GoodJob
14
14
  if sec < 1
15
15
  t 'good_job.duration.milliseconds', ms: (sec * 1000).floor
16
16
  elsif sec < 10
17
- t 'good_job.duration.less_than_10_seconds', sec: sec.floor
17
+ t 'good_job.duration.less_than_10_seconds', sec: number_with_delimiter(sec.floor(1))
18
18
  elsif sec < 60
19
19
  t 'good_job.duration.seconds', sec: sec.floor
20
20
  elsif sec < 3600
@@ -30,6 +30,14 @@ module GoodJob
30
30
  tag.time(text, datetime: timestamp, title: timestamp)
31
31
  end
32
32
 
33
+ def number_to_human(count)
34
+ super(count, **translate_hash("good_job.number.human.decimal_units"))
35
+ end
36
+
37
+ def number_with_delimiter(count)
38
+ super(count, **translate_hash('good_job.number.format'))
39
+ end
40
+
33
41
  def translate_hash(key, **options)
34
42
  translation_exists?(key, **options) ? translate(key, **options) : {}
35
43
  end
@@ -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