good_job 4.6.0 → 4.8.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 +33 -0
- data/README.md +30 -1
- data/app/controllers/good_job/metrics_controller.rb +2 -0
- data/app/controllers/good_job/pauses_controller.rb +34 -0
- data/app/controllers/good_job/performance_controller.rb +15 -11
- data/app/models/good_job/cron_entry.rb +11 -1
- data/app/models/good_job/job.rb +28 -1
- data/app/models/good_job/setting.rb +78 -0
- data/app/views/good_job/batches/_jobs.erb +7 -0
- data/app/views/good_job/jobs/_table.erb +7 -0
- data/app/views/good_job/pauses/_group.html.erb +28 -0
- data/app/views/good_job/pauses/_pause.html.erb +8 -0
- data/app/views/good_job/pauses/index.html.erb +46 -0
- data/app/views/good_job/performance/index.html.erb +46 -2
- data/app/views/good_job/shared/_navbar.erb +6 -0
- data/config/locales/de.yml +18 -2
- data/config/locales/en.yml +18 -2
- data/config/locales/es.yml +19 -3
- data/config/locales/fr.yml +36 -20
- data/config/locales/it.yml +20 -4
- data/config/locales/ja.yml +20 -4
- data/config/locales/ko.yml +21 -5
- data/config/locales/nl.yml +22 -6
- data/config/locales/pt-BR.yml +18 -2
- data/config/locales/ru.yml +22 -6
- data/config/locales/tr.yml +20 -4
- data/config/locales/uk.yml +21 -5
- data/config/routes.rb +6 -0
- data/lib/good_job/configuration.rb +12 -0
- data/lib/good_job/version.rb +1 -1
- data/lib/good_job.rb +32 -0
- metadata +7 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 69c9c36ff0026c4a82ac6db945370e397d218e7976d4fc540aa4144225f75a25
|
4
|
+
data.tar.gz: 4db17623ab0fc1e1747a6c1de6ea596efde905c774a4c8193beb9f7a380c5e31
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4c9972c721472ca50148161ffc9611f59799e501b7c8fb0af5feac5b1a3f0dcdbf906295125bf4376b2e7fda0e72f9a8d27230fdb491fc6ea0c6c78bca8e4655
|
7
|
+
data.tar.gz: 3b5f9fc708ae37ebb1e6a27f6d5f453312193caf78334f210b09dfd0a5d53ba591db1017516cb7c681b1afe01dd1dd66115f4b6728a6fa7cb28a4c1383005d04
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,38 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v4.8.0](https://github.com/bensheldon/good_job/tree/v4.8.0) (2025-01-22)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v4.7.0...v4.8.0)
|
6
|
+
|
7
|
+
**Implemented enhancements:**
|
8
|
+
|
9
|
+
- Add ability to pause jobs by queue, job class, or label \(experimental\) [\#1575](https://github.com/bensheldon/good_job/pull/1575) ([bensheldon](https://github.com/bensheldon))
|
10
|
+
|
11
|
+
**Closed issues:**
|
12
|
+
|
13
|
+
- perform\_now enqueues a job when raising exception [\#1570](https://github.com/bensheldon/good_job/issues/1570)
|
14
|
+
|
15
|
+
**Merged pull requests:**
|
16
|
+
|
17
|
+
- Use Ruby 3.4.1 for development [\#1579](https://github.com/bensheldon/good_job/pull/1579) ([bensheldon](https://github.com/bensheldon))
|
18
|
+
- Fix generator tests [\#1577](https://github.com/bensheldon/good_job/pull/1577) ([bensheldon](https://github.com/bensheldon))
|
19
|
+
- Add the logger gem as a dependency to fix rails head [\#1576](https://github.com/bensheldon/good_job/pull/1576) ([bensheldon](https://github.com/bensheldon))
|
20
|
+
- fix: typo in en.yml when discarding jobs [\#1569](https://github.com/bensheldon/good_job/pull/1569) ([nickstanish](https://github.com/nickstanish))
|
21
|
+
- Bump the all group with 60 updates [\#1567](https://github.com/bensheldon/good_job/pull/1567) ([dependabot[bot]](https://github.com/apps/dependabot))
|
22
|
+
|
23
|
+
## [v4.7.0](https://github.com/bensheldon/good_job/tree/v4.7.0) (2024-12-31)
|
24
|
+
|
25
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v4.6.0...v4.7.0)
|
26
|
+
|
27
|
+
**Implemented enhancements:**
|
28
|
+
|
29
|
+
- Expose good job labels in dashboard [\#1561](https://github.com/bensheldon/good_job/pull/1561) ([BClark88](https://github.com/BClark88))
|
30
|
+
|
31
|
+
**Merged pull requests:**
|
32
|
+
|
33
|
+
- Allow cron configuration `class` value to be a proc [\#1566](https://github.com/bensheldon/good_job/pull/1566) ([bensheldon](https://github.com/bensheldon))
|
34
|
+
- Add Ruby 3.4 to CI [\#1565](https://github.com/bensheldon/good_job/pull/1565) ([Earlopain](https://github.com/Earlopain))
|
35
|
+
|
3
36
|
## [v4.6.0](https://github.com/bensheldon/good_job/tree/v4.6.0) (2024-12-12)
|
4
37
|
|
5
38
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v4.5.1...v4.6.0)
|
data/README.md
CHANGED
@@ -73,6 +73,7 @@ For more of the story of GoodJob, read the [introductory blog post](https://isla
|
|
73
73
|
- [Write tests](#write-tests)
|
74
74
|
- [PgBouncer compatibility](#pgbouncer-compatibility)
|
75
75
|
- [CLI HTTP health check probes](#cli-http-health-check-probes)
|
76
|
+
- [Pausing Jobs](#pausing-jobs)
|
76
77
|
- [Doing your best job with GoodJob](#doing-your-best-job-with-goodjob)
|
77
78
|
- [Sizing jobs: mice and elephants](#sizing-jobs-mice-and-elephants)
|
78
79
|
- [Isolating by total latency](#isolating-by-total-latency)
|
@@ -329,6 +330,8 @@ Available configuration options are:
|
|
329
330
|
config.good_job.probe_handler = 'webrick'
|
330
331
|
```
|
331
332
|
|
333
|
+
- `enable_pauses` (boolean) whether job processing can be paused. Defaults to `false`. You can also set this with the environment variable `GOOD_JOB_ENABLE_PAUSES`.
|
334
|
+
|
332
335
|
By default, GoodJob configures the following execution modes per environment:
|
333
336
|
|
334
337
|
```ruby
|
@@ -1559,9 +1562,35 @@ gem 'webrick'
|
|
1559
1562
|
|
1560
1563
|
If WEBrick is configured to be used, but the dependency is not found, GoodJob will log a warning and fallback to the default probe server.
|
1561
1564
|
|
1565
|
+
### Pausing Jobs
|
1566
|
+
|
1567
|
+
GoodJob allows for pausing jobs by queue or job class. This feature is currently opt-in because the performance impact of loading and filtering these attributes is not yet known. To enable this feature, add the following to your configuration:
|
1568
|
+
|
1569
|
+
> ```ruby
|
1570
|
+
> config.good_job.enable_pauses = true
|
1571
|
+
> ```
|
1572
|
+
|
1573
|
+
Pausing can be done via the Dashboard's Performance page, or in Ruby
|
1574
|
+
|
1575
|
+
```ruby
|
1576
|
+
# To pause:
|
1577
|
+
GoodJob.pause(queue: "default")
|
1578
|
+
GoodJob.pause(job_class: "MyJob")
|
1579
|
+
|
1580
|
+
# To check status
|
1581
|
+
GoodJob.paused # => { queues: ["default"], job_classes: ["MyJob"] }
|
1582
|
+
GoodJob.paused?(queue: "default") # => true
|
1583
|
+
GoodJob.paused?(job_class: "MyJob") # => true
|
1584
|
+
GoodJob.paused? # => true
|
1585
|
+
|
1586
|
+
# To unpause
|
1587
|
+
GoodJob.unpause(queue: "default")
|
1588
|
+
GoodJob.unpause(job_class: "MyJob")
|
1589
|
+
````
|
1590
|
+
|
1562
1591
|
## Doing your best job with GoodJob
|
1563
1592
|
|
1564
|
-
_This section explains how to use GoodJob the most efficiently and performantly, according to its maintainers. GoodJob is very flexible and you don
|
1593
|
+
_This section explains how to use GoodJob the most efficiently and performantly, according to its maintainers. GoodJob is very flexible and you don't necessarily have to use it this way, but the concepts explained here are part of GoodJob's design intent._
|
1565
1594
|
|
1566
1595
|
Background jobs are hard. There are two extremes:
|
1567
1596
|
|
@@ -6,6 +6,7 @@ module GoodJob
|
|
6
6
|
jobs_count = GoodJob::Job.count
|
7
7
|
batches_count = GoodJob::BatchRecord.all.size
|
8
8
|
cron_entries_count = GoodJob::CronEntry.all.size
|
9
|
+
pauses_count = GoodJob::Setting.paused.values.sum(&:count)
|
9
10
|
processes_count = GoodJob::Process.active.count
|
10
11
|
discarded_count = GoodJob::Job.discarded.count
|
11
12
|
|
@@ -13,6 +14,7 @@ module GoodJob
|
|
13
14
|
jobs_count: helpers.number_to_human(jobs_count),
|
14
15
|
batches_count: helpers.number_to_human(batches_count),
|
15
16
|
cron_entries_count: helpers.number_to_human(cron_entries_count),
|
17
|
+
pauses_count: helpers.number_to_human(pauses_count),
|
16
18
|
processes_count: helpers.number_to_human(processes_count),
|
17
19
|
discarded_count: helpers.number_to_human(discarded_count),
|
18
20
|
}
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GoodJob
|
4
|
+
class PausesController < ApplicationController
|
5
|
+
before_action :validate_type, only: [:create, :destroy]
|
6
|
+
def index
|
7
|
+
@paused = GoodJob::Setting.paused
|
8
|
+
end
|
9
|
+
|
10
|
+
def create
|
11
|
+
pause_type = params[:type].to_sym
|
12
|
+
pause_value = params[:value].to_s
|
13
|
+
|
14
|
+
GoodJob::Setting.pause(pause_type => pause_value)
|
15
|
+
redirect_to({ action: :index }, notice: "Successfully paused #{params[:type]} '#{params[:value]}'")
|
16
|
+
end
|
17
|
+
|
18
|
+
def destroy
|
19
|
+
pause_type = params[:type].to_sym
|
20
|
+
pause_value = params[:value].to_s
|
21
|
+
|
22
|
+
GoodJob::Setting.unpause(pause_type => pause_value)
|
23
|
+
redirect_to({ action: :index }, notice: "Successfully unpaused #{params[:type]} '#{params[:value]}'")
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def validate_type
|
29
|
+
return if params[:type].in?(%w[queue job_class label]) && params[:value].to_s.present?
|
30
|
+
|
31
|
+
raise ActionController::BadRequest, "Invalid type"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -3,17 +3,21 @@
|
|
3
3
|
module GoodJob
|
4
4
|
class PerformanceController < ApplicationController
|
5
5
|
def index
|
6
|
-
@performances = GoodJob::Execution
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
6
|
+
@performances = GoodJob::Execution.group(:job_class).select("
|
7
|
+
job_class,
|
8
|
+
COUNT(*) AS executions_count,
|
9
|
+
AVG(duration) AS avg_duration,
|
10
|
+
MIN(duration) AS min_duration,
|
11
|
+
MAX(duration) AS max_duration
|
12
|
+
").order(:job_class)
|
13
|
+
|
14
|
+
@queue_performances = GoodJob::Execution.group(:queue_name).select("
|
15
|
+
queue_name,
|
16
|
+
COUNT(*) AS executions_count,
|
17
|
+
AVG(duration) AS avg_duration,
|
18
|
+
MIN(duration) AS min_duration,
|
19
|
+
MAX(duration) AS max_duration
|
20
|
+
").order(:queue_name)
|
17
21
|
end
|
18
22
|
|
19
23
|
def show
|
@@ -101,8 +101,12 @@ module GoodJob # :nodoc:
|
|
101
101
|
current_thread.cron_key = key
|
102
102
|
current_thread.cron_at = cron_at
|
103
103
|
|
104
|
-
configured_job = job_class.constantize.set(set_value)
|
105
104
|
I18n.with_locale(I18n.default_locale) do
|
105
|
+
job_klass = job_class_value
|
106
|
+
job_klass = job_klass.constantize if job_klass.is_a?(String)
|
107
|
+
next unless job_klass.is_a?(Class)
|
108
|
+
|
109
|
+
configured_job = job_klass.set(set_value)
|
106
110
|
kwargs_value.present? ? configured_job.perform_later(*args_value, **kwargs_value) : configured_job.perform_later(*args_value)
|
107
111
|
end
|
108
112
|
end
|
@@ -118,6 +122,7 @@ module GoodJob # :nodoc:
|
|
118
122
|
set: display_property(set),
|
119
123
|
description: display_property(description),
|
120
124
|
}.tap do |properties|
|
125
|
+
properties[:class] = display_property(job_class) if job_class.present?
|
121
126
|
properties[:args] = display_property(args) if args.present?
|
122
127
|
properties[:kwargs] = display_property(kwargs) if kwargs.present?
|
123
128
|
end
|
@@ -160,6 +165,11 @@ module GoodJob # :nodoc:
|
|
160
165
|
@_fugit ||= Fugit.parse(cron)
|
161
166
|
end
|
162
167
|
|
168
|
+
def job_class_value
|
169
|
+
value = job_class || nil
|
170
|
+
value.respond_to?(:call) ? value.call : value
|
171
|
+
end
|
172
|
+
|
163
173
|
def set_value
|
164
174
|
value = set || {}
|
165
175
|
value.respond_to?(:call) ? value.call : value
|
data/app/models/good_job/job.rb
CHANGED
@@ -96,6 +96,33 @@ module GoodJob
|
|
96
96
|
# @return [ActiveRecord::Relation]
|
97
97
|
scope :only_scheduled, -> { where(arel_table['scheduled_at'].lteq(bind_value('scheduled_at', DateTime.current, ActiveRecord::Type::DateTime))).or(where(scheduled_at: nil)) }
|
98
98
|
|
99
|
+
# Exclude jobs that are paused via queue_name or job_class.
|
100
|
+
# Only applies when enable_pauses configuration is true.
|
101
|
+
# @!method exclude_paused
|
102
|
+
# @!scope class
|
103
|
+
# @return [ActiveRecord::Relation]
|
104
|
+
scope :exclude_paused, lambda {
|
105
|
+
return all unless GoodJob.configuration.enable_pauses
|
106
|
+
|
107
|
+
paused_query = GoodJob::Setting.where(key: GoodJob::Setting::PAUSES)
|
108
|
+
paused_queues_query = paused_query.select("jsonb_array_elements_text(value->'queues')")
|
109
|
+
paused_job_classes_query = paused_query.select("jsonb_array_elements_text(value->'job_classes')")
|
110
|
+
paused_labels_query = paused_query.select("jsonb_array_elements_text(value->'labels')")
|
111
|
+
|
112
|
+
where.not(queue_name: paused_queues_query)
|
113
|
+
.where.not(job_class: paused_job_classes_query)
|
114
|
+
.where(
|
115
|
+
Arel::Nodes::Not.new(
|
116
|
+
Arel::Nodes::NamedFunction.new(
|
117
|
+
"COALESCE", [
|
118
|
+
Arel::Nodes::InfixOperation.new('&&', arel_table['labels'], Arel::Nodes::NamedFunction.new('ARRAY', [paused_labels_query.arel])),
|
119
|
+
Arel::Nodes::SqlLiteral.new('FALSE'),
|
120
|
+
]
|
121
|
+
)
|
122
|
+
)
|
123
|
+
)
|
124
|
+
}
|
125
|
+
|
99
126
|
# Order jobs by priority (highest priority first).
|
100
127
|
# @!method priority_ordered
|
101
128
|
# @!scope class
|
@@ -290,7 +317,7 @@ module GoodJob
|
|
290
317
|
job = nil
|
291
318
|
result = nil
|
292
319
|
|
293
|
-
unfinished.dequeueing_ordered(parsed_queues).only_scheduled.limit(1).with_advisory_lock(select_limit: queue_select_limit) do |jobs|
|
320
|
+
unfinished.dequeueing_ordered(parsed_queues).only_scheduled.exclude_paused.limit(1).with_advisory_lock(select_limit: queue_select_limit) do |jobs|
|
294
321
|
job = jobs.first
|
295
322
|
|
296
323
|
if job&.executable?
|
@@ -4,6 +4,7 @@ module GoodJob
|
|
4
4
|
class Setting < BaseRecord
|
5
5
|
CRON_KEYS_ENABLED = "cron_keys_enabled"
|
6
6
|
CRON_KEYS_DISABLED = "cron_keys_disabled"
|
7
|
+
PAUSES = "pauses"
|
7
8
|
|
8
9
|
self.table_name = 'good_job_settings'
|
9
10
|
self.implicit_order_column = 'created_at'
|
@@ -47,5 +48,82 @@ module GoodJob
|
|
47
48
|
disabled_setting.value << key unless disabled_setting.value.include?(key_string)
|
48
49
|
disabled_setting.save!
|
49
50
|
end
|
51
|
+
|
52
|
+
def self.pause(queue: nil, job_class: nil, label: nil)
|
53
|
+
raise ArgumentError, "Must provide exactly one of queue, job_class, or label" unless [queue, job_class, label].count(&:present?) == 1
|
54
|
+
|
55
|
+
setting = find_or_initialize_by(key: PAUSES) do |record|
|
56
|
+
record.value = { "queues" => [], "job_classes" => [], "labels" => [] }
|
57
|
+
end
|
58
|
+
|
59
|
+
if queue
|
60
|
+
setting.value["queues"] ||= []
|
61
|
+
setting.value["queues"] << queue.to_s unless setting.value["queues"].include?(queue.to_s)
|
62
|
+
elsif job_class
|
63
|
+
setting.value["job_classes"] ||= []
|
64
|
+
setting.value["job_classes"] << job_class.to_s unless setting.value["job_classes"].include?(job_class.to_s)
|
65
|
+
else
|
66
|
+
setting.value["labels"] ||= []
|
67
|
+
setting.value["labels"] << label.to_s unless setting.value["labels"].include?(label.to_s)
|
68
|
+
end
|
69
|
+
setting.save!
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.unpause(queue: nil, job_class: nil, label: nil)
|
73
|
+
raise ArgumentError, "Must provide exactly one of queue, job_class, or label" unless [queue, job_class, label].count(&:present?) == 1
|
74
|
+
|
75
|
+
setting = find_by(key: PAUSES)
|
76
|
+
return unless setting
|
77
|
+
|
78
|
+
if queue
|
79
|
+
return unless setting.value["queues"]&.include?(queue.to_s)
|
80
|
+
|
81
|
+
setting.value["queues"].delete(queue.to_s)
|
82
|
+
elsif job_class
|
83
|
+
return unless setting.value["job_classes"]&.include?(job_class.to_s)
|
84
|
+
|
85
|
+
setting.value["job_classes"].delete(job_class.to_s)
|
86
|
+
else
|
87
|
+
return unless setting.value["labels"]&.include?(label.to_s)
|
88
|
+
|
89
|
+
setting.value["labels"].delete(label.to_s)
|
90
|
+
end
|
91
|
+
setting.save!
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.paused?(queue: nil, job_class: nil, label: nil)
|
95
|
+
raise ArgumentError, "Must provide at most one of queue, job_class, or label" if [queue, job_class, label].count(&:present?) > 1
|
96
|
+
|
97
|
+
if queue
|
98
|
+
queue.in? paused(:queues)
|
99
|
+
elsif job_class
|
100
|
+
job_class.in? paused(:job_classes)
|
101
|
+
elsif label
|
102
|
+
label.in? paused(:labels)
|
103
|
+
else
|
104
|
+
paused.values.any?(&:any?)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.paused(type = nil)
|
109
|
+
setting = find_by(key: PAUSES)
|
110
|
+
pauses = setting&.value&.deep_dup || { "queues" => [], "job_classes" => [], "labels" => [] }
|
111
|
+
pauses = pauses.with_indifferent_access
|
112
|
+
|
113
|
+
case type
|
114
|
+
when :queues
|
115
|
+
pauses["queues"]
|
116
|
+
when :job_classes
|
117
|
+
pauses["job_classes"]
|
118
|
+
when :labels
|
119
|
+
pauses["labels"]
|
120
|
+
else
|
121
|
+
{
|
122
|
+
queues: pauses["queues"] || [],
|
123
|
+
job_classes: pauses["job_classes"] || [],
|
124
|
+
labels: pauses["labels"] || [],
|
125
|
+
}
|
126
|
+
end
|
127
|
+
end
|
50
128
|
end
|
51
129
|
end
|
@@ -5,6 +5,7 @@
|
|
5
5
|
<div class="col-4"><%= t("good_job.models.batch.jobs") %></div>
|
6
6
|
<div class="d-none d-lg-block col-lg-1 text-lg-center"><%= t "good_job.models.job.queue" %></div>
|
7
7
|
<div class="d-none d-lg-block col-lg-1 text-lg-end"><%= t "good_job.models.job.priority" %></div>
|
8
|
+
<div class="d-none d-lg-block col-lg-1 text-lg-end"><%= t "good_job.models.job.labels" %></div>
|
8
9
|
<div class="d-none d-lg-block col-lg-1 text-lg-end"><%= t "good_job.models.job.attempts" %></div>
|
9
10
|
<div class="col text-end">
|
10
11
|
<%= tag.button type: "button", class: "btn btn-sm text-muted", role: "button",
|
@@ -33,6 +34,12 @@
|
|
33
34
|
<div class="d-lg-none small text-muted mt-1"><%= t "good_job.models.job.priority" %></div>
|
34
35
|
<span class="font-monospace fw-bold"><%= job.priority %></span>
|
35
36
|
</div>
|
37
|
+
<div class="col-4 col-lg-1 text-lg-end">
|
38
|
+
<div class="d-lg-none small text-muted mt-1"><%= t "good_job.models.job.labels" %></div>
|
39
|
+
<% job.labels&.each do |label| %>
|
40
|
+
<span class="badge rounded-pill bg-secondary font-monospace"><%= label %></span>
|
41
|
+
<% end %>
|
42
|
+
</div>
|
36
43
|
<div class="col-4 col-lg-1 text-lg-end">
|
37
44
|
<div class="d-lg-none small text-muted mt-1"><%= t "good_job.models.job.attempts" %></div>
|
38
45
|
<% if job.error %>
|
@@ -35,6 +35,7 @@
|
|
35
35
|
</div>
|
36
36
|
<div class="d-none d-lg-block col-lg-1 text-lg-center"><%= t "good_job.models.job.queue" %></div>
|
37
37
|
<div class="d-none d-lg-block col-lg-1 text-lg-end"><%= t "good_job.models.job.priority" %></div>
|
38
|
+
<div class="d-none d-lg-block col-lg-1 text-lg-end"><%= t "good_job.models.job.labels" %></div>
|
38
39
|
<div class="d-none d-lg-block col-lg-1 text-lg-end"><%= t "good_job.models.job.attempts" %></div>
|
39
40
|
<div class="col text-end">
|
40
41
|
<%= tag.button type: "button", class: "btn btn-sm text-muted", role: "button",
|
@@ -82,6 +83,12 @@
|
|
82
83
|
<div class="d-lg-none small text-muted mt-1"><%= t "good_job.models.job.priority" %></div>
|
83
84
|
<%= job.priority %>
|
84
85
|
</div>
|
86
|
+
<div class="col-4 col-lg-1 text-wrap text-lg-end">
|
87
|
+
<div class="d-lg-none small text-muted mt-1"><%= t "good_job.models.job.labels" %></div>
|
88
|
+
<% job.labels&.each do |label| %>
|
89
|
+
<span class="badge rounded-pill bg-secondary font-monospace"><%= label %></span>
|
90
|
+
<% end %>
|
91
|
+
</div>
|
85
92
|
<div class="col-4 col-lg-1 text-lg-end">
|
86
93
|
<div class="d-lg-none small text-muted mt-1"><%= t "good_job.models.job.attempts" %></div>
|
87
94
|
<% if job.error %>
|
@@ -0,0 +1,28 @@
|
|
1
|
+
<% group = type.to_s.pluralize.to_sym %>
|
2
|
+
<% items = paused.fetch(group) { [] } %>
|
3
|
+
<% if items&.any? %>
|
4
|
+
<div class="my-3 card">
|
5
|
+
<div class="list-group list-group-flush text-nowrap" role="table">
|
6
|
+
<header class="list-group-item bg-body-tertiary">
|
7
|
+
<div class="row small text-muted text-uppercase align-items-center">
|
8
|
+
<div class="col-12"><%= t("good_job.pauses.index.#{type}") %></div>
|
9
|
+
</div>
|
10
|
+
</header>
|
11
|
+
|
12
|
+
<% items.each do |value| %>
|
13
|
+
<li class="list-group-item d-flex justify-content-between align-items-center">
|
14
|
+
<%= value %>
|
15
|
+
<%= button_to(
|
16
|
+
{ action: :destroy, type: type, value: value },
|
17
|
+
method: :delete,
|
18
|
+
class: 'btn btn-sm btn-outline-primary',
|
19
|
+
data: { confirm: t('good_job.pauses.index.confirm_unpause', value: value) }
|
20
|
+
) do %>
|
21
|
+
<%= render_icon "play" %>
|
22
|
+
<%= t("good_job.pauses.index.unpause") %>
|
23
|
+
<% end %>
|
24
|
+
</li>
|
25
|
+
<% end %>
|
26
|
+
</div>
|
27
|
+
</div>
|
28
|
+
<% end %>
|
@@ -0,0 +1,8 @@
|
|
1
|
+
<li class="list-group-item d-flex justify-content-between align-items-center">
|
2
|
+
<%= value %>
|
3
|
+
<%= button_to t('good_job.pauses.index.unpause'),
|
4
|
+
{ action: :destroy, type: type, value: value },
|
5
|
+
method: :delete,
|
6
|
+
class: 'btn btn-sm btn-outline-primary',
|
7
|
+
data: { confirm: t('good_job.pauses.index.confirm_unpause', value: value) } %>
|
8
|
+
</li>
|
@@ -0,0 +1,46 @@
|
|
1
|
+
<%= content_for :page_title do %>
|
2
|
+
<%= t('good_job.pauses.index.title') %>
|
3
|
+
<% end %>
|
4
|
+
|
5
|
+
<div class="border-bottom">
|
6
|
+
<h1 class="h2 pt-3 pb-2"><%= t('good_job.pauses.index.title') %></h1>
|
7
|
+
</div>
|
8
|
+
|
9
|
+
<% if GoodJob.configuration.enable_pauses %>
|
10
|
+
<div class="card mb-4">
|
11
|
+
<div class="card-body">
|
12
|
+
<%= form_tag({ action: :create }, method: :post, class: 'row g-3 align-items-end') do %>
|
13
|
+
<div class="col-3">
|
14
|
+
<%= label_tag :type, t('good_job.pauses.index.type'), class: 'form-label' %>
|
15
|
+
<%= select_tag :type,
|
16
|
+
options_for_select([
|
17
|
+
[t('good_job.pauses.index.queue'), 'queue'],
|
18
|
+
[t('good_job.pauses.index.job_class'), 'job_class'],
|
19
|
+
[t('good_job.pauses.index.label'), 'label'],
|
20
|
+
]),
|
21
|
+
class: 'form-select',
|
22
|
+
required: true %>
|
23
|
+
</div>
|
24
|
+
<div class="col-6">
|
25
|
+
<%= label_tag :value, t('good_job.pauses.index.value'), class: 'form-label' %>
|
26
|
+
<%= text_field_tag :value, nil, class: 'form-control', autocomplete: "off", required: true %>
|
27
|
+
</div>
|
28
|
+
<div class="col-3">
|
29
|
+
<%= button_tag type: "submit", class: 'btn btn-primary' do %>
|
30
|
+
<%= render_icon "pause" %>
|
31
|
+
<%= t('good_job.pauses.index.pause', value: '') %>
|
32
|
+
<% end %>
|
33
|
+
</div>
|
34
|
+
<% end %>
|
35
|
+
</div>
|
36
|
+
</div>
|
37
|
+
|
38
|
+
<%= render 'group', paused: @paused, type: :queue %>
|
39
|
+
<%= render 'group', paused: @paused, type: :job_class %>
|
40
|
+
<%= render 'group', paused: @paused, type: :label %>
|
41
|
+
<% else %>
|
42
|
+
<div class="alert alert-warning" role="alert">
|
43
|
+
<p><%= t('good_job.pauses.index.disabled') %>:</p>
|
44
|
+
<pre class="mb-0">config.good_job.enable_pauses = true</pre>
|
45
|
+
</div>
|
46
|
+
<% end %>
|
@@ -10,7 +10,6 @@
|
|
10
10
|
<div class="row small text-muted text-uppercase align-items-center">
|
11
11
|
<div class="col-12 col-lg-4"><%= t ".job_class" %></div>
|
12
12
|
<div class="col-lg-2 d-none d-lg-block"><%= t ".executions" %></div>
|
13
|
-
|
14
13
|
<div class="col-lg-2 d-none d-lg-block"><%= t ".average_duration" %></div>
|
15
14
|
<div class="col-lg-2 d-none d-lg-block"><%= t ".minimum_duration" %></div>
|
16
15
|
<div class="col-lg-2 d-none d-lg-block"><%= t ".maximum_duration" %></div>
|
@@ -20,12 +19,57 @@
|
|
20
19
|
<% @performances.each do |performance| %>
|
21
20
|
<div role="row" class="list-group-item py-3">
|
22
21
|
<div class="row align-items-center">
|
23
|
-
<div class="col-12 col-lg-4"
|
22
|
+
<div class="col-12 col-lg-4">
|
23
|
+
<div class="d-flex gap-3 justify-content-between">
|
24
|
+
<%= link_to performance.job_class, performance_path(performance.job_class) %>
|
25
|
+
</div>
|
26
|
+
</div>
|
24
27
|
<div class="col-6 col-lg-2 text-wrap">
|
25
28
|
<div class="d-lg-none small text-muted mt-1"><%= t ".executions" %></div>
|
26
29
|
<%= performance.executions_count %>
|
27
30
|
</div>
|
31
|
+
<div class="col-6 col-lg-2 text-wrap">
|
32
|
+
<div class="d-lg-none small text-muted mt-1"><%= t ".average_duration" %></div>
|
33
|
+
<%= format_duration performance.avg_duration %>
|
34
|
+
</div>
|
35
|
+
<div class="col-6 col-lg-2 text-wrap">
|
36
|
+
<div class="d-lg-none small text-muted mt-1"><%= t ".minimum_duration" %></div>
|
37
|
+
<%= format_duration performance.min_duration %>
|
38
|
+
</div>
|
39
|
+
<div class="col-6 col-lg-2 text-wrap">
|
40
|
+
<div class="d-lg-none small text-muted mt-1"><%= t ".maximum_duration" %></div>
|
41
|
+
<%= format_duration performance.max_duration %>
|
42
|
+
</div>
|
43
|
+
</div>
|
44
|
+
</div>
|
45
|
+
<% end %>
|
46
|
+
</div>
|
47
|
+
</div>
|
28
48
|
|
49
|
+
<div class="my-3 card">
|
50
|
+
<div class="list-group list-group-flush text-nowrap" role="table">
|
51
|
+
<header class="list-group-item bg-body-tertiary">
|
52
|
+
<div class="row small text-muted text-uppercase align-items-center">
|
53
|
+
<div class="col-12 col-lg-4"><%= t ".queue_name" %></div>
|
54
|
+
<div class="col-lg-2 d-none d-lg-block"><%= t ".executions" %></div>
|
55
|
+
<div class="col-lg-2 d-none d-lg-block"><%= t ".average_duration" %></div>
|
56
|
+
<div class="col-lg-2 d-none d-lg-block"><%= t ".minimum_duration" %></div>
|
57
|
+
<div class="col-lg-2 d-none d-lg-block"><%= t ".maximum_duration" %></div>
|
58
|
+
</div>
|
59
|
+
</header>
|
60
|
+
|
61
|
+
<% @queue_performances.each do |performance| %>
|
62
|
+
<div role="row" class="list-group-item py-3">
|
63
|
+
<div class="row align-items-center">
|
64
|
+
<div class="col-12 col-lg-4">
|
65
|
+
<div class="d-flex gap-3 justify-content-between">
|
66
|
+
<%= performance.queue_name %>
|
67
|
+
</div>
|
68
|
+
</div>
|
69
|
+
<div class="col-6 col-lg-2 text-wrap">
|
70
|
+
<div class="d-lg-none small text-muted mt-1"><%= t ".executions" %></div>
|
71
|
+
<%= performance.executions_count %>
|
72
|
+
</div>
|
29
73
|
<div class="col-6 col-lg-2 text-wrap">
|
30
74
|
<div class="d-lg-none small text-muted mt-1"><%= t ".average_duration" %></div>
|
31
75
|
<%= format_duration performance.avg_duration %>
|
@@ -44,6 +44,12 @@
|
|
44
44
|
<%= t(".performance") %>
|
45
45
|
<% end %>
|
46
46
|
</li>
|
47
|
+
<li class="nav-item">
|
48
|
+
<%= link_to pauses_path, class: ["nav-link", ("active" if controller_name == 'pauses')] do %>
|
49
|
+
<%= t(".pauses") %>
|
50
|
+
<span data-async-values-target="value" data-async-values-key="pauses_count" data-async-values-zero-class="d-none" class="badge bg-warning rounded-pill d-none"></span>
|
51
|
+
<% end %>
|
52
|
+
</li>
|
47
53
|
<li class="nav-item">
|
48
54
|
<%= link_to cleaner_index_path, class: ["nav-link", ("active" if controller_name == 'cleaner')] do %>
|
49
55
|
<%= t(".cleaner") %>
|
data/config/locales/de.yml
CHANGED
@@ -194,6 +194,7 @@ de:
|
|
194
194
|
job:
|
195
195
|
arguments: Argumente
|
196
196
|
attempts: Versuche
|
197
|
+
labels: Etiketten
|
197
198
|
priority: Priorität
|
198
199
|
queue: Warteschlange
|
199
200
|
number:
|
@@ -213,14 +214,28 @@ de:
|
|
213
214
|
thousand: K
|
214
215
|
trillion: T
|
215
216
|
unit: ''
|
217
|
+
pauses:
|
218
|
+
index:
|
219
|
+
confirm_pause: Sind Sie sicher, dass Sie %{value} pausieren möchten?
|
220
|
+
confirm_unpause: Sind Sie sicher, dass Sie %{value} fortsetzen möchten?
|
221
|
+
disabled: Die experimentelle Pausenfunktion von GoodJob ist standardmäßig deaktiviert, da sie die Leistung beeinträchtigen kann. Konfigurieren Sie sie zur Aktivierung
|
222
|
+
job_class: Job-Klasse
|
223
|
+
label: Label
|
224
|
+
pause: Pausieren
|
225
|
+
queue: Warteschlange
|
226
|
+
title: Pausen
|
227
|
+
type: Pausentyp
|
228
|
+
unpause: Fortsetzen
|
229
|
+
value: Wert
|
216
230
|
performance:
|
217
231
|
index:
|
218
232
|
average_duration: Durchschnittliche Dauer
|
219
|
-
chart_title:
|
233
|
+
chart_title: Gesamtausführungszeit in Sekunden
|
220
234
|
executions: Ausführungen
|
221
235
|
job_class: Job-Klasse
|
222
236
|
maximum_duration: Maximale Dauer
|
223
|
-
minimum_duration:
|
237
|
+
minimum_duration: Minimale Dauer
|
238
|
+
queue_name: Warteschlangenname
|
224
239
|
title: Leistung
|
225
240
|
show:
|
226
241
|
slow: Langsam
|
@@ -255,6 +270,7 @@ de:
|
|
255
270
|
jobs: Jobs
|
256
271
|
live_poll: Live Poll
|
257
272
|
name: "GoodJob 👍"
|
273
|
+
pauses: Pausen
|
258
274
|
performance: Leistung
|
259
275
|
processes: Prozesse
|
260
276
|
theme:
|