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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +20 -18
  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/job_scoped.rb +1 -1
  6. data/app/controllers/concerns/mission_control/jobs/not_found_redirections.rb +9 -1
  7. data/app/controllers/concerns/mission_control/jobs/queue_scoped.rb +1 -1
  8. data/app/controllers/mission_control/jobs/bulk_discards_controller.rb +1 -1
  9. data/app/controllers/mission_control/jobs/discards_controller.rb +1 -1
  10. data/app/controllers/mission_control/jobs/dispatches_controller.rb +13 -0
  11. data/app/controllers/mission_control/jobs/jobs_controller.rb +5 -3
  12. data/app/controllers/mission_control/jobs/queues_controller.rb +4 -4
  13. data/app/controllers/mission_control/jobs/recurring_tasks_controller.rb +23 -0
  14. data/app/controllers/mission_control/jobs/retries_controller.rb +1 -1
  15. data/app/controllers/mission_control/jobs/workers_controller.rb +6 -1
  16. data/app/helpers/mission_control/jobs/jobs_helper.rb +6 -2
  17. data/app/helpers/mission_control/jobs/navigation_helper.rb +2 -1
  18. data/app/models/mission_control/jobs/page.rb +8 -8
  19. data/app/models/mission_control/jobs/recurring_task.rb +17 -0
  20. data/app/views/layouts/mission_control/jobs/_application_selection.html.erb +1 -1
  21. data/app/views/mission_control/jobs/jobs/_error_information.html.erb +0 -1
  22. data/app/views/mission_control/jobs/jobs/_general_information.html.erb +1 -1
  23. data/app/views/mission_control/jobs/jobs/_jobs_page.html.erb +8 -10
  24. data/app/views/mission_control/jobs/jobs/_title.html.erb +3 -0
  25. data/app/views/mission_control/jobs/jobs/blocked/_actions.html.erb +3 -0
  26. data/app/views/mission_control/jobs/jobs/blocked/_job.html.erb +3 -0
  27. data/app/views/mission_control/jobs/jobs/index.html.erb +2 -2
  28. data/app/views/mission_control/jobs/jobs/scheduled/_actions.html.erb +4 -0
  29. data/app/views/mission_control/jobs/jobs/scheduled/_job.html.erb +4 -1
  30. data/app/views/mission_control/jobs/queues/_queue_title.html.erb +1 -1
  31. data/app/views/mission_control/jobs/queues/show.html.erb +2 -2
  32. data/app/views/mission_control/jobs/recurring_tasks/_general_information.html.erb +16 -0
  33. data/app/views/mission_control/jobs/recurring_tasks/_recurring_task.html.erb +14 -0
  34. data/app/views/mission_control/jobs/recurring_tasks/_title.html.erb +7 -0
  35. data/app/views/mission_control/jobs/recurring_tasks/index.html.erb +16 -0
  36. data/app/views/mission_control/jobs/recurring_tasks/show.html.erb +14 -0
  37. data/app/views/mission_control/jobs/{workers → shared}/_job.html.erb +8 -1
  38. data/app/views/mission_control/jobs/shared/_jobs.html.erb +14 -0
  39. data/app/views/mission_control/jobs/shared/_pagination_toolbar.html.erb +3 -3
  40. data/app/views/mission_control/jobs/workers/_workers_page.html.erb +15 -0
  41. data/app/views/mission_control/jobs/workers/index.html.erb +2 -13
  42. data/app/views/mission_control/jobs/workers/show.html.erb +9 -2
  43. data/config/routes.rb +2 -2
  44. data/lib/active_job/executing.rb +3 -6
  45. data/lib/active_job/failed.rb +0 -4
  46. data/lib/active_job/job_proxy.rb +6 -0
  47. data/lib/active_job/jobs_relation.rb +17 -6
  48. data/lib/active_job/queue_adapters/resque_ext.rb +1 -1
  49. data/lib/active_job/queue_adapters/solid_queue_ext/recurring_tasks.rb +43 -0
  50. data/lib/active_job/queue_adapters/solid_queue_ext/workers.rb +41 -0
  51. data/lib/active_job/queue_adapters/solid_queue_ext.rb +51 -55
  52. data/lib/mission_control/jobs/adapter.rb +67 -19
  53. data/lib/mission_control/jobs/console/helpers.rb +1 -1
  54. data/lib/mission_control/jobs/engine.rb +13 -3
  55. data/lib/mission_control/jobs/server/recurring_tasks.rb +15 -0
  56. data/lib/mission_control/jobs/server/serializable.rb +1 -1
  57. data/lib/mission_control/jobs/server/workers.rb +3 -5
  58. data/lib/mission_control/jobs/server.rb +1 -1
  59. data/lib/mission_control/jobs/version.rb +1 -1
  60. data/lib/mission_control/jobs/workers_relation.rb +78 -0
  61. data/lib/mission_control/jobs.rb +3 -0
  62. metadata +19 -6
  63. data/app/jobs/mission_control/jobs/application_job.rb +0 -6
  64. data/app/mailers/mission_control/jobs/application_mailer.rb +0 -8
  65. 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: 63e12a3c6650d55dd75e269ca604320316a5400c6e7b9429b16a05de167717a7
