flight_control 2.0.0.beta.1 → 2.0.0.beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -1
- data/README.md +23 -0
- data/lib/active_job/job_proxy.rb +2 -1
- data/lib/active_job/jobs_relation.rb +17 -5
- data/lib/active_job/queue_adapters/solid_queue_ext.rb +43 -5
- data/lib/flight_control/engine.rb +7 -1
- data/lib/flight_control/host_route_helpers.rb +34 -0
- data/lib/flight_control/version.rb +1 -1
- data/lib/flight_control.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: de1aaa590e44b1aafc7e7fce1e61ca8ddd6b472ec507b92a483fdedbc4f93b88
|
|
4
|
+
data.tar.gz: 0b961660c79020fab500de5513623ace40e919dc215ba390b6e01881fbf3d3d6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e0342d2ccd6e3c30b76aa284eca7dcc14d02df7c1f36fcee9820a02b9535fe93775932a684734a12a994c23b7767545957e8469ea3e46ab32245ff7e3852a5d6
|
|
7
|
+
data.tar.gz: dc7d93b5214bed8a8bd122eb5d2918ec2b5841e9ae5ca5aa8cfb3a0b12fea340b7f0c4acc03bab0997aea1b34609377691a2a550f942204bc3e0e02d5f007691
|
data/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
-
## [2.0.0] - 2026-
|
|
5
|
+
## [2.0.0.beta.2] - 2026-09-08
|
|
6
|
+
|
|
7
|
+
Ported from mission_control-jobs (upstream 1.1.0...1.2.0 and later):
|
|
8
|
+
|
|
9
|
+
- Adds `enqueued_at` and `scheduled_at` date-range filters, applied natively by Solid Queue.
|
|
10
|
+
- Adds a `back_to_main_app_path` setting to point the "Back to main app" link somewhere other than the host's root.
|
|
11
|
+
- Routes host-app route helpers used inside the engine through `main_app`, so a base controller redirecting to, say, `new_session_path` keeps working.
|
|
12
|
+
- Loads the engine's stylesheets individually so they also work under Propshaft.
|
|
13
|
+
- Shows the total of pending jobs on the queues page, and how many times a job has been retried.
|
|
14
|
+
- Truncates long job arguments in job lists.
|
|
15
|
+
- Orders failed jobs by when they failed rather than by job id.
|
|
16
|
+
- Submits the job filters on `change` instead of debouncing keystrokes.
|
|
17
|
+
- Swaps only the backtrace when toggling between clean and full.
|
|
18
|
+
- Disables Turbo's prefetching of links on hover.
|
|
19
|
+
- Redirects to the queues index when showing a queue that no longer exists.
|
|
20
|
+
- Shows the raw error when a failed execution's error can't be parsed.
|
|
21
|
+
- Fixes `config.active_job.default_page_size` not being applied.
|
|
22
|
+
- Fixes the "Expires" tooltip of blocked jobs rendering its markup as text.
|
|
23
|
+
- Fixes `JobProxy#duration` when `scheduled_at` isn't set.
|
|
24
|
+
- Avoids N+1 count queries when listing Solid Queue queues.
|
|
25
|
+
|
|
26
|
+
## [2.0.0.beta.1] - 2026-07-20
|
|
6
27
|
|
|
7
28
|
- Drops support for Resque and Async adapters to focus on Solid Queue.
|
|
8
29
|
- Drops support for Rails versions below 8.1.
|
data/README.md
CHANGED
|
@@ -107,6 +107,23 @@ If you do this, you can disable the default HTTP Basic Authentication using the
|
|
|
107
107
|
config.flight_control.http_basic_auth_enabled = false
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
+
Your base controller's code runs inside the engine, whose route helpers take precedence. Route helpers of your app that Flight Control doesn't define itself, like `new_session_path` from Rails' authentication generator, still work as they are, for example from a `before_action` that redirects to the login page. Helpers that both your app and Flight Control define, like `root_path`, resolve to Flight Control's; use `main_app.root_path` to reach your app's.
|
|
111
|
+
|
|
112
|
+
#### Authentication via route constraints
|
|
113
|
+
|
|
114
|
+
You can also keep authentication out of Flight Control's controllers altogether and restrict access when mounting the engine, using [route constraints](https://guides.rubyonrails.org/routing.html#advanced-constraints). For example, with the sessions created by Rails' authentication generator:
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
# config/routes.rb
|
|
118
|
+
Rails.application.routes.draw do
|
|
119
|
+
constraints ->(request) { Session.find_by(id: request.cookie_jar.signed[:session_id])&.user&.admin? } do
|
|
120
|
+
mount FlightControl::Engine, at: "/jobs"
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Requests that don't satisfy the constraint never reach the engine. As with a custom base controller, disable HTTP Basic Authentication if you don't want both.
|
|
126
|
+
|
|
110
127
|
### Other configuration settings
|
|
111
128
|
|
|
112
129
|
Besides `base_controller_class`, you can also set the following for `FlightControl` or `config.flight_control`:
|
|
@@ -116,6 +133,7 @@ Besides `base_controller_class`, you can also set the following for `FlightContr
|
|
|
116
133
|
- `internal_query_count_limit`: in count queries, the maximum number of records that will be counted if the adapter needs to limit these queries. True counts above this number will be returned as `INFINITY`. This keeps count queries fast—defaults to `500,000`
|
|
117
134
|
- `scheduled_job_delay_threshold`: the time duration before a scheduled job is considered delayed. Defaults to `1.minute` (a job is considered delayed if it hasn't transitioned from the `scheduled` status 1 minute after the scheduled time).
|
|
118
135
|
- `show_console_help`: whether to show the console help. If you don't want the console help message, set this to `false`—defaults to `true`.
|
|
136
|
+
- `back_to_main_app_path`: an optional string path for the "Back to main app" link. Example: `'/admin'`. Defaults to the host app's `root_path` when available.
|
|
119
137
|
- `backtrace_cleaner`: a backtrace cleaner used for optionally filtering backtraces on the Failed Jobs detail page. Defaults to `Rails::BacktraceCleaner.new`. See the [Advanced configuration](#advanced-configuration) section for how to configure/override this setting on a per application/server basis.
|
|
120
138
|
- `filter_arguments`: an array of strings representing the job argument keys you want to filter out in the UI. This is useful for hiding sensitive user data. Currently, only root-level hash keys are supported.
|
|
121
139
|
|
|
@@ -223,6 +241,11 @@ ActiveJob.jobs.finished.where(job_class_name: "SomeJob")
|
|
|
223
241
|
|
|
224
242
|
# All jobs in progress being run by a given worker
|
|
225
243
|
ActiveJob.jobs.in_progress.where(worker_id: 42)
|
|
244
|
+
|
|
245
|
+
# Filtering by dates takes a range, which can be open on either end
|
|
246
|
+
ActiveJob.jobs.failed.where(enqueued_at: 2.days.ago..)
|
|
247
|
+
ActiveJob.jobs.scheduled.where(scheduled_at: Time.now..1.hour.from_now)
|
|
248
|
+
ActiveJob.jobs.finished.where(finished_at: ..1.week.ago)
|
|
226
249
|
```
|
|
227
250
|
|
|
228
251
|
Some examples of bulk operations:
|
data/lib/active_job/job_proxy.rb
CHANGED
|
@@ -28,7 +28,8 @@ class ActiveJob::JobProxy < ActiveJob::Base
|
|
|
28
28
|
|
|
29
29
|
def duration
|
|
30
30
|
ended_at = finished_at || failed_at
|
|
31
|
-
|
|
31
|
+
started_at = scheduled_at || enqueued_at
|
|
32
|
+
ended_at - started_at if ended_at && started_at
|
|
32
33
|
end
|
|
33
34
|
|
|
34
35
|
ActiveJob::JobsRelation::STATUSES.each do |status|
|
|
@@ -23,9 +23,9 @@ class ActiveJob::JobsRelation
|
|
|
23
23
|
include Enumerable
|
|
24
24
|
|
|
25
25
|
STATUSES = %i[pending failed in_progress blocked scheduled finished]
|
|
26
|
-
FILTERS = %i[queue_name job_class_name]
|
|
26
|
+
FILTERS = %i[queue_name job_class_name scheduled_at enqueued_at]
|
|
27
27
|
|
|
28
|
-
PROPERTIES = %i[queue_name status offset_value limit_value job_class_name worker_id recurring_task_id finished_at]
|
|
28
|
+
PROPERTIES = %i[queue_name status offset_value limit_value job_class_name worker_id recurring_task_id finished_at scheduled_at enqueued_at]
|
|
29
29
|
attr_reader(*PROPERTIES, :default_page_size)
|
|
30
30
|
|
|
31
31
|
delegate :last, :[], :reverse, to: :to_a
|
|
@@ -52,13 +52,17 @@ class ActiveJob::JobsRelation
|
|
|
52
52
|
# * <tt>:worker_id</tt> - To only include the jobs processed by the provided worker.
|
|
53
53
|
# * <tt>:recurring_task_id</tt> - To only include the jobs corresponding to runs of a recurring task.
|
|
54
54
|
# * <tt>:finished_at</tt> - (Range) To only include the jobs finished between the provided range
|
|
55
|
-
|
|
55
|
+
# * <tt>:scheduled_at</tt> - (Range) To only include the jobs scheduled between the provided range
|
|
56
|
+
# * <tt>:enqueued_at</tt> - (Range) To only include the jobs enqueued between the provided range
|
|
57
|
+
def where(job_class_name: nil, queue_name: nil, worker_id: nil, recurring_task_id: nil, finished_at: nil, scheduled_at: nil, enqueued_at: nil)
|
|
56
58
|
# Remove nil arguments to avoid overriding parameters when concatenating +where+ clauses
|
|
57
59
|
arguments = {job_class_name: job_class_name,
|
|
58
60
|
queue_name: queue_name&.to_s,
|
|
59
61
|
worker_id: worker_id,
|
|
60
62
|
recurring_task_id: recurring_task_id,
|
|
61
|
-
finished_at: finished_at
|
|
63
|
+
finished_at: finished_at,
|
|
64
|
+
scheduled_at: scheduled_at,
|
|
65
|
+
enqueued_at: enqueued_at}.compact
|
|
62
66
|
|
|
63
67
|
clone_with(**arguments)
|
|
64
68
|
end
|
|
@@ -273,7 +277,15 @@ class ActiveJob::JobsRelation
|
|
|
273
277
|
end
|
|
274
278
|
|
|
275
279
|
def satisfy_filter?(job)
|
|
276
|
-
filters.all? { |property| public_send(property)
|
|
280
|
+
filters.all? { |property| satisfies_filter?(public_send(property), job.public_send(property)) }
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def satisfies_filter?(filter_value, job_value)
|
|
284
|
+
if filter_value.is_a?(Range)
|
|
285
|
+
filter_value.cover?(job_value)
|
|
286
|
+
else
|
|
287
|
+
filter_value == job_value
|
|
288
|
+
end
|
|
277
289
|
end
|
|
278
290
|
|
|
279
291
|
def filters
|
|
@@ -5,12 +5,14 @@ module ActiveJob::QueueAdapters::SolidQueueExt
|
|
|
5
5
|
|
|
6
6
|
def queues
|
|
7
7
|
queues = SolidQueue::Queue.all
|
|
8
|
-
|
|
8
|
+
queue_names = queues.map(&:name)
|
|
9
|
+
pauses = SolidQueue::Pause.where(queue_name: queue_names).index_by(&:queue_name)
|
|
10
|
+
sizes = SolidQueue::ReadyExecution.where(queue_name: queue_names).group(:queue_name).count
|
|
9
11
|
|
|
10
12
|
queues.collect do |queue|
|
|
11
13
|
{
|
|
12
14
|
name: queue.name,
|
|
13
|
-
size: queue.
|
|
15
|
+
size: sizes[queue.name] || 0,
|
|
14
16
|
active: pauses[queue.name].nil?
|
|
15
17
|
}
|
|
16
18
|
end
|
|
@@ -41,7 +43,7 @@ module ActiveJob::QueueAdapters::SolidQueueExt
|
|
|
41
43
|
end
|
|
42
44
|
|
|
43
45
|
def supported_job_filters(*)
|
|
44
|
-
[:queue_name, :job_class_name, :finished_at]
|
|
46
|
+
[:queue_name, :job_class_name, :finished_at, :scheduled_at, :enqueued_at]
|
|
45
47
|
end
|
|
46
48
|
|
|
47
49
|
def jobs_count(jobs_relation)
|
|
@@ -129,6 +131,11 @@ module ActiveJob::QueueAdapters::SolidQueueExt
|
|
|
129
131
|
message: solid_queue_job.failed_execution.message,
|
|
130
132
|
backtrace: solid_queue_job.failed_execution.backtrace || []
|
|
131
133
|
end
|
|
134
|
+
rescue JSON::ParserError
|
|
135
|
+
ActiveJob::ExecutionError.new \
|
|
136
|
+
error_class: "",
|
|
137
|
+
message: solid_queue_job.failed_execution.error_before_type_cast,
|
|
138
|
+
backtrace: []
|
|
132
139
|
end
|
|
133
140
|
|
|
134
141
|
def dispatch_immediately(job)
|
|
@@ -183,7 +190,7 @@ module ActiveJob::QueueAdapters::SolidQueueExt
|
|
|
183
190
|
attr_reader :jobs_relation
|
|
184
191
|
|
|
185
192
|
delegate :queue_name, :limit_value, :limit_value_provided?, :offset_value, :job_class_name,
|
|
186
|
-
:default_page_size, :worker_id, :recurring_task_id, :finished_at, to: :jobs_relation
|
|
193
|
+
:default_page_size, :worker_id, :recurring_task_id, :finished_at, :scheduled_at, :enqueued_at, to: :jobs_relation
|
|
187
194
|
|
|
188
195
|
def executions
|
|
189
196
|
execution_class_by_status
|
|
@@ -192,6 +199,8 @@ module ActiveJob::QueueAdapters::SolidQueueExt
|
|
|
192
199
|
.then { |executions| filter_executions_by_class(executions) }
|
|
193
200
|
.then { |executions| filter_executions_by_process_id(executions) }
|
|
194
201
|
.then { |executions| filter_executions_by_task_key(executions) }
|
|
202
|
+
.then { |executions| filter_executions_by_scheduled_at(executions) }
|
|
203
|
+
.then { |executions| filter_executions_by_enqueued_at(executions) }
|
|
195
204
|
.then { |executions| limit(executions) }
|
|
196
205
|
.then { |executions| offset(executions) }
|
|
197
206
|
end
|
|
@@ -201,6 +210,8 @@ module ActiveJob::QueueAdapters::SolidQueueExt
|
|
|
201
210
|
.then { |jobs| filter_jobs_by_queue(jobs) }
|
|
202
211
|
.then { |jobs| filter_jobs_by_class(jobs) }
|
|
203
212
|
.then { |jobs| filter_jobs_by_finished_at(jobs) }
|
|
213
|
+
.then { |jobs| filter_jobs_by_scheduled_at(jobs) }
|
|
214
|
+
.then { |jobs| filter_jobs_by_enqueued_at(jobs) }
|
|
204
215
|
.then { |jobs| limit(jobs) }
|
|
205
216
|
.then { |jobs| offset(jobs) }
|
|
206
217
|
end
|
|
@@ -210,9 +221,13 @@ module ActiveJob::QueueAdapters::SolidQueueExt
|
|
|
210
221
|
end
|
|
211
222
|
|
|
212
223
|
def order_executions(executions)
|
|
213
|
-
# Follow polling order for scheduled executions, the rest newest first
|
|
224
|
+
# Follow polling order for scheduled executions, the rest newest first.
|
|
225
|
+
# Failed executions go by their own id: a job can fail again after being
|
|
226
|
+
# retried, and what's newest is the failure, not the job.
|
|
214
227
|
if solid_queue_status.scheduled?
|
|
215
228
|
executions.ordered
|
|
229
|
+
elsif solid_queue_status.failed?
|
|
230
|
+
executions.order(id: :desc)
|
|
216
231
|
else
|
|
217
232
|
executions.order(job_id: :desc)
|
|
218
233
|
end
|
|
@@ -286,6 +301,29 @@ module ActiveJob::QueueAdapters::SolidQueueExt
|
|
|
286
301
|
finished_at.present? ? jobs.where(finished_at: finished_at) : jobs
|
|
287
302
|
end
|
|
288
303
|
|
|
304
|
+
def filter_executions_by_scheduled_at(executions)
|
|
305
|
+
return executions unless scheduled_at.present?
|
|
306
|
+
|
|
307
|
+
if solid_queue_status.scheduled?
|
|
308
|
+
executions.where(scheduled_at: scheduled_at)
|
|
309
|
+
else
|
|
310
|
+
executions.where(job: {scheduled_at: scheduled_at})
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def filter_jobs_by_scheduled_at(jobs)
|
|
315
|
+
scheduled_at.present? ? jobs.where(scheduled_at: scheduled_at) : jobs
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# Jobs are created when enqueued
|
|
319
|
+
def filter_executions_by_enqueued_at(executions)
|
|
320
|
+
enqueued_at.present? ? executions.where(job: {created_at: enqueued_at}) : executions
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def filter_jobs_by_enqueued_at(jobs)
|
|
324
|
+
enqueued_at.present? ? jobs.where(created_at: enqueued_at) : jobs
|
|
325
|
+
end
|
|
326
|
+
|
|
289
327
|
def limit(executions_or_jobs)
|
|
290
328
|
limit_value.present? ? executions_or_jobs.limit(limit_value) : executions_or_jobs
|
|
291
329
|
end
|
|
@@ -38,12 +38,18 @@ module FlightControl
|
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
+
initializer "flight_control.host_route_helpers" do |app|
|
|
42
|
+
ActiveSupport.on_load(:after_routes_loaded) do
|
|
43
|
+
FlightControl::HostRouteHelpers.define_from(app.routes, FlightControl::Engine.routes)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
41
47
|
initializer "flight_control.http_basic_auth" do |app|
|
|
42
48
|
FlightControl.http_basic_auth_user ||= app.credentials.dig(:flight_control, :http_basic_auth_user)
|
|
43
49
|
FlightControl.http_basic_auth_password ||= app.credentials.dig(:flight_control, :http_basic_auth_password)
|
|
44
50
|
end
|
|
45
51
|
|
|
46
|
-
initializer "flight_control.active_job.extensions" do
|
|
52
|
+
initializer "flight_control.active_job.extensions", before: "active_job.set_configs" do
|
|
47
53
|
ActiveSupport.on_load :active_job do
|
|
48
54
|
include ActiveJob::Querying
|
|
49
55
|
include ActiveJob::Executing
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Route helpers of the host app that the engine doesn't define itself, routed through +main_app+.
|
|
2
|
+
#
|
|
3
|
+
# Flight Control's controllers inherit from the host app's +ApplicationController+ (see
|
|
4
|
+
# +base_controller_class+), so host code such as a +before_action+ redirecting to
|
|
5
|
+
# +new_session_path+ runs inside the engine, where route helpers resolve against the engine's
|
|
6
|
+
# routes and raise +ActionController::UrlGenerationError+. This module defines, for every route
|
|
7
|
+
# helper the host app has and the engine doesn't, a method delegating to the host app's routes,
|
|
8
|
+
# so that code keeps working unchanged.
|
|
9
|
+
#
|
|
10
|
+
# Helpers defined by both, like +root_path+, keep resolving to the engine's, as in any isolated
|
|
11
|
+
# engine; use +main_app.root_path+ for the host's.
|
|
12
|
+
module FlightControl::HostRouteHelpers
|
|
13
|
+
class << self
|
|
14
|
+
def define_from(host_routes, engine_routes)
|
|
15
|
+
undefine_all
|
|
16
|
+
|
|
17
|
+
(host_routes.named_routes.helper_names - engine_routes.named_routes.helper_names).each do |name|
|
|
18
|
+
define_method(name) { |*args| main_app.public_send(name, *args) }
|
|
19
|
+
defined_helpers << name
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def undefine_all
|
|
26
|
+
defined_helpers.each { |name| remove_method(name) }
|
|
27
|
+
defined_helpers.clear
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def defined_helpers
|
|
31
|
+
@defined_helpers ||= []
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
data/lib/flight_control.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: flight_control
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0.0.beta.
|
|
4
|
+
version: 2.0.0.beta.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jury Razumau
|
|
@@ -340,6 +340,7 @@ files:
|
|
|
340
340
|
- lib/flight_control/errors/incompatible_adapter.rb
|
|
341
341
|
- lib/flight_control/errors/resource_not_found.rb
|
|
342
342
|
- lib/flight_control/errors/unsupported_adapter.rb
|
|
343
|
+
- lib/flight_control/host_route_helpers.rb
|
|
343
344
|
- lib/flight_control/i18n_config.rb
|
|
344
345
|
- lib/flight_control/identified_by_name.rb
|
|
345
346
|
- lib/flight_control/identified_elements.rb
|