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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +48 -0
- data/README.md +5 -5
- data/app/charts/good_job/performance_index_chart.rb +3 -0
- data/app/controllers/good_job/cleaner_controller.rb +37 -0
- data/app/controllers/good_job/metrics_controller.rb +2 -0
- data/app/filters/good_job/base_filter.rb +1 -0
- data/app/filters/good_job/jobs_filter.rb +18 -2
- data/app/frontend/good_job/modules/charts.js +12 -0
- data/app/frontend/good_job/modules/html_legend_plugin.js +56 -0
- data/app/frontend/good_job/style.css +13 -0
- data/app/models/good_job/job.rb +6 -1
- data/app/models/good_job/process.rb +31 -1
- data/app/views/good_job/batches/_jobs.erb +5 -4
- data/app/views/good_job/cleaner/index.html.erb +85 -0
- data/app/views/good_job/jobs/_table.erb +3 -2
- data/app/views/good_job/jobs/index.html.erb +1 -1
- data/app/views/good_job/performance/index.html.erb +1 -1
- data/app/views/good_job/performance/show.html.erb +1 -1
- data/app/views/good_job/processes/index.html.erb +1 -0
- data/app/views/good_job/shared/_chart.erb +2 -2
- data/app/views/good_job/shared/_chart_container.erb +21 -0
- data/app/views/good_job/shared/_navbar.erb +6 -0
- data/config/brakeman.ignore +26 -3
- data/config/locales/de.yml +15 -0
- data/config/locales/en.yml +15 -0
- data/config/locales/es.yml +15 -0
- data/config/locales/fr.yml +15 -0
- data/config/locales/it.yml +15 -0
- data/config/locales/ja.yml +15 -0
- data/config/locales/ko.yml +15 -0
- data/config/locales/nl.yml +15 -0
- data/config/locales/pt-BR.yml +15 -0
- data/config/locales/ru.yml +15 -0
- data/config/locales/tr.yml +15 -0
- data/config/locales/uk.yml +15 -0
- data/config/routes.rb +1 -1
- data/lib/good_job/capsule_tracker.rb +2 -19
- data/lib/good_job/cron_manager.rb +24 -12
- data/lib/good_job/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3f0a8926625f33b36c56def63a1507cfb0f8981a876449e907396aa2338b0b78
|
4
|
+
data.tar.gz: ad45bd8133d5f68bcf190bac3f7656864e10df2e49be477024e9f0c8422ce222
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
1718
|
+
The gemfiles in `gemfiles/` can be used to run tests against different rails versions:
|
1719
1719
|
|
1720
1720
|
```bash
|
1721
|
-
# Install
|
1722
|
-
|
1721
|
+
# Install dependencies
|
1722
|
+
BUNDLE_GEMFILE=gemfiles/rails_6.1.gemfile bundle install
|
1723
1723
|
|
1724
|
-
# Run tests
|
1725
|
-
|
1724
|
+
# Run the tests
|
1725
|
+
BUNDLE_GEMFILE=gemfiles/rails_6.1.gemfile bin/rspec
|
1726
1726
|
```
|
1727
1727
|
|
1728
1728
|
### Release
|
@@ -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
|
|
@@ -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
|
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
|
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;
|
data/app/models/good_job/job.rb
CHANGED
@@ -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
|
-
|
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.
|
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.
|
44
|
+
bs_content: job.display_error,
|
45
45
|
}
|
46
46
|
%>
|
47
47
|
<% else %>
|
48
|
-
|
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.
|
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.
|
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
|
-
|
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/
|
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/
|
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/
|
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">
|
@@ -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">
|
data/config/brakeman.ignore
CHANGED
@@ -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":
|
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-
|
74
|
-
"brakeman_version": "6.1
|
96
|
+
"updated": "2024-11-16 17:00:20 -0600",
|
97
|
+
"brakeman_version": "6.2.1"
|
75
98
|
}
|
data/config/locales/de.yml
CHANGED
@@ -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
|
data/config/locales/en.yml
CHANGED
@@ -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
|
data/config/locales/es.yml
CHANGED
@@ -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
|
data/config/locales/fr.yml
CHANGED
@@ -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
|
data/config/locales/it.yml
CHANGED
@@ -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
|
data/config/locales/ja.yml
CHANGED
@@ -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: リアルタイム更新
|
data/config/locales/ko.yml
CHANGED
@@ -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: 실시간 업데이트
|
data/config/locales/nl.yml
CHANGED
@@ -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
|
data/config/locales/pt-BR.yml
CHANGED
@@ -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
|
data/config/locales/ru.yml
CHANGED
@@ -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: Обновления в реальном времени
|
data/config/locales/tr.yml
CHANGED
@@ -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
|
data/config/locales/uk.yml
CHANGED
@@ -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
|
-
|
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
|
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 [
|
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
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
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 |
|
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
|
data/lib/good_job/version.rb
CHANGED
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
|
+
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-
|
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
|