good_job 2.13.2 → 2.14.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 +26 -4
- data/engine/app/assets/good_job/modules/application.js +3 -1
- data/engine/app/assets/good_job/modules/checkbox_toggle.js +51 -0
- data/engine/app/controllers/good_job/jobs_controller.rb +44 -1
- data/engine/app/filters/good_job/base_filter.rb +3 -0
- data/engine/app/filters/good_job/jobs_filter.rb +5 -2
- data/engine/app/helpers/good_job/application_helper.rb +6 -0
- data/engine/app/views/good_job/executions/_table.erb +1 -1
- data/engine/app/views/good_job/jobs/_table.erb +101 -62
- data/engine/app/views/good_job/jobs/index.html.erb +1 -1
- data/engine/config/routes.rb +5 -0
- data/lib/good_job/active_job_job.rb +9 -7
- data/lib/good_job/lockable.rb +10 -0
- data/lib/good_job/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6f311db9741be3dc51422a0bc3dd6dc3ec3a63cb2e570a8584d4cd2eaeea2565
|
4
|
+
data.tar.gz: 025cb4a2ebc678e3a052edf515d12bfb828dd23ab5d25e71debb9162ff41c34c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4ef6d3f5d653481e1307f1c1f50371471fc7387f64b987d856d2b7bdf104b24d435060d9cb2d4d11fcc81e0808fecd564faf7ee2cc52c10c9c910626990818a9
|
7
|
+
data.tar.gz: 7c414de9a084fb505e8ff7306af76dfe026fce8427462c8c9bb051a82be2a80219685af1c4c37ec220c86f1dbd10c23f1651f611883348018224d9e62286467d
|
data/CHANGELOG.md
CHANGED
@@ -1,10 +1,26 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v2.14.0](https://github.com/bensheldon/good_job/tree/v2.14.0) (2022-04-26)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v2.13.2...v2.14.0)
|
6
|
+
|
7
|
+
**Implemented enhancements:**
|
8
|
+
|
9
|
+
- Add mass update operations for jobs to Dashboard [\#578](https://github.com/bensheldon/good_job/pull/578) ([bensheldon](https://github.com/bensheldon))
|
10
|
+
|
11
|
+
**Closed issues:**
|
12
|
+
|
13
|
+
- Allow "mass"-actions through Dashboard \(e.g. retry all\) [\#446](https://github.com/bensheldon/good_job/issues/446)
|
14
|
+
|
15
|
+
**Merged pull requests:**
|
16
|
+
|
17
|
+
- Track down incompatibility/race condition between JRuby and RSpec mocks in tests [\#581](https://github.com/bensheldon/good_job/pull/581) ([bensheldon](https://github.com/bensheldon))
|
18
|
+
|
3
19
|
## [v2.13.2](https://github.com/bensheldon/good_job/tree/v2.13.2) (2022-04-25)
|
4
20
|
|
5
21
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v2.13.1...v2.13.2)
|
6
22
|
|
7
|
-
**
|
23
|
+
**Fixed bugs:**
|
8
24
|
|
9
25
|
- Namespaces assets per Rails docs [\#580](https://github.com/bensheldon/good_job/pull/580) ([kylekthompson](https://github.com/kylekthompson))
|
10
26
|
|
@@ -12,6 +28,11 @@
|
|
12
28
|
|
13
29
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v2.13.0...v2.13.1)
|
14
30
|
|
31
|
+
**Implemented enhancements:**
|
32
|
+
|
33
|
+
- Dashboard: Use toasts to show notices and alerts [\#577](https://github.com/bensheldon/good_job/pull/577) ([bkeepers](https://github.com/bkeepers))
|
34
|
+
- Remove executions from the dashboard [\#576](https://github.com/bensheldon/good_job/pull/576) ([bkeepers](https://github.com/bkeepers))
|
35
|
+
|
15
36
|
**Fixed bugs:**
|
16
37
|
|
17
38
|
- `ActionMailer::MailDeliveryJob` executing twice [\#329](https://github.com/bensheldon/good_job/issues/329)
|
@@ -26,8 +47,6 @@
|
|
26
47
|
|
27
48
|
**Merged pull requests:**
|
28
49
|
|
29
|
-
- Dashboard: Use toasts to show notices and alerts [\#577](https://github.com/bensheldon/good_job/pull/577) ([bkeepers](https://github.com/bkeepers))
|
30
|
-
- Remove executions from the dashboard [\#576](https://github.com/bensheldon/good_job/pull/576) ([bkeepers](https://github.com/bkeepers))
|
31
50
|
- Use javascript importmaps for Dashboard [\#574](https://github.com/bensheldon/good_job/pull/574) ([bensheldon](https://github.com/bensheldon))
|
32
51
|
|
33
52
|
## [v2.13.0](https://github.com/bensheldon/good_job/tree/v2.13.0) (2022-04-19)
|
@@ -50,10 +69,13 @@
|
|
50
69
|
|
51
70
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v2.12.1...v2.12.2)
|
52
71
|
|
72
|
+
**Fixed bugs:**
|
73
|
+
|
74
|
+
- Un-deprecate Adapter's `execution_mode` argument [\#567](https://github.com/bensheldon/good_job/pull/567) ([bensheldon](https://github.com/bensheldon))
|
75
|
+
|
53
76
|
**Merged pull requests:**
|
54
77
|
|
55
78
|
- Dashboard: added NL translations [\#568](https://github.com/bensheldon/good_job/pull/568) ([eelcoj](https://github.com/eelcoj))
|
56
|
-
- Un-deprecate Adapter's `execution_mode` argument [\#567](https://github.com/bensheldon/good_job/pull/567) ([bensheldon](https://github.com/bensheldon))
|
57
79
|
|
58
80
|
## [v2.12.1](https://github.com/bensheldon/good_job/tree/v2.12.1) (2022-04-18)
|
59
81
|
|
@@ -1,12 +1,14 @@
|
|
1
1
|
/*jshint esversion: 6, strict: false */
|
2
2
|
|
3
|
+
import renderCharts from "charts";
|
4
|
+
import checkboxToggle from "checkbox_toggle";
|
3
5
|
import documentReady from "document_ready";
|
4
6
|
import showToasts from "toasts";
|
5
|
-
import renderCharts from "charts";
|
6
7
|
import Poller from "poller";
|
7
8
|
|
8
9
|
documentReady(function() {
|
9
10
|
renderCharts();
|
10
11
|
showToasts();
|
12
|
+
checkboxToggle();
|
11
13
|
Poller.start();
|
12
14
|
});
|
@@ -0,0 +1,51 @@
|
|
1
|
+
/*jshint esversion: 6, strict: false */
|
2
|
+
|
3
|
+
// How to use:
|
4
|
+
//<form data-checkbox-toggle="{key}">
|
5
|
+
// <input type="checkbox" data-checkbox-toggle-all="{key}" />
|
6
|
+
//
|
7
|
+
// <input type="checkbox" data-checkbox-toggle-each="{key}" />
|
8
|
+
// <input type="checkbox" data-checkbox-toggle-each="{key}" />
|
9
|
+
// ...
|
10
|
+
|
11
|
+
export default function checkboxToggle() {
|
12
|
+
document.querySelectorAll("form[data-checkbox-toggle]").forEach(function (form) {
|
13
|
+
const keyName = form.dataset.checkboxToggle;
|
14
|
+
const checkboxToggle = form.querySelector(`input[type=checkbox][data-checkbox-toggle-all=${keyName}]`);
|
15
|
+
const checkboxes = form.querySelectorAll(`input[type=checkbox][data-checkbox-toggle-each=${keyName}]`);
|
16
|
+
const showables = form.querySelectorAll(`[data-checkbox-toggle-show=${keyName}]`);
|
17
|
+
|
18
|
+
// Check or uncheck all checkboxes
|
19
|
+
checkboxToggle.addEventListener("change", function (event) {
|
20
|
+
checkboxes.forEach(function (checkbox) {
|
21
|
+
checkbox.checked = checkboxToggle.checked;
|
22
|
+
});
|
23
|
+
|
24
|
+
showables.forEach(function (showable) {
|
25
|
+
showable.classList.toggle("d-none", !checkboxToggle.checked);
|
26
|
+
showable.disabled = ! checkboxToggle.checked;
|
27
|
+
});
|
28
|
+
});
|
29
|
+
|
30
|
+
// check or uncheck the "all" checkbox when all checkboxes are checked or unchecked
|
31
|
+
form.addEventListener("change", function (event) {
|
32
|
+
if (!event.target.matches(`input[type=checkbox][data-checkbox-toggle-each=${keyName}]`)) {
|
33
|
+
return;
|
34
|
+
}
|
35
|
+
const checkedCount = Array.from(checkboxes).filter(function (checkbox) {
|
36
|
+
return checkbox.checked;
|
37
|
+
}).length;
|
38
|
+
|
39
|
+
const allChecked = checkedCount === checkboxes.length;
|
40
|
+
const indeterminateChecked = !allChecked && checkedCount > 0;
|
41
|
+
|
42
|
+
checkboxToggle.checked = allChecked;
|
43
|
+
checkboxToggle.indeterminate = indeterminateChecked;
|
44
|
+
|
45
|
+
showables.forEach(function (showable) {
|
46
|
+
showable.classList.toggle("d-none", !allChecked);
|
47
|
+
showable.disabled = !allChecked;
|
48
|
+
});
|
49
|
+
});
|
50
|
+
});
|
51
|
+
}
|
@@ -1,6 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module GoodJob
|
3
3
|
class JobsController < GoodJob::ApplicationController
|
4
|
+
DISCARD_MESSAGE = "Discarded through dashboard"
|
5
|
+
|
6
|
+
ACTIONS = {
|
7
|
+
discard: "discarded",
|
8
|
+
reschedule: "rescheduled",
|
9
|
+
retry: "retried",
|
10
|
+
}.freeze
|
11
|
+
|
4
12
|
rescue_from GoodJob::ActiveJobJob::AdapterNotGoodJobError,
|
5
13
|
GoodJob::ActiveJobJob::ActionForStateMismatchError,
|
6
14
|
with: :redirect_on_error
|
@@ -9,6 +17,41 @@ module GoodJob
|
|
9
17
|
@filter = JobsFilter.new(params)
|
10
18
|
end
|
11
19
|
|
20
|
+
def mass_update
|
21
|
+
mass_action = params.fetch(:mass_action, "").to_sym
|
22
|
+
raise ActionController::BadRequest, "#{mass_action} is not a valid mass action" unless mass_action.in?(ACTIONS.keys)
|
23
|
+
|
24
|
+
jobs = if params[:all_job_ids]
|
25
|
+
ActiveJobJob.all
|
26
|
+
else
|
27
|
+
job_ids = params.fetch(:job_ids, [])
|
28
|
+
ActiveJobJob.where(active_job_id: job_ids)
|
29
|
+
end
|
30
|
+
|
31
|
+
processed_jobs = jobs.map do |job|
|
32
|
+
case mass_action
|
33
|
+
when :discard
|
34
|
+
job.discard_job(DISCARD_MESSAGE)
|
35
|
+
when :reschedule
|
36
|
+
job.reschedule_job
|
37
|
+
when :retry
|
38
|
+
job.retry_job
|
39
|
+
end
|
40
|
+
|
41
|
+
job
|
42
|
+
rescue GoodJob::ActiveJobJob::ActionForStateMismatchError
|
43
|
+
nil
|
44
|
+
end.compact
|
45
|
+
|
46
|
+
notice = if processed_jobs.any?
|
47
|
+
"Successfully #{ACTIONS[mass_action]} #{processed_jobs.count} #{'job'.pluralize(processed_jobs.count)}"
|
48
|
+
else
|
49
|
+
"No jobs were #{ACTIONS[mass_action]}"
|
50
|
+
end
|
51
|
+
|
52
|
+
redirect_to jobs_path, notice: notice
|
53
|
+
end
|
54
|
+
|
12
55
|
def show
|
13
56
|
@executions = GoodJob::Execution.active_job_id(params[:id])
|
14
57
|
.order(Arel.sql("COALESCE(scheduled_at, created_at) DESC"))
|
@@ -17,7 +60,7 @@ module GoodJob
|
|
17
60
|
|
18
61
|
def discard
|
19
62
|
@job = ActiveJobJob.find(params[:id])
|
20
|
-
@job.discard_job(
|
63
|
+
@job.discard_job(DISCARD_MESSAGE)
|
21
64
|
redirect_back(fallback_location: jobs_path, notice: "Job has been discarded")
|
22
65
|
end
|
23
66
|
|
@@ -13,8 +13,7 @@ module GoodJob
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def filtered_query
|
16
|
-
query = base_query.includes(:executions)
|
17
|
-
.joins_advisory_locks.select("#{GoodJob::ActiveJobJob.table_name}.*", 'pg_locks.locktype AS locktype')
|
16
|
+
query = base_query.includes(:executions).includes_advisory_locks
|
18
17
|
|
19
18
|
query = query.job_class(params[:job_class]) if params[:job_class].present?
|
20
19
|
query = query.where(queue_name: params[:queue_name]) if params[:queue_name].present?
|
@@ -40,6 +39,10 @@ module GoodJob
|
|
40
39
|
query
|
41
40
|
end
|
42
41
|
|
42
|
+
def filtered_query_count
|
43
|
+
filtered_query.unscope(:select).count
|
44
|
+
end
|
45
|
+
|
43
46
|
private
|
44
47
|
|
45
48
|
def default_base_query
|
@@ -20,5 +20,11 @@ module GoodJob
|
|
20
20
|
|
21
21
|
content_tag :span, status.to_s, class: classes
|
22
22
|
end
|
23
|
+
|
24
|
+
def render_icon(name)
|
25
|
+
# workaround to render svg icons without all of the log messages
|
26
|
+
partial = lookup_context.find_template("good_job/shared/icons/#{name}", [], true)
|
27
|
+
partial.render(self, {})
|
28
|
+
end
|
23
29
|
end
|
24
30
|
end
|
@@ -46,7 +46,7 @@
|
|
46
46
|
</td>
|
47
47
|
<td>
|
48
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
|
-
<%=
|
49
|
+
<%= render_icon "trash" %>
|
50
50
|
<% end %>
|
51
51
|
</td>
|
52
52
|
</tr>
|
@@ -1,72 +1,111 @@
|
|
1
1
|
<div class="my-3" data-gj-poll-replace id="jobs-table">
|
2
2
|
<div class="table-responsive">
|
3
|
-
|
4
|
-
<
|
5
|
-
<
|
6
|
-
<
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
<%= link_to job_path(job.id) do %>
|
29
|
-
<code><%= job.id %></code>
|
3
|
+
<%= form_with(url: mass_update_jobs_path, method: :put, local: true, data: { "checkbox-toggle": "job_ids" }) do |form| %>
|
4
|
+
<table class="table table-hover table-sm mb-0">
|
5
|
+
<thead>
|
6
|
+
<tr>
|
7
|
+
<th><%= check_box_tag('toggle_job_ids', "1", false, data: { "checkbox-toggle-all": "job_ids" }) %></th>
|
8
|
+
<th>ActiveJob ID</th>
|
9
|
+
<th>State</th>
|
10
|
+
<th>Job Class</th>
|
11
|
+
<th>Queue</th>
|
12
|
+
<th>Scheduled At</th>
|
13
|
+
<th>Executions</th>
|
14
|
+
<th>Error</th>
|
15
|
+
<th>
|
16
|
+
ActiveJob Params
|
17
|
+
<%= tag.button "Toggle", type: "button", class: "btn btn-sm btn-outline-primary", role: "button",
|
18
|
+
data: { bs_toggle: "collapse", bs_target: ".job-params" },
|
19
|
+
aria: { expanded: false, controls: jobs.map { |job| "##{dom_id(job, "params")}" }.join(" ") }
|
20
|
+
%>
|
21
|
+
</th>
|
22
|
+
<th>
|
23
|
+
Actions<br>
|
24
|
+
|
25
|
+
<div class="d-inline text-nowrap">
|
26
|
+
<%= form.button type: 'submit', name: 'mass_action', value: 'reschedule', class: 'btn btn-sm btn-outline-primary', title: "Reschedule all", data: { confirm: "Confirm reschedule all", disable: true } do %>
|
27
|
+
<%= render_icon "skip_forward" %> All
|
30
28
|
<% end %>
|
31
|
-
</td>
|
32
|
-
<td><%= status_badge(job.status) %></td>
|
33
|
-
<td><%= job.job_class %></td>
|
34
|
-
<td><%= job.queue_name %></td>
|
35
|
-
<td><%= relative_time(job.scheduled_at || job.created_at) %></td>
|
36
|
-
<td><%= job.executions_count %></td>
|
37
|
-
<td class="text-break"><%= truncate(job.recent_error, length: 1_000) %></td>
|
38
|
-
<td>
|
39
|
-
<%= tag.button "Preview", type: "button", class: "btn btn-sm btn-outline-primary", role: "button",
|
40
|
-
data: { bs_toggle: "collapse", bs_target: "##{dom_id(job, 'params')}" },
|
41
|
-
aria: { expanded: false, controls: dom_id(job, "params") }
|
42
|
-
%>
|
43
|
-
<%= tag.pre JSON.pretty_generate(job.serialized_params), id: dom_id(job, "params"), class: "collapse job-params" %>
|
44
|
-
</td>
|
45
|
-
<td>
|
46
|
-
<div class="text-nowrap">
|
47
|
-
<% job_reschedulable = job.status.in? [:scheduled, :retried, :queued] %>
|
48
|
-
<%= button_to reschedule_job_path(job.id), method: :put, class: "btn btn-sm #{job_reschedulable ? 'btn-outline-primary' : 'btn-outline-secondary'}", form_class: "d-inline-block", disabled: !job_reschedulable, aria: { label: "Reschedule job" }, title: "Reschedule job", data: { confirm: "Confirm reschedule" } do %>
|
49
|
-
<%= render "good_job/shared/icons/skip_forward" %>
|
50
|
-
<% end %>
|
51
29
|
|
52
|
-
|
53
|
-
<%=
|
54
|
-
|
55
|
-
<% end %>
|
30
|
+
<%= form.button type: 'submit', name: 'mass_action', value: 'discard', class: 'btn btn-sm btn-outline-primary', title: "Discard all", data: { confirm: "Confirm discard all", disable: true } do %>
|
31
|
+
<%= render_icon "stop" %> All
|
32
|
+
<% end %>
|
56
33
|
|
57
|
-
|
58
|
-
|
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
|
+
<%= render_icon "arrow_clockwise" %> All
|
36
|
+
<% end %>
|
37
|
+
</div>
|
38
|
+
</tr>
|
39
|
+
<tr class="d-none" data-checkbox-toggle-show="job_ids">
|
40
|
+
<td class="text-center table-warning" colspan="10">
|
41
|
+
<% all_jobs_count = local_assigns[:all_jobs_count] %>
|
42
|
+
<label>
|
43
|
+
<%= check_box_tag "all_job_ids", 1, false, disabled: true, data: { "checkbox-toggle-show": "job_ids"} %>
|
44
|
+
Apply to all <%= all_jobs_count.present? ? number_with_delimiter(all_jobs_count) : "" %> <%= "job".pluralize(all_jobs_count || 99) %>.
|
45
|
+
<em>This could be a lot.</em>
|
46
|
+
</label>
|
47
|
+
</td>
|
48
|
+
</tr>
|
49
|
+
</thead>
|
50
|
+
<tbody>
|
51
|
+
<% if jobs.present? %>
|
52
|
+
<% jobs.each do |job| %>
|
53
|
+
<tr class="<%= dom_class(job) %>" id="<%= dom_id(job) %>">
|
54
|
+
<td><%= check_box_tag 'job_ids[]', job.id, false, data: { "checkbox-toggle-each": "job_ids" } %></td>
|
55
|
+
<td>
|
56
|
+
<%= link_to job_path(job.id) do %>
|
57
|
+
<code><%= job.id %></code>
|
59
58
|
<% end %>
|
60
|
-
</
|
61
|
-
|
59
|
+
</td>
|
60
|
+
<td><%= status_badge(job.status) %></td>
|
61
|
+
<td><%= job.job_class %></td>
|
62
|
+
<td><%= job.queue_name %></td>
|
63
|
+
<td><%= relative_time(job.scheduled_at || job.created_at) %></td>
|
64
|
+
<td><%= job.executions_count %></td>
|
65
|
+
<td class="text-break"><%= truncate(job.recent_error, length: 1_000) %></td>
|
66
|
+
<td>
|
67
|
+
<%= tag.button "Preview", type: "button", class: "btn btn-sm btn-outline-primary", role: "button",
|
68
|
+
data: { bs_toggle: "collapse", bs_target: "##{dom_id(job, 'params')}" },
|
69
|
+
aria: { expanded: false, controls: dom_id(job, "params") }
|
70
|
+
%>
|
71
|
+
<%= tag.pre JSON.pretty_generate(job.serialized_params), id: dom_id(job, "params"), class: "collapse job-params" %>
|
72
|
+
</td>
|
73
|
+
<td>
|
74
|
+
<div class="text-nowrap">
|
75
|
+
<% if job.status.in? [:scheduled, :retried, :queued] %>
|
76
|
+
<%= link_to reschedule_job_path(job.id), method: :put, class: "btn btn-sm btn-outline-primary", title: "Reschedule job", data: { confirm: "Confirm reschedule", disable: true } do %>
|
77
|
+
<%= render_icon "skip_forward" %>
|
78
|
+
<% end %>
|
79
|
+
<% else %>
|
80
|
+
<button class="btn btn-sm btn-outline-secondary" disabled><%= render_icon "skip_forward" %></button>
|
81
|
+
<% end %>
|
82
|
+
|
83
|
+
<% if job.status.in? [:scheduled, :retried, :queued] %>
|
84
|
+
<%= link_to discard_job_path(job.id), method: :put, class: "btn btn-sm btn-outline-primary", title: "Discard job", data: { confirm: "Confirm discard", disable: true } do %>
|
85
|
+
<%= render_icon "stop" %>
|
86
|
+
<% end %>
|
87
|
+
<% else %>
|
88
|
+
<button class="btn btn-sm btn-outline-secondary" disabled><%= render_icon "stop" %></button>
|
89
|
+
<% end %>
|
90
|
+
|
91
|
+
<% if job.status == :discarded %>
|
92
|
+
<%= link_to retry_job_path(job.id), method: :put, class: "btn btn-sm btn-outline-primary", title: "Retry job", data: { confirm: "Confirm retry", disable: true } do %>
|
93
|
+
<%= render_icon "arrow_clockwise" %>
|
94
|
+
<% end %>
|
95
|
+
<% else %>
|
96
|
+
<button class="btn btn-sm btn-outline-secondary" disabled><%= render_icon "arrow_clockwise" %></button>
|
97
|
+
<% end %>
|
98
|
+
</div>
|
99
|
+
</td>
|
100
|
+
</tr>
|
101
|
+
<% end %>
|
102
|
+
<% else %>
|
103
|
+
<tr>
|
104
|
+
<td colspan="10" class="py-2 text-center text-muted">No jobs found.</td>
|
62
105
|
</tr>
|
63
106
|
<% end %>
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
</tr>
|
68
|
-
<% end %>
|
69
|
-
</tbody>
|
70
|
-
</table>
|
107
|
+
</tbody>
|
108
|
+
</table>
|
109
|
+
<% end %>
|
71
110
|
</div>
|
72
111
|
</div>
|
@@ -1,6 +1,6 @@
|
|
1
1
|
<%= render 'good_job/shared/filter', title: "Jobs", filter: @filter %>
|
2
2
|
<%= render 'good_job/shared/chart', chart_data: GoodJob::ScheduledByQueueChart.new(@filter).data %>
|
3
|
-
<%= render 'good_job/jobs/table', jobs: @filter.records %>
|
3
|
+
<%= render 'good_job/jobs/table', jobs: @filter.records, all_jobs_count: @filter.filtered_query_count %>
|
4
4
|
|
5
5
|
<% if @filter.records.present? %>
|
6
6
|
<nav aria-label="Job pagination" class="mt-3" data-gj-poll-replace id="jobs-pagination">
|
data/engine/config/routes.rb
CHANGED
@@ -148,7 +148,7 @@ module GoodJob
|
|
148
148
|
# Tests whether the job is being executed right now.
|
149
149
|
# @return [Boolean]
|
150
150
|
def running?
|
151
|
-
# Avoid N+1 Query: `.
|
151
|
+
# Avoid N+1 Query: `.includes_advisory_locks`
|
152
152
|
if has_attribute?(:locktype)
|
153
153
|
self['locktype'].present?
|
154
154
|
else
|
@@ -157,7 +157,7 @@ module GoodJob
|
|
157
157
|
end
|
158
158
|
|
159
159
|
# Retry a job that has errored and been discarded.
|
160
|
-
# This action will create a new
|
160
|
+
# This action will create a new {Execution} record for the job.
|
161
161
|
# @return [ActiveJob::Base]
|
162
162
|
def retry_job
|
163
163
|
with_advisory_lock do
|
@@ -165,7 +165,7 @@ module GoodJob
|
|
165
165
|
active_job = execution.active_job
|
166
166
|
|
167
167
|
raise AdapterNotGoodJobError unless active_job.class.queue_adapter.is_a? GoodJob::Adapter
|
168
|
-
raise ActionForStateMismatchError
|
168
|
+
raise ActionForStateMismatchError if execution.finished_at.blank? || execution.error.blank?
|
169
169
|
|
170
170
|
# Update the executions count because the previous execution will not have been preserved
|
171
171
|
# Do not update `exception_executions` because that comes from rescue_from's arguments
|
@@ -176,7 +176,7 @@ module GoodJob
|
|
176
176
|
current_thread.execution = execution
|
177
177
|
|
178
178
|
execution.class.transaction(joinable: false, requires_new: true) do
|
179
|
-
new_active_job = active_job.retry_job(wait: 0, error: error)
|
179
|
+
new_active_job = active_job.retry_job(wait: 0, error: execution.error)
|
180
180
|
execution.save
|
181
181
|
end
|
182
182
|
end
|
@@ -189,11 +189,11 @@ module GoodJob
|
|
189
189
|
# @return [void]
|
190
190
|
def discard_job(message)
|
191
191
|
with_advisory_lock do
|
192
|
-
raise ActionForStateMismatchError unless status.in? [:scheduled, :queued, :retried]
|
193
|
-
|
194
192
|
execution = head_execution(reload: true)
|
195
193
|
active_job = execution.active_job
|
196
194
|
|
195
|
+
raise ActionForStateMismatchError if execution.finished_at.present?
|
196
|
+
|
197
197
|
job_error = GoodJob::ActiveJobJob::DiscardJobError.new(message)
|
198
198
|
|
199
199
|
update_execution = proc do
|
@@ -216,7 +216,9 @@ module GoodJob
|
|
216
216
|
# @return [void]
|
217
217
|
def reschedule_job(scheduled_at = Time.current)
|
218
218
|
with_advisory_lock do
|
219
|
-
|
219
|
+
execution = head_execution(reload: true)
|
220
|
+
|
221
|
+
raise ActionForStateMismatchError if execution.finished_at.present?
|
220
222
|
|
221
223
|
execution = head_execution(reload: true)
|
222
224
|
execution.update(scheduled_at: scheduled_at)
|
data/lib/good_job/lockable.rb
CHANGED
@@ -82,6 +82,16 @@ module GoodJob
|
|
82
82
|
joins(sanitize_sql_for_conditions([join_sql, { table_name: table_name }]))
|
83
83
|
end)
|
84
84
|
|
85
|
+
# Joins the current query with Postgres's +pg_locks+ table AND SELECTs the resulting columns
|
86
|
+
# @!method joins_advisory_locks(column: _advisory_lockable_column)
|
87
|
+
# @!scope class
|
88
|
+
# @param column [String, Symbol] column values to Advisory Lock against
|
89
|
+
# @return [ActiveRecord::Relation]
|
90
|
+
scope :includes_advisory_locks, (lambda do |column: _advisory_lockable_column|
|
91
|
+
owns_advisory_lock_sql = "#{connection.quote_table_name('pg_locks')}.#{connection.quote_column_name('pid')} = pg_backend_pid() AS owns_advisory_lock"
|
92
|
+
joins_advisory_locks(column: column).select("#{quoted_table_name}.*, #{connection.quote_table_name('pg_locks')}.locktype, #{owns_advisory_lock_sql}")
|
93
|
+
end)
|
94
|
+
|
85
95
|
# Find records that do not have an advisory lock on them.
|
86
96
|
# @!method advisory_unlocked(column: _advisory_lockable_column)
|
87
97
|
# @!scope class
|
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: 2.
|
4
|
+
version: 2.14.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-04-
|
11
|
+
date: 2022-04-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activejob
|
@@ -362,6 +362,7 @@ files:
|
|
362
362
|
- README.md
|
363
363
|
- engine/app/assets/good_job/modules/application.js
|
364
364
|
- engine/app/assets/good_job/modules/charts.js
|
365
|
+
- engine/app/assets/good_job/modules/checkbox_toggle.js
|
365
366
|
- engine/app/assets/good_job/modules/document_ready.js
|
366
367
|
- engine/app/assets/good_job/modules/poller.js
|
367
368
|
- engine/app/assets/good_job/modules/toasts.js
|