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.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +73 -0
  3. data/README.md +39 -16
  4. data/engine/app/controllers/good_job/base_controller.rb +8 -0
  5. data/engine/app/controllers/good_job/cron_schedules_controller.rb +1 -2
  6. data/engine/app/controllers/good_job/executions_controller.rb +5 -1
  7. data/engine/app/controllers/good_job/{active_jobs_controller.rb → jobs_controller.rb} +6 -2
  8. data/engine/app/filters/good_job/base_filter.rb +101 -0
  9. data/engine/app/filters/good_job/executions_filter.rb +40 -0
  10. data/engine/app/filters/good_job/jobs_filter.rb +46 -0
  11. data/engine/app/helpers/good_job/application_helper.rb +4 -0
  12. data/engine/app/models/good_job/active_job_job.rb +127 -0
  13. data/engine/app/views/good_job/cron_schedules/index.html.erb +51 -7
  14. data/engine/app/views/good_job/executions/index.html.erb +21 -0
  15. data/engine/app/views/good_job/jobs/index.html.erb +7 -0
  16. data/engine/app/views/good_job/{active_jobs → jobs}/show.html.erb +0 -0
  17. data/engine/app/views/good_job/shared/_executions_table.erb +3 -3
  18. data/engine/app/views/good_job/shared/_filter.erb +52 -0
  19. data/engine/app/views/good_job/shared/_jobs_table.erb +56 -0
  20. data/engine/app/views/layouts/good_job/base.html.erb +5 -1
  21. data/engine/config/routes.rb +2 -2
  22. data/lib/good_job/adapter.rb +6 -4
  23. data/lib/good_job/cli.rb +3 -1
  24. data/lib/good_job/configuration.rb +4 -0
  25. data/lib/good_job/cron_entry.rb +65 -0
  26. data/lib/good_job/cron_manager.rb +18 -30
  27. data/lib/good_job/log_subscriber.rb +3 -3
  28. data/lib/good_job/scheduler.rb +2 -1
  29. data/lib/good_job/version.rb +1 -1
  30. metadata +13 -6
  31. data/engine/app/controllers/good_job/dashboards_controller.rb +0 -107
  32. 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">&raquo;</span>
15
+ <% end %>
16
+ </li>
17
+ </ul>
18
+ </nav>
19
+ <% else %>
20
+ <em>No executions present.</em>
21
+ <% end %>
@@ -0,0 +1,7 @@
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
+ <%= render 'good_job/shared/jobs_table', jobs: @filter.records %>
@@ -23,18 +23,18 @@
23
23
  <% executions.each do |execution| %>
24
24
  <tr id="<%= dom_id(execution) %>">
25
25
  <td>
26
- <%= link_to active_job_path(execution.serialized_params['job_id']) do %>
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 active_job_path(execution.active_job_id, anchor: dom_id(execution)) do %>
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&nbsp;
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/check", class: "flex-shrink-0 me-2" %>
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>
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
  GoodJob::Engine.routes.draw do
3
- root to: 'dashboards#index'
3
+ root to: 'executions#index'
4
4
  resources :cron_schedules, only: %i[index]
5
- resources :active_jobs, only: %i[show]
5
+ resources :jobs, only: %i[index show]
6
6
  resources :executions, only: %i[destroy]
7
7
 
8
8
  scope controller: :assets do
@@ -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.cron, start_on_initialize: Rails.application.initialized?) if @configuration.enable_cron?
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
- cron_manager = GoodJob::CronManager.new(configuration.cron, start_on_initialize: true) if configuration.enable_cron?
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::CronManagers>, nil]
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 :schedules
29
+ attr_reader :cron_entries
30
30
 
31
- # @param schedules [Hash]
31
+ # @param cron_entries [Array<CronEntry>]
32
32
  # @param start_on_initialize [Boolean]
33
- def initialize(schedules = {}, start_on_initialize: false)
33
+ def initialize(cron_entries = [], start_on_initialize: false)
34
34
  @running = false
35
- @schedules = schedules
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", cron_jobs: @schedules) do
45
+ ActiveSupport::Notifications.instrument("cron_manager_start.good_job", cron_entries: cron_entries) do
46
46
  @running = true
47
- schedules.each_key { |cron_key| create_task(cron_key) }
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 cron_key [Symbol, String] the key within the schedule to use
82
- def create_task(cron_key)
83
- schedule = @schedules[cron_key]
84
- return false if schedule.blank?
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(thr_cron_key)
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
- schedule = thr_scheduler.schedules.fetch(thr_cron_key).with_indifferent_access
98
- job_class = schedule.fetch(:class).constantize
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
- job_class.set(job_set).perform_later(*job_args)
94
+ cron_entry.enqueue
107
95
  end
108
96
  end
109
97
 
110
- @tasks[cron_key] = future
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
- cron_jobs = event.payload[:cron_jobs]
63
- cron_jobs_count = cron_jobs.size
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} #{'jobs'.pluralize(cron_jobs_count)}."
66
+ "GoodJob started cron with #{cron_jobs_count} #{'job'.pluralize(cron_jobs_count)}."
67
67
  end
68
68
  end
69
69
 
@@ -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
- Rails.application.executor.wrap do
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  module GoodJob
3
3
  # GoodJob gem version.
4
- VERSION = '2.2.0'
4
+ VERSION = '2.4.1'
5
5
  end
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.2.0
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-09-15 00:00:00.000000000 Z
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/views/good_job/active_jobs/show.html.erb
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/dashboards/index.html.erb
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