good_job 2.13.0 → 2.14.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 +53 -1
- data/engine/app/assets/good_job/modules/application.js +14 -0
- data/engine/app/assets/good_job/modules/charts.js +29 -0
- data/engine/app/assets/good_job/modules/checkbox_toggle.js +51 -0
- data/engine/app/assets/good_job/modules/document_ready.js +7 -0
- data/engine/app/assets/good_job/modules/poller.js +93 -0
- data/engine/app/assets/good_job/modules/toasts.js +8 -0
- data/engine/app/assets/good_job/scripts.js +3 -0
- data/engine/app/assets/{style.css → good_job/style.css} +4 -0
- data/engine/app/assets/{vendor → good_job/vendor}/bootstrap/bootstrap.bundle.min.js +0 -0
- data/engine/app/assets/{vendor → good_job/vendor}/bootstrap/bootstrap.min.css +0 -0
- data/engine/app/assets/{vendor → good_job/vendor}/chartjs/chart.min.js +0 -0
- data/engine/app/assets/good_job/vendor/es_module_shims.js +1 -0
- data/engine/app/assets/{vendor → good_job/vendor}/rails_ujs.js +0 -0
- data/engine/app/controllers/good_job/assets_controller.rb +23 -6
- data/engine/app/controllers/good_job/executions_controller.rb +1 -5
- data/engine/app/controllers/good_job/jobs_controller.rb +45 -2
- 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/app/views/good_job/shared/_alert.erb +20 -13
- data/engine/app/views/good_job/shared/_navbar.erb +1 -4
- data/engine/app/views/layouts/good_job/application.html.erb +11 -8
- data/engine/config/locales/en.yml +0 -1
- data/engine/config/locales/es.yml +0 -1
- data/engine/config/locales/nl.yml +0 -1
- data/engine/config/locales/ru.yml +0 -1
- data/engine/config/routes.rb +10 -3
- 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 +15 -10
- data/engine/app/assets/scripts.js +0 -133
- data/engine/app/filters/good_job/executions_filter.rb +0 -41
- data/engine/app/views/good_job/executions/index.html.erb +0 -15
@@ -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">
|
@@ -1,13 +1,20 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
1
|
+
<div class="toast-container position-fixed p-3 start-50 translate-middle-x">
|
2
|
+
<% if notice %>
|
3
|
+
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
|
4
|
+
<div class="toast-body d-flex align-items-center gap-2">
|
5
|
+
<%= render "good_job/shared/icons/check", class: "flex-shrink-0 text-success" %>
|
6
|
+
<div class="flex-fill"><%= notice %></div>
|
7
|
+
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
|
8
|
+
</div>
|
9
|
+
</div>
|
10
|
+
<% end %>
|
11
|
+
<% if alert %>
|
12
|
+
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
|
13
|
+
<div class="toast-body d-flex align-items-center gap-2">
|
14
|
+
<%= render "good_job/shared/icons/exclamation", class: "flex-shrink-0 text-danger" %>
|
15
|
+
<div class="flex-fill"><%= alert %></div>
|
16
|
+
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
|
17
|
+
</div>
|
18
|
+
</div>
|
19
|
+
<% end %>
|
20
|
+
</div>
|
@@ -7,9 +7,6 @@
|
|
7
7
|
|
8
8
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
9
9
|
<ul class="navbar-nav me-auto">
|
10
|
-
<li class="nav-item">
|
11
|
-
<%= link_to t(".executions"), root_path, class: ["nav-link", ("active" if controller_name == 'executions')] %>
|
12
|
-
</li>
|
13
10
|
<li class="nav-item">
|
14
11
|
<%= link_to t(".jobs"), jobs_path, class: ["nav-link", ("active" if controller_name == 'jobs')] %>
|
15
12
|
</li>
|
@@ -22,7 +19,7 @@
|
|
22
19
|
</ul>
|
23
20
|
<div class="nav-item pe-2">
|
24
21
|
<div class="form-check">
|
25
|
-
<input type="checkbox" id="toggle-poll" name="toggle-poll"
|
22
|
+
<input type="checkbox" id="toggle-poll" name="toggle-poll" <%= 'checked' if params[:poll].present? %>>
|
26
23
|
<label for="toggle-poll"><%= t(".live_poll") %></label>
|
27
24
|
</div>
|
28
25
|
</div>
|
@@ -7,21 +7,24 @@
|
|
7
7
|
<%= csrf_meta_tags %>
|
8
8
|
<%= csp_meta_tag %>
|
9
9
|
|
10
|
-
<%#
|
11
|
-
<%=
|
12
|
-
<%=
|
10
|
+
<%# Do not use asset tag helpers to avoid paths being overriden by config.asset_host %>
|
11
|
+
<%= tag.link rel: "stylesheet", media: "screen", href: bootstrap_path(format: :css, v: GoodJob::VERSION, locale: nil), nonce: content_security_policy_nonce %>
|
12
|
+
<%= tag.link rel: "stylesheet", media: "screen", href: style_path(format: :css, v: GoodJob::VERSION, locale: nil), nonce: content_security_policy_nonce %>
|
13
13
|
|
14
|
-
<%=
|
15
|
-
<%=
|
16
|
-
<%=
|
14
|
+
<%= tag.script "", src: bootstrap_path(format: :js, v: GoodJob::VERSION, locale: nil), nonce: content_security_policy_nonce %>
|
15
|
+
<%= tag.script "", src: chartjs_path(format: :js, v: GoodJob::VERSION, locale: nil), nonce: content_security_policy_nonce %>
|
16
|
+
<%= tag.script "", src: rails_ujs_path(format: :js, v: GoodJob::VERSION, locale: nil), nonce: content_security_policy_nonce %>
|
17
17
|
|
18
|
-
<%=
|
18
|
+
<%= tag.script "", src: es_module_shims_path(format: :js, v: GoodJob::VERSION, locale: nil), async: true, nonce: content_security_policy_nonce %>
|
19
|
+
<% importmaps = { imports: GoodJob::AssetsController.js_modules.keys.each_with_object({}) { |module_name, imports| imports[module_name] = modules_path(module_name, format: :js, locale: nil, v: GoodJob::VERSION) } } %>
|
20
|
+
<%= tag.script importmaps.to_json.html_safe, type: "importmap", nonce: content_security_policy_nonce %>
|
21
|
+
<%= tag.script "", src: scripts_path(format: :js, v: GoodJob::VERSION, locale: nil), type: "module", nonce: content_security_policy_nonce %>
|
19
22
|
</head>
|
20
23
|
<body>
|
21
24
|
<div class="d-flex flex-column min-vh-100">
|
22
25
|
<%= render "good_job/shared/navbar" %>
|
23
26
|
|
24
|
-
<div class="container-fluid flex-grow-1">
|
27
|
+
<div class="container-fluid flex-grow-1 relative">
|
25
28
|
<%= render "good_job/shared/alert" %>
|
26
29
|
|
27
30
|
<%= yield %>
|
data/engine/config/routes.rb
CHANGED
@@ -1,10 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
GoodJob::Engine.routes.draw do
|
3
|
-
root to: '
|
3
|
+
root to: redirect(path: 'jobs')
|
4
4
|
|
5
5
|
resources :executions, only: %i[destroy]
|
6
6
|
|
7
7
|
resources :jobs, only: %i[index show] do
|
8
|
+
collection do
|
9
|
+
get :mass_update, to: redirect(path: 'jobs')
|
10
|
+
put :mass_update
|
11
|
+
end
|
12
|
+
|
8
13
|
member do
|
9
14
|
put :discard
|
10
15
|
put :reschedule
|
@@ -20,7 +25,7 @@ GoodJob::Engine.routes.draw do
|
|
20
25
|
|
21
26
|
resources :processes, only: %i[index]
|
22
27
|
|
23
|
-
scope controller: :assets do
|
28
|
+
scope :assets, controller: :assets do
|
24
29
|
constraints(format: :css) do
|
25
30
|
get :bootstrap, action: :bootstrap_css
|
26
31
|
get :style, action: :style_css
|
@@ -28,8 +33,10 @@ GoodJob::Engine.routes.draw do
|
|
28
33
|
|
29
34
|
constraints(format: :js) do
|
30
35
|
get :bootstrap, action: :bootstrap_js
|
31
|
-
get :rails_ujs, action: :rails_ujs_js
|
32
36
|
get :chartjs, action: :chartjs_js
|
37
|
+
get :rails_ujs, action: :rails_ujs_js
|
38
|
+
get :es_module_shims, action: :es_module_shims_js
|
39
|
+
get "modules/:module", action: :modules_js, as: :modules
|
33
40
|
get :scripts, action: :scripts_js
|
34
41
|
end
|
35
42
|
end
|
@@ -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
|
@@ -360,12 +360,19 @@ files:
|
|
360
360
|
- CHANGELOG.md
|
361
361
|
- LICENSE.txt
|
362
362
|
- README.md
|
363
|
-
- engine/app/assets/
|
364
|
-
- engine/app/assets/
|
365
|
-
- engine/app/assets/
|
366
|
-
- engine/app/assets/
|
367
|
-
- engine/app/assets/
|
368
|
-
- engine/app/assets/
|
363
|
+
- engine/app/assets/good_job/modules/application.js
|
364
|
+
- engine/app/assets/good_job/modules/charts.js
|
365
|
+
- engine/app/assets/good_job/modules/checkbox_toggle.js
|
366
|
+
- engine/app/assets/good_job/modules/document_ready.js
|
367
|
+
- engine/app/assets/good_job/modules/poller.js
|
368
|
+
- engine/app/assets/good_job/modules/toasts.js
|
369
|
+
- engine/app/assets/good_job/scripts.js
|
370
|
+
- engine/app/assets/good_job/style.css
|
371
|
+
- engine/app/assets/good_job/vendor/bootstrap/bootstrap.bundle.min.js
|
372
|
+
- engine/app/assets/good_job/vendor/bootstrap/bootstrap.min.css
|
373
|
+
- engine/app/assets/good_job/vendor/chartjs/chart.min.js
|
374
|
+
- engine/app/assets/good_job/vendor/es_module_shims.js
|
375
|
+
- engine/app/assets/good_job/vendor/rails_ujs.js
|
369
376
|
- engine/app/charts/good_job/scheduled_by_queue_chart.rb
|
370
377
|
- engine/app/controllers/good_job/application_controller.rb
|
371
378
|
- engine/app/controllers/good_job/assets_controller.rb
|
@@ -374,13 +381,11 @@ files:
|
|
374
381
|
- engine/app/controllers/good_job/jobs_controller.rb
|
375
382
|
- engine/app/controllers/good_job/processes_controller.rb
|
376
383
|
- engine/app/filters/good_job/base_filter.rb
|
377
|
-
- engine/app/filters/good_job/executions_filter.rb
|
378
384
|
- engine/app/filters/good_job/jobs_filter.rb
|
379
385
|
- engine/app/helpers/good_job/application_helper.rb
|
380
386
|
- engine/app/views/good_job/cron_entries/index.html.erb
|
381
387
|
- engine/app/views/good_job/cron_entries/show.html.erb
|
382
388
|
- engine/app/views/good_job/executions/_table.erb
|
383
|
-
- engine/app/views/good_job/executions/index.html.erb
|
384
389
|
- engine/app/views/good_job/jobs/_table.erb
|
385
390
|
- engine/app/views/good_job/jobs/index.html.erb
|
386
391
|
- engine/app/views/good_job/jobs/show.html.erb
|
@@ -1,133 +0,0 @@
|
|
1
|
-
/*jshint esversion: 6, strict: false */
|
2
|
-
const GOOD_JOB_DEFAULT_POLL_INTERVAL_SECONDS = 30;
|
3
|
-
const GOOD_JOB_MINIMUM_POLL_INTERVAL = 1000;
|
4
|
-
|
5
|
-
const GoodJob = {
|
6
|
-
// Register functions to execute when the DOM is ready
|
7
|
-
ready: (callback) => {
|
8
|
-
if (document.readyState !== "loading") {
|
9
|
-
callback();
|
10
|
-
} else {
|
11
|
-
document.addEventListener("DOMContentLoaded", callback);
|
12
|
-
}
|
13
|
-
},
|
14
|
-
|
15
|
-
init: () => {
|
16
|
-
GoodJob.updateSettings();
|
17
|
-
GoodJob.addListeners();
|
18
|
-
GoodJob.pollUpdates();
|
19
|
-
GoodJob.renderCharts(true);
|
20
|
-
},
|
21
|
-
|
22
|
-
addListeners: () => {
|
23
|
-
const gjActionEls = document.querySelectorAll('[data-gj-action]');
|
24
|
-
|
25
|
-
for (let i = 0; i < gjActionEls.length; i++) {
|
26
|
-
const el = gjActionEls[i];
|
27
|
-
const [eventName, func] = el.dataset.gjAction.split('#');
|
28
|
-
|
29
|
-
el.addEventListener(eventName, GoodJob[func]);
|
30
|
-
}
|
31
|
-
},
|
32
|
-
|
33
|
-
updateSettings: () => {
|
34
|
-
const queryString = window.location.search;
|
35
|
-
const urlParams = new URLSearchParams(queryString);
|
36
|
-
|
37
|
-
// live poll interval and enablement
|
38
|
-
if (urlParams.has('poll')) {
|
39
|
-
const parsedInterval = (parseInt(urlParams.get('poll')) || GOOD_JOB_DEFAULT_POLL_INTERVAL_SECONDS) * 1000;
|
40
|
-
GoodJob.pollInterval = Math.max(parsedInterval, GOOD_JOB_MINIMUM_POLL_INTERVAL);
|
41
|
-
GoodJob.setStorage('pollInterval', GoodJob.pollInterval);
|
42
|
-
|
43
|
-
GoodJob.pollEnabled = true;
|
44
|
-
} else {
|
45
|
-
GoodJob.pollInterval = GoodJob.getStorage('pollInterval') || (GOOD_JOB_DEFAULT_POLL_INTERVAL_SECONDS * 1000);
|
46
|
-
GoodJob.pollEnabled = GoodJob.getStorage('pollEnabled') || false;
|
47
|
-
}
|
48
|
-
|
49
|
-
document.getElementById('toggle-poll').checked = GoodJob.pollEnabled;
|
50
|
-
},
|
51
|
-
|
52
|
-
togglePoll: (ev) => {
|
53
|
-
GoodJob.pollEnabled = ev.currentTarget.checked;
|
54
|
-
GoodJob.setStorage('pollEnabled', GoodJob.pollEnabled);
|
55
|
-
},
|
56
|
-
|
57
|
-
pollUpdates: () => {
|
58
|
-
setTimeout(() => {
|
59
|
-
if (GoodJob.pollEnabled === true) {
|
60
|
-
fetch(window.location.href)
|
61
|
-
.then(resp => resp.text())
|
62
|
-
.then(GoodJob.updateContent)
|
63
|
-
.finally(GoodJob.pollUpdates);
|
64
|
-
} else {
|
65
|
-
GoodJob.pollUpdates();
|
66
|
-
}
|
67
|
-
}, GoodJob.pollInterval);
|
68
|
-
},
|
69
|
-
|
70
|
-
updateContent: (newContent) => {
|
71
|
-
const domParser = new DOMParser();
|
72
|
-
const parsedDOM = domParser.parseFromString(newContent, "text/html");
|
73
|
-
|
74
|
-
const newElements = parsedDOM.querySelectorAll('[data-gj-poll-replace]');
|
75
|
-
|
76
|
-
for (let i = 0; i < newElements.length; i++) {
|
77
|
-
const newEl = newElements[i];
|
78
|
-
const oldEl = document.getElementById(newEl.id);
|
79
|
-
|
80
|
-
if (oldEl) {
|
81
|
-
oldEl.replaceWith(newEl);
|
82
|
-
}
|
83
|
-
}
|
84
|
-
|
85
|
-
GoodJob.renderCharts(false);
|
86
|
-
},
|
87
|
-
|
88
|
-
renderCharts: (animate) => {
|
89
|
-
const charts = document.querySelectorAll('.chart');
|
90
|
-
|
91
|
-
for (let i = 0; i < charts.length; i++) {
|
92
|
-
const chartEl = charts[i];
|
93
|
-
const chartData = JSON.parse(chartEl.dataset.json);
|
94
|
-
|
95
|
-
const ctx = chartEl.getContext('2d');
|
96
|
-
const chart = new Chart(ctx, {
|
97
|
-
type: 'line',
|
98
|
-
data: {
|
99
|
-
labels: chartData.labels,
|
100
|
-
datasets: chartData.datasets
|
101
|
-
},
|
102
|
-
options: {
|
103
|
-
animation: animate,
|
104
|
-
responsive: true,
|
105
|
-
maintainAspectRatio: false,
|
106
|
-
scales: {
|
107
|
-
y: {
|
108
|
-
beginAtZero: true
|
109
|
-
}
|
110
|
-
}
|
111
|
-
}
|
112
|
-
});
|
113
|
-
}
|
114
|
-
},
|
115
|
-
|
116
|
-
getStorage: (key) => {
|
117
|
-
const value = localStorage.getItem('good_job-' + key);
|
118
|
-
|
119
|
-
if (value === 'true') {
|
120
|
-
return true;
|
121
|
-
} else if (value === 'false') {
|
122
|
-
return false;
|
123
|
-
} else {
|
124
|
-
return value;
|
125
|
-
}
|
126
|
-
},
|
127
|
-
|
128
|
-
setStorage: (key, value) => {
|
129
|
-
localStorage.setItem('good_job-' + key, value);
|
130
|
-
}
|
131
|
-
};
|
132
|
-
|
133
|
-
GoodJob.ready(GoodJob.init);
|