mission_control-jobs 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -9
  3. data/app/controllers/concerns/mission_control/jobs/adapter_features.rb +10 -6
  4. data/app/controllers/concerns/mission_control/jobs/failed_jobs_bulk_operations.rb +1 -1
  5. data/app/controllers/concerns/mission_control/jobs/not_found_redirections.rb +9 -1
  6. data/app/controllers/concerns/mission_control/jobs/queue_scoped.rb +1 -1
  7. data/app/controllers/mission_control/jobs/bulk_discards_controller.rb +1 -1
  8. data/app/controllers/mission_control/jobs/discards_controller.rb +1 -1
  9. data/app/controllers/mission_control/jobs/dispatches_controller.rb +13 -0
  10. data/app/controllers/mission_control/jobs/jobs_controller.rb +3 -3
  11. data/app/controllers/mission_control/jobs/queues_controller.rb +4 -4
  12. data/app/controllers/mission_control/jobs/recurring_tasks_controller.rb +23 -0
  13. data/app/controllers/mission_control/jobs/retries_controller.rb +1 -1
  14. data/app/controllers/mission_control/jobs/workers_controller.rb +6 -1
  15. data/app/helpers/mission_control/jobs/jobs_helper.rb +6 -2
  16. data/app/helpers/mission_control/jobs/navigation_helper.rb +2 -1
  17. data/app/models/mission_control/jobs/page.rb +8 -8
  18. data/app/models/mission_control/jobs/recurring_task.rb +17 -0
  19. data/app/views/layouts/mission_control/jobs/_application_selection.html.erb +1 -1
  20. data/app/views/mission_control/jobs/jobs/_error_information.html.erb +0 -1
  21. data/app/views/mission_control/jobs/jobs/_general_information.html.erb +1 -1
  22. data/app/views/mission_control/jobs/jobs/_jobs_page.html.erb +8 -10
  23. data/app/views/mission_control/jobs/jobs/_title.html.erb +3 -0
  24. data/app/views/mission_control/jobs/jobs/blocked/_actions.html.erb +3 -0
  25. data/app/views/mission_control/jobs/jobs/blocked/_job.html.erb +3 -0
  26. data/app/views/mission_control/jobs/jobs/index.html.erb +2 -2
  27. data/app/views/mission_control/jobs/jobs/scheduled/_actions.html.erb +4 -0
  28. data/app/views/mission_control/jobs/jobs/scheduled/_job.html.erb +4 -1
  29. data/app/views/mission_control/jobs/queues/_queue_title.html.erb +1 -1
  30. data/app/views/mission_control/jobs/queues/show.html.erb +2 -2
  31. data/app/views/mission_control/jobs/recurring_tasks/_general_information.html.erb +16 -0
  32. data/app/views/mission_control/jobs/recurring_tasks/_recurring_task.html.erb +14 -0
  33. data/app/views/mission_control/jobs/recurring_tasks/_title.html.erb +7 -0
  34. data/app/views/mission_control/jobs/recurring_tasks/index.html.erb +16 -0
  35. data/app/views/mission_control/jobs/recurring_tasks/show.html.erb +14 -0
  36. data/app/views/mission_control/jobs/{workers → shared}/_job.html.erb +8 -1
  37. data/app/views/mission_control/jobs/shared/_jobs.html.erb +14 -0
  38. data/app/views/mission_control/jobs/shared/_pagination_toolbar.html.erb +3 -3
  39. data/app/views/mission_control/jobs/workers/_workers_page.html.erb +15 -0
  40. data/app/views/mission_control/jobs/workers/index.html.erb +2 -13
  41. data/app/views/mission_control/jobs/workers/show.html.erb +9 -2
  42. data/config/routes.rb +2 -2
  43. data/lib/active_job/executing.rb +3 -6
  44. data/lib/active_job/failed.rb +0 -4
  45. data/lib/active_job/job_proxy.rb +6 -0
  46. data/lib/active_job/jobs_relation.rb +17 -6
  47. data/lib/active_job/queue_adapters/resque_ext.rb +1 -1
  48. data/lib/active_job/queue_adapters/solid_queue_ext/recurring_tasks.rb +43 -0
  49. data/lib/active_job/queue_adapters/solid_queue_ext/workers.rb +41 -0
  50. data/lib/active_job/queue_adapters/solid_queue_ext.rb +51 -55
  51. data/lib/mission_control/jobs/adapter.rb +67 -19
  52. data/lib/mission_control/jobs/console/helpers.rb +1 -1
  53. data/lib/mission_control/jobs/engine.rb +13 -3
  54. data/lib/mission_control/jobs/server/recurring_tasks.rb +15 -0
  55. data/lib/mission_control/jobs/server/serializable.rb +1 -1
  56. data/lib/mission_control/jobs/server/workers.rb +3 -5
  57. data/lib/mission_control/jobs/server.rb +1 -1
  58. data/lib/mission_control/jobs/version.rb +1 -1
  59. data/lib/mission_control/jobs/workers_relation.rb +78 -0
  60. data/lib/mission_control/jobs.rb +3 -0
  61. metadata +19 -6
  62. data/app/jobs/mission_control/jobs/application_job.rb +0 -6
  63. data/app/mailers/mission_control/jobs/application_mailer.rb +0 -8
  64. data/app/views/mission_control/jobs/workers/_jobs.html.erb +0 -20
