flight_control 2.0.0.beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +12 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +265 -0
  5. data/Rakefile +6 -0
  6. data/lib/active_job/errors/invalid_operation.rb +5 -0
  7. data/lib/active_job/errors/job_not_found_error.rb +14 -0
  8. data/lib/active_job/errors/query_error.rb +5 -0
  9. data/lib/active_job/executing.rb +41 -0
  10. data/lib/active_job/execution_error.rb +8 -0
  11. data/lib/active_job/failed.rb +7 -0
  12. data/lib/active_job/job_proxy.rb +39 -0
  13. data/lib/active_job/jobs_relation.rb +321 -0
  14. data/lib/active_job/querying.rb +45 -0
  15. data/lib/active_job/queue.rb +63 -0
  16. data/lib/active_job/queue_adapters/solid_queue_ext/recurring_tasks.rb +53 -0
  17. data/lib/active_job/queue_adapters/solid_queue_ext/workers.rb +42 -0
  18. data/lib/active_job/queue_adapters/solid_queue_ext.rb +309 -0
  19. data/lib/active_job/queues.rb +34 -0
  20. data/lib/flight_control/adapter.rb +155 -0
  21. data/lib/flight_control/application.rb +20 -0
  22. data/lib/flight_control/applications.rb +8 -0
  23. data/lib/flight_control/arguments_filter.rb +25 -0
  24. data/lib/flight_control/authentication.rb +68 -0
  25. data/lib/flight_control/console/connect_to.rb +15 -0
  26. data/lib/flight_control/console/context.rb +11 -0
  27. data/lib/flight_control/console/jobs_help.rb +23 -0
  28. data/lib/flight_control/engine.rb +102 -0
  29. data/lib/flight_control/errors/incompatible_adapter.rb +2 -0
  30. data/lib/flight_control/errors/resource_not_found.rb +2 -0
  31. data/lib/flight_control/errors/unsupported_adapter.rb +2 -0
  32. data/lib/flight_control/i18n_config.rb +17 -0
  33. data/lib/flight_control/identified_by_name.rb +18 -0
  34. data/lib/flight_control/identified_elements.rb +24 -0
  35. data/lib/flight_control/server/recurring_tasks.rb +15 -0
  36. data/lib/flight_control/server/serializable.rb +24 -0
  37. data/lib/flight_control/server/workers.rb +13 -0
  38. data/lib/flight_control/server.rb +33 -0
  39. data/lib/flight_control/tasks.rb +6 -0
  40. data/lib/flight_control/version.rb +3 -0
  41. data/lib/flight_control/workers_relation.rb +79 -0
  42. data/lib/flight_control.rb +39 -0
  43. metadata +377 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1ad093eeb61f357dc4aead0c094eb413607dfa234cd7cdf4ed15579a0fa0af51
