good_job 2.14.4 → 2.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +8 -7
- data/engine/app/controllers/good_job/jobs_controller.rb +10 -3
- data/engine/app/helpers/good_job/application_helper.rb +43 -13
- data/engine/app/views/good_job/cron_entries/index.html.erb +1 -1
- data/engine/app/views/good_job/jobs/_executions.erb +46 -0
- data/engine/app/views/good_job/jobs/_table.erb +13 -1
- data/engine/app/views/good_job/jobs/show.html.erb +56 -2
- data/engine/app/views/good_job/shared/_filter.erb +1 -1
- data/engine/app/views/good_job/shared/icons/_check.html.erb +4 -3
- data/engine/app/views/good_job/shared/icons/_clock.html.erb +5 -0
- data/engine/app/views/good_job/shared/icons/_dash_circle.html.erb +5 -0
- data/engine/app/views/good_job/shared/icons/_exclamation.html.erb +4 -3
- data/engine/config/locales/en.yml +13 -0
- data/engine/config/locales/es.yml +13 -0
- data/engine/config/locales/nl.yml +13 -0
- data/engine/config/locales/ru.yml +13 -0
- data/engine/config/routes.rb +1 -3
- data/lib/good_job/active_job_job.rb +16 -32
- data/lib/good_job/cli.rb +5 -5
- data/lib/good_job/configuration.rb +10 -0
- data/lib/good_job/execution.rb +61 -2
- data/lib/good_job/job_performer.rb +1 -1
- data/lib/good_job/log_subscriber.rb +2 -2
- data/lib/good_job/version.rb +1 -1
- data/lib/good_job.rb +12 -9
- metadata +5 -4
- data/engine/app/controllers/good_job/executions_controller.rb +0 -10
- data/engine/app/views/good_job/executions/_table.erb +0 -62
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b978264b509be9bd83e1e5e67f291b2889deff86c8316f876ab9def8264a7dd0
|
4
|
+
data.tar.gz: 9b4172f97c1c7406d3c51ec81f926adaca801baa004aed743a8cac757bb2ce86
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a2696f821699ea080d2b77bb7704192222e6347d1b2be7fcf7a5e049d7b8979ceea3f5236c165fb27bb17fa5f9d8c7d2e5bb863b9c7298de9efb892facb6bca7
|
7
|
+
data.tar.gz: 0ab7a37d403e1bec9c0638835c903aff4a1168f33143c7c27acd87c07bf95a61c1475c3e61bea0a7590d167144761cc64b7bc81566746c854c2ae9698aa999fe
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,24 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v2.15.0](https://github.com/bensheldon/good_job/tree/v2.15.0) (2022-05-18)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v2.14.4...v2.15.0)
|
6
|
+
|
7
|
+
**Implemented enhancements:**
|
8
|
+
|
9
|
+
- Adds the ability to delete jobs on the dashboard; add `cleanup_discarded_jobs` option to retain discarded jobs during cleanup [\#597](https://github.com/bensheldon/good_job/pull/597) ([TAGraves](https://github.com/TAGraves))
|
10
|
+
- Dashboard: show more details about jobs [\#575](https://github.com/bensheldon/good_job/pull/575) ([bkeepers](https://github.com/bkeepers))
|
11
|
+
|
12
|
+
**Closed issues:**
|
13
|
+
|
14
|
+
- Show status on jobs\#show page [\#547](https://github.com/bensheldon/good_job/issues/547)
|
15
|
+
|
16
|
+
**Merged pull requests:**
|
17
|
+
|
18
|
+
- Remove ability to destroy individual Executions from Dashboard; rename "Toggle" to "Inspect" everywhere [\#601](https://github.com/bensheldon/good_job/pull/601) ([bensheldon](https://github.com/bensheldon))
|
19
|
+
- Disable ActiveRecord Connection Reaper in test [\#600](https://github.com/bensheldon/good_job/pull/600) ([bensheldon](https://github.com/bensheldon))
|
20
|
+
- Update README dashboard screenshot [\#599](https://github.com/bensheldon/good_job/pull/599) ([aried3r](https://github.com/aried3r))
|
21
|
+
|
3
22
|
## [v2.14.4](https://github.com/bensheldon/good_job/tree/v2.14.4) (2022-05-15)
|
4
23
|
|
5
24
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v2.14.3...v2.14.4)
|
data/README.md
CHANGED
@@ -185,7 +185,7 @@ separate isolated execution pools with semicolons and threads with colons.
|
|
185
185
|
|
186
186
|
#### `good_job cleanup_preserved_jobs`
|
187
187
|
|
188
|
-
`good_job cleanup_preserved_jobs`
|
188
|
+
`good_job cleanup_preserved_jobs` destroys preserved job records. See `GoodJob.preserve_job_records` for when this command is useful.
|
189
189
|
|
190
190
|
```bash
|
191
191
|
$ bundle exec good_job help cleanup_preserved_jobs
|
@@ -194,11 +194,11 @@ Usage:
|
|
194
194
|
good_job cleanup_preserved_jobs
|
195
195
|
|
196
196
|
Options:
|
197
|
-
[--before-seconds-ago=SECONDS] #
|
197
|
+
[--before-seconds-ago=SECONDS] # Destroy records finished more than this many seconds ago (env var: GOOD_JOB_CLEANUP_PRESERVED_JOBS_BEFORE_SECONDS_AGO, default: 86400)
|
198
198
|
|
199
|
-
|
199
|
+
Destroys preserved job records.
|
200
200
|
|
201
|
-
By default, GoodJob
|
201
|
+
By default, GoodJob destroys job records when the job is performed and this
|
202
202
|
command is not necessary.
|
203
203
|
|
204
204
|
However, when `GoodJob.preserve_job_records = true`, the jobs will be
|
@@ -206,7 +206,7 @@ preserved in the database. This is useful when wanting to analyze or
|
|
206
206
|
inspect job performance.
|
207
207
|
|
208
208
|
If you are preserving job records this way, use this command regularly
|
209
|
-
to
|
209
|
+
to destroy old records and preserve space in your database.
|
210
210
|
```
|
211
211
|
|
212
212
|
### Configuration options
|
@@ -269,6 +269,7 @@ Available configuration options are:
|
|
269
269
|
- `shutdown_timeout` (float) number of seconds to wait for jobs to finish when shutting down before stopping the thread. Defaults to forever: `-1`. You can also set this with the environment variable `GOOD_JOB_SHUTDOWN_TIMEOUT`.
|
270
270
|
- `enable_cron` (boolean) whether to run cron process. Defaults to `false`. You can also set this with the environment variable `GOOD_JOB_ENABLE_CRON`.
|
271
271
|
- `cron` (hash) cron configuration. Defaults to `{}`. You can also set this as a JSON string with the environment variable `GOOD_JOB_CRON`
|
272
|
+
- `cleanup_discarded_jobs` (boolean) whether to destroy discarded jobs when cleaning up preserved jobs using the `$ good_job cleanup_preserved_jobs` CLI command or calling `GoodJob.cleanup_preserved_jobs`. Defaults to `true`. Can also be set with the environment variable `GOOD_JOB_CLEANUP_DISCARDED_JOBS`. _This configuration is only used when {GoodJob.preserve_job_records} is `true`._
|
272
273
|
- `cleanup_preserved_jobs_before_seconds_ago` (integer) number of seconds to preserve jobs when using the `$ good_job cleanup_preserved_jobs` CLI command or calling `GoodJob.cleanup_preserved_jobs`. Defaults to `86400` (1 day). Can also be set with the environment variable `GOOD_JOB_CLEANUP_PRESERVED_JOBS_BEFORE_SECONDS_AGO`. _This configuration is only used when {GoodJob.preserve_job_records} is `true`._
|
273
274
|
- `cleanup_interval_jobs` (integer) Number of jobs a Scheduler will execute before cleaning up preserved jobs. Defaults to `nil`. Can also be set with the environment variable `GOOD_JOB_CLEANUP_INTERVAL_JOBS`.
|
274
275
|
- `cleanup_interval_seconds` (integer) Number of seconds a Scheduler will wait before cleaning up preserved jobs. Defaults to `nil`. Can also be set with the environment variable `GOOD_JOB_CLEANUP_INTERVAL_SECONDS`.
|
@@ -826,7 +827,7 @@ If your application is already using an ActiveJob backend, you will need to inst
|
|
826
827
|
|
827
828
|
GoodJob is fully instrumented with [`ActiveSupport::Notifications`](https://edgeguides.rubyonrails.org/active_support_instrumentation.html#introduction-to-instrumentation).
|
828
829
|
|
829
|
-
By default, GoodJob will
|
830
|
+
By default, GoodJob will destroy job records after they are run, regardless of whether they succeed or not (raising a kind of `StandardError`), unless they are interrupted (raising a kind of `Exception`).
|
830
831
|
|
831
832
|
To preserve job records for later inspection, set an initializer:
|
832
833
|
|
@@ -835,7 +836,7 @@ To preserve job records for later inspection, set an initializer:
|
|
835
836
|
GoodJob.preserve_job_records = true
|
836
837
|
```
|
837
838
|
|
838
|
-
It is also necessary to
|
839
|
+
It is also necessary to destroy these preserved jobs from the database after a certain time period:
|
839
840
|
|
840
841
|
- For example, in a Rake task:
|
841
842
|
|
@@ -7,6 +7,7 @@ module GoodJob
|
|
7
7
|
discard: "discarded",
|
8
8
|
reschedule: "rescheduled",
|
9
9
|
retry: "retried",
|
10
|
+
destroy: "destroyed",
|
10
11
|
}.freeze
|
11
12
|
|
12
13
|
rescue_from GoodJob::ActiveJobJob::AdapterNotGoodJobError,
|
@@ -36,6 +37,8 @@ module GoodJob
|
|
36
37
|
job.reschedule_job
|
37
38
|
when :retry
|
38
39
|
job.retry_job
|
40
|
+
when :destroy
|
41
|
+
job.destroy_job
|
39
42
|
end
|
40
43
|
|
41
44
|
job
|
@@ -53,9 +56,7 @@ module GoodJob
|
|
53
56
|
end
|
54
57
|
|
55
58
|
def show
|
56
|
-
@
|
57
|
-
.order(Arel.sql("COALESCE(scheduled_at, created_at) DESC"))
|
58
|
-
redirect_to jobs_path, alert: "Executions for Active Job #{params[:id]} not found" if @executions.empty?
|
59
|
+
@job = ActiveJobJob.find(params[:id])
|
59
60
|
end
|
60
61
|
|
61
62
|
def discard
|
@@ -76,6 +77,12 @@ module GoodJob
|
|
76
77
|
redirect_back(fallback_location: jobs_path, notice: "Job has been retried")
|
77
78
|
end
|
78
79
|
|
80
|
+
def destroy
|
81
|
+
@job = ActiveJobJob.find(params[:id])
|
82
|
+
@job.destroy_job
|
83
|
+
redirect_back(fallback_location: jobs_path, notice: "Job has been destroyed")
|
84
|
+
end
|
85
|
+
|
79
86
|
private
|
80
87
|
|
81
88
|
def redirect_on_error(exception)
|
@@ -1,24 +1,54 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module GoodJob
|
3
3
|
module ApplicationHelper
|
4
|
-
def
|
5
|
-
|
4
|
+
def format_duration(sec)
|
5
|
+
return unless sec
|
6
|
+
|
7
|
+
if sec < 1
|
8
|
+
t 'duration.milliseconds', ms: (sec * 1000).floor
|
9
|
+
elsif sec < 10
|
10
|
+
t 'duration.less_than_10_seconds', sec: sec.floor
|
11
|
+
elsif sec < 60
|
12
|
+
t 'duration.seconds', sec: sec.floor
|
13
|
+
elsif sec < 3600
|
14
|
+
t 'duration.minutes', min: (sec / 60).floor, sec: (sec % 60).floor
|
15
|
+
else
|
16
|
+
t 'duration.hours', hour: (sec / 3600).floor, min: ((sec % 3600) / 60).floor
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def relative_time(timestamp, **args)
|
21
|
+
text = timestamp.future? ? "in #{time_ago_in_words(timestamp, **args)}" : "#{time_ago_in_words(timestamp, **args)} ago"
|
6
22
|
tag.time(text, datetime: timestamp, title: timestamp)
|
7
23
|
end
|
8
24
|
|
25
|
+
STATUS_ICONS = {
|
26
|
+
discarded: "exclamation",
|
27
|
+
finished: "check",
|
28
|
+
queued: "dash_circle",
|
29
|
+
retried: "arrow_clockwise",
|
30
|
+
running: "play",
|
31
|
+
scheduled: "clock",
|
32
|
+
}.freeze
|
33
|
+
|
34
|
+
STATUS_COLOR = {
|
35
|
+
discarded: "danger",
|
36
|
+
finished: "success",
|
37
|
+
queued: "warning",
|
38
|
+
retried: "secondary",
|
39
|
+
running: "primary",
|
40
|
+
scheduled: "secondary",
|
41
|
+
}.freeze
|
42
|
+
|
9
43
|
def status_badge(status)
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
when :queued, :scheduled, :retried
|
14
|
-
"badge rounded-pill bg-secondary"
|
15
|
-
when :running
|
16
|
-
"badge rounded-pill bg-primary"
|
17
|
-
when :discarded
|
18
|
-
"badge rounded-pill bg-danger"
|
19
|
-
end
|
44
|
+
content_tag :span, status_icon(status, class: "text-white") + t(status, scope: '.status'),
|
45
|
+
class: "badge rounded-pill bg-#{STATUS_COLOR.fetch(status)} d-inline-flex gap-2 ps-1 pe-3 align-items-center"
|
46
|
+
end
|
20
47
|
|
21
|
-
|
48
|
+
def status_icon(status, **options)
|
49
|
+
options[:class] ||= "text-#{STATUS_COLOR.fetch(status)}"
|
50
|
+
icon = render_icon STATUS_ICONS.fetch(status)
|
51
|
+
content_tag :span, icon, **options
|
22
52
|
end
|
23
53
|
|
24
54
|
def render_icon(name)
|
@@ -27,7 +27,7 @@
|
|
27
27
|
<td class="font-monospace"><%= cron_entry.key %></td>
|
28
28
|
<td class="font-monospace"><%= cron_entry.schedule %></td>
|
29
29
|
<td>
|
30
|
-
<%= tag.button("
|
30
|
+
<%= tag.button("Inspect", type: "button", class: "btn btn-sm btn-outline-primary", role: "button",
|
31
31
|
data: { bs_toggle: "collapse", bs_target: "##{dom_id(cron_entry, 'properties')}" },
|
32
32
|
aria: { expanded: false, controls: dom_id(cron_entry, 'properties') }) %>
|
33
33
|
<%= tag.pre(JSON.pretty_generate(cron_entry.display_properties), id: dom_id(cron_entry, 'properties'), class: "collapse cron-entry-properties") %>
|
@@ -0,0 +1,46 @@
|
|
1
|
+
<h5>Executions</h5>
|
2
|
+
<div class="card mb-4" data-live-poll-region="executions-table">
|
3
|
+
<div class="list-group list-group-flush">
|
4
|
+
<% executions.each do |execution| %>
|
5
|
+
<%= tag.div id: dom_id(execution), class: "list-group-item py-3" do %>
|
6
|
+
<div class="d-md-flex">
|
7
|
+
<div class="flex-fill">
|
8
|
+
<div class="small text-muted">
|
9
|
+
#<%= execution.number %>:
|
10
|
+
<%= tag.code link_to(execution.id, "##{dom_id(execution)}", class: "text-muted text-decoration-none") %>
|
11
|
+
</div>
|
12
|
+
<div class="d-flex gap-2 align-items-center text-muted mt-1">
|
13
|
+
<%= status_badge execution.status %>
|
14
|
+
<%= relative_time execution.last_status_at, include_seconds: true %>
|
15
|
+
|
16
|
+
<% if execution.runtime_latency %>
|
17
|
+
• <div><%= format_duration execution.runtime_latency %> runtime</div>
|
18
|
+
<% end %>
|
19
|
+
<% if execution.queue_latency %>
|
20
|
+
• <div><%= format_duration execution.queue_latency %> in queue</div>
|
21
|
+
<% end %>
|
22
|
+
</div>
|
23
|
+
</div>
|
24
|
+
<div>
|
25
|
+
<div class="mt-4 d-flex gap-2">
|
26
|
+
<%= tag.button type: "button", class: "btn btn-sm btn-outline-primary", role: "button",
|
27
|
+
data: { bs_toggle: "collapse", bs_target: "##{dom_id(execution, 'params')}" },
|
28
|
+
aria: { expanded: false, controls: dom_id(execution, "params") } do %>
|
29
|
+
Inspect
|
30
|
+
<% end %>
|
31
|
+
</div>
|
32
|
+
</div>
|
33
|
+
</div>
|
34
|
+
<% if execution.error %>
|
35
|
+
<div class="mt-3">
|
36
|
+
<strong class="small">Error:</strong>
|
37
|
+
<pre class="text-wrap text-break m-0"><%= execution.error %></pre>
|
38
|
+
</div>
|
39
|
+
<% end %>
|
40
|
+
<div>
|
41
|
+
<%= tag.pre JSON.pretty_generate(execution.serialized_params), id: dom_id(execution, "params"), class: "collapse bg-light card card-body p-3 my-3" %>
|
42
|
+
</div>
|
43
|
+
<% end %>
|
44
|
+
<% end %>
|
45
|
+
</div>
|
46
|
+
</div>
|
@@ -34,6 +34,10 @@
|
|
34
34
|
<%= form.button type: 'submit', name: 'mass_action', value: 'retry', class: 'btn btn-sm btn-outline-primary', title: "Retry all", data: { confirm: "Confirm retry all", disable: true } do %>
|
35
35
|
<%= render_icon "arrow_clockwise" %> All
|
36
36
|
<% end %>
|
37
|
+
|
38
|
+
<%= form.button type: 'submit', name: 'mass_action', value: 'destroy', class: 'btn btn-sm btn-outline-primary', title: "Destroy all", data: { confirm: "Confirm destroy all", disable: true } do %>
|
39
|
+
<%= render_icon "trash" %> All
|
40
|
+
<% end %>
|
37
41
|
</div>
|
38
42
|
</th>
|
39
43
|
</tr>
|
@@ -63,7 +67,7 @@
|
|
63
67
|
<td><%= job.executions_count %></td>
|
64
68
|
<td class="text-break"><%= truncate(job.recent_error, length: 1_000) %></td>
|
65
69
|
<td>
|
66
|
-
<%= tag.button "
|
70
|
+
<%= tag.button "Inspect", type: "button", class: "btn btn-sm btn-outline-primary", role: "button",
|
67
71
|
data: { bs_toggle: "collapse", bs_target: "##{dom_id(job, 'params')}" },
|
68
72
|
aria: { expanded: false, controls: dom_id(job, "params") }
|
69
73
|
%>
|
@@ -94,6 +98,14 @@
|
|
94
98
|
<% else %>
|
95
99
|
<button class="btn btn-sm btn-outline-secondary" disabled><%= render_icon "arrow_clockwise" %></button>
|
96
100
|
<% end %>
|
101
|
+
|
102
|
+
<% if job.status.in? [:discarded, :finished] %>
|
103
|
+
<%= link_to job_path(job.id), method: :delete, class: "btn btn-sm btn-outline-primary", title: "Destroy job", data: { confirm: "Confirm destroy", disable: true } do %>
|
104
|
+
<%= render_icon "trash" %>
|
105
|
+
<% end %>
|
106
|
+
<% else %>
|
107
|
+
<button class="btn btn-sm btn-outline-secondary" disabled><%= render_icon "trash" %></button>
|
108
|
+
<% end %>
|
97
109
|
</div>
|
98
110
|
</td>
|
99
111
|
</tr>
|
@@ -1,3 +1,57 @@
|
|
1
|
-
<
|
1
|
+
<div class="break-out bg-light border-bottom py-2 mb-3">
|
2
|
+
<div class="container-fluid pt-2">
|
3
|
+
<div class="d-flex align-items-center">
|
4
|
+
<div class="flex-fill">
|
5
|
+
<nav aria-label="breadcrumb">
|
6
|
+
<ol class="breadcrumb small mb-0">
|
7
|
+
<li class="breadcrumb-item"><%= link_to "Jobs", jobs_path %></li>
|
8
|
+
<li class="breadcrumb-item active" aria-current="page">ActiveJob ID: <%= tag.code @job.id %></li>
|
9
|
+
</ol>
|
10
|
+
</nav>
|
11
|
+
<h2 class="mb-1"><%= tag.code @job.job_class %></h2>
|
12
|
+
<div class="text-muted small d-flex gap-2">
|
13
|
+
<div>Queue: <%= tag.strong @job.queue_name %></div>
|
14
|
+
•
|
15
|
+
<div>Priority: <%= tag.strong @job.priority %></div>
|
16
|
+
</div>
|
17
|
+
</div>
|
18
|
+
<div>
|
19
|
+
<% job_reschedulable = @job.status.in? [:scheduled, :retried, :queued] %>
|
20
|
+
<%= button_to reschedule_job_path(@job.id), method: :put,
|
21
|
+
class: "btn btn-sm #{job_reschedulable ? 'btn-outline-primary' : 'btn-outline-secondary'}",
|
22
|
+
form_class: "d-inline-block",
|
23
|
+
disabled: !job_reschedulable,
|
24
|
+
aria: { label: "Reschedule job" },
|
25
|
+
title: "Reschedule job",
|
26
|
+
data: { confirm: "Confirm reschedule" } do %>
|
27
|
+
<%= render_icon "skip_forward" %>
|
28
|
+
Reschedule
|
29
|
+
<% end %>
|
2
30
|
|
3
|
-
|
31
|
+
<% job_discardable = @job.status.in? [:scheduled, :retried, :queued] %>
|
32
|
+
<%= button_to discard_job_path(@job.id), method: :put, class: "btn btn-sm #{job_discardable ? 'btn-outline-primary' : 'btn-outline-secondary'}", form_class: "d-inline-block", disabled: !job_discardable, aria: { label: "Discard job" }, title: "Discard job", data: { confirm: "Confirm discard" } do %>
|
33
|
+
<%= render_icon "stop" %>
|
34
|
+
Discard
|
35
|
+
<% end %>
|
36
|
+
|
37
|
+
<%= button_to retry_job_path(@job.id), method: :put, class: "btn btn-sm #{@job.status == :discarded ? 'btn-outline-primary' : 'btn-outline-secondary'}", form_class: "d-inline-block", disabled: @job.status != :discarded, aria: { label: "Retry job" }, title: "Retry job", data: { confirm: "Confirm retry" } do %>
|
38
|
+
<%= render_icon "arrow_clockwise" %>
|
39
|
+
Retry
|
40
|
+
<% end %>
|
41
|
+
|
42
|
+
<% job_destroyable = @job.status.in? [:discarded, :finished] %>
|
43
|
+
<%= button_to job_path(@job.id), method: :delete, class: "btn btn-sm #{job_destroyable ? 'btn-outline-primary' : 'btn-outline-secondary'}", form_class: "d-inline-block", disabled: !job_destroyable, aria: { label: "Destroy job" }, title: "Destroy job", data: { confirm: "Confirm destroy" } do %>
|
44
|
+
<%= render_icon "trash" %>
|
45
|
+
Destroy
|
46
|
+
<% end %>
|
47
|
+
</div>
|
48
|
+
</div>
|
49
|
+
</div>
|
50
|
+
</div>
|
51
|
+
|
52
|
+
<div class="my-4">
|
53
|
+
<h5>Arguments</h5>
|
54
|
+
<%= tag.pre @job.serialized_params["arguments"].map(&:inspect).join(', ') %>
|
55
|
+
</div>
|
56
|
+
|
57
|
+
<%= render 'executions', executions: @job.executions.reverse %>
|
@@ -10,7 +10,7 @@
|
|
10
10
|
<% filter.states.each do |name, count| %>
|
11
11
|
<li class="nav-item">
|
12
12
|
<%= link_to url_for({state: name}), class: "nav-link #{"active" if params[:state] == name}" do %>
|
13
|
-
<%= name.
|
13
|
+
<%= t(name, scope: '.status') %>
|
14
14
|
<span class="badge bg-primary rounded-pill <%= "bg-secondary" if count == 0 %>"><%= count %></span>
|
15
15
|
<% end %>
|
16
16
|
</li>
|
@@ -1,4 +1,5 @@
|
|
1
|
-
<!-- https://icons.getbootstrap.com/icons/check-circle
|
2
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-check-circle
|
3
|
-
<path d="
|
1
|
+
<!-- https://icons.getbootstrap.com/icons/check-circle/ -->
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-check-circle" viewBox="0 0 16 16">
|
3
|
+
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
|
4
|
+
<path d="M10.97 4.97a.235.235 0 0 0-.02.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-1.071-1.05z" />
|
4
5
|
</svg>
|
@@ -0,0 +1,5 @@
|
|
1
|
+
<!-- https://icons.getbootstrap.com/icons/clock/ -->
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-clock" viewBox="0 0 16 16">
|
3
|
+
<path d="M8 3.5a.5.5 0 0 0-1 0V9a.5.5 0 0 0 .252.434l3.5 2a.5.5 0 0 0 .496-.868L8 8.71V3.5z" />
|
4
|
+
<path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm7-8A7 7 0 1 1 1 8a7 7 0 0 1 14 0z" />
|
5
|
+
</svg>
|
@@ -0,0 +1,5 @@
|
|
1
|
+
<!-- https://icons.getbootstrap.com/icons/dash-circle/ -->
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-dash-circle" viewBox="0 0 16 16">
|
3
|
+
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
|
4
|
+
<path d="M4 8a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 4 8z" />
|
5
|
+
</svg>
|
@@ -1,4 +1,5 @@
|
|
1
|
-
<!-- https://icons.getbootstrap.com/icons/exclamation-
|
2
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-exclamation-
|
3
|
-
<path d="M8
|
1
|
+
<!-- https://icons.getbootstrap.com/icons/exclamation-circle/ -->
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-exclamation-circle" viewBox="0 0 16 16">
|
3
|
+
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
|
4
|
+
<path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z" />
|
4
5
|
</svg>
|
@@ -39,6 +39,12 @@ en:
|
|
39
39
|
x_years:
|
40
40
|
one: 1 year
|
41
41
|
other: "%{count} years"
|
42
|
+
duration:
|
43
|
+
hours: "%{hour}h %{min}m"
|
44
|
+
less_than_10_seconds: "%{sec}s"
|
45
|
+
milliseconds: "%{ms}ms"
|
46
|
+
minutes: "%{min}m %{sec}s"
|
47
|
+
seconds: "%{sec}s"
|
42
48
|
good_job:
|
43
49
|
shared:
|
44
50
|
footer:
|
@@ -50,3 +56,10 @@ en:
|
|
50
56
|
live_poll: Live Poll
|
51
57
|
name: "GoodJob 👍"
|
52
58
|
processes: Processes
|
59
|
+
status:
|
60
|
+
discarded: Discarded
|
61
|
+
finished: Finished
|
62
|
+
queued: Queued
|
63
|
+
retried: Retried
|
64
|
+
running: Running
|
65
|
+
scheduled: Scheduled
|
@@ -39,6 +39,12 @@ es:
|
|
39
39
|
x_years:
|
40
40
|
one: 1 año
|
41
41
|
other: "%{count} años"
|
42
|
+
duration:
|
43
|
+
hours: "%{hour}h %{min}m"
|
44
|
+
less_than_10_seconds: "%{sec}s"
|
45
|
+
milliseconds: "%{ms}ms"
|
46
|
+
minutes: "%{min}m %{sec}s"
|
47
|
+
seconds: "%{sec}s"
|
42
48
|
good_job:
|
43
49
|
shared:
|
44
50
|
footer:
|
@@ -50,3 +56,10 @@ es:
|
|
50
56
|
live_poll: En vivo
|
51
57
|
name: "GoodJob 👍"
|
52
58
|
processes: Procesos
|
59
|
+
status:
|
60
|
+
discarded: Descartado
|
61
|
+
finished: Acabado
|
62
|
+
queued: Puesto en cola
|
63
|
+
retried: reintentado
|
64
|
+
running: Corriendo
|
65
|
+
scheduled: Programado
|
@@ -39,6 +39,12 @@ nl:
|
|
39
39
|
x_years:
|
40
40
|
one: 1 jaar
|
41
41
|
other: "%{count} jaren"
|
42
|
+
duration:
|
43
|
+
hours: "%{hour}h %{min}m"
|
44
|
+
less_than_10_seconds: "%{sec}s"
|
45
|
+
milliseconds: "%{ms}ms"
|
46
|
+
minutes: "%{min}m %{sec}s"
|
47
|
+
seconds: "%{sec}s"
|
42
48
|
good_job:
|
43
49
|
shared:
|
44
50
|
footer:
|
@@ -50,3 +56,10 @@ nl:
|
|
50
56
|
live_poll: Live Poll
|
51
57
|
name: "GoodJob 👍"
|
52
58
|
processes: Processen
|
59
|
+
status:
|
60
|
+
discarded: weggegooid
|
61
|
+
finished: Afgewerkt
|
62
|
+
queued: In de wachtrij
|
63
|
+
retried: Opnieuw geprobeerd
|
64
|
+
running: Rennen
|
65
|
+
scheduled: Gepland
|
@@ -63,6 +63,12 @@ ru:
|
|
63
63
|
many: "%{count} лет"
|
64
64
|
one: 1 год
|
65
65
|
other: "%{count} года"
|
66
|
+
duration:
|
67
|
+
hours: "%{hour}h %{min}m"
|
68
|
+
less_than_10_seconds: "%{sec}s"
|
69
|
+
milliseconds: "%{ms}мс"
|
70
|
+
minutes: "%{min}м %{sec}с"
|
71
|
+
seconds: "%{sec}s"
|
66
72
|
good_job:
|
67
73
|
shared:
|
68
74
|
footer:
|
@@ -74,3 +80,10 @@ ru:
|
|
74
80
|
live_poll: Живой Опрос
|
75
81
|
name: "GoodJob 👍"
|
76
82
|
processes: Процессы
|
83
|
+
status:
|
84
|
+
discarded: Отброшено
|
85
|
+
finished: Законченный
|
86
|
+
queued: В очереди
|
87
|
+
retried: Повторная попытка
|
88
|
+
running: Бег
|
89
|
+
scheduled: по расписанию
|
data/engine/config/routes.rb
CHANGED
@@ -2,9 +2,7 @@
|
|
2
2
|
GoodJob::Engine.routes.draw do
|
3
3
|
root to: redirect(path: 'jobs')
|
4
4
|
|
5
|
-
resources :
|
6
|
-
|
7
|
-
resources :jobs, only: %i[index show] do
|
5
|
+
resources :jobs, only: %i[index show destroy] do
|
8
6
|
collection do
|
9
7
|
get :mass_update, to: redirect(path: 'jobs')
|
10
8
|
put :mass_update
|
@@ -54,9 +54,11 @@ module GoodJob
|
|
54
54
|
# Advisory locked and executing
|
55
55
|
scope :running, -> { where(finished_at: nil).joins_advisory_locks.where.not(pg_locks: { locktype: nil }) }
|
56
56
|
# Completed executing successfully
|
57
|
-
scope :finished, -> { where.not(finished_at: nil)
|
57
|
+
scope :finished, -> { not_discarded.where.not(finished_at: nil) }
|
58
58
|
# Errored but will not be retried
|
59
59
|
scope :discarded, -> { where.not(finished_at: nil).where.not(error: nil) }
|
60
|
+
# Not errored
|
61
|
+
scope :not_discarded, -> { where(error: nil) }
|
60
62
|
|
61
63
|
# The job's ActiveJob UUID
|
62
64
|
# @return [String]
|
@@ -71,38 +73,8 @@ module GoodJob
|
|
71
73
|
end
|
72
74
|
|
73
75
|
# The status of the Job, based on the state of its most recent execution.
|
74
|
-
# There are 3 buckets of non-overlapping statuses:
|
75
|
-
# 1. The job will be executed
|
76
|
-
# - queued: The job will execute immediately when an execution thread becomes available.
|
77
|
-
# - scheduled: The job is scheduled to execute in the future.
|
78
|
-
# - retried: The job previously errored on execution and will be re-executed in the future.
|
79
|
-
# 2. The job is being executed
|
80
|
-
# - running: the job is actively being executed by an execution thread
|
81
|
-
# 3. The job will not execute
|
82
|
-
# - finished: The job executed successfully
|
83
|
-
# - discarded: The job previously errored on execution and will not be re-executed in the future.
|
84
|
-
#
|
85
76
|
# @return [Symbol]
|
86
|
-
|
87
|
-
execution = head_execution
|
88
|
-
if execution.finished_at.present?
|
89
|
-
if execution.error.present?
|
90
|
-
:discarded
|
91
|
-
else
|
92
|
-
:finished
|
93
|
-
end
|
94
|
-
elsif (execution.scheduled_at || execution.created_at) > DateTime.current
|
95
|
-
if execution.serialized_params.fetch('executions', 0) > 1
|
96
|
-
:retried
|
97
|
-
else
|
98
|
-
:scheduled
|
99
|
-
end
|
100
|
-
elsif running?
|
101
|
-
:running
|
102
|
-
else
|
103
|
-
:queued
|
104
|
-
end
|
105
|
-
end
|
77
|
+
delegate :status, :last_status_at, to: :head_execution
|
106
78
|
|
107
79
|
# This job's most recent {Execution}
|
108
80
|
# @param reload [Booelan] whether to reload executions
|
@@ -225,6 +197,18 @@ module GoodJob
|
|
225
197
|
end
|
226
198
|
end
|
227
199
|
|
200
|
+
# Destroy all of a discarded or finished job's executions from the database so that it will no longer appear on the dashboard.
|
201
|
+
# @return [void]
|
202
|
+
def destroy_job
|
203
|
+
with_advisory_lock do
|
204
|
+
execution = head_execution(reload: true)
|
205
|
+
|
206
|
+
raise ActionForStateMismatchError if execution.finished_at.blank?
|
207
|
+
|
208
|
+
destroy
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
228
212
|
# Utility method to determine which execution record is used to represent this job
|
229
213
|
# @return [String]
|
230
214
|
def _execution_id
|
data/lib/good_job/cli.rb
CHANGED
@@ -120,11 +120,11 @@ module GoodJob
|
|
120
120
|
default_task :start
|
121
121
|
|
122
122
|
# @!macro thor.desc
|
123
|
-
desc :cleanup_preserved_jobs, "
|
123
|
+
desc :cleanup_preserved_jobs, "Destroys preserved job records."
|
124
124
|
long_desc <<~DESCRIPTION
|
125
|
-
|
125
|
+
Destroys preserved job records.
|
126
126
|
|
127
|
-
By default, GoodJob
|
127
|
+
By default, GoodJob destroys job records when the job is performed and this
|
128
128
|
command is not necessary.
|
129
129
|
|
130
130
|
However, when `GoodJob.preserve_job_records = true`, the jobs will be
|
@@ -132,13 +132,13 @@ module GoodJob
|
|
132
132
|
inspect job performance.
|
133
133
|
|
134
134
|
If you are preserving job records this way, use this command regularly
|
135
|
-
to
|
135
|
+
to destroy old records and preserve space in your database.
|
136
136
|
|
137
137
|
DESCRIPTION
|
138
138
|
method_option :before_seconds_ago,
|
139
139
|
type: :numeric,
|
140
140
|
banner: 'SECONDS',
|
141
|
-
desc: "
|
141
|
+
desc: "Destroy records finished more than this many seconds ago (env var: GOOD_JOB_CLEANUP_PRESERVED_JOBS_BEFORE_SECONDS_AGO, default: 86400)"
|
142
142
|
|
143
143
|
def cleanup_preserved_jobs
|
144
144
|
set_up_application!
|
@@ -171,6 +171,16 @@ module GoodJob
|
|
171
171
|
cron.map { |cron_key, params| GoodJob::CronEntry.new(params.merge(key: cron_key)) }
|
172
172
|
end
|
173
173
|
|
174
|
+
# Whether to destroy discarded jobs when cleaning up preserved jobs.
|
175
|
+
# This configuration is only used when {GoodJob.preserve_job_records} is +true+.
|
176
|
+
# @return [Boolean]
|
177
|
+
def cleanup_discarded_jobs?
|
178
|
+
return rails_config[:cleanup_discarded_jobs] unless rails_config[:cleanup_discarded_jobs].nil?
|
179
|
+
return ActiveModel::Type::Boolean.new.cast(env['GOOD_JOB_CLEANUP_DISCARDED_JOBS']) unless env['GOOD_JOB_CLEANUP_DISCARDED_JOBS'].nil?
|
180
|
+
|
181
|
+
true
|
182
|
+
end
|
183
|
+
|
174
184
|
# Number of seconds to preserve jobs when using the +good_job cleanup_preserved_jobs+ CLI command.
|
175
185
|
# This configuration is only used when {GoodJob.preserve_job_records} is +true+.
|
176
186
|
# @return [Integer]
|
data/lib/good_job/execution.rb
CHANGED
@@ -88,7 +88,7 @@ module GoodJob
|
|
88
88
|
# @return [ActiveRecord::Relation]
|
89
89
|
scope :priority_ordered, -> { order('priority DESC NULLS LAST') }
|
90
90
|
|
91
|
-
# Order jobs by scheduled
|
91
|
+
# Order jobs by scheduled or created (oldest first).
|
92
92
|
# @!method schedule_ordered
|
93
93
|
# @!scope class
|
94
94
|
# @return [ActiveRecord::Relation]
|
@@ -96,7 +96,7 @@ module GoodJob
|
|
96
96
|
|
97
97
|
# Get Jobs were completed before the given timestamp. If no timestamp is
|
98
98
|
# provided, get all jobs that have been completed. By default, GoodJob
|
99
|
-
#
|
99
|
+
# destroys jobs after they are completed and this will find no jobs.
|
100
100
|
# However, if you have changed {GoodJob.preserve_job_records}, this may
|
101
101
|
# find completed Jobs.
|
102
102
|
# @!method finished(timestamp = nil)
|
@@ -272,6 +272,65 @@ module GoodJob
|
|
272
272
|
end
|
273
273
|
end
|
274
274
|
|
275
|
+
# There are 3 buckets of non-overlapping statuses:
|
276
|
+
# 1. The job will be executed
|
277
|
+
# - queued: The job will execute immediately when an execution thread becomes available.
|
278
|
+
# - scheduled: The job is scheduled to execute in the future.
|
279
|
+
# - retried: The job previously errored on execution and will be re-executed in the future.
|
280
|
+
# 2. The job is being executed
|
281
|
+
# - running: the job is actively being executed by an execution thread
|
282
|
+
# 3. The job will not execute
|
283
|
+
# - finished: The job executed successfully
|
284
|
+
# - discarded: The job previously errored on execution and will not be re-executed in the future.
|
285
|
+
#
|
286
|
+
# @return [Symbol]
|
287
|
+
def status
|
288
|
+
if finished_at.present?
|
289
|
+
if error.present?
|
290
|
+
:discarded
|
291
|
+
else
|
292
|
+
:finished
|
293
|
+
end
|
294
|
+
elsif (scheduled_at || created_at) > DateTime.current
|
295
|
+
if serialized_params.fetch('executions', 0) > 1
|
296
|
+
:retried
|
297
|
+
else
|
298
|
+
:scheduled
|
299
|
+
end
|
300
|
+
elsif running?
|
301
|
+
:running
|
302
|
+
else
|
303
|
+
:queued
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
def running?
|
308
|
+
performed_at? && !finished_at?
|
309
|
+
end
|
310
|
+
|
311
|
+
def number
|
312
|
+
serialized_params.fetch('executions', 0) + 1
|
313
|
+
end
|
314
|
+
|
315
|
+
# The last relevant timestamp for this execution
|
316
|
+
def last_status_at
|
317
|
+
finished_at || performed_at || scheduled_at || created_at
|
318
|
+
end
|
319
|
+
|
320
|
+
# Time between when this job was expected to run and when it started running
|
321
|
+
def queue_latency
|
322
|
+
now = Time.zone.now
|
323
|
+
expected_start = scheduled_at || created_at
|
324
|
+
actual_start = performed_at || now
|
325
|
+
|
326
|
+
actual_start - expected_start unless expected_start >= now
|
327
|
+
end
|
328
|
+
|
329
|
+
# Time between when this job started and finished
|
330
|
+
def runtime_latency
|
331
|
+
(finished_at || Time.zone.now) - performed_at if performed_at
|
332
|
+
end
|
333
|
+
|
275
334
|
private
|
276
335
|
|
277
336
|
def active_job_data
|
@@ -140,10 +140,10 @@ module GoodJob
|
|
140
140
|
# @!macro notification_responder
|
141
141
|
def cleanup_preserved_jobs(event)
|
142
142
|
timestamp = event.payload[:timestamp]
|
143
|
-
|
143
|
+
destroyed_records_count = event.payload[:destroyed_records_count]
|
144
144
|
|
145
145
|
info do
|
146
|
-
"GoodJob
|
146
|
+
"GoodJob destroyed #{destroyed_records_count} preserved #{'job'.pluralize(destroyed_records_count)} finished before #{timestamp}."
|
147
147
|
end
|
148
148
|
end
|
149
149
|
|
data/lib/good_job/version.rb
CHANGED
data/lib/good_job.rb
CHANGED
@@ -43,7 +43,7 @@ module GoodJob
|
|
43
43
|
# @!attribute [rw] preserve_job_records
|
44
44
|
# @!scope class
|
45
45
|
# Whether to preserve job records in the database after they have finished (default: +false+).
|
46
|
-
# By default, GoodJob
|
46
|
+
# By default, GoodJob destroys job records after the job is completed successfully.
|
47
47
|
# If you want to preserve jobs for latter inspection, set this to +true+.
|
48
48
|
# If you want to preserve only jobs that finished with error for latter inspection, set this to +:on_unhandled_error+.
|
49
49
|
# If +true+, you will need to clean out jobs using the +good_job cleanup_preserved_jobs+ CLI command or
|
@@ -126,25 +126,28 @@ module GoodJob
|
|
126
126
|
end
|
127
127
|
end
|
128
128
|
|
129
|
-
#
|
130
|
-
# By default, GoodJob
|
129
|
+
# Destroys preserved job records.
|
130
|
+
# By default, GoodJob destroys job records when the job is performed and this
|
131
131
|
# method is not necessary. However, when `GoodJob.preserve_job_records = true`,
|
132
132
|
# the jobs will be preserved in the database. This is useful when wanting to
|
133
133
|
# analyze or inspect job performance.
|
134
134
|
# If you are preserving job records this way, use this method regularly to
|
135
|
-
#
|
136
|
-
# @params older_than [nil,Numeric,ActiveSupport::Duration] Jobs older than this will be
|
137
|
-
# @return [Integer] Number of jobs that were
|
135
|
+
# destroy old records and preserve space in your database.
|
136
|
+
# @params older_than [nil,Numeric,ActiveSupport::Duration] Jobs older than this will be destroyed (default: +86400+).
|
137
|
+
# @return [Integer] Number of jobs that were destroyed.
|
138
138
|
def self.cleanup_preserved_jobs(older_than: nil)
|
139
|
-
|
139
|
+
configuration = GoodJob::Configuration.new({})
|
140
|
+
older_than ||= configuration.cleanup_preserved_jobs_before_seconds_ago
|
140
141
|
timestamp = Time.current - older_than
|
142
|
+
include_discarded = configuration.cleanup_discarded_jobs?
|
141
143
|
|
142
144
|
ActiveSupport::Notifications.instrument("cleanup_preserved_jobs.good_job", { older_than: older_than, timestamp: timestamp }) do |payload|
|
143
145
|
old_jobs = GoodJob::ActiveJobJob.where('finished_at <= ?', timestamp)
|
146
|
+
old_jobs = old_jobs.not_discarded unless include_discarded
|
144
147
|
old_jobs_count = old_jobs.count
|
145
148
|
|
146
|
-
GoodJob::Execution.where(job: old_jobs).
|
147
|
-
payload[:
|
149
|
+
GoodJob::Execution.where(job: old_jobs).destroy_all
|
150
|
+
payload[:destroyed_records_count] = old_jobs_count
|
148
151
|
end
|
149
152
|
end
|
150
153
|
|
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: 2.
|
4
|
+
version: 2.15.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: 2022-05-
|
11
|
+
date: 2022-05-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activejob
|
@@ -377,7 +377,6 @@ files:
|
|
377
377
|
- engine/app/controllers/good_job/application_controller.rb
|
378
378
|
- engine/app/controllers/good_job/assets_controller.rb
|
379
379
|
- engine/app/controllers/good_job/cron_entries_controller.rb
|
380
|
-
- engine/app/controllers/good_job/executions_controller.rb
|
381
380
|
- engine/app/controllers/good_job/jobs_controller.rb
|
382
381
|
- engine/app/controllers/good_job/processes_controller.rb
|
383
382
|
- engine/app/filters/good_job/base_filter.rb
|
@@ -385,7 +384,7 @@ files:
|
|
385
384
|
- engine/app/helpers/good_job/application_helper.rb
|
386
385
|
- engine/app/views/good_job/cron_entries/index.html.erb
|
387
386
|
- engine/app/views/good_job/cron_entries/show.html.erb
|
388
|
-
- engine/app/views/good_job/
|
387
|
+
- engine/app/views/good_job/jobs/_executions.erb
|
389
388
|
- engine/app/views/good_job/jobs/_table.erb
|
390
389
|
- engine/app/views/good_job/jobs/index.html.erb
|
391
390
|
- engine/app/views/good_job/jobs/show.html.erb
|
@@ -397,6 +396,8 @@ files:
|
|
397
396
|
- engine/app/views/good_job/shared/_navbar.erb
|
398
397
|
- engine/app/views/good_job/shared/icons/_arrow_clockwise.html.erb
|
399
398
|
- engine/app/views/good_job/shared/icons/_check.html.erb
|
399
|
+
- engine/app/views/good_job/shared/icons/_clock.html.erb
|
400
|
+
- engine/app/views/good_job/shared/icons/_dash_circle.html.erb
|
400
401
|
- engine/app/views/good_job/shared/icons/_exclamation.html.erb
|
401
402
|
- engine/app/views/good_job/shared/icons/_play.html.erb
|
402
403
|
- engine/app/views/good_job/shared/icons/_skip_forward.html.erb
|
@@ -1,10 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
module GoodJob
|
3
|
-
class ExecutionsController < GoodJob::ApplicationController
|
4
|
-
def destroy
|
5
|
-
deleted_count = GoodJob::Execution.where(id: params[:id]).delete_all
|
6
|
-
message = deleted_count.positive? ? { notice: "Job execution deleted" } : { alert: "Job execution not deleted" }
|
7
|
-
redirect_back fallback_location: jobs_path, **message
|
8
|
-
end
|
9
|
-
end
|
10
|
-
end
|
@@ -1,62 +0,0 @@
|
|
1
|
-
<div class="my-3" data-live-poll-region="executions-table">
|
2
|
-
<div class="table-responsive">
|
3
|
-
<table class="table table-hover table-sm mb-0" id="executions_index_table">
|
4
|
-
<thead>
|
5
|
-
<tr>
|
6
|
-
<th>ActiveJob ID</th>
|
7
|
-
<th>Execution ID</th>
|
8
|
-
<th>Job Class</th>
|
9
|
-
<th>Queue</th>
|
10
|
-
<th>Scheduled At</th>
|
11
|
-
<th>Error</th>
|
12
|
-
<th>
|
13
|
-
ActiveJob Params
|
14
|
-
<%= tag.button "Toggle", type: "button", class: "btn btn-sm btn-outline-primary", role: "button",
|
15
|
-
data: { bs_toggle: "collapse", bs_target: ".job-params" },
|
16
|
-
aria: { expanded: false, controls: executions.map { |execution| "##{dom_id(execution, "params")}" }.join(" ") }
|
17
|
-
%>
|
18
|
-
</th>
|
19
|
-
<th>Actions</th>
|
20
|
-
</tr>
|
21
|
-
</thead>
|
22
|
-
<tbody>
|
23
|
-
<% if executions.present? %>
|
24
|
-
<% executions.each do |execution| %>
|
25
|
-
<tr id="<%= dom_id(execution) %>">
|
26
|
-
<td>
|
27
|
-
<%= link_to job_path(execution.serialized_params['job_id']) do %>
|
28
|
-
<code><%= execution.active_job_id %></code>
|
29
|
-
<% end %>
|
30
|
-
</td>
|
31
|
-
<td>
|
32
|
-
<%= link_to job_path(execution.active_job_id, anchor: dom_id(execution)) do %>
|
33
|
-
<code><%= execution.id %></code>
|
34
|
-
<% end %>
|
35
|
-
</td>
|
36
|
-
<td><%= execution.serialized_params['job_class'] %></td>
|
37
|
-
<td><%= execution.queue_name %></td>
|
38
|
-
<td><%= relative_time(execution.scheduled_at || execution.created_at) %></td>
|
39
|
-
<td class="text-break"><%= truncate(execution.error, length: 1_000) %></td>
|
40
|
-
<td>
|
41
|
-
<%= tag.button "Preview", type: "button", class: "btn btn-sm btn-outline-primary", role: "button",
|
42
|
-
data: { bs_toggle: "collapse", bs_target: "##{dom_id(execution, 'params')}" },
|
43
|
-
aria: { expanded: false, controls: dom_id(execution, "params") }
|
44
|
-
%>
|
45
|
-
<%= tag.pre JSON.pretty_generate(execution.serialized_params), id: dom_id(execution, "params"), class: "collapse job-params" %>
|
46
|
-
</td>
|
47
|
-
<td>
|
48
|
-
<%= button_to execution_path(execution.id), method: :delete, class: "btn btn-sm btn-outline-danger", title: "Delete execution", data: { confirm: "Confirm delete" } do %>
|
49
|
-
<%= render_icon "trash" %>
|
50
|
-
<% end %>
|
51
|
-
</td>
|
52
|
-
</tr>
|
53
|
-
<% end %>
|
54
|
-
<% else %>
|
55
|
-
<tr>
|
56
|
-
<td colspan="8" class="py-2 text-center text-muted">No executions found.</td>
|
57
|
-
</tr>
|
58
|
-
<% end %>
|
59
|
-
</tbody>
|
60
|
-
</table>
|
61
|
-
</div>
|
62
|
-
</div>
|