4
- data.tar.gz: 617b2ea6236f481ed4ee6b6c22e5b72570b75d9e2bcf69e0cf60ba18eec69e06
3
+ metadata.gz: 8ea81c77ce22c040d30fc8246943945d526a85d1999b0cad6120d59b2a8adeda
4
+ data.tar.gz: c26e71001519a3cb2326b20eff4d634e180d81b9c334cc7a41ca2148ecfa610c
5
5
  SHA512:
6
- metadata.gz: 91a3206ed4772d01f441d7fe6ed10b0e8dc27beb6828333b3805ad65cc314d13157c9a6e2d07f0bed46f09f911549d28250244f320a9dc637883135e5c10f627
7
- data.tar.gz: c6f173db8d4d4be438b91ad8925d381f5eb44aabda18fcd9f4b174f91bf80922c779ca7eb71132046c952b496c379209c29c7da1aec94f667f01365a51b79c3a
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, but at a minimum, it allows you to inspect job queues, jobs currently waiting in those queues, and inspecting and retrying or discarding failed jobs.
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, and the following setting:
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, 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 handy here.
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(queue: "some_queue")
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(job_class: "SomeJob")
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(job_class: "SomeJob").limit(10).offset(5)
196
+ ActiveJob.jobs.pending.where(job_class_name: "SomeJob").limit(10).offset(5)
195
197
 
196
- # For adpaters that support these statuses:
198
+ # For adapters that support these statuses:
197
199
  # All scheduled/in-progress/finished jobs of a given class
198
- ActiveJob.jobs.scheudled.where(job_class: "SomeJob")
199
- ActiveJob.jobs.in_progress.where(job_class: "SomeJob")
200
- ActiveJob.jobs.finished.where(job_class: "SomeJob")
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(job_class: "SomeJob").retry_all
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(job_class: "SomeJob").discard_all
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.supported_statuses & ActiveJob::JobsRelation::STATUSES
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
- ApplicationJob.jobs.failed.where(**@job_filters).limit(MAX_NUMBER_OF_JOBS_FOR_BULK_OPERATIONS)
15
+ ActiveJob.jobs.failed.where(**@job_filters).limit(MAX_NUMBER_OF_JOBS_FOR_BULK_OPERATIONS)
16
16
  end
17
17
  end
@@ -2,7 +2,7 @@ module MissionControl::Jobs::JobScoped
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  included do
5
- before_action :set_job, except: :index
5
+ before_action :set_job
6
6
  end
7
7
 
8
8
  private
@@ -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 root_url, alert: error.message
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::Base.queues[params[:queue_id]] or raise MissionControl::Jobs::Errors::ResourceNotFound, "Queue '#{params[:queue_id]}' not found"
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
- ApplicationJob.jobs.failed
17
+ ActiveJob.jobs.failed
18
18
  end
19
19
  end
20
20
  end
@@ -8,6 +8,6 @@ class MissionControl::Jobs::DiscardsController < MissionControl::Jobs::Applicati
8
8
 
9
9
  private
10
10
  def jobs_relation
11
- ApplicationJob.jobs.failed
11
+ ActiveJob.jobs.failed
12
12
  end
13
13
  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 = ApplicationJob.queues.map(&:name)
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
- ApplicationJob.jobs.with_status(jobs_status)
27
+ ActiveJob.jobs.with_status(jobs_status)
26
28
  end
27
29
 
28
30
  def filtered_jobs
29
- ApplicationJob.jobs.where(**@job_filters)
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 = ApplicationJob.queues[params[:id]]
14
+ @queue = ActiveJob.queues[params[:id]]
15
15
  end
16
16
 
17
17
  def filtered_queues
18
- if prefix = ApplicationJob.queue_name_prefix
19
- ApplicationJob.queues.select { |queue| queue.name.start_with?(prefix) }
18
+ if prefix = ActiveJob::Base.queue_name_prefix
19
+ ActiveJob.queues.select { |queue| queue.name.start_with?(prefix) }
20
20
  else
