solid_job_monitor 1.0.0

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 (147) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +106 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/config/mission_control_jobs_manifest.js +4 -0
  6. data/app/assets/stylesheets/mission_control/jobs/application.css +16 -0
  7. data/app/assets/stylesheets/mission_control/jobs/bulma.min.css +21013 -0
  8. data/app/assets/stylesheets/mission_control/jobs/forms.css +8 -0
  9. data/app/assets/stylesheets/mission_control/jobs/jobs.css +68 -0
  10. data/app/controllers/concerns/mission_control/jobs/adapter_features.rb +24 -0
  11. data/app/controllers/concerns/mission_control/jobs/application_scoped.rb +38 -0
  12. data/app/controllers/concerns/mission_control/jobs/basic_authentication.rb +33 -0
  13. data/app/controllers/concerns/mission_control/jobs/failed_jobs_bulk_operations.rb +17 -0
  14. data/app/controllers/concerns/mission_control/jobs/job_filters.rb +41 -0
  15. data/app/controllers/concerns/mission_control/jobs/job_scoped.rb +16 -0
  16. data/app/controllers/concerns/mission_control/jobs/not_found_redirections.rb +36 -0
  17. data/app/controllers/concerns/mission_control/jobs/queue_scoped.rb +12 -0
  18. data/app/controllers/mission_control/jobs/application_controller.rb +32 -0
  19. data/app/controllers/mission_control/jobs/bulk_discards_controller.rb +20 -0
  20. data/app/controllers/mission_control/jobs/bulk_retries_controller.rb +10 -0
  21. data/app/controllers/mission_control/jobs/discards_controller.rb +18 -0
  22. data/app/controllers/mission_control/jobs/dispatches_controller.rb +18 -0
  23. data/app/controllers/mission_control/jobs/jobs_controller.rb +54 -0
  24. data/app/controllers/mission_control/jobs/progresses_controller.rb +27 -0
  25. data/app/controllers/mission_control/jobs/queues/pauses_controller.rb +15 -0
  26. data/app/controllers/mission_control/jobs/queues_controller.rb +16 -0
  27. data/app/controllers/mission_control/jobs/recurring_tasks_controller.rb +38 -0
  28. data/app/controllers/mission_control/jobs/retries_controller.rb +13 -0
  29. data/app/controllers/mission_control/jobs/stops_controller.rb +22 -0
  30. data/app/controllers/mission_control/jobs/workers_controller.rb +23 -0
  31. data/app/helpers/mission_control/jobs/application_helper.rb +9 -0
  32. data/app/helpers/mission_control/jobs/dates_helper.rb +19 -0
  33. data/app/helpers/mission_control/jobs/interface_helper.rb +23 -0
  34. data/app/helpers/mission_control/jobs/jobs_helper.rb +94 -0
  35. data/app/helpers/mission_control/jobs/navigation_helper.rb +60 -0
  36. data/app/javascript/mission_control/jobs/application.js +4 -0
  37. data/app/javascript/mission_control/jobs/controllers/application.js +9 -0
  38. data/app/javascript/mission_control/jobs/controllers/form_controller.js +21 -0
  39. data/app/javascript/mission_control/jobs/controllers/index.js +11 -0
  40. data/app/javascript/mission_control/jobs/controllers/progress_controller.js +55 -0
  41. data/app/javascript/mission_control/jobs/helpers/debounce_helpers.js +9 -0
  42. data/app/javascript/mission_control/jobs/helpers/index.js +1 -0
  43. data/app/models/mission_control/jobs/application_record.rb +7 -0
  44. data/app/models/mission_control/jobs/current.rb +3 -0
  45. data/app/models/mission_control/jobs/job_progress.rb +7 -0
  46. data/app/models/mission_control/jobs/page.rb +48 -0
  47. data/app/models/mission_control/jobs/recurring_task.rb +25 -0
  48. data/app/models/mission_control/jobs/worker.rb +17 -0
  49. data/app/views/layouts/mission_control/jobs/_application_selection.html.erb +14 -0
  50. data/app/views/layouts/mission_control/jobs/_flash.html.erb +9 -0
  51. data/app/views/layouts/mission_control/jobs/_navigation.html.erb +9 -0
  52. data/app/views/layouts/mission_control/jobs/application.html.erb +27 -0
  53. data/app/views/layouts/mission_control/jobs/application_selection/_applications.html.erb +13 -0
  54. data/app/views/layouts/mission_control/jobs/application_selection/_servers.html.erb +15 -0
  55. data/app/views/mission_control/jobs/jobs/_error_information.html.erb +22 -0
  56. data/app/views/mission_control/jobs/jobs/_filters.html.erb +55 -0
  57. data/app/views/mission_control/jobs/jobs/_general_information.html.erb +85 -0
  58. data/app/views/mission_control/jobs/jobs/_job.html.erb +13 -0
  59. data/app/views/mission_control/jobs/jobs/_jobs_page.html.erb +13 -0
  60. data/app/views/mission_control/jobs/jobs/_progress_bar.html.erb +16 -0
  61. data/app/views/mission_control/jobs/jobs/_raw_data.html.erb +4 -0
  62. data/app/views/mission_control/jobs/jobs/_title.html.erb +22 -0
  63. data/app/views/mission_control/jobs/jobs/_toolbar.html.erb +18 -0
  64. data/app/views/mission_control/jobs/jobs/blocked/_actions.html.erb +3 -0
  65. data/app/views/mission_control/jobs/jobs/blocked/_job.html.erb +8 -0
  66. data/app/views/mission_control/jobs/jobs/failed/_actions.html.erb +5 -0
  67. data/app/views/mission_control/jobs/jobs/failed/_backtrace_toggle.html.erb +14 -0
  68. data/app/views/mission_control/jobs/jobs/failed/_job.html.erb +7 -0
  69. data/app/views/mission_control/jobs/jobs/finished/_job.html.erb +2 -0
  70. data/app/views/mission_control/jobs/jobs/in_progress/_actions.html.erb +6 -0
  71. data/app/views/mission_control/jobs/jobs/in_progress/_job.html.erb +13 -0
  72. data/app/views/mission_control/jobs/jobs/index.html.erb +19 -0
  73. data/app/views/mission_control/jobs/jobs/scheduled/_actions.html.erb +5 -0
  74. data/app/views/mission_control/jobs/jobs/scheduled/_job.html.erb +13 -0
  75. data/app/views/mission_control/jobs/jobs/show.html.erb +6 -0
  76. data/app/views/mission_control/jobs/queues/_actions.html.erb +7 -0
  77. data/app/views/mission_control/jobs/queues/_job.html.erb +15 -0
  78. data/app/views/mission_control/jobs/queues/_queue.html.erb +16 -0
  79. data/app/views/mission_control/jobs/queues/_queue_title.html.erb +17 -0
  80. data/app/views/mission_control/jobs/queues/index.html.erb +16 -0
  81. data/app/views/mission_control/jobs/queues/show.html.erb +25 -0
  82. data/app/views/mission_control/jobs/recurring_tasks/_actions.html.erb +5 -0
  83. data/app/views/mission_control/jobs/recurring_tasks/_general_information.html.erb +37 -0
  84. data/app/views/mission_control/jobs/recurring_tasks/_recurring_task.html.erb +22 -0
  85. data/app/views/mission_control/jobs/recurring_tasks/_title.html.erb +10 -0
  86. data/app/views/mission_control/jobs/recurring_tasks/index.html.erb +18 -0
  87. data/app/views/mission_control/jobs/recurring_tasks/show.html.erb +14 -0
  88. data/app/views/mission_control/jobs/shared/_job.html.erb +27 -0
  89. data/app/views/mission_control/jobs/shared/_jobs.html.erb +14 -0
  90. data/app/views/mission_control/jobs/shared/_pagination_toolbar.html.erb +7 -0
  91. data/app/views/mission_control/jobs/workers/_configuration.html.erb +6 -0
  92. data/app/views/mission_control/jobs/workers/_raw_data.html.erb +6 -0
  93. data/app/views/mission_control/jobs/workers/_title.html.erb +11 -0
  94. data/app/views/mission_control/jobs/workers/_worker.html.erb +21 -0
  95. data/app/views/mission_control/jobs/workers/_workers_page.html.erb +15 -0
  96. data/app/views/mission_control/jobs/workers/index.html.erb +6 -0
  97. data/app/views/mission_control/jobs/workers/show.html.erb +14 -0
  98. data/config/importmap.rb +6 -0
  99. data/config/routes.rb +35 -0
  100. data/db/migrate/20260828105144_create_mission_control_job_progresses.rb +16 -0
  101. data/lib/active_job/errors/invalid_operation.rb +5 -0
  102. data/lib/active_job/errors/job_not_found_error.rb +14 -0
  103. data/lib/active_job/errors/query_error.rb +5 -0
  104. data/lib/active_job/executing.rb +40 -0
  105. data/lib/active_job/execution_error.rb +8 -0
  106. data/lib/active_job/failed.rb +7 -0
  107. data/lib/active_job/job_proxy.rb +38 -0
  108. data/lib/active_job/jobs_relation.rb +318 -0
  109. data/lib/active_job/progress_reporting.rb +21 -0
  110. data/lib/active_job/querying.rb +44 -0
  111. data/lib/active_job/queue.rb +62 -0
  112. data/lib/active_job/queue_adapters/async_ext.rb +49 -0
  113. data/lib/active_job/queue_adapters/resque_ext.rb +317 -0
  114. data/lib/active_job/queue_adapters/solid_queue_ext/recurring_tasks.rb +52 -0
  115. data/lib/active_job/queue_adapters/solid_queue_ext/workers.rb +41 -0
  116. data/lib/active_job/queue_adapters/solid_queue_ext.rb +313 -0
  117. data/lib/active_job/queues.rb +33 -0
  118. data/lib/mission_control/jobs/adapter.rb +156 -0
  119. data/lib/mission_control/jobs/application.rb +20 -0
  120. data/lib/mission_control/jobs/applications.rb +8 -0
  121. data/lib/mission_control/jobs/arguments_filter.rb +24 -0
  122. data/lib/mission_control/jobs/authentication.rb +67 -0
  123. data/lib/mission_control/jobs/console/connect_to.rb +15 -0
  124. data/lib/mission_control/jobs/console/context.rb +11 -0
  125. data/lib/mission_control/jobs/console/jobs_help.rb +23 -0
  126. data/lib/mission_control/jobs/engine.rb +118 -0
  127. data/lib/mission_control/jobs/errors/incompatible_adapter.rb +2 -0
  128. data/lib/mission_control/jobs/errors/resource_not_found.rb +2 -0
  129. data/lib/mission_control/jobs/i18n_config.rb +17 -0
  130. data/lib/mission_control/jobs/identified_by_name.rb +18 -0
  131. data/lib/mission_control/jobs/identified_elements.rb +23 -0
  132. data/lib/mission_control/jobs/job_stopped_error.rb +5 -0
  133. data/lib/mission_control/jobs/progress/null_store.rb +25 -0
  134. data/lib/mission_control/jobs/progress/reporter.rb +63 -0
  135. data/lib/mission_control/jobs/progress/store.rb +72 -0
  136. data/lib/mission_control/jobs/progress.rb +17 -0
  137. data/lib/mission_control/jobs/server/recurring_tasks.rb +15 -0
  138. data/lib/mission_control/jobs/server/serializable.rb +24 -0
  139. data/lib/mission_control/jobs/server/workers.rb +13 -0
  140. data/lib/mission_control/jobs/server.rb +27 -0
  141. data/lib/mission_control/jobs/tasks.rb +8 -0
  142. data/lib/mission_control/jobs/version.rb +5 -0
  143. data/lib/mission_control/jobs/workers_relation.rb +78 -0
  144. data/lib/mission_control/jobs.rb +43 -0
  145. data/lib/resque/thread_safe_redis.rb +34 -0
  146. data/lib/solid_job_monitor.rb +6 -0
  147. metadata +527 -0
