good_job 4.4.1 → 4.5.0

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +48 -0
  3. data/README.md +5 -5
  4. data/app/charts/good_job/performance_index_chart.rb +3 -0
  5. data/app/controllers/good_job/cleaner_controller.rb +37 -0
  6. data/app/controllers/good_job/metrics_controller.rb +2 -0
  7. data/app/filters/good_job/base_filter.rb +1 -0
  8. data/app/filters/good_job/jobs_filter.rb +18 -2
  9. data/app/frontend/good_job/modules/charts.js +12 -0
  10. data/app/frontend/good_job/modules/html_legend_plugin.js +56 -0
  11. data/app/frontend/good_job/style.css +13 -0
  12. data/app/models/good_job/job.rb +6 -1
  13. data/app/models/good_job/process.rb +31 -1
  14. data/app/views/good_job/batches/_jobs.erb +5 -4
  15. data/app/views/good_job/cleaner/index.html.erb +85 -0
  16. data/app/views/good_job/jobs/_table.erb +3 -2
  17. data/app/views/good_job/jobs/index.html.erb +1 -1
  18. data/app/views/good_job/performance/index.html.erb +1 -1
  19. data/app/views/good_job/performance/show.html.erb +1 -1
  20. data/app/views/good_job/processes/index.html.erb +1 -0
  21. data/app/views/good_job/shared/_chart.erb +2 -2
  22. data/app/views/good_job/shared/_chart_container.erb +21 -0
  23. data/app/views/good_job/shared/_navbar.erb +6 -0
  24. data/config/brakeman.ignore +26 -3
  25. data/config/locales/de.yml +15 -0
  26. data/config/locales/en.yml +15 -0
  27. data/config/locales/es.yml +15 -0
  28. data/config/locales/fr.yml +15 -0
  29. data/config/locales/it.yml +15 -0
  30. data/config/locales/ja.yml +15 -0
  31. data/config/locales/ko.yml +15 -0
  32. data/config/locales/nl.yml +15 -0
  33. data/config/locales/pt-BR.yml +15 -0
  34. data/config/locales/ru.yml +15 -0
  35. data/config/locales/tr.yml +15 -0
  36. data/config/locales/uk.yml +15 -0
  37. data/config/routes.rb +1 -1
  38. data/lib/good_job/capsule_tracker.rb +2 -19
  39. data/lib/good_job/cron_manager.rb +24 -12
  40. data/lib/good_job/version.rb +1 -1
  41. metadata +6 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 68dae79a3d669ccfd101719015e70aee8b54230c339012351ad677862732dc70
4
- data.tar.gz: d1242d0a898eff92aa4492d174de9cc73b0e610c5b1f56e358a304a864e39493
3
+ metadata.gz: 3f0a8926625f33b36c56def63a1507cfb0f8981a876449e907396aa2338b0b78
4
+ data.tar.gz: ad45bd8133d5f68bcf190bac3f7656864e10df2e49be477024e9f0c8422ce222
5
5
  SHA512:
6
- metadata.gz: 4bac343036b068fa03559d168346c37e41b110e4e9e902af8c58209880afdf75e44301ea845aca208ba583245fd4f2aa486d80b0905492e830501851b5190bd8
7
- data.tar.gz: 576f825db2e84ae3ae3e19ccdf3e3a7becd2db81fb1960ebd26f18aa0779224ec4e79b0a976cd256cc0e19303e3b53d88b41f10be6a405ce284a731dd99654b4
6
+ metadata.gz: 2f2e5c4ac4020dfd7aaed53bfb9b7e7480ca91e0aa7cfcbc385ac60d91a4b08e40397734993ffdffc9d5121f40afab04540d363fef925bf8a30bf987938514ab
7
+ data.tar.gz: 3460da22048d4fad135e12644c043723b2dcba8137a69a3b5bebf3b5df11e1c1a0ed0f94a8924b64cab4d1ce8b76bd55e6090a25c43f23afde41b27d398cf59a
data/CHANGELOG.md CHANGED
@@ -1,5 +1,53 @@
1
1
  # Changelog
2
2
 