@@ -0,0 +1,15 @@
1
+ <table class="workers table jobs is-hoverable is-fullwidth">
2
+ <tbody>
3
+ <thead>
4
+ <tr>
5
+ <th>Worker</th>
6
+ <th>Hostname</th>
7
+ <th style="width: 35%;">Jobs</th>
8
+ <th>Last heartbeat</th>
9
+ </tr>
10
+ </thead>
11
+
12
+ <%= render partial: "mission_control/jobs/workers/worker", collection: workers_page.records %>
13
+
14
+ </tbody>
15
+ </table>
@@ -1,17 +1,6 @@
1
1
  <% navigation(title: "Workers", section: :workers) %>
2
2
 
3
- <table class="workers table jobs is-hoverable is-fullwidth">
4
- <tbody>
5
- <thead>
6
- <tr>
7
- <th>Worker</th>
8
- <th>Hostname</th>
9
- <th style="width: 35%;">Jobs</th>
10
- <th>Last heartbeat</th>
11
- </tr>
12
- </thead>
3
+ <%= render "mission_control/jobs/workers/workers_page", workers_page: @workers_page %>
13
4
 
14
- <%= render partial: "mission_control/jobs/workers/worker", collection: @workers %>
5
+ <%= render "mission_control/jobs/shared/pagination_toolbar", page: @workers_page, filter_param: {} %>
15
6
 
16
- </tbody>
17
- </table>
@@ -2,6 +2,13 @@
2
2
 
3
3
  <%= render "mission_control/jobs/workers/title", worker: @worker %>
4
4
  <%= render "mission_control/jobs/workers/configuration", worker: @worker %>
5
- <%= render "mission_control/jobs/workers/jobs", worker: @worker %>
6
- <%= render "mission_control/jobs/workers/raw_data", worker: @worker %>
7
5
 
6
+ <% if @worker.jobs.empty? %>
7
+ <%= blank_status_notice "This worker is idle" %>
8
+ <% else %>
9
+ <h2 class="subtitle">Running <%= pluralize @worker.jobs.size, "job" %></h2>
10
+
11
+ <%= render "mission_control/jobs/shared/jobs", jobs: @worker.jobs %>
12
+ <% end %>
13
+
14
+ <%= render "mission_control/jobs/workers/raw_data", worker: @worker %>
data/config/routes.rb CHANGED
@@ -9,6 +9,7 @@ MissionControl::Jobs::Engine.routes.draw do
9
9
  resources :jobs, only: :show do
10
10
  resource :retry, only: :create
11
11
  resource :discard, only: :create
12
+ resource :dispatch, only: :create
12
13
 
13
14
  collection do
14
15
  resource :bulk_retries, only: :create
@@ -19,6 +20,7 @@ MissionControl::Jobs::Engine.routes.draw do
19
20
  resources :jobs, only: :index, path: ":status/jobs"
20
21
 
21
22
  resources :workers, only: [ :index, :show ]
23
+ resources :recurring_tasks, only: [ :index, :show ]
22
24
  end
23
25
 
24
26
  # Allow referencing urls without providing an application_id. It will default to the first one.
@@ -27,7 +29,5 @@ MissionControl::Jobs::Engine.routes.draw do
27
29
  resources :jobs, only: :show
28
30
  resources :jobs, only: :index, path: ":status/jobs"
29
31
 
30
- resources :workers, only: [ :index, :show ]
31
-
32
32
  root to: "queues#index"
33
33
  end
@@ -4,9 +4,8 @@ module ActiveJob::Executing
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  included do
7
- attr_accessor :raw_data, :position, :finished_at, :blocked_by, :blocked_until, :worker_id, :started_at
7
+ attr_accessor :raw_data, :position, :finished_at, :blocked_by, :blocked_until, :worker_id, :started_at, :status
8
8
  attr_reader :serialized_arguments