@@ -0,0 +1,317 @@
1
+ module ActiveJob::QueueAdapters::ResqueExt
2
+ include MissionControl::Jobs::Adapter
3
+
4
+ def initialize(redis = Resque.redis)
5
+ super()
6
+ @redis = redis
7
+ end
8
+
9
+ def activating(&block)
10
+ Resque.with_per_thread_redis_override(redis, &block)
11
+ end
12
+
13
+ def queues
14
+ queues = queue_names
15
+ active_statuses = []
16
+ counts = []
17
+
18
+ redis.multi do |multi|
19
+ queues.each do |queue_name|
20
+ active_statuses << multi.mget("pause:queue:#{queue_name}", "pause:all")
21
+ counts << multi.llen("queue:#{queue_name}")
22
+ end
23
+ end
24
+
25
+ queues.collect.with_index do |queue_name, index|
26
+ { name: queue_name, active: active_statuses[index].value.compact.empty?, size: counts[index].value }
27
+ end
28
+ end
29
+
30
+ def queue_size(queue_name)
31
+ Resque.size queue_name
32
+ end
33
+
34
+ def clear_queue(queue_name)
35
+ Resque.remove_queue(queue_name)
36
+ end
37
+
38
+ def pause_queue(queue_name)
39
+ ResquePauseHelper.pause(queue_name)
40
+ end
41
+
42
+ def resume_queue(queue_name)
43
+ ResquePauseHelper.unpause(queue_name)
44
+ end
45
+
46
+ def queue_paused?(queue_name)
47
+ ResquePauseHelper.paused?(queue_name)
48
+ end
49
+
50
+ def supported_job_filters(jobs_relation)
51
+ if jobs_relation.pending? then [ :queue_name ]
52
+ else []
53
+ end
54
+ end
55
+
56
+ def jobs_count(jobs_relation)
57
+ resque_jobs_for(jobs_relation).count
58
+ end
59
+
60
+ def fetch_jobs(jobs_relation)
61
+ resque_jobs_for(jobs_relation).all
62
+ end
63
+
64
+ def retry_all_jobs(jobs_relation)
65
+ resque_jobs_for(jobs_relation).retry_all
66
+ end
67
+
68
+ def retry_job(job, jobs_relation)
69
+ resque_jobs_for(jobs_relation).retry_job(job)
70
+ end
71
+
72
+ def discard_all_jobs(jobs_relation)
73
+ resque_jobs_for(jobs_relation).discard_all
74
+ end
75
+
76
+ def discard_job(job, jobs_relation)
77
+ resque_jobs_for(jobs_relation).discard(job)
78
+ end
79
+
80
+ def find_job(job_id, jobs_relation)
81
+ resque_jobs_for(jobs_relation).find_job(job_id)
82
+ end
83
+
84
+ def supports_queue_pausing?
85
+ defined?(ResquePauseHelper)
86
+ end
87
+
88
+ private
89
+ attr_reader :redis
90
+
91
+ def queue_names
92
+ Resque.queues
93
+ end
94
+
95
+ def resque_jobs_for(jobs_relation)
96
+ ResqueJobs.new(jobs_relation, redis: redis)
97
+ end
98
+
99
+ class ResqueJobs
100
+ attr_reader :jobs_relation
101
+
102
+ delegate :default_page_size, :paginated?, :limit_value_provided?, to: :jobs_relation
103
+
104
+ def initialize(jobs_relation, redis:)
105
+ @jobs_relation = jobs_relation
106
+ @redis = redis
107
+ end
108
+
109
+ def count
110
+ if paginated?
111
+ count_fetched_jobs # no direct way of counting jobs
112
+ else
113
+ direct_jobs_count
114
+ end
115
+ end
116
+
117
+ def all
118
+ @all ||= fetch_resque_jobs.collect.with_index { |resque_job, index| deserialize_resque_job(resque_job, index) if resque_job.is_a?(Hash) }.compact
119
+ end
120
+
121
+ def retry_all
122
+ if use_batches?
123
+ retry_all_in_batches
124
+ else
125
+ retry_jobs(jobs_relation.to_a.reverse)
126
+ end
127
+ end
128
+
129
+ def retry_job(job)
130
+ # Not named just +retry+ because it collides with reserved Ruby keyword.
131
+ resque_requeue_and_discard(job)
132
+ end
133
+
134
+ def discard_all
135
+ if jobs_relation.failed? && targeting_all_jobs?
136
+ clear_failed_queue
137
+ else
138
+ discard_all_one_by_one
139
+ end
140
+ end
141
+
142
+ def discard(job)
143
+ redis.multi do |multi|
144
+ multi.lset(queue_redis_key, job.position, SENTINEL)
145
+ multi.lrem(queue_redis_key, 1, SENTINEL)
146
+ end
147
+ rescue Redis::CommandError => error
148
+ handle_resque_job_error(job, error)
149
+ end
150
+
151
+ def find_job(job_id)
152
+ jobs_by_id[job_id]
153
+ end
154
+
155
+ private
156
+ attr_reader :redis
157
+
158
+ SENTINEL = "" # See +Resque::Datastore#remove_from_failed_queue+
159
+
160
+ # Redis transactions severely speed up operations, specially when the network latency is high.
161
+ # We limit the transaction size because large batches can result in redis timeout errors.
162
+ MAX_REDIS_TRANSACTION_SIZE = 100
163
+
164
+ def targeting_all_jobs?
165
+ !paginated? && !jobs_relation.filtering_needed?
166
+ end
167
+
168
+ def fetch_resque_jobs
169
+ if jobs_relation.failed? || jobs_relation.queue_name.blank?
170
+ fetch_failed_resque_jobs
171
+ else
172
+ fetch_queue_resque_jobs
173
+ end
174
+ end
175
+
176
+ def fetch_failed_resque_jobs
177
+ Array.wrap(Resque::Failure.all(jobs_relation.offset_value, jobs_relation.limit_value))
178
+ end
179
+
180
+ def fetch_queue_resque_jobs
181
+ unless jobs_relation.queue_name.present?
182
+ raise ActiveJob::Errors::QueryError, "This adapter requires a queue name unless fetching failed jobs"
183
+ end
184
+ Array.wrap(Resque.peek(jobs_relation.queue_name, jobs_relation.offset_value, jobs_relation.limit_value))
185
+ end
186
+
187
+ def deserialize_resque_job(resque_job_hash, index)
188
+ args_hash = extract_args_hash(resque_job_hash)
189
+ ActiveJob::JobProxy.new(args_hash).tap do |job|
190
+ job.last_execution_error = execution_error_from_resque_job(resque_job_hash)
191
+ job.raw_data = resque_job_hash
192
+ job.filtered_raw_data = filter_raw_data_arguments(resque_job_hash)
193
+ job.position = jobs_relation.offset_value + index
194
+ job.failed_at = resque_job_hash["failed_at"]&.to_datetime&.utc
195
+ job.status = job.failed_at.present? ? :failed : :pending
196
+ end
197
+ end
198
+
199
+ def filter_raw_data_arguments(raw_data)
200
+ raw_data.deep_dup.tap do |filtered_data|
201
+ if args_hash = extract_args_hash(filtered_data)
202
+ args_hash["arguments"] = MissionControl::Jobs.job_arguments_filter.apply_to(args_hash["arguments"])
203
+ end
204
+ end
205
+ end
206
+
207
+ def extract_args_hash(raw_data)
208
+ (raw_data.dig("payload", "args") || raw_data.dig("args"))&.first
209
+ end
210
+
211
+ def execution_error_from_resque_job(resque_job_hash)
212
+ if resque_job_hash["exception"].present?
213
+ ActiveJob::ExecutionError.new \
214
+ error_class: resque_job_hash["exception"],
215
+ message: resque_job_hash["error"],
216
+ backtrace: resque_job_hash["backtrace"]
217
+ end
218
+ end
219
+
220
+ def direct_jobs_count
221
+ jobs_relation.failed? ? failed_jobs_count : pending_jobs_count
222
+ end
223
+
224
+ def pending_jobs_count
225
+ Resque.queue_sizes.inject(0) do |sum, (queue_name, queue_size)|
226
+ if jobs_relation.queue_name.blank? || jobs_relation.queue_name == queue_name
227
+ sum + queue_size
228
+ else
229
+ sum
230
+ end
231
+ end
232
+ end
233
+
234
+ def failed_jobs_count
235
+ Resque.data_store.num_failed
236
+ end
237
+
238
+ def count_fetched_jobs
239
+ all.size
240
+ end
241
+
242
+ def queue_redis_key
243
+ jobs_relation.failed? ? "failed" : "queue:#{jobs_relation.queue_name}"
244
+ end
245
+
246
+ def clear_failed_queue
247
+ Resque::Failure.clear("failed")
248
+ end
249
+
250
+ def retry_jobs(jobs)
251
+ in_transactional_jobs_batches(jobs) do |jobs_batch|
252
+ jobs_batch.each { |job| retry_job(job) }
253
+ end
254
+ end
255
+
256
+ def in_transactional_jobs_batches(jobs)
257
+ jobs.each_slice(MAX_REDIS_TRANSACTION_SIZE) do |jobs_batch|
258
+ redis.multi do |multi|
259
+ yield jobs_batch
260
+ end
261
+ end
262
+ end
263
+
264
+ def use_batches?
265
+ !jobs_relation.limit_value_provided? && jobs_relation.count > default_page_size
266
+ end
267
+
268
+ def retry_all_in_batches
269
+ jobs_relation.in_batches(order: :desc, &:retry_all)
270
+ end
271
+
272
+ def resque_requeue_and_discard(job)
273
+ requeue(job)
274
+ discard(job)
275
+ end
276
+
277
+ def requeue(job)
278
+ resque_job = job.raw_data
279
+ resque_job["retried_at"] = Time.now.utc.strftime("%Y/%m/%d %H:%M:%S")
280
+
281
+ redis.lset(queue_redis_key, job.position, Resque.encode(resque_job))
282
+ Resque::Job.create(resque_job["queue"], resque_job["payload"]["class"], *resque_job["payload"]["args"])
283
+ rescue Redis::CommandError => error
284
+ handle_resque_job_error(job, error)
285
+ end
286
+
287
+ def discard_all_one_by_one
288
+ if use_batches?
289
+ discard_all_in_batches
290
+ else
291
+ discard_jobs(jobs_relation.to_a.reverse)
292
+ end
293
+ end
294
+
295
+ def discard_jobs(jobs)
296
+ in_transactional_jobs_batches(jobs) do |jobs_batch|
297
+ jobs_batch.each { |job| discard(job) }
298
+ end
299
+ end
300
+
301
+ def discard_all_in_batches
302
+ jobs_relation.in_batches(order: :desc, &:discard_all)
303
+ end
304
+
305
+ def jobs_by_id
306
+ @jobs_by_id ||= all.index_by(&:job_id)
307
+ end
308
+
309
+ def handle_resque_job_error(job, error)
310
+ if error.message =~/no such key/i
311
+ raise ActiveJob::Errors::JobNotFoundError.new(job, jobs_relation)
312
+ else
313
+ raise error
314
+ end
315
+ end
316
+ end
317
+ end
@@ -0,0 +1,52 @@
1
+ module ActiveJob::QueueAdapters::SolidQueueExt::RecurringTasks
2
+ def supports_recurring_tasks?
3
+ true
4
+ end
5
+
6
+ def recurring_tasks
7
+ tasks = SolidQueue::RecurringTask.all
8
+ last_enqueued_at_times = recurring_task_last_enqueued_at(tasks.map(&:key))
9
+
10
+ tasks.collect do |task|
11
+ recurring_task_attributes_from_solid_queue_recurring_task(task).merge \
12
+ last_enqueued_at: last_enqueued_at_times[task.key]
13
+ end
14
+ end
15
+
16
+ def find_recurring_task(task_id)
17
+ if task = SolidQueue::RecurringTask.find_by(key: task_id)
18
+ recurring_task_attributes_from_solid_queue_recurring_task(task).merge \
19
+ last_enqueued_at: recurring_task_last_enqueued_at(task.key).values&.first
20
+ end
21
+ end
22
+
23
+ def enqueue_recurring_task(task_id)
24
+ if task = SolidQueue::RecurringTask.find_by(key: task_id)
25
+ task.enqueue(at: Time.now)
26
+ end
27
+ end
28
+
29
+ def can_enqueue_recurring_task?(task_id)
30
+ if task = SolidQueue::RecurringTask.find_by(key: task_id)
31
+ task.valid?
32
+ end
33
+ end
34
+
35
+ private
36
+ def recurring_task_attributes_from_solid_queue_recurring_task(task)
37
+ {
38
+ id: task.key,
39
+ job_class_name: task.class_name,
40
+ command: task.command,
41
+ arguments: task.arguments,
42
+ schedule: task.schedule,
43
+ next_time: task.next_time,
44
+ queue_name: task.queue_name,
45
+ priority: task.priority
46
+ }
47
+ end
48
+
49
+ def recurring_task_last_enqueued_at(task_keys)
50
+ SolidQueue::RecurringExecution.where(task_key: task_keys).group(:task_key).maximum(:run_at)
51
+ end
52
+ 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