good_job 2.2.0 → 2.4.1

Sign up to get free protection for your applications and to get access to all the features.
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