9
- attr_writer :status
10
9
 
11
10
  thread_cattr_accessor :current_queue_adapter
12
11
  end
@@ -25,10 +24,8 @@ module ActiveJob::Executing
25
24
  jobs_relation_for_discarding.discard_job(self)
26
25
  end
27
26
 
28
- def status
29
- return @status if @status.present?
30
-
31
- failed? ? :failed : :pending
27
+ def dispatch
28
+ ActiveJob.jobs.blocked.dispatch_job(self)
32
29
  end
33
30
 
34
31
  private
@@ -4,8 +4,4 @@ module ActiveJob::Failed
4
4
  included do
5
5
  attr_accessor :last_execution_error, :failed_at
6
6
  end
7
-
8
- def failed?
9
- last_execution_error.present?
10
- end
11
7
  end
@@ -23,4 +23,10 @@ class ActiveJob::JobProxy < ActiveJob::Base
23
23
  def perform_now
24
24
  raise UnsupportedError, "A JobProxy doesn't support immediate execution, only enqueuing."
25
25
  end
26
+
27
+ ActiveJob::JobsRelation::STATUSES.each do |status|
28
+ define_method "#{status}?" do
29
+ self.status == status
30
+ end
31
+ end
26
32
  end
@@ -1,12 +1,12 @@
1
1
  # A relation of jobs that can be filtered and acted on.
2
2
  #
3
- # Relations of jobs are normally fetched via +ActiveJob::Base.jobs+
3
+ # Relations of jobs are normally fetched via +ActiveJob.jobs+
4
4
  # or through a given queue (+ActiveJob::Queue#jobs+).
5
5
  #
6
6
  # This class offers a fluid interface to query a subset of jobs. For
7
7
  # example:
8
8
  #
9
- # queue = ActiveJob::Base.queues[:default]
9
+ # queue = ActiveJob.queues[:default]
10
10
  # queue.jobs.limit(10).where(job_class_name: "DummyJob").last
11
11
  #
12
12
  # Relations are enumerable, so you can use +Enumerable+ methods on them.
@@ -25,7 +25,7 @@ class ActiveJob::JobsRelation
25
25
  STATUSES = %i[ pending failed in_progress blocked scheduled finished ]
26
26
  FILTERS = %i[ queue_name job_class_name ]
27
27
 
28
- PROPERTIES = %i[ queue_name status offset_value limit_value job_class_name worker_id ]
28
+ PROPERTIES = %i[ queue_name status offset_value limit_value job_class_name worker_id recurring_task_id ]
29
29
  attr_reader *PROPERTIES, :default_page_size
30
30
 
31
31
  delegate :last, :[], :reverse, to: :to_a
@@ -50,9 +50,15 @@ class ActiveJob::JobsRelation
50
50
  # for large sets of jobs.
51
51
  # * <tt>:queue_name</tt> - To only include the jobs in the provided queue.
52
52
  # * <tt>:worker_id</tt> - To only include the jobs processed by the provided worker.
53
- def where(job_class_name: nil, queue_name: nil, worker_id: nil)
53
+ # * <tt>:recurring_task_id</tt> - To only include the jobs corresponding to runs of a recurring task.
54
+ def where(job_class_name: nil, queue_name: nil, worker_id: nil, recurring_task_id: nil)
54
55
  # Remove nil arguments to avoid overriding parameters when concatenating +where+ clauses
55
- arguments = { job_class_name: job_class_name, queue_name: queue_name, worker_id: worker_id }.compact.collect { |key, value| [ key, value.to_s ] }.to_h
56
+ arguments = { job_class_name: job_class_name,
57
+ queue_name: queue_name,
58
+ worker_id: worker_id,
59
+ recurring_task_id: recurring_task_id
60
+ }.compact.collect { |key, value| [ key, value.to_s ] }.to_h
61
+
56
62
  clone_with **arguments
57
63
  end
58
64
 
@@ -147,6 +153,11 @@ class ActiveJob::JobsRelation
147
153
  queue_adapter.discard_job(job, self)
148
154
  end
149
155
 
156
+ # Dispatch the provided job.
157
+ def dispatch_job(job)
158
+ queue_adapter.dispatch_job(job, self)
159
+ end
160
+
150
161
  # Find a job by id.
151
162
  #
152
163
  # Returns nil when not found.
@@ -258,7 +269,7 @@ class ActiveJob::JobsRelation
258
269
  end
259
270
 
