good_job 4.4.1 → 4.5.0

Sign up to get free protection for your applications and to get access to all the features.
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