4
+ data.tar.gz: a7774ba9273438d984569b4f739795019caaea77d1f1e42b033cd862e0af9231
5
+ SHA512:
6
+ metadata.gz: 9ce93746fa317b44831c7fa4d12bca2590d25a76f4eeb375041df08895f326d1e8c25a414d10da06638b0c072c933c84bdbbe338bbf27a04dd77d0e93e8c9324
7
+ data.tar.gz: 986514c76126354436fe67b584289a17cf864314f126c9f7c159f1207e48c7dd3816860b82fde3115cc04ecde18e7e483528ae3b93a7d3f8127c2264f89053c5
data/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ # Changelog
2
+
3
+ ## [Unreleased]
4
+
5
+ ## [2.0.0] - 2026-07-20
6
+
7
+ - Drops support for Resque and Async adapters to focus on Solid Queue.
8
+ - Drops support for Rails versions below 8.1.
9
+ - Lists newest jobs first on all pages.
10
+ - Adds a way to run any Active Job and set parameters for it.
11
+ - Shows job run duration in tables with failed and finished jobs.
12
+ - Shows Enqueued and Finished as timestamps in addition to relative time.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 37signals, LLC
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,265 @@
1
+ # Flight Control
2
+
3
+ A Rails-based frontend for managing [Solid Queue](https://github.com/rails/solid_queue) jobs through Active Job. It lets you inspect job queues and jobs in every status, retry or discard failed jobs, manage recurring tasks and workers, and enqueue any job defined in your application straight from the UI.
4
+
5
+ Flight Control is a fork of [Mission Control — Jobs](https://github.com/rails/mission_control-jobs) that focuses exclusively on Solid Queue. Resque support has been removed.
6
+
7
+ ## Installation
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem "flight_control"
12
+ ```
13
+
14
+ And then execute:
15
+ ```bash
16
+ $ bundle install
17
+ ```
18
+
19
+ Flight Control requires Solid Queue (>= 1.0) configured as your Active Job queue adapter.
20
+
21
+ ## Basic configuration
22
+
23
+ Mount the Flight Control engine where you wish to have it accessible from your app, in your `routes.rb` file:
24
+
25
+ ```ruby
26
+ Rails.application.routes.draw do
27
+ # ...
28
+ mount FlightControl::Engine, at: "/jobs"
29
+ ```
30
+
31
+ Once you set up [Authentication](#authentication), you should be able to access Flight Control's UI, where you can browse the existing queues, jobs pending in these queues, jobs in different statuses, and discard and retry failed jobs:
32
+
33
+ ![Queues tab in a simple app](docs/images/queues-simple.png)
34
+
35
+ ![Failed jobs tab in a simple app](docs/images/failed-jobs-simple.png)
36
+
37
+ ### API-only apps or apps using `vite_rails` and other asset pipelines outside Rails
38
+
39
+ If you want to use this gem with an [API-only Rails app](https://guides.rubyonrails.org/api_app.html) or an app that's using `vite_ruby`/`vite_rails`, or some other custom asset pipeline different from Sprockets and Propshaft, you need one more thing: configure an asset pipeline so you can serve the JavaScript and CSS included in this gem. We recommend to use [`Propshaft`](https://github.com/rails/propshaft). You simply need to add this line to your application's Gemfile:
40
+
41
+ ```ruby
42
+ gem "propshaft"
43
+ ```
44
+
45
+ Then execute
46
+ ```bash
47
+ $ bundle install
48
+ ```
49
+
50
+ Then, make sure you add a step to your deployment pipeline to precompile assets:
51
+ ```
52
+ RAILS_ENV=production rails assets:precompile
53
+ ```
54
+
55
+ For example, if you're using the Dockerfile generated by Rails with an API-only app or having skipped the assets pipeline, re-add:
56
+ ```
57
+ # Precompiling assets for production without requiring secret RAILS_MASTER_KEY
58
+ RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile
59
+ ```
60
+
61
+ ### Authentication
62
+
63
+ Flight Control comes with **HTTP basic authentication enabled and closed** by default. Credentials are stored in [Rails's credentials](https://edgeguides.rubyonrails.org/security.html#custom-credentials) like this:
64
+ ```yml
65
+ flight_control:
66
+ http_basic_auth_user: dev
67
+ http_basic_auth_password: secret
68
+ ```
69
+
70
+ If no credentials are configured, Flight Control won't be accessible. To set these up, you can run the generator provided like this:
71
+
72
+ ```
73
+ bin/rails flight_control:authentication:configure
74
+ ```
75
+
76
+ To set them up for different environments you can use the `RAILS_ENV` environment variable, like this:
77
+ ```
78
+ RAILS_ENV=production bin/rails flight_control:authentication:configure
79
+ ```
80
+
81
+ User and password can also be configured by hand like this:
82
+
83
+ ```ruby
84
+ Rails.application.configure do
85
+ FlightControl.http_basic_auth_user = "dev"
86
+ FlightControl.http_basic_auth_password = "secret"
87
+ end
88
+ ```
89
+
90
+ #### Custom authentication
91
+
92
+ You can provide your own authentication mechanism, for example, if you have a certain type of admin user in your app that can access Flight Control. To make this easier, you can specify a different controller as the base class for Flight Control's controllers. By default, Flight Control's controllers will extend the host app's `ApplicationController`, but you can change this easily:
93
+
94
+ ```ruby
95
+ Rails.application.configure do
96
+ FlightControl.base_controller_class = "AdminController"
97
+ end
98
+ ```
99
+
100
+ Or, in your environment config or `application.rb`:
101
+ ```ruby
102
+ config.flight_control.base_controller_class = "AdminController"
103
+ ```
104
+
105
+ If you do this, you can disable the default HTTP Basic Authentication using the following option:
106
+ ```ruby
107
+ config.flight_control.http_basic_auth_enabled = false
108
+ ```
109
+
110
+ ### Other configuration settings
111
+
112
+ Besides `base_controller_class`, you can also set the following for `FlightControl` or `config.flight_control`:
113
+
114
+ - `logger`: the logger you want Flight Control to use. Defaults to `ActiveSupport::Logger.new(nil)` (no logging). Notice that this is different from Active Job's logger or Active Job's backend's configured logger.
115
+ - `delay_between_bulk_operation_batches`: how long to wait between batches when performing bulk operations, such as _discard all_ or _retry all_ jobs—defaults to `0`
116
+ - `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
+ - `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
+ - `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`.
119
+ - `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
+ - `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
+
122
+ This library extends Active Job with a querying interface and the following setting:
123
+ - `config.active_job.default_page_size`: the internal batch size that Active Job will use when sending queries to the underlying adapter and the batch size for the bulk operations defined above—defaults to `1000`.
124
+
125
+ ## Advanced configuration
126
+
127
+ Without adding any additional configuration to [the one described before](#basic-configuration), Flight Control will be configured with one single app and a single Solid Queue server.
128
+
129
+ If you manage multiple apps' job backends from a single, centralized app, you can configure the different apps and/or different servers in an initializer like this:
130
+
131
+ ```ruby
132
+ FlightControl.applications.add("hey", solid_queue: ActiveJob::QueueAdapters::SolidQueueAdapter.new)
133
+ FlightControl.applications.add("campfire", solid_queue: ActiveJob::QueueAdapters::SolidQueueAdapter.new)
134
+ ```
135
+
136
+ When you have multiple apps and servers configured, you can choose between them with select and toggle menus:
137
+
138
+ ![Queues tab with multiple apps and servers](docs/images/queues-multiple.png)
139
+
140
+ ## Basic UI usage
141
+
142
+ Besides inspecting the queues and the jobs in them, and discarding and retrying failed jobs, you can inspect jobs in different statuses, filter them by _queue name_ and _job class name_, pause and un-pause queues, inspect workers, know which jobs are being run by what worker, check a specific job or a specific worker, and manage recurring tasks.
143
+
144
+ Job lists are sorted newest first: the most recently enqueued (or finished) jobs appear at the top. Scheduled jobs are the exception; they're listed in the order they will run.
145
+
146
+ ### Running jobs from the UI
147
+
148
+ The **Run job** tab lets you enqueue any job class defined in your application. Pick the job class, provide its arguments as a JSON array, and optionally override the queue. A trailing JSON object is passed to the job as keyword arguments; for example, for a job defined as `def perform(post_id, notify: false)` you'd enter:
149
+
150
+ ```json
151
+ [ 123, { "notify": true } ]
152
+ ```
153
+
154
+ Arguments must be expressible as JSON, so jobs that take records or other rich objects as arguments can't be launched this way.
155
+
156
+ ![Default queue tab](docs/images/default-queue.png)
157
+
158
+ ![In-progress jobs tab](docs/images/in-progress-jobs.png)
159
+
160
+ ![Workers tab](docs/images/workers.png)
161
+
162
+ ![Single job](docs/images/single-job.png)
163
+
164
+ ![Single worker](docs/images/single-worker.png)
165
+
166
+
167
+ ## Console helpers, scripting and dealing with big sets of jobs
168
+
169
+ Besides the UI, Flight Control provides a light console helper to switch between applications and servers. Some potentially destructive actions aren't exposed via the UI (for example, discarding jobs that aren't failed, although this might change in the future), but you can always perform these from the console if you know very well what you're doing.
170
+
171
+ It's also possible that you need to deal with very big sets of jobs that are unmanageable via the UI or that you wish to write a script to deal with an incident, some cleanup or some data migration. The console helpers and the querying API with which we've extended Active Job come in handy here.
172
+
173
+ First, when connecting to the Rails console, you'll see this new message:
174
+
175
+ ```
176
+ bin/rails c
177
+
178
+
179
+ Type 'jobs_help' to see how to connect to the available job servers to manage jobs
180
+ ```
181
+
182
+ Typing `jobs_help`, you'll get clear instructions about how to switch between applications and servers:
183
+
184
+ ```
185
+ >> jobs_help
186
+ You can connect to a job server with
187
+ connect_to "<app_id>:<server_id>"
188
+
189
+ Available job servers:
190
+ * hey:solid_queue
191
+ * campfire:solid_queue
192
+ ```
193
+
194
+ And then:
195
+ ```
196
+ >> connect_to hey:solid_queue
197
+ Connected to hey:solid_queue
198
+ ```
199
+
200
+ Now you're ready to query and operate over jobs for this server via the API. Some examples of queries:
201
+
202
+ ```ruby
203
+
204
+ # All jobs
205
+ ActiveJob.jobs
206
+
207
+ # All failed jobs
208
+ ActiveJob.jobs.failed
209
+
210
+ # All pending jobs in some queue
211
+ ActiveJob.jobs.pending.where(queue_name: "some_queue")
212
+
213
+ # All failed jobs of a given class
214
+ ActiveJob.jobs.failed.where(job_class_name: "SomeJob")
215
+
216
+ # All pending jobs of a given class with limit and offset
217
+ ActiveJob.jobs.pending.where(job_class_name: "SomeJob").limit(10).offset(5)
218
+
219
+ # All scheduled/in-progress/finished jobs of a given class
220
+ ActiveJob.jobs.scheduled.where(job_class_name: "SomeJob")
221
+ ActiveJob.jobs.in_progress.where(job_class_name: "SomeJob")
222
+ ActiveJob.jobs.finished.where(job_class_name: "SomeJob")
223
+
224
+ # All jobs in progress being run by a given worker
225
+ ActiveJob.jobs.in_progress.where(worker_id: 42)
226
+ ```
227
+
228
+ Some examples of bulk operations:
229
+
230
+ ```ruby
231
+ # Retry all the jobs (only possible for failed jobs)
232
+ ActiveJob.jobs.failed.retry_all
233
+
234
+ # Retry all the jobs of a given class (only possible for failed jobs)
235
+ ActiveJob.jobs.failed.where(job_class_name: "SomeJob").retry_all
236
+
237
+ # Discard all failed jobs
238
+ ActiveJob.jobs.failed.discard_all
239
+
240
+ # Discard all pending jobs of a given class
241
+ ActiveJob.jobs.pending.where(job_class_name: "SomeJob").discard_all
242
+ # Or all pending jobs in a given queue:
243
+ ActiveJob.jobs.pending.where(queue_name: "some-queue").discard_all
244
+ ```
245
+
246
+ When performing these bulk operations in the console, a delay of 2 seconds between batches processed will be introduced, set via [`delay_between_bulk_operation_batches`](#other-configuration-settings). You can modify it as
247
+ ```ruby
248
+ FlightControl.delay_between_bulk_operation_batches = 5.seconds
249
+ ```
250
+
251
+ ## Contributing
252
+
253
+ Thanks for your interest in contributing! To get the app running locally, just run:
254
+ ```
255
+ bin/setup
256
+ ```
257
+ This will load a bunch of jobs as seeds.
258
+
259
+ We have both unit and functional tests and system tests. If you want to run system tests, you'd need to install [ChromeDriver](https://chromedriver.chromium.org/). Then, you'll be able to run the tests as:
260
+ ```
261
+ bin/rails test test/system
262
+ ```
263
+
264
+ ## License
265
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). Flight Control is a fork of [Mission Control — Jobs](https://github.com/rails/mission_control-jobs), © 37signals, also MIT-licensed.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ require "bundler/gem_tasks"
@@ -0,0 +1,5 @@
1
+ module ActiveJob
2
+ module Errors
3
+ class InvalidOperation < StandardError; end
4
+ end
5
+ end
@@ -0,0 +1,14 @@
1
+ module ActiveJob
2
+ module Errors
3
+ class JobNotFoundError < StandardError
4
+ attr_reader :job_relation
5
+
6
+ def initialize(job_or_job_id, job_relation)
7
+ @job_relation = job_relation
8
+
9
+ job_id = job_or_job_id.is_a?(ActiveJob::Base) ? job_or_job_id.job_id : job_or_job_id
10
+ super("Job with id '#{job_id}' not found")
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ module ActiveJob
2
+ module Errors
3
+ class QueryError < StandardError; end
4
+ end
5
+ end
@@ -0,0 +1,41 @@
1
+ # TODO: These (or a version of them) should be moved to +ActiveJob::Core+
2
+ # and related concerns when upstreamed.
3
+ module ActiveJob::Executing
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ attr_accessor :raw_data, :position, :finished_at, :blocked_by, :blocked_until, :worker_id, :started_at, :status
8
+ attr_reader :serialized_arguments
9
+
10
+ thread_cattr_accessor :current_queue_adapter
11
+ end
12
+
13
+ class_methods do
14
+ def queue_adapter
15
+ ActiveJob::Base.current_queue_adapter || super
16
+ end
17
+ end
18
+
19
+ def retry
20
+ ActiveJob.jobs.failed.retry_job(self)
21
+ end
22
+
23
+ def discard
24
+ jobs_relation_for_discarding.discard_job(self)
25
+ end
26
+
27
+ def dispatch
28
+ ActiveJob.jobs.dispatch_job(self)
29
+ end
30
+
31
+ private
32
+
33
+ def jobs_relation_for_discarding
34
+ case status
35
+ when :failed then ActiveJob.jobs.failed
36
+ when :pending then ActiveJob.jobs.pending.where(queue_name: queue_name)
37
+ else
38
+ ActiveJob.jobs
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,8 @@
1
+ # Information about a given error when executing a job.
2
+ #
3
+ # It's attached to failed jobs at +ActiveJob::Base#last_execution_error+.
4
+ ActiveJob::ExecutionError = Struct.new(:error_class, :message, :backtrace) do
5
+ def to_s
6
+ "ERROR #{error_class}: #{message}\n#{backtrace&.collect { |line| "\t#{line}" }&.join("\n")}"
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ module ActiveJob::Failed
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ attr_accessor :last_execution_error, :failed_at
6
+ end
7
+ end
@@ -0,0 +1,39 @@
1
+ # A proxy for managing jobs without having to load the corresponding
2
+ # job class.
3
+ #
4
+ # This is useful for managing jobs without having the job classes
5
+ # present in the code base.
6
+ class ActiveJob::JobProxy < ActiveJob::Base
7
+ class UnsupportedError < StandardError; end
8
+
9
+ attr_reader :job_class_name
10
+ # Raw data with the sensitive user data filtered out.
11
+ attr_accessor :filtered_raw_data
12
+
13
+ def initialize(job_data)
14
+ super
15
+ @job_class_name = job_data["job_class"]
16
+ deserialize(job_data)
17
+ end
18
+
19
+ def serialize
20
+ super.tap do |json|
21
+ json["job_class"] = @job_class_name
22
+ end
23
+ end
24
+
25
+ def perform_now
26
+ raise UnsupportedError, "A JobProxy doesn't support immediate execution, only enqueuing."
27
+ end
28
+
29
+ def duration
30
+ ended_at = finished_at || failed_at
31
+ ended_at - scheduled_at if ended_at && scheduled_at
32
+ end
33
+
34
+ ActiveJob::JobsRelation::STATUSES.each do |status|
35
+ define_method "#{status}?" do
36
+ self.status == status
37
+ end
38
+ end
39
+ end