260
271
  def filters
261
- @filters ||= FILTERS.select { |property| public_send(property).present? && !queue_adapter.supports_filter?(self, property) }
272
+ @filters ||= FILTERS.select { |property| public_send(property).present? && !queue_adapter.supports_job_filter?(self, property) }
262
273
  end
263
274
 
264
275
  def ensure_failed_status
@@ -47,7 +47,7 @@ module ActiveJob::QueueAdapters::ResqueExt
47
47
  ResquePauseHelper.paused?(queue_name)
48
48
  end
49
49
 
50
- def supported_filters(jobs_relation)
50
+ def supported_job_filters(jobs_relation)
51
51
  if jobs_relation.pending? then [ :queue_name ]
52
52
  else []
53
53
  end
@@ -0,0 +1,43 @@
1
+ module ActiveJob::QueueAdapters::SolidQueueExt::RecurringTasks
2
+ def supports_recurring_tasks?
3
+ true
4
+ end
5
+
6
+ def recurring_tasks
7
+ tasks = recurring_tasks_from_dispatchers
8
+ last_enqueued_at_times = recurring_task_last_enqueued_at(tasks.keys)
9
+
10
+ recurring_tasks_from_dispatchers.collect do |task_id, task_attrs|
11
+ recurring_task_attributes_from_solid_queue_task_attributes(task_attrs).merge \
12
+ id: task_id,
13
+ last_enqueued_at: last_enqueued_at_times[task_id]
14
+ end
15
+ end
16
+
17
+ def find_recurring_task(task_id)
18
+ if task_attrs = recurring_tasks_from_dispatchers[task_id]
19
+ recurring_task_attributes_from_solid_queue_task_attributes(task_attrs).merge \
20
+ id: task_id,
21
+ last_enqueued_at: recurring_task_last_enqueued_at(task_id).values&.first
22
+ end
23
+ end
24
+
25
+ private
26
+ def recurring_tasks_from_dispatchers
27
+ SolidQueue::Process.where(kind: "Dispatcher").flat_map do |process|
28
+ process.metadata["recurring_schedule"]
29
+ end.compact.reduce({}, &:merge)
30
+ end
31
+
32
+ def recurring_task_attributes_from_solid_queue_task_attributes(task_attributes)
33
+ {
34
+ job_class_name: task_attributes["class_name"],
35
+ arguments: task_attributes["arguments"],
36
+ schedule: task_attributes["schedule"]
37
+ }
38
+ end
39
+
40
+ def recurring_task_last_enqueued_at(task_keys)
41
+ SolidQueue::RecurringExecution.where(task_key: task_keys).group(:task_key).maximum(:run_at)
42
+ end
43
+ end
@@ -0,0 +1,41 @@
1
+ module ActiveJob::QueueAdapters::SolidQueueExt::Workers
2
+ def exposes_workers?
3
+ true
4
+ end
5
+
6
+ def fetch_workers(workers_relation)
7
+ solid_queue_processes_from_workers_relation(workers_relation).collect do |process|
8
+ worker_from_solid_queue_process(process)
9
+ end
10
+ end
11
+
12
+ def count_workers(workers_relation)
13
+ solid_queue_processes_from_workers_relation(workers_relation).count
14
+ end
15
+
16
+ def find_worker(worker_id)
17
+ if process = SolidQueue::Process.find_by(id: worker_id)
18
+ worker_attributes_from_solid_queue_process(process)
19
+ end
20
+ end
21
+
22
+ private
23
+ def solid_queue_processes_from_workers_relation(relation)
24
+ SolidQueue::Process.where(kind: "Worker").offset(relation.offset_value).limit(relation.limit_value)
25
+ end
26
+
27
+ def worker_from_solid_queue_process(process)
28
+ MissionControl::Jobs::Worker.new(queue_adapter: self, **worker_attributes_from_solid_queue_process(process))
29
+ end
30
+
31
+ def worker_attributes_from_solid_queue_process(process)
32
+ {
33
+ id: process.id,
34
+ name: "PID: #{process.pid}",
35
+ hostname: process.hostname,
36
+ last_heartbeat_at: process.last_heartbeat_at,
37
+ configuration: process.metadata,
38
+ raw_data: process.as_json
39
+ }
40
+ end
41
+ end
@@ -1,5 +1,6 @@
1
1
  module ActiveJob::QueueAdapters::SolidQueueExt
2
2
  include MissionControl::Jobs::Adapter
3
+ include RecurringTasks, Workers
3
4
 
4
5
  def queues
5
6
  queues = SolidQueue::Queue.all