3
+ ## [v4.5.0](https://github.com/bensheldon/good_job/tree/v4.5.0) (2024-11-22)
4
+
5
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v4.4.2...v4.5.0)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - Add "Discard Cleaner" page to dashboard UI [\#1538](https://github.com/bensheldon/good_job/pull/1538) ([lucasfcunha](https://github.com/lucasfcunha))
10
+ - Add Process memory usage and fix process state update [\#1516](https://github.com/bensheldon/good_job/pull/1516) ([noma4i](https://github.com/noma4i))
11
+
12
+ **Fixed bugs:**
13
+
14
+ - Fix cron double-enqueue because delay close to 0.01 and possibly clock-drift [\#1543](https://github.com/bensheldon/good_job/pull/1543) ([ccouton](https://github.com/ccouton))
15
+ - Fix badge color for running jobs [\#1525](https://github.com/bensheldon/good_job/pull/1525) ([Wittiest](https://github.com/Wittiest))
16
+
17
+ **Closed issues:**
18
+
19
+ - Can't load Dashboard [\#1532](https://github.com/bensheldon/good_job/issues/1532)
20
+ - Should we clean up batches if discarded callback jobs exist? [\#1528](https://github.com/bensheldon/good_job/issues/1528)
21
+ - Modify error color for Running tab when a job has 1 attempt [\#1518](https://github.com/bensheldon/good_job/issues/1518)
22
+ - Silence development warning output [\#1509](https://github.com/bensheldon/good_job/issues/1509)
23
+ - Proposal - A better way of managing errors through the GoodJob UI [\#1464](https://github.com/bensheldon/good_job/issues/1464)
24
+
25
+ **Merged pull requests:**
26
+
27
+ - Ignore some warnings with the `warning` gem [\#1545](https://github.com/bensheldon/good_job/pull/1545) ([Earlopain](https://github.com/Earlopain))
28
+ - Remove unneeded include of pg\_locks in query when displaying jobs table [\#1541](https://github.com/bensheldon/good_job/pull/1541) ([jgrau](https://github.com/jgrau))
29
+ - Update development environment to Rails 8 [\#1539](https://github.com/bensheldon/good_job/pull/1539) ([bensheldon](https://github.com/bensheldon))
30
+ - Bump the bundler-dependencies group with 9 updates [\#1534](https://github.com/bensheldon/good_job/pull/1534) ([dependabot[bot]](https://github.com/apps/dependabot))
31
+ - Bump the bundler-lint group with 5 updates [\#1533](https://github.com/bensheldon/good_job/pull/1533) ([dependabot[bot]](https://github.com/apps/dependabot))
32
+ - Bump rexml from 3.3.8 to 3.3.9 [\#1530](https://github.com/bensheldon/good_job/pull/1530) ([dependabot[bot]](https://github.com/apps/dependabot))
33
+ - Deprecate GoodJob::Job\#recent\_error [\#1526](https://github.com/bensheldon/good_job/pull/1526) ([Wittiest](https://github.com/Wittiest))
34
+
35
+ ## [v4.4.2](https://github.com/bensheldon/good_job/tree/v4.4.2) (2024-10-18)
36
+
37
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v4.4.1...v4.4.2)
38
+
39
+ **Merged pull requests:**
40
+
41
+ - Bump rails from 7.1.4 to 7.1.4.1 [\#1524](https://github.com/bensheldon/good_job/pull/1524) ([Earlopain](https://github.com/Earlopain))
42
+ - Drop compatibility code for `ForkTracker` [\#1519](https://github.com/bensheldon/good_job/pull/1519) ([Earlopain](https://github.com/Earlopain))
43
+ - Add vertical legend to performance dashboard [\#1517](https://github.com/bensheldon/good_job/pull/1517) ([Wittiest](https://github.com/Wittiest))
44
+ - Bump the bundler-dependencies group across 1 directory with 10 updates [\#1515](https://github.com/bensheldon/good_job/pull/1515) ([dependabot[bot]](https://github.com/apps/dependabot))
45
+ - Manually bump tapioca / regenerate rbi [\#1514](https://github.com/bensheldon/good_job/pull/1514) ([Earlopain](https://github.com/Earlopain))
46
+ - Remove rack from the gemfile [\#1512](https://github.com/bensheldon/good_job/pull/1512) ([Earlopain](https://github.com/Earlopain))
47
+ - Add regression test for searchable numeric arguments [\#1510](https://github.com/bensheldon/good_job/pull/1510) ([bensheldon](https://github.com/bensheldon))
48
+ - Better parallelize CI jobs [\#1507](https://github.com/bensheldon/good_job/pull/1507) ([Earlopain](https://github.com/Earlopain))
49
+ - Bump webrick from 1.8.1 to 1.8.2 [\#1503](https://github.com/bensheldon/good_job/pull/1503) ([dependabot[bot]](https://github.com/apps/dependabot))
50
+
3
51
  ## [v4.4.1](https://github.com/bensheldon/good_job/tree/v4.4.1) (2024-10-10)
4
52
 
5
53
  [Full Changelog](https://github.com/bensheldon/good_job/compare/v4.4.0...v4.4.1)
data/README.md CHANGED
@@ -1715,14 +1715,14 @@ Environment variables that may help with debugging:
1715
1715
  - `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.
1716
1716
  - `SHOW_BROWSER=1`: Run system tests headfully with Chrome/Chromedriver. Use `binding.irb` in the system tests to pause.
1717
1717
 
1718
- Appraisal can be used to run a test matrix of multiple versions of Rails:
1718
+ The gemfiles in `gemfiles/` can be used to run tests against different rails versions:
1719
1719
 
1720
1720
  ```bash
1721
- # Install Appraisal matrix of gemfiles
1722
- bin/appraisal
1721
+ # Install dependencies
1722
+ BUNDLE_GEMFILE=gemfiles/rails_6.1.gemfile bundle install
1723
1723
 
1724
- # Run tests against matrix
1725
- bin/appraisal bin/rspec
1724
+ # Run the tests
1725
+ BUNDLE_GEMFILE=gemfiles/rails_6.1.gemfile bin/rspec
1726
1726
  ```
1727
1727
 
1728
1728
  ### Release
@@ -56,6 +56,9 @@ module GoodJob
56
56
  display: true,
57
57
  text: I18n.t("good_job.performance.index.chart_title"),
58
58
  },
59
+ legend: {
60
+ vertical: true,
61
+ },
59
62
  },
60
63
  scales: {
61
64
  y: {
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GoodJob
4
+ class CleanerController < ApplicationController
5
+ def index
6
+ @filter = JobsFilter.new(params)
7
+
8
+ @discarded_jobs_grouped_by_exception =
9
+ GoodJob::Job.discarded
10
+ .select(<<-SQL.squish)
11
+ SPLIT_PART(error, ': ', 1) AS exception_class,
12
+ count(id) AS failed,
13
+ COUNT(id) FILTER (WHERE "finished_at" > NOW() - INTERVAL '1 HOUR') AS last_1_hour,
14
+ COUNT(id) FILTER (WHERE "finished_at" > NOW() - INTERVAL '3 HOURS') AS last_3_hours,
15
+ COUNT(id) FILTER (WHERE "finished_at" > NOW() - INTERVAL '24 HOURS') AS last_24_hours,
16
+ COUNT(id) FILTER (WHERE "finished_at" > NOW() - INTERVAL '3 DAYS') AS last_3_days,
17
+ COUNT(id) FILTER (WHERE "finished_at" > NOW() - INTERVAL '7 DAYS') AS last_7_days
18
+ SQL
19
+ .order(:exception_class)
20
+ .group(:exception_class)
21
+
22
+ @discarded_jobs_grouped_by_class =
23
+ GoodJob::Job.discarded
24
+ .select(<<-SQL.squish)
25
+ job_class,
26
+ count(id) AS failed,
27
+ COUNT(*) FILTER (WHERE "finished_at" > NOW() - INTERVAL '1 HOUR') AS last_1_hour,
28
+ COUNT(*) FILTER (WHERE "finished_at" > NOW() - INTERVAL '3 HOURS') AS last_3_hours,
29
+ COUNT(*) FILTER (WHERE "finished_at" > NOW() - INTERVAL '24 HOURS') AS last_24_hours,
30
+ COUNT(*) FILTER (WHERE "finished_at" > NOW() - INTERVAL '3 DAYS') AS last_3_days,
31
+ COUNT(*) FILTER (WHERE "finished_at" > NOW() - INTERVAL '7 DAYS') AS last_7_days
32
+ SQL
33
+ .order(:job_class)
34
+ .group(:job_class)
35
+ end
36
+ end
37
+ end
@@ -7,12 +7,14 @@ module GoodJob
7
7
  batches_count = GoodJob::BatchRecord.all.size
8
8
  cron_entries_count = GoodJob::CronEntry.all.size
9
9
  processes_count = GoodJob::Process.active.count
10
+ discarded_count = GoodJob::Job.discarded.count
10
11
 
11
12
  render json: {
12
13
  jobs_count: helpers.number_to_human(jobs_count),
13
14
  batches_count: helpers.number_to_human(batches_count),
14
15
  cron_entries_count: helpers.number_to_human(cron_entries_count),
15
16
  processes_count: helpers.number_to_human(processes_count),
17
+ discarded_count: helpers.number_to_human(discarded_count),
16
18
  }
17
19
  end
18
20
 
@@ -54,6 +54,7 @@ module GoodJob
54
54
  query: params[:query],
55
55
  state: params[:state],
56
56
  cron_key: params[:cron_key],
57
+ finished_since: params[:finished_since],
57
58
  }.merge(override).delete_if { |_, v| v.blank? }
58
59
  end
59
60
 
@@ -27,6 +27,7 @@ module GoodJob
27
27
  query = query.where(queue_name: filter_params[:queue_name]) if filter_params[:queue_name].present?
28
28
  query = query.search_text(filter_params[:query]) if filter_params[:query].present?
29
29
  query = query.where(cron_key: filter_params[:cron_key]) if filter_params[:cron_key].present?
30
+ query = query.where(finished_at: finished_since(filter_params[:finished_since])..) if filter_params[:finished_since].present?
30
31
 
31
32
  if filter_params[:state]
32
33
  case filter_params[:state]
@@ -39,7 +40,7 @@ module GoodJob
39
40
  when 'scheduled'
40
41
  query = query.scheduled
41
42
  when 'running'
42
- query = query.running.select("#{GoodJob::Job.table_name}.*", 'pg_locks.locktype')
43
+ query = query.running
43
44
  when 'queued'
44
45
  query = query.queued
45
46
  end
@@ -55,11 +56,26 @@ module GoodJob
55
56
  private
56
57
 
57
58
  def query_for_records
58
- filtered_query.includes_advisory_locks
59
+ filtered_query
59
60
  end
60
61
 
61
62
  def default_base_query
62
63
  GoodJob::Job.all
63
64
  end
65
+
66
+ def finished_since(finished_since)
67
+ case finished_since
68
+ when '1_hour_ago'
69
+ 1.hour.ago
70
+ when '3_hours_ago'
71
+ 3.hours.ago
72
+ when '24_hours_ago'
73
+ 24.hours.ago
74
+ when '3_days_ago'
75
+ 3.days.ago
76
+ when '7_days_ago'
77
+ 7.days.ago
78
+ end
79
+ end
64
80
  end
65
81
  end
@@ -1,3 +1,5 @@
1
+ import htmlLegendPlugin from "html_legend_plugin";
2
+
1
3
  function renderCharts(animate) {
2
4
  const charts = document.querySelectorAll('.chart');
3
5
 
@@ -5,6 +7,16 @@ function renderCharts(animate) {
5
7
  const chartEl = charts[i];
6
8
  const chartData = JSON.parse(chartEl.dataset.json);
7
9
  chartData.options ||= {};
10
+
11
+ if (chartData.options.plugins?.legend?.vertical) {
12
+ chartData.plugins = [htmlLegendPlugin];
13
+ chartData.options.plugins = {
14
+ ...chartData.options.plugins,
15
+ legend: {
16
+ display: false,
17
+ }
18
+ }
19
+ }
8
20
  chartData.options.animation = animate;
9
21
  chartData.options.responsive = true;
10
22
  chartData.options.maintainAspectRatio = false;
@@ -0,0 +1,56 @@
1
+ const generateListItem = (item) => {
2
+ const li = document.createElement('li');
3
+ li.className = 'd-flex align-items-center text-nowrap mb-2';
4
+
5
+ const boxSpan = document.createElement('span');
6
+ boxSpan.className = 'legend-item-color-box';
7
+ boxSpan.style.background = item.fillStyle;
8
+ boxSpan.style.borderColor = item.strokeStyle;
9
+ boxSpan.style.borderWidth = item.lineWidth + 'px';
10
+
11
+ const textContainer = document.createElement('p');
12
+ textContainer.className = 'item-text m-0 small';
13
+ textContainer.style.color = item.fontColor;
14
+ textContainer.style.textDecoration = item.hidden ? 'line-through' : '';
15
+
16
+ const text = document.createTextNode(item.text);
17
+ textContainer.appendChild(text);
18
+
19
+ li.appendChild(boxSpan);
20
+ li.appendChild(textContainer);
21
+
22
+ return li;
23
+ }
24
+
25
+ const htmlLegendPlugin = {
26
+ id: 'htmlLegend',
27
+ afterUpdate(chart, _args, _options) {
28
+ const {type} = chart.config;
29
+ const ul = document.getElementById('chart-legend-ul');
30
+
31
+ // Remove old legend items
32
+ while (ul.firstChild) {
33
+ ul.firstChild.remove();
34
+ }
35
+
36
+ // Reuse the built-in legendItems generator
37
+ const items = chart.options.plugins.legend.labels.generateLabels(chart);
38
+
39
+ items.forEach(item => {
40
+ const li = generateListItem(item);
41
+ ul.appendChild(li);
42
+
43
+ li.onclick = () => {
44
+ if (type === 'pie' || type === 'doughnut') {
45
+ // Pie and doughnut charts only have a single dataset and visibility is per item
46
+ chart.toggleDataVisibility(item.index);
47
+ } else {
48
+ chart.setDatasetVisibility(item.datasetIndex, !chart.isDatasetVisible(item.datasetIndex));
49
+ }
50
+ chart.update();
51
+ };
52
+ });
53
+ }
54
+ };
55
+
56
+ export { htmlLegendPlugin as default };
@@ -24,6 +24,19 @@
24
24
  height: 200px;
25
25
  }
26
26
 
27
+ .legend-item-color-box {
28
+ display: inline-block;
29
+ flex-shrink: 0;
30
+ height: 20px;
31
+ width: 20px;
32
+ border-style: solid;
33
+ margin-right: 3px;
34
+ }
35
+
36
+ #chart-legend-container {
37
+ height: 200px;
38
+ }
39
+
27
40
  /* Break out of a container */
28
41
  .break-out {
29
42
  width:100vw;
@@ -390,7 +390,12 @@ module GoodJob
390
390
  # If the job has been retried, the error will be fetched from the previous {Execution} record.
391
391
  # @return [String]
392
392
  def recent_error
393
- error || executions[-2]&.error
393
+ GoodJob.deprecator.warn(<<~DEPRECATION)
394
+ The `GoodJob::Job#recent_error` method is deprecated and will be removed in the next major release.
395
+
396
+ Replace usage of GoodJob::Job#recent_error with `GoodJob::Job#error`.
397
+ DEPRECATION
398
+ error
394
399
  end
395
400
 
396
401
  # Errors for the job to be displayed in the Dashboard.
@@ -12,6 +12,28 @@ module GoodJob # :nodoc:
12
12
  STALE_INTERVAL = 30.seconds
13
13
  # Interval until the process record is treated as expired
14
14
  EXPIRED_INTERVAL = 5.minutes
15
+ PROCESS_MEMORY = case RUBY_PLATFORM
16
+ when /linux/
17
+ lambda do |pid|
18
+ File.readlines("/proc/#{pid}/smaps_rollup").each do |line|
19
+ next unless line.start_with?('Pss:')
20
+
21
+ break line.split[1].to_i
22
+ end
23
+ rescue Errno::ENOENT
24
+ File.readlines("/proc/#{pid}/status").each do |line|
25
+ next unless line.start_with?('VmRSS:')
26
+
27
+ break line.split[1].to_i
28
+ end
29
+ end
30
+ when /darwin|bsd/
31
+ lambda do |pid|
32
+ `ps -o pid,rss -p #{pid.to_i}`.lines.last.split.last.to_i
33
+ end
34
+ else
35
+ ->(_pid) { 0 }
36
+ end
15
37
 
16
38
  self.table_name = 'good_job_processes'
17
39
  self.implicit_order_column = 'created_at'
@@ -56,6 +78,13 @@ module GoodJob # :nodoc:
56
78
  end
57
79
  end
58
80
 
81
+ # @return [Integer]
82
+ def self.memory_usage(pid)
83
+ PROCESS_MEMORY.call(pid)
84
+ rescue StandardError
85
+ 0
86
+ end
87
+
59
88
  def self.find_or_create_record(id:, with_advisory_lock: false)
60
89
  attributes = {
61
90
  id: id,
@@ -83,6 +112,7 @@ module GoodJob # :nodoc:
83
112
  {
84
113
  hostname: Socket.gethostname,
85
114
  pid: ::Process.pid,
115
+ memory: memory_usage(::Process.pid),
86
116
  proctitle: $PROGRAM_NAME,
87
117
  preserve_job_records: GoodJob.preserve_job_records,
88
118
  retry_on_unhandled_error: GoodJob.retry_on_unhandled_error,
@@ -98,8 +128,8 @@ module GoodJob # :nodoc:
98
128
  end
99
129
 
100
130
  def refresh
101
- self.state = self.class.process_state
102
131
  reload # verify the record still exists in the database
132
+ self.state = self.class.process_state
103
133
  update(state: state, updated_at: Time.current)
104
134
  rescue ActiveRecord::RecordNotFound
105
135
  @new_record = true
@@ -35,17 +35,18 @@
35
35
  </div>
36
36
  <div class="col-4 col-lg-1 text-lg-end">
37
37
  <div class="d-lg-none small text-muted mt-1"><%= t "good_job.models.job.attempts" %></div>
38
- <% if job.executions_count > 0 && job.status != :finished %>
38
+ <% if job.error %>
39
39
  <%= tag.span job.executions_count, class: "badge rounded-pill bg-danger",
40
40
  data: {
41
41
  bs_toggle: "popover",
42
42
  bs_trigger: "hover focus click",
43
43
  bs_placement: "bottom",
44
- bs_content: job.recent_error,
44
+ bs_content: job.display_error,
45
45
  }
46
46
  %>
47
47
  <% else %>
48
- <span class="badge bg-secondary rounded-pill"><%= job.executions_count %></span>
48
+ <% executions_badge_color = job.executions_count > 1 ? "bg-warning" : "bg-secondary" %>
49
+ <span class="badge rounded-pill <%= executions_badge_color %>"><%= job.executions_count %></span>
49
50
  <% end %>
50
51
  </div>
51
52
  <div class="mt-3 mt-lg-0 col d-flex gap-3 align-items-center justify-content-end">
@@ -79,7 +80,7 @@
79
80
  <% end %>
80
81
  </li>
81
82
  <li>
82
- <%= link_to job_path(job.id), method: :delete, class: "dropdown-item #{'disabled' unless job.status.in? [:discarded, :finished]}", title: t(".actions.destroy"), data: { confirm: t(".actions.confirm_destroy"), disable: true } do %>
83
+ <%= link_to job_path(job.id), method: :delete, class: "dropdown-item #{'disabled' unless job.finished?}", title: t(".actions.destroy"), data: { confirm: t(".actions.confirm_destroy"), disable: true } do %>
83
84
  <%= render_icon "trash" %>
84
85
  <%= t "good_job.actions.destroy" %>
85
86
  <% end %>
@@ -0,0 +1,85 @@
1
+ <div class="border-bottom">
2
+ <h2 class="pt-3 pb-2"><%= t ".title" %></h2>
3
+ </div>
4
+
5
+ <div>
6
+ <h4 class="pt-3 pb-2"><%= t ".grouped_by_class" %></h4>
7
+ <table class="table align-middle" id="by-job-class">
8
+ <thead>
9
+ <tr>
10
+ <th scope="col" class="col-3"><%= t ".class" %></th>
11
+ <th scope="col" class="text-center"><%= t ".all" %></th>
12
+ <th scope="col" class="text-center"><%= t ".last_1_hour" %></th>
13
+ <th scope="col" class="text-center"><%= t ".last_3_hours" %></th>
14
+ <th scope="col" class="text-center"><%= t ".last_24_hours" %></th>
15
+ <th scope="col" class="text-center"><%= t ".last_3_days" %></th>
16
+ <th scope="col" class="text-center"><%= t ".last_7_days" %></th>
17
+ </tr>
18
+ </thead>
19
+ <tbody>
20
+ <% @discarded_jobs_grouped_by_class.each do |discard_job| %>
21
+ <tr>
22
+ <td scope="row" class="col-3 text-break"><%= discard_job.job_class %></td>
23
+ <td class="text-center "><%= link_to discard_job.failed, jobs_path(@filter.to_params(job_class: discard_job.job_class, state: 'discarded')) %></td>
24
+ <td class="text-center"><%= link_to discard_job.last_1_hour, jobs_path(@filter.to_params(job_class: discard_job.job_class, state: 'discarded', finished_since: '1_hour_ago')) %></td>
25
+ <td class="text-center"><%= link_to discard_job.last_3_hours, jobs_path(@filter.to_params(job_class: discard_job.job_class, state: 'discarded', finished_since: '3_hours_ago')) %></td>
26
+ <td class="text-center"><%= link_to discard_job.last_24_hours, jobs_path(@filter.to_params(job_class: discard_job.job_class, state: 'discarded', finished_since: '24_hours_ago')) %></td>
27
+ <td class="text-center"><%= link_to discard_job.last_3_days, jobs_path(@filter.to_params(job_class: discard_job.job_class, state: 'discarded', finished_since: '3_days_ago')) %></td>
28
+ <td class="text-center"><%= link_to discard_job.last_7_days, jobs_path(@filter.to_params(job_class: discard_job.job_class, state: 'discarded', finished_since: '7_days_ago')) %></td>
29
+ </tr>
30
+ <% end %>
31
+ </tbody>
32
+ <tfoot>
33
+ <tr>
34
+ <td scope="row" class="col-3 fw-bold"><%= t ".total" %></td>
35
+ <td class="text-center fw-bold"><%= link_to @discarded_jobs_grouped_by_class.sum(&:failed), jobs_path(@filter.to_params(state: 'discarded')) %></td>
36
+ <td class="text-center fw-bold"><%= link_to @discarded_jobs_grouped_by_class.sum(&:last_1_hour), jobs_path(@filter.to_params(state: 'discarded', finished_since: '1_hour_ago')) %></td>
37
+ <td class="text-center fw-bold"><%= link_to @discarded_jobs_grouped_by_class.sum(&:last_3_hours), jobs_path(@filter.to_params(state: 'discarded', finished_since: '3_hours_ago')) %></td>
38
+ <td class="text-center fw-bold"><%= link_to @discarded_jobs_grouped_by_class.sum(&:last_24_hours), jobs_path(@filter.to_params(state: 'discarded', finished_since: '24_hours_ago')) %></td>
39
+ <td class="text-center fw-bold"><%= link_to @discarded_jobs_grouped_by_class.sum(&:last_3_days), jobs_path(@filter.to_params(state: 'discarded', finished_since: '3_days_ago')) %></td>
40
+ <td class="text-center fw-bold"><%= link_to @discarded_jobs_grouped_by_class.sum(&:last_7_days), jobs_path(@filter.to_params(state: 'discarded', finished_since: '7_days_ago')) %></td>
41
+ </tr>
42
+ </tfoot>
43
+ </table>
44
+ </div>
45
+
46
+ <div>
47
+ <h4 class="pt-3 pb-2"><%= t ".grouped_by_exception" %></h4>
48
+ <table class="table align-middle" id="by-exception">
49
+ <thead>
50
+ <tr>
51
+ <th scope="col" class="col-3"><%= t ".exception" %></th>
52
+ <th scope="col" class="text-center"><%= t ".all" %></th>
53
+ <th scope="col" class="text-center"><%= t ".last_1_hour" %></th>
54
+ <th scope="col" class="text-center"><%= t ".last_3_hours" %></th>
55
+ <th scope="col" class="text-center"><%= t ".last_24_hours" %></th>
56
+ <th scope="col" class="text-center"><%= t ".last_3_days" %></th>
57
+ <th scope="col" class="text-center"><%= t ".last_7_days" %></th>
58
+ </tr>
59
+ </thead>
60
+ <tbody>
61
+ <% @discarded_jobs_grouped_by_exception.each do |discard_job| %>
62
+ <tr>
63
+ <td scope="row" class="col-3 text-break"><%= discard_job.exception_class %></td>
64
+ <td class="text-center"><%= link_to discard_job.failed, jobs_path(@filter.to_params(state: 'discarded', query: discard_job.exception_class)) %></td>
65
+ <td class="text-center"><%= link_to discard_job.last_1_hour, jobs_path(@filter.to_params(state: 'discarded', query: discard_job.exception_class, finished_since: '1_hour_ago')) %></td>
66
+ <td class="text-center"><%= link_to discard_job.last_3_hours, jobs_path(@filter.to_params(state: 'discarded', query: discard_job.exception_class, finished_since: '3_hours_ago')) %></td>
67
+ <td class="text-center"><%= link_to discard_job.last_24_hours, jobs_path(@filter.to_params(state: 'discarded', query: discard_job.exception_class, finished_since: '24_hours_ago')) %></td>
68
+ <td class="text-center"><%= link_to discard_job.last_3_days, jobs_path(@filter.to_params(state: 'discarded', query: discard_job.exception_class, finished_since: '3_days_ago')) %></td>
69
+ <td class="text-center"><%= link_to discard_job.last_7_days, jobs_path(@filter.to_params(state: 'discarded', query: discard_job.exception_class, finished_since: '7_days_ago')) %></td>
70
+ </tr>
71
+ <% end %>
72
+ </tbody>
73
+ <tfoot>
74
+ <tr>
75
+ <td scope="row" class="col-3 fw-bold"><%= t ".total" %></td>
76
+ <td class="text-center fw-bold"><%= link_to @discarded_jobs_grouped_by_exception.sum(&:failed), jobs_path(@filter.to_params(state: 'discarded')) %></td>
77
+ <td class="text-center fw-bold"><%= link_to @discarded_jobs_grouped_by_exception.sum(&:last_1_hour), jobs_path(@filter.to_params(state: 'discarded', finished_since: '1_hour_ago')) %></td>
78
+ <td class="text-center fw-bold"><%= link_to @discarded_jobs_grouped_by_exception.sum(&:last_3_hours), jobs_path(@filter.to_params(state: 'discarded', finished_since: '3_hours_ago')) %></td>
79
+ <td class="text-center fw-bold"><%= link_to @discarded_jobs_grouped_by_exception.sum(&:last_24_hours), jobs_path(@filter.to_params(state: 'discarded', finished_since: '24_hours_ago')) %></td>
80
+ <td class="text-center fw-bold"><%= link_to @discarded_jobs_grouped_by_exception.sum(&:last_3_days), jobs_path(@filter.to_params(state: 'discarded', finished_since: '3_days_ago')) %></td>
81
+ <td class="text-center fw-bold"><%= link_to @discarded_jobs_grouped_by_exception.sum(&:last_7_days), jobs_path(@filter.to_params(state: 'discarded', finished_since: '7_days_ago')) %></td>
82
+ </tr>
83
+ </tfoot>
84
+ </table>
85
+ </div>
@@ -84,7 +84,7 @@
84
84
  </div>
85
85
  <div class="col-4 col-lg-1 text-lg-end">
86
86
  <div class="d-lg-none small text-muted mt-1"><%= t "good_job.models.job.attempts" %></div>
87
- <% if job.executions_count > 0 && job.status != :succeeded %>
87
+ <% if job.error %>
88
88
  <%= tag.span job.executions_count, class: "badge rounded-pill bg-danger",
89
89
  data: {
90
90
  bs_toggle: "popover",
@@ -94,7 +94,8 @@
94
94
  }
95
95
  %>
96
96
  <% else %>
97
- <span class="badge bg-secondary rounded-pill"><%= job.executions_count %></span>
97
+ <% executions_badge_color = job.executions_count > 1 ? "bg-warning" : "bg-secondary" %>
98
+ <span class="badge rounded-pill <%= executions_badge_color %>"><%= job.executions_count %></span>
98
99
  <% end %>
99
100
  </div>
100
101
  <div class="mt-3 mt-lg-0 col">
@@ -1,5 +1,5 @@
1
1
  <%= render 'good_job/shared/filter', title: t("good_job.shared.navbar.jobs"), filter: @filter %>
2
- <%= render 'good_job/shared/chart', chart_data: GoodJob::ScheduledByQueueChart.new(@filter).data %>
2
+ <%= render 'good_job/shared/chart_container', chart_data: GoodJob::ScheduledByQueueChart.new(@filter).data %>
3
3
 
4
4
  <div data-live-poll-region="jobs-table">
5
5
  <%= render 'good_job/jobs/table', jobs: @filter.records, filter: @filter %>
@@ -2,7 +2,7 @@
2
2
  <h2 class="pt-3 pb-2"><%= t ".title" %></h2>
3
3
  </div>
4
4
 
5
- <%= render 'good_job/shared/chart', chart_data: GoodJob::PerformanceIndexChart.new.data %>
5
+ <%= render 'good_job/shared/chart_container', chart_data: GoodJob::PerformanceIndexChart.new.data %>
6
6
 
7
7
  <div class="my-3 card">
8
8
  <div class="list-group list-group-flush text-nowrap" role="table">
@@ -2,4 +2,4 @@
2
2
  <h2 class="pt-3 pb-2"><%= t ".title" %> - <%= @job_class %></h2>
3
3
  </div>
4
4
 
5
- <%= render 'good_job/shared/chart', chart_data: GoodJob::PerformanceShowChart.new(@job_class).data %>
5
+ <%= render 'good_job/shared/chart_container', chart_data: GoodJob::PerformanceShowChart.new(@job_class).data %>
@@ -38,6 +38,7 @@
38
38
  <span class="badge rounded-pill bg-body-secondary text-secondary"><%= process.state["pid"] %></span>
39
39
  <span class="text-muted small">@</span>
40
40
  <span class="badge rounded-pill bg-body-secondary text-secondary"><%= process.state["hostname"] %></span>
41
+ <span class="badge rounded-pill bg-body-secondary text-secondary"><%= (process.state["memory"] / 1024).to_i %> MB</span>
41
42
  </div>
42
43
  </div>
43
44
  <div class="col">
@@ -1,5 +1,5 @@
1
- <div class="py-4" data-live-poll-region="chart">
2
- <div class="chart-wrapper container-fluid">
1
+ <div class="" data-live-poll-region="chart">
2
+ <div class="chart-wrapper">
3
3
  <canvas class="chart" data-json="<%= chart_data.to_json %>"></canvas>
4
4
  </div>
5
5
  </div>
@@ -0,0 +1,21 @@
1
+ <% vertical_legend = chart_data.dig(:options, :plugins, :legend, :vertical) %>
2
+
3
+ <div class="container-fluid">
4
+ <div class="row d-flex">
5
+ <% if vertical_legend %>
6
+ <div class="row d-flex">
7
+ <div class="col-md-10">
8
+ <%= render 'good_job/shared/chart', chart_data: chart_data %>
9
+ </div>
10
+ <div class="col-md-2 d-flex flex-column">
11
+ <div id="chart-legend-container" class="flex-fill overflow-auto">
12
+ <ul id="chart-legend-ul" class="list-unstyled p-0 mx-0 py-2">
13
+ </ul>
14
+ </div>
15
+ </div>
16
+ </div>
17
+ <% else %>
18
+ <%= render 'good_job/shared/chart', chart_data: chart_data %>
19
+ <% end %>
20
+ </div>
21
+ </div>
@@ -44,6 +44,12 @@
44
44
  <%= t(".performance") %>
45
45
  <% end %>
46
46
  </li>
47
+ <li class="nav-item">
48
+ <%= link_to cleaner_index_path, class: ["nav-link", ("active" if controller_name == 'cleaner')] do %>
49
+ <%= t(".cleaner") %>
50
+ <span data-async-values-target="value" data-async-values-key="discarded_count" class="badge bg-secondary rounded-pill d-none"></span>
51
+ <% end %>
52
+ </li>
47
53
  </ul>
48
54
 
49
55
  <ul class="navbar-nav">
@@ -53,7 +53,7 @@
53
53
  "check_name": "SQL",
54
54
  "message": "Possible SQL injection",
55
55
  "file": "app/models/good_job/job.rb",
56
- "line": 140,
56
+ "line": 135,
57
57
  "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
58
58
  "code": "Arel.sql(\"(CASE #{queues.map.with_index do\n sanitize_sql_array([\"WHEN queue_name = ? THEN ?\", queue_name, index])\n end.join(\" \")} ELSE #{queues.size} END)\")",
59
59
  "render_path": null,
@@ -68,8 +68,31 @@
68
68
  89
69
69
  ],
70
70
  "note": "Developer provided value, queue_name, is sanitized."
71
+ },
72
+ {
73
+ "warning_type": "Command Injection",
74
+ "warning_code": 14,
75
+ "fingerprint": "dbb80167f57edebfd9da72e1278d425095a0329755e24d3c50e0fda6bb21c097",
76
+ "check_name": "Execute",
77
+ "message": "Possible command injection",
78
+ "file": "app/models/good_job/process.rb",
79
+ "line": 32,
80
+ "link": "https://brakemanscanner.org/docs/warning_types/command_injection/",
81
+ "code": "`ps -o pid,rss -p #{pid.to_i}`",
82
+ "render_path": null,
83
+ "location": {
84
+ "type": "method",
85
+ "class": "Process",
86
+ "method": null
87
+ },
88
+ "user_input": "pid.to_i",
89
+ "confidence": "Medium",
90
+ "cwe_id": [
91
+ 77
92
+ ],
93
+ "note": ""
71
94
  }
72
95
  ],
73
- "updated": "2024-07-18 18:05:56 -0700",
74
- "brakeman_version": "6.1.2"
96
+ "updated": "2024-11-16 17:00:20 -0600",
97
+ "brakeman_version": "6.2.1"
75
98
  }
@@ -35,6 +35,20 @@ de:
35
35
  callback_jobs: Callback-Jobs
36
36
  table:
37
37
  no_batches_found: Keine Batches gefunden.
38
+ cleaner:
39
+ index:
40
+ all: All
41
+ class: Class
42
+ exception: Exception
43
+ grouped_by_class: Discards grouped by Class
44
+ grouped_by_exception: Discards grouped by Exception
45
+ last_1_hour: Last 1 hour
46
+ last_24_hours: Last 24 hours
47
+ last_3_days: Last 3 days
48
+ last_3_hours: Last 3 hours
49
+ last_7_days: Last 7 days
50
+ title: Discard Cleaner
51
+ total: Total
38
52
  cron_entries:
39
53
  actions:
40
54
  confirm_disable: Bist du sicher, dass du diesen Cron-Eintrag deaktivieren willst?
@@ -236,6 +250,7 @@ de:
236
250
  search: Suchen
237
251
  navbar:
238
252
  batches: Batches
253
+ cleaner: Discard Cleaner
239
254
  cron_schedules: Cron
240
255
  jobs: Jobs
241
256
  live_poll: Live Poll
@@ -35,6 +35,20 @@ en:
35
35
  callback_jobs: Callback Jobs
36
36
  table:
37
37
  no_batches_found: No batches found.
38
+ cleaner:
39
+ index:
40
+ all: All
41
+ class: Class
42
+ exception: Exception
43
+ grouped_by_class: Discards grouped by Class
44
+ grouped_by_exception: Discards grouped by Exception
45
+ last_1_hour: Last 1 hour
46
+ last_24_hours: Last 24 hours
47
+ last_3_days: Last 3 days
48
+ last_3_hours: Last 3 hours
49
+ last_7_days: Last 7 days
50
+ title: Discard Cleaner
51
+ total: Total
38
52
  cron_entries:
39
53
  actions:
40
54
  confirm_disable: Are you sure you want to disable this cron entry?
@@ -236,6 +250,7 @@ en:
236
250
  search: Search
237
251
  navbar:
238
252
  batches: Batches
253
+ cleaner: Discard Cleaner
239
254
  cron_schedules: Cron
240
255
  jobs: Jobs
241
256
  live_poll: Live Poll
@@ -35,6 +35,20 @@ es:
35
35
  callback_jobs: Callback Jobs
36
36
  table:
37
37
  no_batches_found: No hay lotes.
38
+ cleaner:
39
+ index:
40
+ all: All
41
+ class: Class
42
+ exception: Exception
43
+ grouped_by_class: Discards grouped by Class
44
+ grouped_by_exception: Discards grouped by Exception
45
+ last_1_hour: Last 1 hour
46
+ last_24_hours: Last 24 hours
47
+ last_3_days: Last 3 days
48
+ last_3_hours: Last 3 hours
49
+ last_7_days: Last 7 days
50
+ title: Discard Cleaner
51
+ total: Total
38
52
  cron_entries:
39
53
  actions:
40
54
  confirm_disable: "¿Estás seguro que querés deshabilitar esta tarea programada?"
@@ -236,6 +250,7 @@ es:
236
250
  search: Buscar
237
251
  navbar:
238
252
  batches: Lotes
253
+ cleaner: Discard Cleaner
239
254
  cron_schedules: Cron
240
255
  jobs: Tareas
241
256
  live_poll: En vivo
@@ -35,6 +35,20 @@ fr:
35
35
  callback_jobs: Jobs de rappel
36
36
  table:
37
37
  no_batches_found: Aucun lot trouvé.
38
+ cleaner:
39
+ index:
40
+ all: All
41
+ class: Class
42
+ exception: Exception
43
+ grouped_by_class: Discards grouped by Class
44
+ grouped_by_exception: Discards grouped by Exception
45
+ last_1_hour: Last 1 hour
46
+ last_24_hours: Last 24 hours
47
+ last_3_days: Last 3 days
48
+ last_3_hours: Last 3 hours
49
+ last_7_days: Last 7 days
50
+ title: Discard Cleaner
51
+ total: Total
38
52
  cron_entries:
39
53
  actions:
40
54
  confirm_disable: Voulez-vous vraiment désactiver cette entrée cron ?
@@ -236,6 +250,7 @@ fr:
236
250
  search: Rechercher
237
251
  navbar:
238
252
  batches: Lots
253
+ cleaner: Discard Cleaner
239
254
  cron_schedules: Cron
240
255
  jobs: Jobs
241
256
  live_poll: En direct
@@ -35,6 +35,20 @@ it:
35
35
  callback_jobs: Job di callback
36
36
  table:
37
37
  no_batches_found: Nessun batch trovato.
38
+ cleaner:
39
+ index:
40
+ all: All
41
+ class: Class
42
+ exception: Exception
43
+ grouped_by_class: Discards grouped by Class
44
+ grouped_by_exception: Discards grouped by Exception
45
+ last_1_hour: Last 1 hour
46
+ last_24_hours: Last 24 hours
47
+ last_3_days: Last 3 days
48
+ last_3_hours: Last 3 hours
49
+ last_7_days: Last 7 days
50
+ title: Discard Cleaner
51
+ total: Total
38
52
  cron_entries:
39
53
  actions:
40
54
  confirm_disable: Sei sicuro di voler disabilitare questa voce cron?
@@ -236,6 +250,7 @@ it:
236
250
  search: Cerca
237
251
  navbar:
238
252
  batches: Batch
253
+ cleaner: Discard Cleaner
239
254
  cron_schedules: Cron
240
255
  jobs: Job
241
256
  live_poll: Live Poll
@@ -35,6 +35,20 @@ ja:
35
35
  callback_jobs: コールバックジョブ
36
36
  table:
37
37
  no_batches_found: バッチが見つかりませんでした。
38
+ cleaner:
39
+ index:
40
+ all: All
41
+ class: Class
42
+ exception: Exception
43
+ grouped_by_class: Discards grouped by Class
44
+ grouped_by_exception: Discards grouped by Exception
45
+ last_1_hour: Last 1 hour
46
+ last_24_hours: Last 24 hours
47
+ last_3_days: Last 3 days
48
+ last_3_hours: Last 3 hours
49
+ last_7_days: Last 7 days
50
+ title: Discard Cleaner
51
+ total: Total
38
52
  cron_entries:
39
53
  actions:
40
54
  confirm_disable: このcronエントリを無効化してもよろしいですか?
@@ -236,6 +250,7 @@ ja:
236
250
  search: 検索
237
251
  navbar:
238
252
  batches: バッチ
253
+ cleaner: Discard Cleaner
239
254
  cron_schedules: Cron
240
255
  jobs: ジョブ
241
256
  live_poll: リアルタイム更新
@@ -35,6 +35,20 @@ ko:
35
35
  callback_jobs: 콜백 작업
36
36
  table:
37
37
  no_batches_found: 배치를 찾을 수 없습니다.
38
+ cleaner:
39
+ index:
40
+ all: All
41
+ class: Class
42
+ exception: Exception
43
+ grouped_by_class: Discards grouped by Class
44
+ grouped_by_exception: Discards grouped by Exception
45
+ last_1_hour: Last 1 hour
46
+ last_24_hours: Last 24 hours
47
+ last_3_days: Last 3 days
48
+ last_3_hours: Last 3 hours
49
+ last_7_days: Last 7 days
50
+ title: Discard Cleaner
51
+ total: Total
38
52
  cron_entries:
39
53
  actions:
40
54
  confirm_disable: 이 cron 엔트리를 비활성화하시겠습니까?
@@ -236,6 +250,7 @@ ko:
236
250
  search: 검색
237
251
  navbar:
238
252
  batches: 배치
253
+ cleaner: Discard Cleaner
239
254
  cron_schedules: Cron
240
255
  jobs: 작업
241
256
  live_poll: 실시간 업데이트
@@ -35,6 +35,20 @@ nl:
35
35
  callback_jobs: Terugbelopdrachten
36
36
  table:
37
37
  no_batches_found: Geen batches gevonden.
38
+ cleaner:
39
+ index:
40
+ all: All
41
+ class: Class
42
+ exception: Exception
43
+ grouped_by_class: Discards grouped by Class
44
+ grouped_by_exception: Discards grouped by Exception
45
+ last_1_hour: Last 1 hour
46
+ last_24_hours: Last 24 hours
47
+ last_3_days: Last 3 days
48
+ last_3_hours: Last 3 hours
49
+ last_7_days: Last 7 days
50
+ title: Discard Cleaner
51
+ total: Total
38
52
  cron_entries:
39
53
  actions:
40
54
  confirm_disable: Weet u zekerf dat u deze cron-vermelding wilt uitschakelen?
@@ -236,6 +250,7 @@ nl:
236
250
  search: Zoekopdracht
237
251
  navbar:
238
252
  batches: Batches
253
+ cleaner: Discard Cleaner
239
254
  cron_schedules: Cron
240
255
  jobs: Taken
241
256
  live_poll: Live Poll
@@ -35,6 +35,20 @@ pt-BR:
35
35
  callback_jobs: Tarefas de Callback
36
36
  table:
37
37
  no_batches_found: Nenhum lote encontrado.
38
+ cleaner:
39
+ index:
40
+ all: All
41
+ class: Class
42
+ exception: Exception
43
+ grouped_by_class: Discards grouped by Class
44
+ grouped_by_exception: Discards grouped by Exception
45
+ last_1_hour: Last 1 hour
46
+ last_24_hours: Last 24 hours
47
+ last_3_days: Last 3 days
48
+ last_3_hours: Last 3 hours
49
+ last_7_days: Last 7 days
50
+ title: Discard Cleaner
51
+ total: Total
38
52
  cron_entries:
39
53
  actions:
40
54
  confirm_disable: Tem certeza de que deseja desativar esta tarefa programada?
@@ -236,6 +250,7 @@ pt-BR:
236
250
  search: Pesquisar
237
251
  navbar:
238
252
  batches: Lotes
253
+ cleaner: Discard Cleaner
239
254
  cron_schedules: Agendamentos
240
255
  jobs: Tarefas
241
256
  live_poll: Acompanhamento ao Vivo
@@ -35,6 +35,20 @@ ru:
35
35
  callback_jobs: Коллбеки
36
36
  table:
37
37
  no_batches_found: Группы заданий не найдены.
38
+ cleaner:
39
+ index:
40
+ all: All
41
+ class: Class
42
+ exception: Exception
43
+ grouped_by_class: Discards grouped by Class
44
+ grouped_by_exception: Discards grouped by Exception
45
+ last_1_hour: Last 1 hour
46
+ last_24_hours: Last 24 hours
47
+ last_3_days: Last 3 days
48
+ last_3_hours: Last 3 hours
49
+ last_7_days: Last 7 days
50
+ title: Discard Cleaner
51
+ total: Total
38
52
  cron_entries:
39
53
  actions:
40
54
  confirm_disable: Вы уверены, что хотите отключить эту задачу cron?
@@ -262,6 +276,7 @@ ru:
262
276
  search: Поиск
263
277
  navbar:
264
278
  batches: Пакетные задания
279
+ cleaner: Discard Cleaner
265
280
  cron_schedules: Cron
266
281
  jobs: Задания
267
282
  live_poll: Обновления в реальном времени
@@ -35,6 +35,20 @@ tr:
35
35
  callback_jobs: Geri Çağırma İşleri
36
36
  table:
37
37
  no_batches_found: Toplu İşlem bulunamadı.
38
+ cleaner:
39
+ index:
40
+ all: All
41
+ class: Class
42
+ exception: Exception
43
+ grouped_by_class: Discards grouped by Class
44
+ grouped_by_exception: Discards grouped by Exception
45
+ last_1_hour: Last 1 hour
46
+ last_24_hours: Last 24 hours
47
+ last_3_days: Last 3 days
48
+ last_3_hours: Last 3 hours
49
+ last_7_days: Last 7 days
50
+ title: Discard Cleaner
51
+ total: Total
38
52
  cron_entries:
39
53
  actions:
40
54
  confirm_disable: Bu cron girişini devre dışı bırakmak istediğinizden emin misiniz?
@@ -236,6 +250,7 @@ tr:
236
250
  search: Ara
237
251
  navbar:
238
252
  batches: Toplu İşler
253
+ cleaner: Discard Cleaner
239
254
  cron_schedules: Cron
240
255
  jobs: İşler
241
256
  live_poll: Anlık Güncelleme
@@ -35,6 +35,20 @@ uk:
35
35
  callback_jobs: Задачі з зворотнім викликом
36
36
  table:
37
37
  no_batches_found: Пакети не знайдені.
38
+ cleaner:
39
+ index:
40
+ all: All
41
+ class: Class
42
+ exception: Exception
43
+ grouped_by_class: Discards grouped by Class
44
+ grouped_by_exception: Discards grouped by Exception
45
+ last_1_hour: Last 1 hour
46
+ last_24_hours: Last 24 hours
47
+ last_3_days: Last 3 days
48
+ last_3_hours: Last 3 hours
49
+ last_7_days: Last 7 days
50
+ title: Discard Cleaner
51
+ total: Total
38
52
  cron_entries:
39
53
  actions:
40
54
  confirm_disable: Ви впевнені, що хочете вимкнути цю cron-запис?
@@ -262,6 +276,7 @@ uk:
262
276
  search: Пошук
263
277
  navbar:
264
278
  batches: Пакети
279
+ cleaner: Discard Cleaner
265
280
  cron_schedules: Cron
266
281
  jobs: Задачі
267
282
  live_poll: Живе Опитування
data/config/routes.rb CHANGED
@@ -34,8 +34,8 @@ GoodJob::Engine.routes.draw do
34
34
  end
35
35
 
36
36
  resources :processes, only: %i[index]
37
-
38
37
  resources :performance, only: %i[index show]
38
+ resources :cleaner, only: %i[index]
39
39
 
40
40
  scope :frontend, controller: :frontends, defaults: { version: GoodJob::VERSION.tr(".", "-") } do
41
41
  get "modules/:version/:id", action: :module, as: :frontend_module, constraints: { format: 'js' }
@@ -32,15 +32,7 @@ module GoodJob # :nodoc:
32
32
  @record = nil
33
33
  @refresh_task = nil
34
34
 
35
- # AS::ForkTracker is only present on Rails v6.1+.
36
- # Fall back to PID checking if ForkTracker is not available
37
- if defined?(ActiveSupport::ForkTracker)
38
- ActiveSupport::ForkTracker.after_fork { reset }
39
- @forktracker = true
40
- else
41
- @ruby_pid = ::Process.pid
42
- @forktracker = false
43
- end
35
+ ActiveSupport::ForkTracker.after_fork { reset }
44
36
 
45
37
  self.class.instances << self
46
38
  end
@@ -53,7 +45,6 @@ module GoodJob # :nodoc:
53
45
  synchronize do
54
46
  next if @locks.zero?
55
47
 
56
- reset_on_fork
57
48
  if @record
58
49
  @record.refresh_if_stale
59
50
  else
@@ -153,7 +144,7 @@ module GoodJob # :nodoc:
153
144
  # Tests whether an active advisory lock has been taken on the record.
154
145
  # @return [Boolean]
155
146
  def advisory_locked?
156
- @advisory_locked_connection&.weakref_alive? && @advisory_locked_connection&.active?
147
+ @advisory_locked_connection&.weakref_alive? && @advisory_locked_connection.active?
157
148
  end
158
149
 
159
150
  # @!visibility private
@@ -204,14 +195,6 @@ module GoodJob # :nodoc:
204
195
  synchronize { ns_reset }
205
196
  end
206
197
 
207
- def reset_on_fork
208
- return if Concurrent.on_jruby?
209
- return if @forktracker || ::Process.pid == @ruby_pid
210
-
211
- @ruby_pid = ::Process.pid
212
- ns_reset
213
- end
214
-
215
198
  def ns_reset
216
199
  @record_id = SecureRandom.uuid
217
200
  @record = nil
@@ -26,11 +26,13 @@ module GoodJob # :nodoc:
26
26
  end
27
27
 
28
28
  # Execution configuration to be scheduled
29
- # @return [Hash]
29
+ # @return [Array<CronEntry>]
30
30
  attr_reader :cron_entries
31
31
 
32
32
  # @param cron_entries [Array<CronEntry>]
33
33
  # @param start_on_initialize [Boolean]
34
+ # @param graceful_restart_period [ActiveSupport::Duration, nil]
35
+ # @param executor [Concurrent::Executor]
34
36
  def initialize(cron_entries = [], start_on_initialize: false, graceful_restart_period: nil, executor: Concurrent.global_io_executor)
35
37
  @executor = executor
36
38
  @running = false
@@ -82,16 +84,26 @@ module GoodJob # :nodoc:
82
84
 
83
85
  # Enqueues a scheduled task
84
86
  # @param cron_entry [CronEntry] the CronEntry object to schedule
85
- # @param previously_at [Date, Time, ActiveSupport::TimeWithZone, nil] the last, +in-memory+, scheduled time the cron task was intended to run
86
- def create_task(cron_entry, previously_at: nil)
87
- cron_at = cron_entry.next_at(previously_at: previously_at)
88
- delay = [(cron_at - Time.current).to_f, 0].max
89
- future = Concurrent::ScheduledTask.new(delay, args: [self, cron_entry, cron_at], executor: @executor) do |thr_scheduler, thr_cron_entry, thr_cron_at|
90
- # Re-schedule the next cron task before executing the current task
91
- thr_scheduler.create_task(thr_cron_entry, previously_at: thr_cron_at)
92
-
93
- Rails.application.executor.wrap do
94
- cron_entry.enqueue(thr_cron_at) if thr_cron_entry.enabled?
87
+ # @param at [Time, nil] When a task needs to optionally be rescheduled because of clock-drift or other inaccuracy
88
+ # @param previously_at [Time, nil] the last +in-memory+ scheduled time the cron task was intended to run
89
+ def create_task(cron_entry, at: nil, previously_at: nil)
90
+ cron_at = at || cron_entry.next_at(previously_at: previously_at)
91
+
92
+ # ScheduledTask runs immediately if delay is <= 0.01; avoid ever scheduling the task before the intended time
93
+ # https://github.com/ruby-concurrency/concurrent-ruby/blob/56227a4c3ebdd53b8b0976eb8296ceb7a093496f/lib/concurrent-ruby/concurrent/executor/timer_set.rb#L97
94
+ delay = cron_at <= Time.current ? 0.0 : [(cron_at - Time.current).to_f, 0.02].max
95
+
96
+ future = Concurrent::ScheduledTask.new(delay, args: [self, cron_entry, cron_at, previously_at], executor: @executor) do |thr_manager, thr_cron_entry, thr_cron_at|
97
+ if thr_cron_at && thr_cron_at > Time.current
98
+ # If clock drift or other inaccuracy, reschedule the task again
99
+ thr_manager.create_task(thr_cron_entry, at: thr_cron_at, previously_at: previously_at)
100
+ else
101
+ # Re-schedule the next cron task before executing the current task
102
+ thr_manager.create_task(thr_cron_entry, previously_at: thr_cron_at)
103
+
104
+ Rails.application.executor.wrap do
105
+ cron_entry.enqueue(thr_cron_at) if thr_cron_entry.enabled?
106
+ end
95
107
  end
96
108
  end
97
109
 
@@ -108,7 +120,7 @@ module GoodJob # :nodoc:
108
120
 
109
121
  time_period = @graceful_restart_period.ago..Time.current
110
122
  cron_entry.within(time_period).each do |cron_at|
111
- future = Concurrent::Future.new(args: [self, cron_entry, cron_at], executor: @executor) do |_thr_scheduler, thr_cron_entry, thr_cron_at|
123
+ future = Concurrent::Future.new(args: [self, cron_entry, cron_at], executor: @executor) do |_thr_manager, thr_cron_entry, thr_cron_at|
112
124
  Rails.application.executor.wrap do
113
125
  cron_entry.enqueue(thr_cron_at) if thr_cron_entry.enabled?
114
126
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module GoodJob
4
4
  # GoodJob gem version.
5
- VERSION = '4.4.1'
5
+ VERSION = '4.5.0'
6
6
 
7
7
  # GoodJob version as Gem::Version object
8
8
  GEM_VERSION = Gem::Version.new(VERSION)
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: 4.4.1
4
+ version: 4.5.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: 2024-10-10 00:00:00.000000000 Z
11
+ date: 2024-11-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob
@@ -254,6 +254,7 @@ files:
254
254
  - app/charts/good_job/scheduled_by_queue_chart.rb
255
255
  - app/controllers/good_job/application_controller.rb
256
256
  - app/controllers/good_job/batches_controller.rb
257
+ - app/controllers/good_job/cleaner_controller.rb
257
258
  - app/controllers/good_job/cron_entries_controller.rb
258
259
  - app/controllers/good_job/frontends_controller.rb
259
260
  - app/controllers/good_job/jobs_controller.rb
@@ -269,6 +270,7 @@ files:
269
270
  - app/frontend/good_job/modules/charts.js
270
271
  - app/frontend/good_job/modules/checkbox_toggle.js
271
272
  - app/frontend/good_job/modules/document_ready.js
273
+ - app/frontend/good_job/modules/html_legend_plugin.js
272
274
  - app/frontend/good_job/modules/live_poll.js
273
275
  - app/frontend/good_job/modules/popovers.js
274
276
  - app/frontend/good_job/modules/theme_controller.js
@@ -304,6 +306,7 @@ files:
304
306
  - app/views/good_job/batches/_table.erb
305
307
  - app/views/good_job/batches/index.html.erb
306
308
  - app/views/good_job/batches/show.html.erb
309
+ - app/views/good_job/cleaner/index.html.erb
307
310
  - app/views/good_job/cron_entries/index.html.erb
308
311
  - app/views/good_job/cron_entries/show.html.erb
309
312
  - app/views/good_job/jobs/_executions.erb
@@ -315,6 +318,7 @@ files:
315
318
  - app/views/good_job/processes/index.html.erb
316
319
  - app/views/good_job/shared/_alert.erb
317
320
  - app/views/good_job/shared/_chart.erb
321
+ - app/views/good_job/shared/_chart_container.erb
318
322
  - app/views/good_job/shared/_filter.erb
319
323
  - app/views/good_job/shared/_footer.erb
320
324
  - app/views/good_job/shared/_navbar.erb