mission_control-jobs 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +20 -18
- data/app/controllers/concerns/mission_control/jobs/adapter_features.rb +10 -6
- data/app/controllers/concerns/mission_control/jobs/failed_jobs_bulk_operations.rb +1 -1
- data/app/controllers/concerns/mission_control/jobs/job_scoped.rb +1 -1
- data/app/controllers/concerns/mission_control/jobs/not_found_redirections.rb +9 -1
- data/app/controllers/concerns/mission_control/jobs/queue_scoped.rb +1 -1
- data/app/controllers/mission_control/jobs/bulk_discards_controller.rb +1 -1
- data/app/controllers/mission_control/jobs/discards_controller.rb +1 -1
- data/app/controllers/mission_control/jobs/dispatches_controller.rb +13 -0
- data/app/controllers/mission_control/jobs/jobs_controller.rb +5 -3
- data/app/controllers/mission_control/jobs/queues_controller.rb +4 -4
- data/app/controllers/mission_control/jobs/recurring_tasks_controller.rb +23 -0
- data/app/controllers/mission_control/jobs/retries_controller.rb +1 -1
- data/app/controllers/mission_control/jobs/workers_controller.rb +6 -1
- data/app/helpers/mission_control/jobs/jobs_helper.rb +6 -2
- data/app/helpers/mission_control/jobs/navigation_helper.rb +2 -1
- data/app/models/mission_control/jobs/page.rb +8 -8
- data/app/models/mission_control/jobs/recurring_task.rb +17 -0
- data/app/views/layouts/mission_control/jobs/_application_selection.html.erb +1 -1
- data/app/views/mission_control/jobs/jobs/_error_information.html.erb +0 -1
- data/app/views/mission_control/jobs/jobs/_general_information.html.erb +1 -1
- data/app/views/mission_control/jobs/jobs/_jobs_page.html.erb +8 -10
- data/app/views/mission_control/jobs/jobs/_title.html.erb +3 -0
- data/app/views/mission_control/jobs/jobs/blocked/_actions.html.erb +3 -0
- data/app/views/mission_control/jobs/jobs/blocked/_job.html.erb +3 -0
- data/app/views/mission_control/jobs/jobs/index.html.erb +2 -2
- data/app/views/mission_control/jobs/jobs/scheduled/_actions.html.erb +4 -0
- data/app/views/mission_control/jobs/jobs/scheduled/_job.html.erb +4 -1
- data/app/views/mission_control/jobs/queues/_queue_title.html.erb +1 -1
- data/app/views/mission_control/jobs/queues/show.html.erb +2 -2
- data/app/views/mission_control/jobs/recurring_tasks/_general_information.html.erb +16 -0
- data/app/views/mission_control/jobs/recurring_tasks/_recurring_task.html.erb +14 -0
- data/app/views/mission_control/jobs/recurring_tasks/_title.html.erb +7 -0
- data/app/views/mission_control/jobs/recurring_tasks/index.html.erb +16 -0
- data/app/views/mission_control/jobs/recurring_tasks/show.html.erb +14 -0
- data/app/views/mission_control/jobs/{workers → shared}/_job.html.erb +8 -1
- data/app/views/mission_control/jobs/shared/_jobs.html.erb +14 -0
- data/app/views/mission_control/jobs/shared/_pagination_toolbar.html.erb +3 -3
- data/app/views/mission_control/jobs/workers/_workers_page.html.erb +15 -0
- data/app/views/mission_control/jobs/workers/index.html.erb +2 -13
- data/app/views/mission_control/jobs/workers/show.html.erb +9 -2
- data/config/routes.rb +2 -2
- data/lib/active_job/executing.rb +3 -6
- data/lib/active_job/failed.rb +0 -4
- data/lib/active_job/job_proxy.rb +6 -0
- data/lib/active_job/jobs_relation.rb +17 -6
- data/lib/active_job/queue_adapters/resque_ext.rb +1 -1
- data/lib/active_job/queue_adapters/solid_queue_ext/recurring_tasks.rb +43 -0
- data/lib/active_job/queue_adapters/solid_queue_ext/workers.rb +41 -0
- data/lib/active_job/queue_adapters/solid_queue_ext.rb +51 -55
- data/lib/mission_control/jobs/adapter.rb +67 -19
- data/lib/mission_control/jobs/console/helpers.rb +1 -1
- data/lib/mission_control/jobs/engine.rb +13 -3
- data/lib/mission_control/jobs/server/recurring_tasks.rb +15 -0
- data/lib/mission_control/jobs/server/serializable.rb +1 -1
- data/lib/mission_control/jobs/server/workers.rb +3 -5
- data/lib/mission_control/jobs/server.rb +1 -1
- data/lib/mission_control/jobs/version.rb +1 -1
- data/lib/mission_control/jobs/workers_relation.rb +78 -0
- data/lib/mission_control/jobs.rb +3 -0
- metadata +19 -6
- data/app/jobs/mission_control/jobs/application_job.rb +0 -6
- data/app/mailers/mission_control/jobs/application_mailer.rb +0 -8
- data/app/views/mission_control/jobs/workers/_jobs.html.erb +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8ea81c77ce22c040d30fc8246943945d526a85d1999b0cad6120d59b2a8adeda
|
4
|
+
data.tar.gz: c26e71001519a3cb2326b20eff4d634e180d81b9c334cc7a41ca2148ecfa610c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 611f367a1baab8aa025f761abbd9bf6e10cc60ab9c4475d7487eea37e54d9b95e63bd87999d96a018586d198ee59a1635b237756d17480efc255895cdf144059
|
7
|
+
data.tar.gz: 5b958d96eb931ff86b0fe98ac437a6d1a4c87c2c9cc6363845ad35082317ab2b0de9566ad22e791831d0e2385172659df42ad2db27bac8cc5bc60646d8cc1029
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Mission Control — Jobs
|
2
2
|
|
3
|
-
This gem provides a Rails-based frontend to Active Job adapters. It currently supports [Resque](https://github.com/resque/resque/) and [Solid Queue](https://github.com/basecamp/solid_queue). Its features depend on those offered by the adapter itself
|
3
|
+
This gem provides a Rails-based frontend to Active Job adapters. It currently supports [Resque](https://github.com/resque/resque/) and [Solid Queue](https://github.com/basecamp/solid_queue). Its features depend on those offered by the adapter itself. At a minimum, it allows you to inspect job queues and jobs currently waiting in those queues and inspect and retry or discard failed jobs.
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
Add this line to your application's Gemfile:
|
@@ -54,13 +54,15 @@ Besides `base_controller_class`, you can also set the following for `MissionCont
|
|
54
54
|
- `logger`: the logger you want Mission Control Jobs 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.
|
55
55
|
- `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`
|
56
56
|
- `adapters`: a list of adapters that you want Mission Control to use and extend. By default this will be the adapter you have set for `active_job.queue_adapter`.
|
57
|
+
- `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`
|
58
|
+
- `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).
|
57
59
|
|
58
|
-
This library extends Active Job with a querying interface
|
60
|
+
This library extends Active Job with a querying interface and the following setting:
|
59
61
|
- `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`.
|
60
62
|
|
61
63
|
## Advanced configuration
|
62
64
|
|
63
|
-
When we built Mission Control Jobs, we did it with the idea of managing multiple apps' job backends from a single, centralized app that we used for monitoring, alerts and other tools that related to all our apps. Some of our apps run in more than one datacenter and we run different Resque instances with different Redis configurations in each. Because of this, we added support for multiple apps and multiple adapters per app. Even when running Mission Control Job within the app it manages and a single DC, as we migrated from Resque to Solid Queue, we needed to manage both adapters from Mission Control.
|
65
|
+
When we built Mission Control Jobs, we did it with the idea of managing multiple apps' job backends from a single, centralized app that we used for monitoring, alerts and other tools that related to all our apps. Some of our apps run in more than one datacenter, and we run different Resque instances with different Redis configurations in each. Because of this, we added support for multiple apps and multiple adapters per app. Even when running Mission Control Job within the app it manages, and a single DC, as we migrated from Resque to Solid Queue, we needed to manage both adapters from Mission Control.
|
64
66
|
|
65
67
|
Without adding any additional configuration to [the one described before](#basic-configuration), Mission Control will be configured with one single app and a single server for your configured `active_job.queue_adapter`.
|
66
68
|
|
@@ -105,9 +107,9 @@ SERVERS_BY_APP.each do |app, servers|
|
|
105
107
|
end
|
106
108
|
```
|
107
109
|
|
108
|
-
This is an example for two different apps, BC4 and HEY, each one with two servers. BC4 has two Resque servers with two different configurations and HEY has one Resque server and one Solid Queue server.
|
110
|
+
This is an example for two different apps, BC4 and HEY, each one with two servers. BC4 has two Resque servers with two different configurations, and HEY has one Resque server and one Solid Queue server.
|
109
111
|
|
110
|
-
Currently only one Solid Queue configuration is supported, but support for several Solid Queue backends (with different databases) [is planned](https://github.com/basecamp/mission_control-jobs/issues/35).
|
112
|
+
Currently, only one Solid Queue configuration is supported, but support for several Solid Queue backends (with different databases) [is planned](https://github.com/basecamp/mission_control-jobs/issues/35).
|
111
113
|
|
112
114
|
This is how we set Resque and Solid Queue together when we migrated from one to the other:
|
113
115
|
|
@@ -141,9 +143,9 @@ As mentioned, the features available in Mission Control depend on the adapter yo
|
|
141
143
|
|
142
144
|
## Console helpers, scripting and dealing with big sets of jobs
|
143
145
|
|
144
|
-
Besides the UI, Mission Control provides a light console helper to switch between applications and adapters. 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.
|
146
|
+
Besides the UI, Mission Control provides a light console helper to switch between applications and adapters. 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.
|
145
147
|
|
146
|
-
It's also possible that you need to deal with very big sets of jobs that are unmanageable via the UI
|
148
|
+
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.
|
147
149
|
|
148
150
|
First, when connecting to the Rails console, you'll see this new message:
|
149
151
|
|
@@ -159,7 +161,7 @@ Typing `jobs_help`, you'll get clear instructions about how to switch between ap
|
|
159
161
|
```
|
160
162
|
>> jobs_help
|
161
163
|
You can connect to a job server with
|
162
|
-
connect_to <app_id>:<server_id>
|
164
|
+
connect_to "<app_id>:<server_id>"
|
163
165
|
|
164
166
|
Available job servers:
|
165
167
|
* bc4:resque_ashburn
|
@@ -185,19 +187,19 @@ ActiveJob.jobs
|
|
185
187
|
ActiveJob.jobs.failed
|
186
188
|
|
187
189
|
# All pending jobs in some queue
|
188
|
-
ActiveJob.jobs.pending.where(
|
190
|
+
ActiveJob.jobs.pending.where(queue_name: "some_queue")
|
189
191
|
|
190
192
|
# All failed jobs of a given class
|
191
|
-
ActiveJob.jobs.failed.where(
|
193
|
+
ActiveJob.jobs.failed.where(job_class_name: "SomeJob")
|
192
194
|
|
193
195
|
# All pending jobs of a given class with limit and offset
|
194
|
-
ActiveJob.jobs.pending.where(
|
196
|
+
ActiveJob.jobs.pending.where(job_class_name: "SomeJob").limit(10).offset(5)
|
195
197
|
|
196
|
-
# For
|
198
|
+
# For adapters that support these statuses:
|
197
199
|
# All scheduled/in-progress/finished jobs of a given class
|
198
|
-
ActiveJob.jobs.
|
199
|
-
ActiveJob.jobs.in_progress.where(
|
200
|
-
ActiveJob.jobs.finished.where(
|
200
|
+
ActiveJob.jobs.scheduled.where(job_class_name: "SomeJob")
|
201
|
+
ActiveJob.jobs.in_progress.where(job_class_name: "SomeJob")
|
202
|
+
ActiveJob.jobs.finished.where(job_class_name: "SomeJob")
|
201
203
|
|
202
204
|
# For adapters that support filtering by worker:
|
203
205
|
# All jobs in progress being run by a given worker
|
@@ -211,13 +213,13 @@ Some examples of bulk operations:
|
|
211
213
|
ActiveJob.jobs.failed.retry_all
|
212
214
|
|
213
215
|
# Retry all the jobs of a given class (only possible for failed jobs)
|
214
|
-
ActiveJob.jobs.failed.where(
|
216
|
+
ActiveJob.jobs.failed.where(job_class_name: "SomeJob").retry_all
|
215
217
|
|
216
218
|
# Discard all failed jobs
|
217
219
|
ActiveJob.jobs.failed.discard_all
|
218
220
|
|
219
221
|
# Discard all pending jobs of a given class
|
220
|
-
ActiveJob.jobs.pending.where(
|
222
|
+
ActiveJob.jobs.pending.where(job_class_name: "SomeJob").discard_all
|
221
223
|
# Or all pending jobs in a given queue:
|
222
224
|
ActiveJob.jobs.pending.where(queue_name: "some-queue").discard_all
|
223
225
|
```
|
@@ -229,7 +231,7 @@ MissionControl::Jobs.delay_between_bulk_operation_batches = 5.seconds
|
|
229
231
|
|
230
232
|
## Contributing
|
231
233
|
|
232
|
-
Thanks for your interest in contributing! To get the app running locally just run:
|
234
|
+
Thanks for your interest in contributing! To get the app running locally, just run:
|
233
235
|
```
|
234
236
|
bin/setup
|
235
237
|
```
|
@@ -2,19 +2,23 @@ module MissionControl::Jobs::AdapterFeatures
|
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
4
|
included do
|
5
|
-
helper_method :supported_job_statuses, :queue_pausing_supported?, :workers_exposed?
|
5
|
+
helper_method :supported_job_statuses, :queue_pausing_supported?, :workers_exposed?, :recurring_tasks_supported?
|
6
6
|
end
|
7
7
|
|
8
8
|
private
|
9
|
-
def workers_exposed?
|
10
|
-
MissionControl::Jobs::Current.server.queue_adapter.exposes_workers?
|
11
|
-
end
|
12
|
-
|
13
9
|
def supported_job_statuses
|
14
|
-
MissionControl::Jobs::Current.server.queue_adapter.
|
10
|
+
MissionControl::Jobs::Current.server.queue_adapter.supported_job_statuses & ActiveJob::JobsRelation::STATUSES
|
15
11
|
end
|
16
12
|
|
17
13
|
def queue_pausing_supported?
|
18
14
|
MissionControl::Jobs::Current.server.queue_adapter.supports_queue_pausing?
|
19
15
|
end
|
16
|
+
|
17
|
+
def workers_exposed?
|
18
|
+
MissionControl::Jobs::Current.server.queue_adapter.exposes_workers?
|
19
|
+
end
|
20
|
+
|
21
|
+
def recurring_tasks_supported?
|
22
|
+
MissionControl::Jobs::Current.server.queue_adapter.supports_recurring_tasks?
|
23
|
+
end
|
20
24
|
end
|
@@ -12,6 +12,6 @@ module MissionControl::Jobs::FailedJobsBulkOperations
|
|
12
12
|
# or causing replication lag in MySQL). This should be enough for most scenarios. For
|
13
13
|
# cases where we need to retry a huge sets of jobs, we offer a runbook that uses the API.
|
14
14
|
def bulk_limited_filtered_failed_jobs
|
15
|
-
|
15
|
+
ActiveJob.jobs.failed.where(**@job_filters).limit(MAX_NUMBER_OF_JOBS_FOR_BULK_OPERATIONS)
|
16
16
|
end
|
17
17
|
end
|
@@ -7,7 +7,7 @@ module MissionControl::Jobs::NotFoundRedirections
|
|
7
7
|
end
|
8
8
|
|
9
9
|
rescue_from(MissionControl::Jobs::Errors::ResourceNotFound) do |error|
|
10
|
-
redirect_to
|
10
|
+
redirect_to best_location_for_resource_not_found_error(error), alert: error.message
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
@@ -22,4 +22,12 @@ module MissionControl::Jobs::NotFoundRedirections
|
|
22
22
|
root_path
|
23
23
|
end
|
24
24
|
end
|
25
|
+
|
26
|
+
def best_location_for_resource_not_found_error(error)
|
27
|
+
if error.message.match?(/recurring task/i)
|
28
|
+
application_recurring_tasks_path(@application)
|
29
|
+
else
|
30
|
+
root_url
|
31
|
+
end
|
32
|
+
end
|
25
33
|
end
|
@@ -7,6 +7,6 @@ module MissionControl::Jobs::QueueScoped
|
|
7
7
|
|
8
8
|
private
|
9
9
|
def set_queue
|
10
|
-
@queue = ActiveJob
|
10
|
+
@queue = ActiveJob.queues[params[:queue_id]] or raise MissionControl::Jobs::Errors::ResourceNotFound, "Queue '#{params[:queue_id]}' not found"
|
11
11
|
end
|
12
12
|
end
|
@@ -14,7 +14,7 @@ class MissionControl::Jobs::BulkDiscardsController < MissionControl::Jobs::Appli
|
|
14
14
|
bulk_limited_filtered_failed_jobs
|
15
15
|
else
|
16
16
|
# we don't want to apply any limit since "discarding all" without parameters can be optimized in the adapter as a much faster operation
|
17
|
-
|
17
|
+
ActiveJob.jobs.failed
|
18
18
|
end
|
19
19
|
end
|
20
20
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class MissionControl::Jobs::DispatchesController < MissionControl::Jobs::ApplicationController
|
2
|
+
include MissionControl::Jobs::JobScoped
|
3
|
+
|
4
|
+
def create
|
5
|
+
@job.dispatch
|
6
|
+
redirect_to application_jobs_url(@application, :blocked), notice: "Dispatched job with id #{@job.job_id}"
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
def jobs_relation
|
11
|
+
ApplicationJob.jobs.blocked
|
12
|
+
end
|
13
|
+
end
|
@@ -1,9 +1,11 @@
|
|
1
1
|
class MissionControl::Jobs::JobsController < MissionControl::Jobs::ApplicationController
|
2
2
|
include MissionControl::Jobs::JobScoped, MissionControl::Jobs::JobFilters
|
3
3
|
|
4
|
+
skip_before_action :set_job, only: :index
|
5
|
+
|
4
6
|
def index
|
5
7
|
@job_class_names = jobs_with_status.job_class_names
|
6
|
-
@queue_names =
|
8
|
+
@queue_names = ActiveJob.queues.map(&:name)
|
7
9
|
|
8
10
|
@jobs_page = MissionControl::Jobs::Page.new(filtered_jobs_with_status, page: params[:page].to_i)
|
9
11
|
@jobs_count = @jobs_page.total_count
|
@@ -22,11 +24,11 @@ class MissionControl::Jobs::JobsController < MissionControl::Jobs::ApplicationCo
|
|
22
24
|
end
|
23
25
|
|
24
26
|
def jobs_with_status
|
25
|
-
|
27
|
+
ActiveJob.jobs.with_status(jobs_status)
|
26
28
|
end
|
27
29
|
|
28
30
|
def filtered_jobs
|
29
|
-
|
31
|
+
ActiveJob.jobs.where(**@job_filters)
|
30
32
|
end
|
31
33
|
|
32
34
|
helper_method :jobs_status
|
@@ -11,14 +11,14 @@ class MissionControl::Jobs::QueuesController < MissionControl::Jobs::Application
|
|
11
11
|
|
12
12
|
private
|
13
13
|
def set_queue
|
14
|
-
@queue =
|
14
|
+
@queue = ActiveJob.queues[params[:id]]
|
15
15
|
end
|
16
16
|
|
17
17
|
def filtered_queues
|
18
|
-
if prefix =
|
19
|
-
|
18
|
+
if prefix = ActiveJob::Base.queue_name_prefix
|
19
|
+
ActiveJob.queues.select { |queue| queue.name.start_with?(prefix) }
|
20
20
|
else
|
21
|
-
|
21
|
+
ActiveJob.queues
|
22
22
|
end
|
23
23
|
end
|
24
24
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class MissionControl::Jobs::RecurringTasksController < MissionControl::Jobs::ApplicationController
|
2
|
+
before_action :ensure_supported_recurring_tasks
|
3
|
+
before_action :set_recurring_task, only: :show
|
4
|
+
|
5
|
+
def index
|
6
|
+
@recurring_tasks = MissionControl::Jobs::Current.server.recurring_tasks
|
7
|
+
end
|
8
|
+
|
9
|
+
def show
|
10
|
+
@jobs_page = MissionControl::Jobs::Page.new(@recurring_task.jobs, page: params[:page].to_i)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
def ensure_supported_recurring_tasks
|
15
|
+
unless recurring_tasks_supported?
|
16
|
+
redirect_to root_url, alert: "This server doesn't support recurring tasks"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def set_recurring_task
|
21
|
+
@recurring_task = MissionControl::Jobs::Current.server.find_recurring_task(params[:id])
|
22
|
+
end
|
23
|
+
end
|
@@ -2,7 +2,8 @@ class MissionControl::Jobs::WorkersController < MissionControl::Jobs::Applicatio
|
|
2
2
|
before_action :ensure_exposed_workers
|
3
3
|
|
4
4
|
def index
|
5
|
-
@
|
5
|
+
@workers_page = MissionControl::Jobs::Page.new(workers_relation, page: params[:page].to_i)
|
6
|
+
@workers_count = @workers_page.total_count
|
6
7
|
end
|
7
8
|
|
8
9
|
def show
|
@@ -15,4 +16,8 @@ class MissionControl::Jobs::WorkersController < MissionControl::Jobs::Applicatio
|
|
15
16
|
redirect_to root_url, alert: "This server doesn't expose workers"
|
16
17
|
end
|
17
18
|
end
|
19
|
+
|
20
|
+
def workers_relation
|
21
|
+
MissionControl::Jobs::Current.server.workers_relation
|
22
|
+
end
|
18
23
|
end
|
@@ -18,14 +18,18 @@ module MissionControl::Jobs::JobsHelper
|
|
18
18
|
def attribute_names_for_job_status(status)
|
19
19
|
case status.to_s
|
20
20
|
when "failed" then [ "Error", "" ]
|
21
|
-
when "blocked" then [ "Queue", "Blocked by", "Block expiry" ]
|
21
|
+
when "blocked" then [ "Queue", "Blocked by", "Block expiry", "" ]
|
22
22
|
when "finished" then [ "Queue", "Finished" ]
|
23
|
-
when "scheduled" then [ "Queue", "Scheduled" ]
|
23
|
+
when "scheduled" then [ "Queue", "Scheduled", "" ]
|
24
24
|
when "in_progress" then [ "Queue", "Run by", "Running for" ]
|
25
25
|
else []
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
+
def job_delayed?(job)
|
30
|
+
job.scheduled_at.before?(MissionControl::Jobs.scheduled_job_delay_threshold.ago)
|
31
|
+
end
|
32
|
+
|
29
33
|
private
|
30
34
|
def renderable_job_arguments_for(job)
|
31
35
|
job.serialized_arguments.collect do |argument|
|
@@ -8,6 +8,7 @@ module MissionControl::Jobs::NavigationHelper
|
|
8
8
|
end
|
9
9
|
|
10
10
|
sections[:workers] = [ "Workers", application_workers_path(@application) ] if workers_exposed?
|
11
|
+
sections[:recurring_tasks] = [ "Recurring tasks", application_recurring_tasks_path(@application) ] if recurring_tasks_supported?
|
11
12
|
end
|
12
13
|
end
|
13
14
|
|
@@ -45,7 +46,7 @@ module MissionControl::Jobs::NavigationHelper
|
|
45
46
|
end
|
46
47
|
|
47
48
|
def jobs_count_with_status(status)
|
48
|
-
count =
|
49
|
+
count = ActiveJob.jobs.with_status(status).count
|
49
50
|
count.infinite? ? "..." : number_to_human(count)
|
50
51
|
end
|
51
52
|
end
|
@@ -1,16 +1,16 @@
|
|
1
1
|
class MissionControl::Jobs::Page
|
2
2
|
DEFAULT_PAGE_SIZE = 10
|
3
3
|
|
4
|
-
attr_reader :
|
4
|
+
attr_reader :records, :index, :page_size
|
5
5
|
|
6
|
-
def initialize(
|
7
|
-
@
|
6
|
+
def initialize(relation, page: 1, page_size: DEFAULT_PAGE_SIZE)
|
7
|
+
@relation = relation
|
8
8
|
@page_size = page_size
|
9
9
|
@index = [ page, 1 ].max
|
10
10
|
end
|
11
11
|
|
12
|
-
def
|
13
|
-
|
12
|
+
def records
|
13
|
+
@relation.limit(page_size).offset(offset)
|
14
14
|
end
|
15
15
|
|
16
16
|
def first?
|
@@ -18,7 +18,7 @@ class MissionControl::Jobs::Page
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def last?
|
21
|
-
index == pages_count || empty? ||
|
21
|
+
index == pages_count || empty? || records.empty?
|
22
22
|
end
|
23
23
|
|
24
24
|
def empty?
|
@@ -34,11 +34,11 @@ class MissionControl::Jobs::Page
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def pages_count
|
37
|
-
(total_count.to_f /
|
37
|
+
(total_count.to_f / page_size).ceil unless total_count.infinite?
|
38
38
|
end
|
39
39
|
|
40
40
|
def total_count
|
41
|
-
@total_count ||=
|
41
|
+
@total_count ||= @relation.count # Potentially expensive when filtering a lot of records, with the adapter in charge of doing the filtering in memory
|
42
42
|
end
|
43
43
|
|
44
44
|
private
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class MissionControl::Jobs::RecurringTask
|
2
|
+
include ActiveModel::Model
|
3
|
+
|
4
|
+
attr_accessor :id, :job_class_name, :arguments, :schedule, :last_enqueued_at
|
5
|
+
|
6
|
+
def initialize(queue_adapter: ActiveJob::Base.queue_adapter, **kwargs)
|
7
|
+
@queue_adapter = queue_adapter
|
8
|
+
super(**kwargs)
|
9
|
+
end
|
10
|
+
|
11
|
+
def jobs
|
12
|
+
ActiveJob::JobsRelation.new(queue_adapter: queue_adapter).where(recurring_task_id: id)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
attr_reader :queue_adapter
|
17
|
+
end
|
@@ -1,15 +1,13 @@
|
|
1
1
|
<table class="jobs <%= jobs_status %> table queues is-hoverable is-fullwidth">
|
2
|
-
<tbody>
|
3
2
|
<thead>
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
3
|
+
<tr>
|
4
|
+
<th style="width: 35%;">Job</th>
|
5
|
+
<% attribute_names_for_job_status(jobs_status).each do |attribute| %>
|
6
|
+
<th><%= attribute %></th>
|
7
|
+
<% end %>
|
8
|
+
</tr>
|
10
9
|
</thead>
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
<tbody>
|
11
|
+
<%= render partial: "mission_control/jobs/jobs/job", collection: jobs_page.records %>
|
14
12
|
</tbody>
|
15
13
|
</table>
|
@@ -1,3 +1,6 @@
|
|
1
1
|
<td><%= link_to job.queue_name, application_queue_path(@application, job.queue) %></td>
|
2
2
|
<td><div class="is-family-monospace is-size-7"><%= job.blocked_by %></div></td>
|
3
3
|
<td><%= bidirectional_time_distance_in_words_with_title(job.blocked_until) %></td>
|
4
|
+
<td class="pr-0">
|
5
|
+
<%= render "mission_control/jobs/jobs/blocked/actions", job: job %>
|
6
|
+
</td>
|
@@ -3,7 +3,7 @@
|
|
3
3
|
<% if @jobs_page.empty? && !active_filters? %>
|
4
4
|
<%= blank_status_notice "There are no #{jobs_status.dasherize} jobs #{blank_status_emoji(jobs_status)}" %>
|
5
5
|
<% else %>
|
6
|
-
<div class="level">
|
6
|
+
<div class="level is-flex-wrap-wrap">
|
7
7
|
<%= render "mission_control/jobs/jobs/filters", job_class_names: @job_class_names, queue_names: @queue_names %>
|
8
8
|
<%= render "mission_control/jobs/jobs/toolbar", jobs_count: @jobs_count %>
|
9
9
|
</div>
|
@@ -13,7 +13,7 @@
|
|
13
13
|
<% else %>
|
14
14
|
<%= render "mission_control/jobs/jobs/jobs_page", jobs_page: @jobs_page %>
|
15
15
|
|
16
|
-
<%= render "mission_control/jobs/shared/pagination_toolbar",
|
16
|
+
<%= render "mission_control/jobs/shared/pagination_toolbar", page: @jobs_page, filter_param: jobs_filter_param %>
|
17
17
|
<% end %>
|
18
18
|
<% end %>
|
19
19
|
|
@@ -1,7 +1,10 @@
|
|
1
1
|
<td><%= link_to job.queue_name, application_queue_path(@application, job.queue) %></td>
|
2
2
|
<td>
|
3
3
|
<%= bidirectional_time_distance_in_words_with_title(job.scheduled_at) %>
|
4
|
-
<% if job
|
4
|
+
<% if job_delayed?(job) %>
|
5
5
|
<div class="is-danger tag ml-4">delayed</div>
|
6
6
|
<% end %>
|
7
7
|
</td>
|
8
|
+
<td class="pr-0">
|
9
|
+
<%= render "mission_control/jobs/jobs/scheduled/actions", job: job %>
|
10
|
+
</td>
|
@@ -14,12 +14,12 @@
|
|
14
14
|
</tr>
|
15
15
|
</thead>
|
16
16
|
|
17
|
-
<%= render partial: "mission_control/jobs/queues/job", collection: @jobs_page.
|
17
|
+
<%= render partial: "mission_control/jobs/queues/job", collection: @jobs_page.records %>
|
18
18
|
|
19
19
|
</tbody>
|
20
20
|
</table>
|
21
21
|
|
22
|
-
<%= render "mission_control/jobs/shared/pagination_toolbar",
|
22
|
+
<%= render "mission_control/jobs/shared/pagination_toolbar", page: @jobs_page, filter_param: {} %>
|
23
23
|
<% end %>
|
24
24
|
|
25
25
|
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<table class="table">
|
2
|
+
<tbody>
|
3
|
+
<tr>
|
4
|
+
<th>Job class</th>
|
5
|
+
<td><%= recurring_task.job_class_name %></td>
|
6
|
+
</tr>
|
7
|
+
<tr>
|
8
|
+
<th>Arguments</th>
|
9
|
+
<td><div class="is-family-monospace"><%= recurring_task.arguments.join(",") %></div></td>
|
10
|
+
</tr>
|
11
|
+
<tr>
|
12
|
+
<th>Schedule</th>
|
13
|
+
<td><%= recurring_task.schedule %></td>
|
14
|
+
</tr>
|
15
|
+
</tbody>
|
16
|
+
</table>
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<tr class="recurring_task">
|
2
|
+
<td>
|
3
|
+
<%= link_to recurring_task.id, application_recurring_task_path(@application, recurring_task.id) %>
|
4
|
+
</td>
|
5
|
+
<td>
|
6
|
+
<%= link_to recurring_task.job_class_name, application_recurring_task_path(@application, recurring_task.id) %>
|
7
|
+
|
8
|
+
<% if recurring_task.arguments.present? %>
|
9
|
+
<div class="is-family-monospace"><%= recurring_task.arguments.join(",") %></div>
|
10
|
+
<% end %>
|
11
|
+
</td>
|
12
|
+
<td> <%= recurring_task.schedule %> </td>
|
13
|
+
<td><div class="has-text-grey"><%= recurring_task.last_enqueued_at ? bidirectional_time_distance_in_words_with_title(recurring_task.last_enqueued_at) : "Never" %></div></td>
|
14
|
+
</tr>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<% navigation(title: "Recurring tasks", section: :recurring_tasks) %>
|
2
|
+
|
3
|
+
<table class="recurring-tasks table jobs is-hoverable is-fullwidth">
|
4
|
+
<tbody>
|
5
|
+
<thead>
|
6
|
+
<tr>
|
7
|
+
<th></th>
|
8
|
+
<th>Job</th>
|
9
|
+
<th>Schedule</th>
|
10
|
+
<th>Last enqueued at</th>
|
11
|
+
</tr>
|
12
|
+
</thead>
|
13
|
+
|
14
|
+
<%= render partial: "mission_control/jobs/recurring_tasks/recurring_task", collection: @recurring_tasks %>
|
15
|
+
</tbody>
|
16
|
+
</table>
|