@@ -34,40 +35,26 @@ module ActiveJob::QueueAdapters::SolidQueueExt
34
35
  find_queue_by_name(queue_name).paused?
35
36
  end
36
37
 
37
- def supported_statuses
38
- RelationAdapter::STATUS_MAP.keys
38
+ def supported_job_statuses
39
+ SolidQueueJobs::STATUS_MAP.keys
39
40
  end
40
41
 
41
- def supported_filters(*)
42
+ def supported_job_filters(*)
42
43
  [ :queue_name, :job_class_name ]
43
44
  end
44
45
 
45
- def exposes_workers?
46
- true
47
- end
48
-
49
- def workers
50
- SolidQueue::Process.where(kind: "Worker").collect do |process|
51
- worker_attributes_from_solid_queue_process(process)
52
- end
53
- end
54
-
55
- def find_worker(worker_id)
56
- if process = SolidQueue::Process.find_by(id: worker_id)
57
- worker_attributes_from_solid_queue_process(process)
58
- end
59
- end
60
-
61
46
  def jobs_count(jobs_relation)
62
- RelationAdapter.new(jobs_relation).count
47
+ SolidQueueJobs.new(jobs_relation).count
63
48
  end
64
49
 
65
50
  def fetch_jobs(jobs_relation)
66
- find_solid_queue_jobs_within(jobs_relation).map { |job| deserialize_and_proxy_solid_queue_job(job, jobs_relation.status) }
51
+ SolidQueueJobs.new(jobs_relation).jobs.map do |job|
52
+ deserialize_and_proxy_solid_queue_job(job, jobs_relation.status)
53
+ end
67
54
  end
68
55
 
69
56
  def retry_all_jobs(jobs_relation)
70
- RelationAdapter.new(jobs_relation).retry_all
57
+ SolidQueueJobs.new(jobs_relation).retry_all
71
58
  end
72
59
 
73
60
  def retry_job(job, jobs_relation)
@@ -75,13 +62,17 @@ module ActiveJob::QueueAdapters::SolidQueueExt
75
62
  end
76
63
 
77
64
  def discard_all_jobs(jobs_relation)
78
- RelationAdapter.new(jobs_relation).discard_all
65
+ SolidQueueJobs.new(jobs_relation).discard_all
79
66
  end
80
67
 
81
68
  def discard_job(job, jobs_relation)
82
69
  find_solid_queue_job!(job.job_id, jobs_relation).discard
83
70
  end
84
71
 
72
+ def dispatch_job(job, jobs_relation)
73
+ dispatch_immediately find_solid_queue_job!(job.job_id, jobs_relation)
74
+ end
75
+
85
76
  def find_job(job_id, *)
86
77
  if job = SolidQueue::Job.where(active_job_id: job_id).order(:id).last
87
78
  deserialize_and_proxy_solid_queue_job job
@@ -93,27 +84,12 @@ module ActiveJob::QueueAdapters::SolidQueueExt
93
84
  SolidQueue::Queue.find_by_name(queue_name)
94
85
  end
95
86
 
96
- def worker_attributes_from_solid_queue_process(process)
97
- {
98
- id: process.id,
99
- name: "PID: #{process.pid}",
100
- hostname: process.hostname,
101
- last_heartbeat_at: process.last_heartbeat_at,
102
- configuration: process.metadata,
103
- raw_data: process.as_json
104
- }
105
- end
106
-
107
87
  def find_solid_queue_job!(job_id, jobs_relation)
108
88
  find_solid_queue_job(job_id, jobs_relation) or raise ActiveJob::Errors::JobNotFoundError.new(job_id, jobs_relation)
109
89
  end
110
90
 
111
91
  def find_solid_queue_job(job_id, jobs_relation)
112
- RelationAdapter.new(jobs_relation).find_job(job_id)
113
- end
114
-
115
- def find_solid_queue_jobs_within(jobs_relation)
116
- RelationAdapter.new(jobs_relation).jobs
92
+ SolidQueueJobs.new(jobs_relation).find_job(job_id)
117
93
  end
118
94
 
119
95
  def deserialize_and_proxy_solid_queue_job(solid_queue_job, job_status = nil)
@@ -134,7 +110,7 @@ module ActiveJob::QueueAdapters::SolidQueueExt
134
110
  end
135
111
 
136
112
  def status_from_solid_queue_job(solid_queue_job)
137
- RelationAdapter::STATUS_MAP.invert[solid_queue_job.status]
113
+ SolidQueueJobs::STATUS_MAP.invert[solid_queue_job.status]
138
114
  end
