good_job 2.2.0 → 2.4.1
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 +73 -0
- data/README.md +39 -16
- data/engine/app/controllers/good_job/base_controller.rb +8 -0
- data/engine/app/controllers/good_job/cron_schedules_controller.rb +1 -2
- data/engine/app/controllers/good_job/executions_controller.rb +5 -1
- data/engine/app/controllers/good_job/{active_jobs_controller.rb → jobs_controller.rb} +6 -2
- data/engine/app/filters/good_job/base_filter.rb +101 -0
- data/engine/app/filters/good_job/executions_filter.rb +40 -0
- data/engine/app/filters/good_job/jobs_filter.rb +46 -0
- data/engine/app/helpers/good_job/application_helper.rb +4 -0
- data/engine/app/models/good_job/active_job_job.rb +127 -0
- data/engine/app/views/good_job/cron_schedules/index.html.erb +51 -7
- data/engine/app/views/good_job/executions/index.html.erb +21 -0
- data/engine/app/views/good_job/jobs/index.html.erb +7 -0
- data/engine/app/views/good_job/{active_jobs → jobs}/show.html.erb +0 -0
- data/engine/app/views/good_job/shared/_executions_table.erb +3 -3
- data/engine/app/views/good_job/shared/_filter.erb +52 -0
- data/engine/app/views/good_job/shared/_jobs_table.erb +56 -0
- data/engine/app/views/layouts/good_job/base.html.erb +5 -1
- data/engine/config/routes.rb +2 -2
- data/lib/good_job/adapter.rb +6 -4
- data/lib/good_job/cli.rb +3 -1
- data/lib/good_job/configuration.rb +4 -0
- data/lib/good_job/cron_entry.rb +65 -0
- data/lib/good_job/cron_manager.rb +18 -30
- data/lib/good_job/log_subscriber.rb +3 -3
- data/lib/good_job/scheduler.rb +2 -1
- data/lib/good_job/version.rb +1 -1
- metadata +13 -6
- data/engine/app/controllers/good_job/dashboards_controller.rb +0 -107
- data/engine/app/views/good_job/dashboards/index.html.erb +0 -54
@@ -0,0 +1,21 @@
|
|
1
|
+
<div class="card my-3 p-6">
|
2
|
+
<%= render 'good_job/shared/chart', chart_data: @filter.chart_data %>
|
3
|
+
</div>
|
4
|
+
|
5
|
+
<%= render 'good_job/shared/filter', filter: @filter %>
|
6
|
+
|
7
|
+
<% if @filter.records.present? %>
|
8
|
+
<%= render 'good_job/shared/executions_table', executions: @filter.records %>
|
9
|
+
|
10
|
+
<nav aria-label="Job pagination" class="mt-3">
|
11
|
+
<ul class="pagination">
|
12
|
+
<li class="page-item">
|
13
|
+
<%= link_to({ after_scheduled_at: (@filter.last.scheduled_at || @filter.last.created_at), after_id: @filter.last.id }, class: "page-link") do %>
|
14
|
+
Older executions <span aria-hidden="true">»</span>
|
15
|
+
<% end %>
|
16
|
+
</li>
|
17
|
+
</ul>
|
18
|
+
</nav>
|
19
|
+
<% else %>
|
20
|
+
<em>No executions present.</em>
|
21
|
+
<% end %>
|
File without changes
|
@@ -23,18 +23,18 @@
|
|
23
23
|
<% executions.each do |execution| %>
|
24
24
|
<tr id="<%= dom_id(execution) %>">
|
25
25
|
<td>
|
26
|
-
<%= link_to
|
26
|
+
<%= link_to job_path(execution.serialized_params['job_id']) do %>
|
27
27
|
<code><%= execution.active_job_id %></code>
|
28
28
|
<% end %>
|
29
29
|
</td>
|
30
30
|
<td>
|
31
|
-
<%= link_to
|
31
|
+
<%= link_to job_path(execution.active_job_id, anchor: dom_id(execution)) do %>
|
32
32
|
<code><%= execution.id %></code>
|
33
33
|
<% end %>
|
34
34
|
</td>
|
35
35
|
<td><%= execution.serialized_params['job_class'] %></td>
|
36
36
|
<td><%= execution.queue_name %></td>
|
37
|
-
<td><%= execution.scheduled_at || execution.created_at %></td>
|
37
|
+
<td><%= relative_time(execution.scheduled_at || execution.created_at) %></td>
|
38
38
|
<td class="text-break"><%= truncate(execution.error, length: 1_000) %></td>
|
39
39
|
<td>
|
40
40
|
<%= tag.button "Preview", type: "button", class: "btn btn-sm btn-outline-primary", role: "button",
|
@@ -0,0 +1,52 @@
|
|
1
|
+
<div class='card mb-2'>
|
2
|
+
<div class='card-body d-flex flex-wrap'>
|
3
|
+
|
4
|
+
<div class='me-4'>
|
5
|
+
<small>Filter by job class</small>
|
6
|
+
<br>
|
7
|
+
<% @filter.job_classes.each do |name, count| %>
|
8
|
+
<% if params[:job_class] == name %>
|
9
|
+
<%= link_to(@filter.to_params(job_class: nil), class: 'btn btn-sm btn-outline-secondary active', role: "button", "aria-pressed": true) do %>
|
10
|
+
<%= name %> (<%= count %>)
|
11
|
+
<% end %>
|
12
|
+
<% else %>
|
13
|
+
<%= link_to(@filter.to_params(job_class: name), class: 'btn btn-sm btn-outline-secondary', role: "button") do %>
|
14
|
+
<%= name %> (<%= count %>)
|
15
|
+
<% end %>
|
16
|
+
<% end %>
|
17
|
+
<% end %>
|
18
|
+
</div>
|
19
|
+
|
20
|
+
<div class='me-4'>
|
21
|
+
<small>Filter by state</small>
|
22
|
+
<br>
|
23
|
+
<% @filter.states.each do |name, count| %>
|
24
|
+
<% if params[:state] == name %>
|
25
|
+
<%= link_to(@filter.to_params(state: nil), class: 'btn btn-sm btn-outline-secondary active', role: "button", "aria-pressed": true) do %>
|
26
|
+
<%= name %> (<%= count %>)
|
27
|
+
<% end %>
|
28
|
+
<% else %>
|
29
|
+
<%= link_to(@filter.to_params(state: name), class: 'btn btn-sm btn-outline-secondary', role: "button") do %>
|
30
|
+
<%= name %> (<%= count %>)
|
31
|
+
<% end %>
|
32
|
+
<% end %>
|
33
|
+
<% end %>
|
34
|
+
</div>
|
35
|
+
|
36
|
+
<div>
|
37
|
+
<small>Filter by queue</small>
|
38
|
+
<br>
|
39
|
+
<% @filter.queues.each do |name, count| %>
|
40
|
+
<% if params[:queue_name] == name %>
|
41
|
+
<%= link_to(@filter.to_params(queue_name: nil), class: 'btn btn-sm btn-outline-secondary active', role: "button", "aria-pressed": true) do %>
|
42
|
+
<%= name %> (<%= count %>)
|
43
|
+
<% end %>
|
44
|
+
<% else %>
|
45
|
+
<%= link_to(@filter.to_params(queue_name: name), class: 'btn btn-sm btn-outline-secondary', role: "button") do %>
|
46
|
+
<%= name %> (<%= count %>)
|
47
|
+
<% end %>
|
48
|
+
<% end %>
|
49
|
+
<% end %>
|
50
|
+
</div>
|
51
|
+
</div>
|
52
|
+
</div>
|
@@ -0,0 +1,56 @@
|
|
1
|
+
<div class="card my-3">
|
2
|
+
<div class="table-responsive">
|
3
|
+
<table class="table card-table table-bordered table-hover table-sm mb-0">
|
4
|
+
<thead>
|
5
|
+
<tr>
|
6
|
+
<th>ActiveJob ID</th>
|
7
|
+
<th>State</th>
|
8
|
+
<th>Job Class</th>
|
9
|
+
<th>Queue</th>
|
10
|
+
<th>Scheduled At</th>
|
11
|
+
<th>Executions</th>
|
12
|
+
<th>Error</th>
|
13
|
+
<th>
|
14
|
+
ActiveJob Params
|
15
|
+
<%= tag.button "Toggle", type: "button", class: "btn btn-sm btn-outline-primary", role: "button",
|
16
|
+
data: { bs_toggle: "collapse", bs_target: ".job-params" },
|
17
|
+
aria: { expanded: false, controls: jobs.map { |job| "##{dom_id(job, "params")}" }.join(" ") }
|
18
|
+
%>
|
19
|
+
</th>
|
20
|
+
<th>Actions</th>
|
21
|
+
</tr>
|
22
|
+
</thead>
|
23
|
+
<tbody>
|
24
|
+
<% jobs.each do |job| %>
|
25
|
+
<tr id="<%= dom_id(job) %>">
|
26
|
+
<td>
|
27
|
+
<%= link_to job_path(job.id) do %>
|
28
|
+
<code><%= job.id %></code>
|
29
|
+
<% end %>
|
30
|
+
</td>
|
31
|
+
<td>
|
32
|
+
<span class="badge bg-secondary"><%= job.status %></span>
|
33
|
+
</td>
|
34
|
+
<td><%= job.job_class %></td>
|
35
|
+
<td><%= job.queue_name %></td>
|
36
|
+
<td><%= relative_time(job.scheduled_at || job.created_at) %></td>
|
37
|
+
<td><%= job.executions_count %></td>
|
38
|
+
<td class="text-break"><%= truncate(job.recent_error, length: 1_000) %></td>
|
39
|
+
<td>
|
40
|
+
<%= tag.button "Preview", type: "button", class: "btn btn-sm btn-outline-primary", role: "button",
|
41
|
+
data: { bs_toggle: "collapse", bs_target: "##{dom_id(job, 'params')}" },
|
42
|
+
aria: { expanded: false, controls: dom_id(job, "params") }
|
43
|
+
%>
|
44
|
+
<%= tag.pre JSON.pretty_generate(job.serialized_params), id: dom_id(job, "params"), class: "collapse job-params" %>
|
45
|
+
</td>
|
46
|
+
<!-- <td>-->
|
47
|
+
<%#= button_to execution_path(execution.id), method: :delete, class: "btn btn-sm btn-outline-danger", title: "Delete execution" do %>
|
48
|
+
<%#= render "good_job/shared/icons/trash" %>
|
49
|
+
<%# end %>
|
50
|
+
<!-- </td>-->
|
51
|
+
</tr>
|
52
|
+
<% end %>
|
53
|
+
</tbody>
|
54
|
+
</table>
|
55
|
+
</div>
|
56
|
+
</div>
|
@@ -25,6 +25,9 @@
|
|
25
25
|
<li class="nav-item">
|
26
26
|
<%= link_to "All Executions", root_path, class: ["nav-link", ("active" if current_page?(root_path))] %>
|
27
27
|
</li>
|
28
|
+
<li class="nav-item">
|
29
|
+
<%= link_to "All Jobs", jobs_path, class: ["nav-link", ("active" if current_page?(jobs_path))] %>
|
30
|
+
</li>
|
28
31
|
<li class="nav-item">
|
29
32
|
<%= link_to "Cron Schedules", cron_schedules_path, class: ["nav-link", ("active" if current_page?(cron_schedules_path))] %>
|
30
33
|
</li>
|
@@ -46,6 +49,7 @@
|
|
46
49
|
</li>
|
47
50
|
-->
|
48
51
|
</ul>
|
52
|
+
<div class="text-muted" title="Now is <%= Time.current %>">Times are displayed in <%= Time.current.zone %> timezone</div>
|
49
53
|
</div>
|
50
54
|
</div>
|
51
55
|
</nav>
|
@@ -65,7 +69,7 @@
|
|
65
69
|
</div>
|
66
70
|
<% elsif alert %>
|
67
71
|
<div class="alert alert-warning alert-dismissible fade show d-flex align-items-center offset-md-3 col-6" role="alert">
|
68
|
-
<%= render "good_job/shared/icons/
|
72
|
+
<%= render "good_job/shared/icons/exclamation", class: "flex-shrink-0 me-2" %>
|
69
73
|
<div><%= alert %></div>
|
70
74
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
71
75
|
</div>
|
data/engine/config/routes.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
GoodJob::Engine.routes.draw do
|
3
|
-
root to: '
|
3
|
+
root to: 'executions#index'
|
4
4
|
resources :cron_schedules, only: %i[index]
|
5
|
-
resources :
|
5
|
+
resources :jobs, only: %i[index show]
|
6
6
|
resources :executions, only: %i[destroy]
|
7
7
|
|
8
8
|
scope controller: :assets do
|
data/lib/good_job/adapter.rb
CHANGED
@@ -38,7 +38,7 @@ module GoodJob
|
|
38
38
|
@notifier.recipients << [@scheduler, :create_thread]
|
39
39
|
@poller.recipients << [@scheduler, :create_thread]
|
40
40
|
|
41
|
-
@cron_manager = GoodJob::CronManager.new(@configuration.
|
41
|
+
@cron_manager = GoodJob::CronManager.new(@configuration.cron_entries, start_on_initialize: Rails.application.initialized?) if @configuration.enable_cron?
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
@@ -64,10 +64,12 @@ module GoodJob
|
|
64
64
|
|
65
65
|
if execute_inline?
|
66
66
|
begin
|
67
|
-
execution.perform
|
67
|
+
result = execution.perform
|
68
68
|
ensure
|
69
69
|
execution.advisory_unlock
|
70
70
|
end
|
71
|
+
|
72
|
+
raise result.unhandled_error if result.unhandled_error
|
71
73
|
else
|
72
74
|
job_state = { queue_name: execution.queue_name }
|
73
75
|
job_state[:scheduled_at] = execution.scheduled_at if execution.scheduled_at
|
@@ -101,14 +103,14 @@ module GoodJob
|
|
101
103
|
# @return [Boolean]
|
102
104
|
def execute_async?
|
103
105
|
@configuration.execution_mode == :async_all ||
|
104
|
-
@configuration.execution_mode.in?([:async, :async_server]) && in_server_process?
|
106
|
+
(@configuration.execution_mode.in?([:async, :async_server]) && in_server_process?)
|
105
107
|
end
|
106
108
|
|
107
109
|
# Whether in +:external+ execution mode.
|
108
110
|
# @return [Boolean]
|
109
111
|
def execute_externally?
|
110
112
|
@configuration.execution_mode == :external ||
|
111
|
-
@configuration.execution_mode.in?([:async, :async_server]) && !in_server_process?
|
113
|
+
(@configuration.execution_mode.in?([:async, :async_server]) && !in_server_process?)
|
112
114
|
end
|
113
115
|
|
114
116
|
# Whether in +:inline+ execution mode.
|
data/lib/good_job/cli.rb
CHANGED
@@ -91,7 +91,9 @@ module GoodJob
|
|
91
91
|
scheduler = GoodJob::Scheduler.from_configuration(configuration, warm_cache_on_initialize: true)
|
92
92
|
notifier.recipients << [scheduler, :create_thread]
|
93
93
|
poller.recipients << [scheduler, :create_thread]
|
94
|
-
|
94
|
+
|
95
|
+
cron_manager = GoodJob::CronManager.new(configuration.cron_entries, start_on_initialize: true) if configuration.enable_cron?
|
96
|
+
|
95
97
|
@stop_good_job_executable = false
|
96
98
|
%w[INT TERM].each do |signal|
|
97
99
|
trap(signal) { @stop_good_job_executable = true }
|
@@ -165,6 +165,10 @@ module GoodJob
|
|
165
165
|
{}
|
166
166
|
end
|
167
167
|
|
168
|
+
def cron_entries
|
169
|
+
cron.map { |cron_key, params| GoodJob::CronEntry.new(params.merge(key: cron_key)) }
|
170
|
+
end
|
171
|
+
|
168
172
|
# Number of seconds to preserve jobs when using the +good_job cleanup_preserved_jobs+ CLI command.
|
169
173
|
# This configuration is only used when {GoodJob.preserve_job_records} is +true+.
|
170
174
|
# @return [Integer]
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "concurrent/hash"
|
3
|
+
require "concurrent/scheduled_task"
|
4
|
+
require "fugit"
|
5
|
+
|
6
|
+
module GoodJob # :nodoc:
|
7
|
+
#
|
8
|
+
# A CronEntry represents a single scheduled item's properties.
|
9
|
+
#
|
10
|
+
class CronEntry
|
11
|
+
include ActiveModel::Model
|
12
|
+
|
13
|
+
attr_reader :params
|
14
|
+
|
15
|
+
def initialize(params = {})
|
16
|
+
@params = params.with_indifferent_access
|
17
|
+
end
|
18
|
+
|
19
|
+
def key
|
20
|
+
params.fetch(:key)
|
21
|
+
end
|
22
|
+
alias id key
|
23
|
+
|
24
|
+
def job_class
|
25
|
+
params.fetch(:class)
|
26
|
+
end
|
27
|
+
|
28
|
+
def cron
|
29
|
+
params.fetch(:cron)
|
30
|
+
end
|
31
|
+
|
32
|
+
def set
|
33
|
+
params[:set]
|
34
|
+
end
|
35
|
+
|
36
|
+
def args
|
37
|
+
params[:args]
|
38
|
+
end
|
39
|
+
|
40
|
+
def description
|
41
|
+
params[:description]
|
42
|
+
end
|
43
|
+
|
44
|
+
def next_at
|
45
|
+
fugit = Fugit::Cron.parse(cron)
|
46
|
+
fugit.next_time
|
47
|
+
end
|
48
|
+
|
49
|
+
def enqueue
|
50
|
+
job_class.constantize.set(set_value).perform_later(*args_value)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def set_value
|
56
|
+
value = set || {}
|
57
|
+
value.respond_to?(:call) ? value.call : value
|
58
|
+
end
|
59
|
+
|
60
|
+
def args_value
|
61
|
+
value = args || []
|
62
|
+
value.respond_to?(:call) ? value.call : value
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -11,7 +11,7 @@ module GoodJob # :nodoc:
|
|
11
11
|
# @!attribute [r] instances
|
12
12
|
# @!scope class
|
13
13
|
# List of all instantiated CronManagers in the current process.
|
14
|
-
# @return [Array<GoodJob::
|
14
|
+
# @return [Array<GoodJob::CronManager>, nil]
|
15
15
|
cattr_reader :instances, default: [], instance_reader: false
|
16
16
|
|
17
17
|
# Task observer for cron task
|
@@ -26,13 +26,13 @@ module GoodJob # :nodoc:
|
|
26
26
|
|
27
27
|
# Execution configuration to be scheduled
|
28
28
|
# @return [Hash]
|
29
|
-
attr_reader :
|
29
|
+
attr_reader :cron_entries
|
30
30
|
|
31
|
-
# @param
|
31
|
+
# @param cron_entries [Array<CronEntry>]
|
32
32
|
# @param start_on_initialize [Boolean]
|
33
|
-
def initialize(
|
33
|
+
def initialize(cron_entries = [], start_on_initialize: false)
|
34
34
|
@running = false
|
35
|
-
@
|
35
|
+
@cron_entries = cron_entries
|
36
36
|
@tasks = Concurrent::Hash.new
|
37
37
|
|
38
38
|
self.class.instances << self
|
@@ -42,9 +42,11 @@ module GoodJob # :nodoc:
|
|
42
42
|
|
43
43
|
# Schedule tasks that will enqueue jobs based on their schedule
|
44
44
|
def start
|
45
|
-
ActiveSupport::Notifications.instrument("cron_manager_start.good_job",
|
45
|
+
ActiveSupport::Notifications.instrument("cron_manager_start.good_job", cron_entries: cron_entries) do
|
46
46
|
@running = true
|
47
|
-
|
47
|
+
cron_entries.each do |cron_entry|
|
48
|
+
create_task(cron_entry)
|
49
|
+
end
|
48
50
|
end
|
49
51
|
end
|
50
52
|
|
@@ -78,36 +80,22 @@ module GoodJob # :nodoc:
|
|
78
80
|
end
|
79
81
|
|
80
82
|
# Enqueues a scheduled task
|
81
|
-
# @param
|
82
|
-
def create_task(
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
fugit = Fugit::Cron.parse(schedule.fetch(:cron))
|
87
|
-
delay = [(fugit.next_time - Time.current).to_f, 0].max
|
88
|
-
|
89
|
-
future = Concurrent::ScheduledTask.new(delay, args: [self, cron_key]) do |thr_scheduler, thr_cron_key|
|
83
|
+
# @param cron_entry [CronEntry] the CronEntry object to schedule
|
84
|
+
def create_task(cron_entry)
|
85
|
+
delay = [(cron_entry.next_at - Time.current).to_f, 0].max
|
86
|
+
future = Concurrent::ScheduledTask.new(delay, args: [self, cron_entry]) do |thr_scheduler, thr_cron_entry|
|
90
87
|
# Re-schedule the next cron task before executing the current task
|
91
|
-
thr_scheduler.create_task(
|
92
|
-
|
93
|
-
CurrentThread.reset
|
94
|
-
CurrentThread.cron_key = thr_cron_key
|
88
|
+
thr_scheduler.create_task(thr_cron_entry)
|
95
89
|
|
96
90
|
Rails.application.executor.wrap do
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
job_set_value = schedule.fetch(:set, {})
|
101
|
-
job_set = job_set_value.respond_to?(:call) ? job_set_value.call : job_set_value
|
102
|
-
|
103
|
-
job_args_value = schedule.fetch(:args, [])
|
104
|
-
job_args = job_args_value.respond_to?(:call) ? job_args_value.call : job_args_value
|
91
|
+
CurrentThread.reset
|
92
|
+
CurrentThread.cron_key = thr_cron_entry.key
|
105
93
|
|
106
|
-
|
94
|
+
cron_entry.enqueue
|
107
95
|
end
|
108
96
|
end
|
109
97
|
|
110
|
-
@tasks[
|
98
|
+
@tasks[cron_entry.key] = future
|
111
99
|
future.add_observer(self.class, :task_observer)
|
112
100
|
future.execute
|
113
101
|
end
|
@@ -59,11 +59,11 @@ module GoodJob
|
|
59
59
|
|
60
60
|
# @!macro notification_responder
|
61
61
|
def cron_manager_start(event)
|
62
|
-
|
63
|
-
cron_jobs_count =
|
62
|
+
cron_entries = event.payload[:cron_entries]
|
63
|
+
cron_jobs_count = cron_entries.size
|
64
64
|
|
65
65
|
info do
|
66
|
-
"GoodJob started cron with #{cron_jobs_count} #{'
|
66
|
+
"GoodJob started cron with #{cron_jobs_count} #{'job'.pluralize(cron_jobs_count)}."
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
data/lib/good_job/scheduler.rb
CHANGED
@@ -230,7 +230,8 @@ module GoodJob # :nodoc:
|
|
230
230
|
# @return [void]
|
231
231
|
def create_task(delay = 0)
|
232
232
|
future = Concurrent::ScheduledTask.new(delay, args: [performer], executor: executor, timer_set: timer_set) do |thr_performer|
|
233
|
-
|
233
|
+
Thread.current.name = Thread.current.name.sub("-worker-", "-thread-") if Thread.current.name
|
234
|
+
Rails.application.reloader.wrap do
|
234
235
|
thr_performer.next
|
235
236
|
end
|
236
237
|
end
|
data/lib/good_job/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: good_job
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Sheldon
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-10-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activejob
|
@@ -351,18 +351,24 @@ files:
|
|
351
351
|
- engine/app/assets/vendor/bootstrap/bootstrap.min.css
|
352
352
|
- engine/app/assets/vendor/chartist/chartist.css
|
353
353
|
- engine/app/assets/vendor/chartist/chartist.js
|
354
|
-
- engine/app/controllers/good_job/active_jobs_controller.rb
|
355
354
|
- engine/app/controllers/good_job/assets_controller.rb
|
356
355
|
- engine/app/controllers/good_job/base_controller.rb
|
357
356
|
- engine/app/controllers/good_job/cron_schedules_controller.rb
|
358
|
-
- engine/app/controllers/good_job/dashboards_controller.rb
|
359
357
|
- engine/app/controllers/good_job/executions_controller.rb
|
358
|
+
- engine/app/controllers/good_job/jobs_controller.rb
|
359
|
+
- engine/app/filters/good_job/base_filter.rb
|
360
|
+
- engine/app/filters/good_job/executions_filter.rb
|
361
|
+
- engine/app/filters/good_job/jobs_filter.rb
|
360
362
|
- engine/app/helpers/good_job/application_helper.rb
|
361
|
-
- engine/app/
|
363
|
+
- engine/app/models/good_job/active_job_job.rb
|
362
364
|
- engine/app/views/good_job/cron_schedules/index.html.erb
|
363
|
-
- engine/app/views/good_job/
|
365
|
+
- engine/app/views/good_job/executions/index.html.erb
|
366
|
+
- engine/app/views/good_job/jobs/index.html.erb
|
367
|
+
- engine/app/views/good_job/jobs/show.html.erb
|
364
368
|
- engine/app/views/good_job/shared/_chart.erb
|
365
369
|
- engine/app/views/good_job/shared/_executions_table.erb
|
370
|
+
- engine/app/views/good_job/shared/_filter.erb
|
371
|
+
- engine/app/views/good_job/shared/_jobs_table.erb
|
366
372
|
- engine/app/views/good_job/shared/icons/_check.html.erb
|
367
373
|
- engine/app/views/good_job/shared/icons/_exclamation.html.erb
|
368
374
|
- engine/app/views/good_job/shared/icons/_trash.html.erb
|
@@ -381,6 +387,7 @@ files:
|
|
381
387
|
- lib/good_job/adapter.rb
|
382
388
|
- lib/good_job/cli.rb
|
383
389
|
- lib/good_job/configuration.rb
|
390
|
+
- lib/good_job/cron_entry.rb
|
384
391
|
- lib/good_job/cron_manager.rb
|
385
392
|
- lib/good_job/current_thread.rb
|
386
393
|
- lib/good_job/daemon.rb
|
@@ -1,107 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
module GoodJob
|
3
|
-
class DashboardsController < GoodJob::BaseController
|
4
|
-
class ExecutionFilter
|
5
|
-
attr_accessor :params
|
6
|
-
|
7
|
-
def initialize(params)
|
8
|
-
@params = params
|
9
|
-
end
|
10
|
-
|
11
|
-
def last
|
12
|
-
@_last ||= executions.last
|
13
|
-
end
|
14
|
-
|
15
|
-
def executions
|
16
|
-
after_scheduled_at = params[:after_scheduled_at].present? ? Time.zone.parse(params[:after_scheduled_at]) : nil
|
17
|
-
sql = GoodJob::Execution.display_all(after_scheduled_at: after_scheduled_at, after_id: params[:after_id])
|
18
|
-
.limit(params.fetch(:limit, 25))
|
19
|
-
sql = sql.job_class(params[:job_class]) if params[:job_class]
|
20
|
-
if params[:state]
|
21
|
-
case params[:state]
|
22
|
-
when 'finished'
|
23
|
-
sql = sql.finished
|
24
|
-
when 'unfinished'
|
25
|
-
sql = sql.unfinished
|
26
|
-
when 'running'
|
27
|
-
sql = sql.running
|
28
|
-
when 'errors'
|
29
|
-
sql = sql.where.not(error: nil)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
sql
|
33
|
-
end
|
34
|
-
|
35
|
-
def states
|
36
|
-
{
|
37
|
-
'finished' => GoodJob::Execution.finished.count,
|
38
|
-
'unfinished' => GoodJob::Execution.unfinished.count,
|
39
|
-
'running' => GoodJob::Execution.running.count,
|
40
|
-
'errors' => GoodJob::Execution.where.not(error: nil).count,
|
41
|
-
}
|
42
|
-
end
|
43
|
-
|
44
|
-
def job_classes
|
45
|
-
GoodJob::Execution.group("serialized_params->>'job_class'").count
|
46
|
-
.sort_by { |name, _count| name }
|
47
|
-
end
|
48
|
-
|
49
|
-
def to_params(override)
|
50
|
-
{
|
51
|
-
state: params[:state],
|
52
|
-
job_class: params[:job_class],
|
53
|
-
}.merge(override).delete_if { |_, v| v.nil? }
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
def index
|
58
|
-
@filter = ExecutionFilter.new(params)
|
59
|
-
|
60
|
-
count_query = Arel.sql(GoodJob::Execution.pg_or_jdbc_query(<<~SQL.squish))
|
61
|
-
SELECT *
|
62
|
-
FROM generate_series(
|
63
|
-
date_trunc('hour', $1::timestamp),
|
64
|
-
date_trunc('hour', $2::timestamp),
|
65
|
-
'1 hour'
|
66
|
-
) timestamp
|
67
|
-
LEFT JOIN (
|
68
|
-
SELECT
|
69
|
-
date_trunc('hour', scheduled_at) AS scheduled_at,
|
70
|
-
queue_name,
|
71
|
-
count(*) AS count
|
72
|
-
FROM (
|
73
|
-
SELECT
|
74
|
-
COALESCE(scheduled_at, created_at)::timestamp AS scheduled_at,
|
75
|
-
queue_name
|
76
|
-
FROM good_jobs
|
77
|
-
) sources
|
78
|
-
GROUP BY date_trunc('hour', scheduled_at), queue_name
|
79
|
-
) sources ON sources.scheduled_at = timestamp
|
80
|
-
ORDER BY timestamp ASC
|
81
|
-
SQL
|
82
|
-
|
83
|
-
current_time = Time.current
|
84
|
-
binds = [[nil, current_time - 1.day], [nil, current_time]]
|
85
|
-
executions_data = GoodJob::Execution.connection.exec_query(count_query, "GoodJob Dashboard Chart", binds)
|
86
|
-
|
87
|
-
queue_names = executions_data.map { |d| d['queue_name'] }.uniq
|
88
|
-
labels = []
|
89
|
-
queues_data = executions_data.to_a.group_by { |d| d['timestamp'] }.each_with_object({}) do |(timestamp, values), hash|
|
90
|
-
labels << timestamp.in_time_zone.strftime('%H:%M %z')
|
91
|
-
queue_names.each do |queue_name|
|
92
|
-
(hash[queue_name] ||= []) << values.find { |d| d['queue_name'] == queue_name }&.[]('count')
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
@chart = {
|
97
|
-
labels: labels,
|
98
|
-
series: queues_data.map do |queue, data|
|
99
|
-
{
|
100
|
-
name: queue,
|
101
|
-
data: data,
|
102
|
-
}
|
103
|
-
end,
|
104
|
-
}
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|