21
- ApplicationJob.queues
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
@@ -8,6 +8,6 @@ class MissionControl::Jobs::RetriesController < MissionControl::Jobs::Applicatio
8
8
 
9
9
  private
10
10
  def jobs_relation
11
- ApplicationJob.jobs.failed
11
+ ActiveJob.jobs.failed
12
12
  end
13
13
  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
- @workers = MissionControl::Jobs::Current.server.workers.sort_by { |worker| -worker.jobs.count }
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 = ApplicationJob.jobs.with_status(status).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 :jobs_relation, :index, :page_size
4
+ attr_reader :records, :index, :page_size
5
5
 
6
- def initialize(jobs_relation, page: 1, page_size: DEFAULT_PAGE_SIZE)
7
- @jobs_relation = jobs_relation
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 jobs
13
- jobs_relation.limit(page_size).offset(offset)
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? || jobs.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 / 10).ceil unless total_count.infinite?
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 ||= jobs_relation.count # Potentially expensive when filtering and a lot of jobs, with adapter in charge of doing the filtering in memory
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,5 +1,5 @@
1
1
  <nav class="navbar" role="navigation" aria-label="main navigation">
2
- <div class="navbar-menu is-active">
2
+ <div class="navbar-menu is-active mb-4">
3
3
  <div class="navbar-start">
4
4
  </div>
5
5
 
@@ -1,5 +1,4 @@
1
1
  <% if job.failed? %>
2
- <a id="error"></a>
3
2
  <h2 class="subtitle">Error information</h2>
4
3
 
5
4
  <table class="table">
@@ -10,7 +10,7 @@
10
10
  </tr>
11
11
  <tr>
12
12
  <th>Job id</th>
13
- <td><%= job.job_id %> </td>
13
+ <td><%= job.job_id %></td>
14
14
  </tr>
15
15
  <tr>
16
16
  <th>Queue</th>
@@ -1,15 +1,13 @@
1
1
  <table class="jobs <%= jobs_status %> table queues is-hoverable is-fullwidth">
2
- <tbody>
3
2
  <thead>
4
- <tr>
5
- <th style="width: 35%;">Job</th>
6
- <% attribute_names_for_job_status(jobs_status).each do |attribute| %>
7
- <th><%= attribute %></th>
8
- <% end %>
9
- </tr>
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
- <%= render partial: "mission_control/jobs/jobs/job", collection: jobs_page.jobs %>
13
-
10
+ <tbody>
11
+ <%= render partial: "mission_control/jobs/jobs/job", collection: jobs_page.records %>
14
12
  </tbody>
15
13
  </table>
@@ -8,6 +8,9 @@
8
8
  <% if job.failed? %>
9
9
  <%= render "mission_control/jobs/jobs/failed/actions", job: job %>
10
10
  <% end %>
11
+ <% if job.blocked? %>
12
+ <%= render "mission_control/jobs/jobs/blocked/actions", job: job %>
13
+ <% end %>
11
14
  </div>
12
15
  </div>
13
16
  </h1>
@@ -0,0 +1,3 @@
1
+ <div class="buttons is-right">
2
+ <%= button_to "Dispatch", application_job_dispatch_path(@application, job.job_id), class: "button is-warning is-light mr-0" %>
3
+ </div>
@@ -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", jobs_page: @jobs_page %>
16
+ <%= render "mission_control/jobs/shared/pagination_toolbar", page: @jobs_page, filter_param: jobs_filter_param %>
17
17
  <% end %>
18
18
  <% end %>
19
19
 
@@ -0,0 +1,4 @@
1
+ <div class="buttons is-right">
2
+ <%= button_to "Discard", application_job_discard_path(@application, job.job_id), class: "button is-danger is-light mr-0",
3
+ form: { data: { turbo_confirm: "This will delete the job and can't be undone. Are you sure?" } } %>
4
+ </div>
@@ -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.scheduled_at.before? 1.minute.ago %>
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,4 +14,4 @@
14
14
  </div>
15
15
  </h1>
16
16
 
17
- <h2 class="subtitle"><%= queue.size %> pending jobs</h2>
17
+ <h2 class="subtitle"><%= pluralize queue.size, "pending job" %></h2>
@@ -14,12 +14,12 @@
14
14
  </tr>
15
15
  </thead>
16
16
 
17
- <%= render partial: "mission_control/jobs/queues/job", collection: @jobs_page.jobs %>
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", jobs_page: @jobs_page %>
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,7 @@
1
+ <h1 class="title">
2
+ <div class="level">
3
+ <div class="level-left">
4
+ <%= recurring_task.id %>
5
+ </div>
6
+ </div>
7
+ </h1>
@@ -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>