139
115
 
140
116
  def execution_error_from_solid_queue_job(solid_queue_job)
@@ -146,7 +122,14 @@ module ActiveJob::QueueAdapters::SolidQueueExt
146
122
  end
147
123
  end
148
124
 
149
- class RelationAdapter
125
+ def dispatch_immediately(job)
126
+ SolidQueue::Job.transaction do
127
+ job.dispatch_bypassing_concurrency_limits
128
+ job.blocked_execution.destroy!
129
+ end
130
+ end
131
+
132
+ class SolidQueueJobs
150
133
  STATUS_MAP = {
151
134
  pending: :ready,
152
135
  failed: :failed,
@@ -161,7 +144,7 @@ module ActiveJob::QueueAdapters::SolidQueueExt
161
144
  end
162
145
 
163
146
  def jobs
164
- solid_queue_status.finished? ? order_finished_jobs(finished_jobs) : order_executions(executions).map(&:job)
147
+ solid_queue_status.finished? ? order_finished_jobs(finished_jobs) : order_executions(executions).map(&:job).compact
165
148
  end
166
149
 
167
150
  def count
@@ -169,7 +152,7 @@ module ActiveJob::QueueAdapters::SolidQueueExt
169
152
  end
170
153
 
171
154
  def find_job(active_job_id)
172
- if job = SolidQueue::Job.find_by(active_job_id: active_job_id)
155
+ if job = SolidQueue::Job.where(active_job_id: active_job_id).order(:id).last
173
156
  job if matches_relation_filters?(job)
174
157
  end
175
158
  end
@@ -185,13 +168,16 @@ module ActiveJob::QueueAdapters::SolidQueueExt
185
168
  private
186
169
  attr_reader :jobs_relation
187
170
 
188
- delegate :queue_name, :limit_value, :limit_value_provided?, :offset_value, :job_class_name, :default_page_size, :worker_id, to: :jobs_relation
171
+ delegate :queue_name, :limit_value, :limit_value_provided?, :offset_value, :job_class_name,
172
+ :default_page_size, :worker_id, :recurring_task_id, to: :jobs_relation
189
173
 
190
174
  def executions
191
- execution_class_by_status.includes(job: "#{solid_queue_status}_execution")
175
+ execution_class_by_status
176
+ .then { |executions| include_execution_association(executions) }
192
177
  .then { |executions| filter_executions_by_queue(executions) }
193
178
  .then { |executions| filter_executions_by_class(executions) }
194
179
  .then { |executions| filter_executions_by_process_id(executions) }
180
+ .then { |executions| filter_executions_by_task_key(executions) }
195
181
  .then { |executions| limit(executions) }
196
182
  .then { |executions| offset(executions) }
197
183
  end
@@ -209,10 +195,11 @@ module ActiveJob::QueueAdapters::SolidQueueExt
209
195
  end
210
196
 
211
197
  def order_executions(executions)
212
- # Follow polling order for scheduled executions, the rest by job_id
213
- if solid_queue_status.scheduled? then executions.ordered
214
- else
215
- executions.order(:job_id)
198
+ case
199
+ # Follow polling order for scheduled executions, the rest by job_id, desc or asc
200
+ when solid_queue_status.scheduled? then executions.ordered
201
+ when recurring_task_id.present? then executions.order(job_id: :desc)
202
+ else executions.order(job_id: :asc)
216
203
  end
217
204
  end
218
205
 
@@ -224,21 +211,26 @@ module ActiveJob::QueueAdapters::SolidQueueExt
224
211
  solid_queue_status.finished? ? finished_jobs.count : executions.count
225
212
  end
226
213
 
227
- INTERNAL_COUNT_LIMIT = 500_000 # Hard limit to keep unlimited count queries fast enough
228
-
229
214
  def internally_limited_count
230
- limited_count = solid_queue_status.finished? ? finished_jobs.limit(INTERNAL_COUNT_LIMIT + 1).count : executions.limit(INTERNAL_COUNT_LIMIT + 1).count
231
- (limited_count == INTERNAL_COUNT_LIMIT + 1) ? Float::INFINITY : limited_count
215
+ count_limit = MissionControl::Jobs.internal_query_count_limit + 1
216
+ limited_count = solid_queue_status.finished? ? finished_jobs.limit(count_limit).count : executions.limit(count_limit).count
217
+ (limited_count == count_limit) ? Float::INFINITY : limited_count
232
218
  end
233
219
 
234
220
  def execution_class_by_status
235
- if solid_queue_status.present? && !solid_queue_status.finished?
221
+ if recurring_task_id.present?
222
+ SolidQueue::RecurringExecution
223
+ elsif solid_queue_status.present? && !solid_queue_status.finished?
236
224
  "SolidQueue::#{solid_queue_status.capitalize}Execution".safe_constantize
237
225
  else
238
226
  raise ActiveJob::Errors::QueryError, "Status not supported: #{jobs_relation.status}"
239
227
  end
240
228
  end
241
229
 
230
+ def include_execution_association(executions)
231
+ solid_queue_status.present? ? executions.includes(job: "#{solid_queue_status}_execution") : executions.includes(:job)
232
+ end
233
+
242
234
  def filter_executions_by_queue(executions)
243
235
  return executions unless queue_name.present?
244
236
 
@@ -263,10 +255,14 @@ module ActiveJob::QueueAdapters::SolidQueueExt
263
255
  if solid_queue_status.claimed?
264
256
  executions.where(process_id: worker_id)
265
257
  else
266
- raise ActiveJob::Errors::QueryError, "Filtering by worker ID is not supported for status #{jobs_relation.status}"
258
+ raise ActiveJob::Errors::QueryError, "Filtering by worker id is not supported for status #{jobs_relation.status}"
267
259
  end
268
260
  end
269
261
 
262
+ def filter_executions_by_task_key(executions)
263
+ recurring_task_id.present? ? executions.where(task_key: recurring_task_id) : executions
264
+ end
265
+
270
266
  def filter_jobs_by_class(jobs)
271
267
  job_class_name.present? ? jobs.where(class_name: job_class_name) : jobs
272
268
  end
@@ -3,17 +3,21 @@ module MissionControl::Jobs::Adapter
3
3
  block.call
4
4
  end
5
5
 
6
- def supported_statuses
6
+ def supports_job_status?(status)
7
+ supported_job_statuses.include?(status)
8
+ end
9
+
10
+ def supported_job_statuses
7
11
  # All adapters need to support these at a minimum
8
12
  [ :pending, :failed ]
9
13
  end
10
14
 
11
- def supports_filter?(jobs_relation, filter)
12
- supported_filters(jobs_relation).include?(filter)
15
+ def supports_job_filter?(jobs_relation, filter)
16
+ supported_job_filters(jobs_relation).include?(filter)
13
17
  end
14
18
 
15
19
  # List of filters supported natively. Non-supported filters are done in memory.
16
- def supported_filters(jobs_relation)
20
+ def supported_job_filters(jobs_relation)
17
21
  []
18
22
  end
19
23
 
@@ -25,11 +29,38 @@ module MissionControl::Jobs::Adapter
25
29
  false
26
30
  end
27
31
 
32
+ def supports_recurring_tasks?
33
+ false
34
+ end
35
+
36
+ # Returns an array with the list of recurring tasks. Each task is represented as a hash
37
+ # with these attributes:
38
+ # {
39
+ # id: "periodic-job",
40
+ # job_class_name: "MyJob",
41
+ # arguments: [ 123, { arg: :value }]
42
+ # schedule: "every monday at 9 am",
43
+ # last_enqueued_at: Fri, 26 Jan 2024 20:31:09.652174000 UTC +00:00,
44
+ # }
45
+ def recurring_tasks
46
+ if supports_recurring_tasks?
47
+ raise_incompatible_adapter_error_from :recurring_tasks
48
+ end
49
+ end
50
+
51
+ # Returns a recurring task represented by a hash as indicated above
52
+ def find_recurring_task(recurring_task_id)
53
+ if supports_recurring_tasks?
54
+ raise_incompatible_adapter_error_from :find_recurring_task
55
+ end
56
+ end
57
+
58
+
28
59
  # Returns an array with the list of workers. Each worker is represented as a hash
29
60
  # with these attributes:
30
61
  # {
31
62
  # id: 123,
32
- # name: "adapter-name",
63
+ # name: "worker-name",
33
64
  # hostname: "hey-default-101",
34
65
  # last_heartbeat_at: Fri, 26 Jan 2024 20:31:09.652174000 UTC +00:00,
35
66
  # configuration: { ... }
@@ -37,10 +68,18 @@ module MissionControl::Jobs::Adapter
37
68
  # }
38
69
  def workers
39
70
  if exposes_workers?
40
- raise MissionControl::Jobs::Errors::IncompatibleAdapter("Adapter must implement `workers`")
71
+ raise_incompatible_adapter_error_from :workers
72
+ end
73
+ end
74
+
75
+ # Returns a worker represented by a hash as indicated above
76
+ def find_worker(worker_id)
77
+ if exposes_workers?
78
+ raise_incompatible_adapter_error_from :find_worker
41
79
  end
42
80
  end
43
81
 
82
+
44
83
  # Returns an array with the list of queues. Each queue is represented as a hash
45
84
  # with these attributes:
46
85
  # {
@@ -49,60 +88,69 @@ module MissionControl::Jobs::Adapter
49
88
  # active: true
50
89
  # }
51
90
  def queues
52
- raise MissionControl::Jobs::Errors::IncompatibleAdapter("Adapter must implement `queue_names`")
91
+ raise_incompatible_adapter_error_from :queue_names
53
92
  end
54
93
 
55
94
  def queue_size(queue_name)
56
- raise MissionControl::Jobs::Errors::IncompatibleAdapter("Adapter must implement `queue_size`")
95
+ raise_incompatible_adapter_error_from :queue_size
57
96
  end
58
97
 
59
98
  def clear_queue(queue_name)
60
- raise MissionControl::Jobs::Errors::IncompatibleAdapter("Adapter must implement `clear_queue`")
99
+ raise_incompatible_adapter_error_from :clear_queue
61
100
  end
62
101
 
63
102
  def pause_queue(queue_name)
64
103
  if supports_queue_pausing?
65
- raise MissionControl::Jobs::Errors::IncompatibleAdapter("Adapter must implement `pause_queue`")
104
+ raise_incompatible_adapter_error_from :pause_queue
66
105
  end
67
106
  end
68
107
 
69
108
  def resume_queue(queue_name)
70
109
  if supports_queue_pausing?
71
- raise MissionControl::Jobs::Errors::IncompatibleAdapter("Adapter must implement `resume_queue`")
110
+ raise_incompatible_adapter_error_from :resume_queue
72
111
  end
73
112
  end
74
113
 
75
114
  def queue_paused?(queue_name)
76
115
  if supports_queue_pausing?
77
- raise MissionControl::Jobs::Errors::IncompatibleAdapter("Adapter must implement `queue_paused?`")
116
+ raise_incompatible_adapter_error_from :queue_paused?
78
117
  end
79
118
  end
80
119
 
81
120
  def jobs_count(jobs_relation)
82
- raise MissionControl::Jobs::Errors::IncompatibleAdapter("Adapter must implement `jobs_count`")
121
+ raise_incompatible_adapter_error_from :jobs_count
83
122
  end
84
123
 
85
124
  def fetch_jobs(jobs_relation)
86
- raise MissionControl::Jobs::Errors::IncompatibleAdapter("Adapter must implement `fetch_jobs`")
125
+ raise_incompatible_adapter_error_from :fetch_jobs
87
126
  end
88
127
 
89
128
  def retry_all_jobs(jobs_relation)
90
- raise MissionControl::Jobs::Errors::IncompatibleAdapter("Adapter must implement `retry_all_jobs`")
129
+ raise_incompatible_adapter_error_from :retry_all_jobs
91
130
  end
92
131
 
93
132
  def retry_job(job, jobs_relation)
94
- raise MissionControl::Jobs::Errors::IncompatibleAdapter("Adapter must implement `retry_job`")
133
+ raise_incompatible_adapter_error_from :retry_job
95
134
  end
96
135
 
97
136
  def discard_all_jobs(jobs_relation)
98
- raise MissionControl::Jobs::Errors::IncompatibleAdapter("Adapter must implement `discard_all_jobs`")
137
+ raise_incompatible_adapter_error_from :discard_all_jobs
99
138
  end
100
139
 
101
140
  def discard_job(job, jobs_relation)
102
- raise MissionControl::Jobs::Errors::IncompatibleAdapter("Adapter must implement `discard_job`")
141
+ raise_incompatible_adapter_error_from :discard_job
142
+ end
143
+
144
+ def dispatch_job(job, jobs_relation)
145
+ raise_incompatible_adapter_error_from :dispatch_job
103
146
  end
104
147
 
105
148
  def find_job(job_id, *)
106
- raise MissionControl::Jobs::Errors::IncompatibleAdapter("Adapter must implement `find_job`")
149
+ raise_incompatible_adapter_error_from :find_job
107
150
  end
151
+
152
+ private
153
+ def raise_incompatible_adapter_error_from(method_name)
154
+ raise MissionControl::Jobs::Errors::IncompatibleAdapter, "Adapter #{ActiveJob.adapter_name(self)} must implement `#{method_name}`"
155
+ end
108
156
  end