mission_control-jobs 0.3.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +19 -1
  3. data/app/assets/config/mission_control_jobs_manifest.js +4 -0
  4. data/app/assets/stylesheets/mission_control/jobs/application.css +16 -2
  5. data/app/assets/stylesheets/mission_control/jobs/jobs.css +31 -2
  6. data/app/controllers/mission_control/jobs/application_controller.rb +0 -14
  7. data/app/controllers/mission_control/jobs/discards_controller.rb +6 -1
  8. data/app/controllers/mission_control/jobs/jobs_controller.rb +1 -0
  9. data/app/helpers/mission_control/jobs/dates_helper.rb +2 -16
  10. data/app/helpers/mission_control/jobs/jobs_helper.rb +12 -3
  11. data/app/helpers/mission_control/jobs/navigation_helper.rb +13 -1
  12. data/app/models/mission_control/jobs/recurring_task.rb +1 -1
  13. data/app/views/layouts/mission_control/jobs/application.html.erb +2 -2
  14. data/app/views/mission_control/jobs/jobs/_error_information.html.erb +5 -1
  15. data/app/views/mission_control/jobs/jobs/_filters.html.erb +4 -4
  16. data/app/views/mission_control/jobs/jobs/_general_information.html.erb +3 -3
  17. data/app/views/mission_control/jobs/jobs/_job.html.erb +1 -1
  18. data/app/views/mission_control/jobs/jobs/_jobs_page.html.erb +1 -1
  19. data/app/views/mission_control/jobs/jobs/_title.html.erb +3 -0
  20. data/app/views/mission_control/jobs/jobs/blocked/_job.html.erb +1 -1
  21. data/app/views/mission_control/jobs/jobs/failed/_backtrace_toggle.html.erb +14 -0
  22. data/app/views/mission_control/jobs/jobs/failed/_job.html.erb +1 -1
  23. data/app/views/mission_control/jobs/jobs/finished/_job.html.erb +1 -1
  24. data/app/views/mission_control/jobs/jobs/in_progress/_job.html.erb +1 -1
  25. data/app/views/mission_control/jobs/jobs/scheduled/_job.html.erb +1 -1
  26. data/app/views/mission_control/jobs/queues/_job.html.erb +1 -1
  27. data/app/views/mission_control/jobs/queues/index.html.erb +2 -2
  28. data/app/views/mission_control/jobs/queues/show.html.erb +1 -1
  29. data/app/views/mission_control/jobs/recurring_tasks/_recurring_task.html.erb +2 -1
  30. data/app/views/mission_control/jobs/recurring_tasks/index.html.erb +1 -0
  31. data/app/views/mission_control/jobs/shared/_job.html.erb +3 -3
  32. data/app/views/mission_control/jobs/shared/_jobs.html.erb +2 -2
  33. data/app/views/mission_control/jobs/workers/_worker.html.erb +1 -1
  34. data/app/views/mission_control/jobs/workers/_workers_page.html.erb +1 -1
  35. data/config/importmap.rb +1 -1
  36. data/lib/active_job/queue_adapters/async_ext.rb +49 -0
  37. data/lib/active_job/queue_adapters/resque_ext.rb +2 -2
  38. data/lib/active_job/queue_adapters/solid_queue_ext/recurring_tasks.rb +1 -0
  39. data/lib/mission_control/jobs/application.rb +4 -1
  40. data/lib/mission_control/jobs/engine.rb +9 -16
  41. data/lib/mission_control/jobs/server.rb +3 -2
  42. data/lib/mission_control/jobs/version.rb +1 -1
  43. data/lib/mission_control/jobs.rb +2 -0
  44. metadata +71 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d7d0d51d0662e0351ff33ed1b2a480a7f1d11c593eefbe89a630a980de5a1b24
4
- data.tar.gz: a9c05be3a1b2b0975c3d7095ac3d3433c502b5afd87a942996ced2aa01485b6c
3
+ metadata.gz: 4f46c22e66d7fc57d1543dd2fa8863b8b7284019ea4eb47b879a06702c0d89b9
4
+ data.tar.gz: b891c7389226107a06ce9c5e53b824b91bf3be55e1d3ffe49a2e6419887f5553
5
5
  SHA512:
6
- metadata.gz: de6116de1a5b4fd7b87d4aba9e245953147edaa3cf8dd9e5e464be815dec79d74977dbe60b638246a6fe3625f9cb050aaceb9cd09c73eda857b1756006700328
7
- data.tar.gz: 4e133652b36ec90c22fc9e77170e985d6b18c1a68ae73ce43361a1ea5be9c14820f8db594ce713bbcf7a8a6540766f9f6d4a5974dd51a9510fb94040f57fde76
6
+ metadata.gz: bfc58d081a8dacd1f1c680284a342d6d078f60992dafb0bc2ba5b90cf80e17fe86a240d2fdc5d987adb15231474542f2fe4a067db77884ce2fe523e598ba6b2a
7
+ data.tar.gz: c8dcc5e285e09c4a1b9607633c82565329496fe9d8adab7288a0d39895ac0dc0cb7ffe90ed31d30320c172224659c7a67441bd70c4e4d957c4d643862e955367
data/README.md CHANGED
@@ -57,6 +57,7 @@ Besides `base_controller_class`, you can also set the following for `MissionCont
57
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
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).
59
59
  - `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`.
60
+ - `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.
60
61
 
61
62
  This library extends Active Job with a querying interface and the following setting:
62
63
  - `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`.
@@ -107,7 +108,24 @@ SERVERS_BY_APP.each do |app, servers|
107
108
  ActiveJob::QueueAdapters::SolidQueueAdapter.new
108
109
  end
109
110
 
110
- [ server, queue_adapter ]
111
+ # Default:
112
+ #
113
+ # @return Array<String, ActiveJob::QueueAdapters::Base)
114
+ # An array where:
115
+ # * the String represents the symbolic name for this server within the UI
116
+ # * ActiveJob::QueueAdapters::Base adapter instance used to access this Application Server/Service
117
+ [ server, queue_adapter ]
118
+
119
+ # Optional return formats:
120
+ #
121
+ # @return Array<String, Array<ActiveJob::QueueAdapters::Base>>
122
+ # * This is equivalent, and behaves identically to, the format the default format above.
123
+ # [ server, [ queue_adapter ]] # without optional backtrace cleaner
124
+ #
125
+ # @return Array<String, Array<ActiveJob::QueueAdapters::Base, ActiveSupport::BacktraceCleaner>>
126
+ # * This format adds an optional ActiveSupport::BacktraceCleaner to override the system wide
127
+ # backtrace cleaner for *this* Application Server/Service.
128
+ # [ server, [ queue_adapter, BacktraceCleaner.new ]] # with optional backtrace cleaner
111
129
  end.to_h
112
130
 
113
131
  MissionControl::Jobs.applications.add(app, queue_adapters_by_name)
@@ -0,0 +1,4 @@
1
+ //= link_directory ../stylesheets/mission_control/jobs .css
2
+ //= link_directory ../../javascript/mission_control/jobs .js
3
+ //= link_directory ../../javascript/mission_control/jobs/controllers .js
4
+ //= link_directory ../../javascript/mission_control/jobs/helpers .js
@@ -1,2 +1,16 @@
1
- @import url("./forms.css");
2
- @import url("./jobs.css");
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
16
+
@@ -2,6 +2,35 @@
2
2
  width: 15rem;
3
3
  }
4
4
 
5
- .jobs td {
6
- word-break: break-all;
5
+ table.jobs {
6
+ td {
7
+ word-break: break-all;
8
+ }
9
+ th.job-header{
10
+ width: 30%;
11
+ }
12
+ th.duration-header {
13
+ width: 20%;
14
+ }
15
+ &.queues th.job-header {
16
+ width: 30%;
17
+ }
18
+ &.failed th.job-header {
19
+ width: 35%;
20
+ }
21
+ &.in_progress th.job-header {
22
+ width: 50%;
23
+ }
24
+ &.blocked th.job-header {
25
+ width: 45%;
26
+ }
27
+ &.scheduled th.job-header {
28
+ width: 60%;
29
+ }
30
+ &.finished th.job-header {
31
+ width: 65%;
32
+ }
33
+ &.workers th.job-header {
34
+ width: 40%;
35
+ }
7
36
  }
@@ -1,25 +1,11 @@
1
1
  class MissionControl::Jobs::ApplicationController < MissionControl::Jobs.base_controller_class.constantize
2
- ActionController::Base::MODULES.each do |mod|
3
- include mod unless self < mod
4
- end
5
-
6
2
  layout "mission_control/jobs/application"
7
3
 
8
- # Include helpers if not already included
9
- helper MissionControl::Jobs::ApplicationHelper unless self < MissionControl::Jobs::ApplicationHelper
10
- helper Importmap::ImportmapTagsHelper unless self < Importmap::ImportmapTagsHelper
11
-
12
4
  include MissionControl::Jobs::ApplicationScoped, MissionControl::Jobs::NotFoundRedirections
13
5
  include MissionControl::Jobs::AdapterFeatures
14
6
 
15
- around_action :set_current_locale
16
-
17
7
  private
18
8
  def default_url_options
19
9
  { server_id: MissionControl::Jobs::Current.server }
20
10
  end
21
-
22
- def set_current_locale(&block)
23
- I18n.with_locale(:en, &block)
24
- end
25
11
  end
@@ -3,11 +3,16 @@ class MissionControl::Jobs::DiscardsController < MissionControl::Jobs::Applicati
3
3
 
4
4
  def create
5
5
  @job.discard
6
- redirect_to application_jobs_url(@application, :failed), notice: "Discarded job with id #{@job.job_id}"
6
+ redirect_to redirect_location, notice: "Discarded job with id #{@job.job_id}"
7
7
  end
8
8
 
9
9
  private
10
10
  def jobs_relation
11
11
  ActiveJob.jobs.failed
12
12
  end
13
+
14
+ def redirect_location
15
+ status = @job.status.presence_in(supported_job_statuses) || :failed
16
+ application_jobs_url(@application, status)
17
+ end
13
18
  end
@@ -15,6 +15,7 @@ class MissionControl::Jobs::JobsController < MissionControl::Jobs::ApplicationCo
15
15
  end
16
16
 
17
17
  private
18
+
18
19
  def jobs_relation
19
20
  filtered_jobs
20
21
  end
@@ -1,19 +1,5 @@
1
1
  module MissionControl::Jobs::DatesHelper
2
- def time_ago_in_words_with_title(time)
3
- tag.span time_ago_in_words(time), title: time.to_fs(:long)
4
- end
5
-
6
- def time_distance_in_words_with_title(time)
7
- tag.span distance_of_time_in_words_to_now(time, include_seconds: true), title: "Since #{time.to_fs(:long)}"
8
- end
9
-
10
- def bidirectional_time_distance_in_words_with_title(time)
11
- time_distance = if time.past?
12
- "#{distance_of_time_in_words_to_now(time, include_seconds: true)} ago"
13
- else
14
- "in #{distance_of_time_in_words_to_now(time, include_seconds: true)}"
15
- end
16
-
17
- tag.span time_distance, title: time.to_fs(:long)
2
+ def formatted_time(time)
3
+ time.in_time_zone.strftime("%Y-%m-%d %H:%M:%S.%3N %Z")
18
4
  end
19
5
  end
@@ -11,8 +11,16 @@ module MissionControl::Jobs::JobsHelper
11
11
  "#{job.last_execution_error.error_class}: #{job.last_execution_error.message}"
12
12
  end
13
13
 
14
- def failed_job_backtrace(job)
15
- job.last_execution_error.backtrace.join("\n")
14
+ def clean_backtrace?
15
+ params["clean_backtrace"] == "true"
16
+ end
17
+
18
+ def failed_job_backtrace(job, server)
19
+ if clean_backtrace? && server&.backtrace_cleaner
20
+ server.backtrace_cleaner.clean(job.last_execution_error.backtrace).join("\n")
21
+ else
22
+ job.last_execution_error.backtrace.join("\n")
23
+ end
16
24
  end
17
25
 
18
26
  def attribute_names_for_job_status(status)
@@ -21,7 +29,7 @@ module MissionControl::Jobs::JobsHelper
21
29
  when "blocked" then [ "Queue", "Blocked by", "Block expiry", "" ]
22
30
  when "finished" then [ "Queue", "Finished" ]
23
31
  when "scheduled" then [ "Queue", "Scheduled", "" ]
24
- when "in_progress" then [ "Queue", "Run by", "Running for" ]
32
+ when "in_progress" then [ "Queue", "Run by", "Running since" ]
25
33
  else []
26
34
  end
27
35
  end
@@ -31,6 +39,7 @@ module MissionControl::Jobs::JobsHelper
31
39
  end
32
40
 
33
41
  private
42
+
34
43
  def renderable_job_arguments_for(job)
35
44
  job.serialized_arguments.collect do |argument|
36
45
  as_renderable_argument(argument)
@@ -47,6 +47,18 @@ module MissionControl::Jobs::NavigationHelper
47
47
 
48
48
  def jobs_count_with_status(status)
49
49
  count = ActiveJob.jobs.with_status(status).count
50
- count.infinite? ? "..." : number_to_human(count)
50
+ if count.infinite?
51
+ "..."
52
+ else
53
+ number_to_human(count,
54
+ format: "%n%u",
55
+ units: {
56
+ thousand: "K",
57
+ million: "M",
58
+ billion: "B",
59
+ trillion: "T",
60
+ quadrillion: "Q"
61
+ })
62
+ end
51
63
  end
52
64
  end
@@ -1,7 +1,7 @@
1
1
  class MissionControl::Jobs::RecurringTask
2
2
  include ActiveModel::Model
3
3
 
4
- attr_accessor :id, :job_class_name, :command, :arguments, :schedule, :last_enqueued_at, :queue_name, :priority
4
+ attr_accessor :id, :job_class_name, :command, :arguments, :schedule, :last_enqueued_at, :next_time, :queue_name, :priority
5
5
 
6
6
  def initialize(queue_adapter: ActiveJob::Base.queue_adapter, **kwargs)
7
7
  @queue_adapter = queue_adapter
@@ -7,9 +7,9 @@
7
7
 
8
8
  <meta name="viewport" content="width=device-width,initial-scale=1">
9
9
  <meta name="turbo-cache-control" content="no-cache">
10
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
10
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@1.0.1/css/bulma.min.css">
11
11
  <%= stylesheet_link_tag "mission_control/jobs/application", "data-turbo-track": "reload" %>
12
- <%= javascript_importmap_tags "application-mcj" %>
12
+ <%= javascript_importmap_tags "application", importmap: MissionControl::Jobs.importmap %>
13
13
  </head>
14
14
  <body>
15
15
 
@@ -14,5 +14,9 @@
14
14
  </tbody>
15
15
  </table>
16
16
 
17
- <pre class="is-family-monospace mb-4"><%= failed_job_backtrace(job) %></pre>
17
+ <% if @server.backtrace_cleaner %>
18
+ <%= render "mission_control/jobs/jobs/failed/backtrace_toggle", application: @application, job: job %>
19
+ <% end %>
20
+
21
+ <pre class="is-family-monospace mb-4 backtrace-content"><%= failed_job_backtrace(job, @server) %></pre>
18
22
  <% end %>
@@ -14,15 +14,15 @@
14
14
 
15
15
  <%= hidden_field_tag :server_id, MissionControl::Jobs::Current.server.id %>
16
16
 
17
- <datalist id="job-classes" style="display: none;">
17
+ <datalist id="job-classes" class="is-hidden">
18
18
  <% job_class_names.each do |job_class_name| %>
19
- <option value="<%= job_class_name %>" />
19
+ <option value="<%= job_class_name %>"></option>
20
20
  <% end %>
21
21
  </datalist>
22
22
 
23
- <datalist id="queue-names" style="display: none;">
23
+ <datalist id="queue-names" class="is-hidden">
24
24
  <% queue_names.each do |queue_name| %>
25
- <option value="<%= queue_name %>" />
25
+ <option value="<%= queue_name %>"></option>
26
26
  <% end %>
27
27
  </datalist>
28
28
  <% end %>
@@ -23,14 +23,14 @@
23
23
  <tr>
24
24
  <th>Enqueued</th>
25
25
  <td>
26
- <%= time_ago_in_words_with_title(job.enqueued_at.to_datetime) %> ago
26
+ <%= formatted_time(job.enqueued_at.to_datetime) %>
27
27
  </td>
28
28
  </tr>
29
29
  <% if job.failed? %>
30
30
  <tr>
31
31
  <th>Failed</th>
32
32
  <td>
33
- <%= time_ago_in_words_with_title(job.failed_at) %> ago
33
+ <%= formatted_time(job.failed_at) %>
34
34
  </td>
35
35
  </tr>
36
36
  <% end %>
@@ -38,7 +38,7 @@
38
38
  <tr>
39
39
  <th>Finished at</th>
40
40
  <td>
41
- <%= time_ago_in_words_with_title(job.finished_at) %> ago
41
+ <%= formatted_time(job.finished_at) %>
42
42
  </td>
43
43
  </tr>
44
44
  <% end %>
@@ -6,7 +6,7 @@
6
6
  <div class="is-family-monospace"><%= job_arguments(job) %></div>
7
7
  <% end %>
8
8
 
9
- <div class="has-text-grey is-size-7">Enqueued <%= time_ago_in_words_with_title(job.enqueued_at.to_datetime) %> ago</div>
9
+ <div class="has-text-grey is-size-7">Enqueued <%= formatted_time(job.enqueued_at.to_datetime) %></div>
10
10
  </td>
11
11
 
12
12
  <%= render "mission_control/jobs/jobs/#{jobs_status}/job", job: job %>
@@ -1,7 +1,7 @@
1
1
  <table class="jobs <%= jobs_status %> table queues is-hoverable is-fullwidth">
2
2
  <thead>
3
3
  <tr>
4
- <th style="width: 35%;">Job</th>
4
+ <th class="job-header">Job</th>
5
5
  <% attribute_names_for_job_status(jobs_status).each do |attribute| %>
6
6
  <th><%= attribute %></th>
7
7
  <% end %>
@@ -11,6 +11,9 @@
11
11
  <% if job.blocked? %>
12
12
  <%= render "mission_control/jobs/jobs/blocked/actions", job: job %>
13
13
  <% end %>
14
+ <% if job.scheduled? %>
15
+ <%= render "mission_control/jobs/jobs/scheduled/actions", job: job %>
16
+ <% end %>
14
17
  </div>
15
18
  </div>
16
19
  </h1>
@@ -1,6 +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
- <td><%= bidirectional_time_distance_in_words_with_title(job.blocked_until) %></td>
3
+ <td><%= formatted_time(job.blocked_until) %></td>
4
4
  <td class="pr-0">
5
5
  <%= render "mission_control/jobs/jobs/blocked/actions", job: job %>
6
6
  </td>
@@ -0,0 +1,14 @@
1
+ <%# locals: (application:, job:) %>
2
+
3
+ <div class="is-flex is-justify-content-flex-end mb-2 mr-2 backtrace-toggle-selector">
4
+ <div class="tabs is-toggle is-toggle-rounded is-small">
5
+ <ul>
6
+ <li class="<%= class_names('backtrace-clean-link', 'is-active' => clean_backtrace?) %>">
7
+ <%= link_to "Clean", application_job_path(application, job.job_id, clean_backtrace: true) %>
8
+ </li>
9
+ <li class="<%= class_names('backtrace-full-link', 'is-active' => !clean_backtrace?) %>">
10
+ <%= link_to "Full", application_job_path(application, job.job_id, clean_backtrace: false) %>
11
+ </li>
12
+ </ul>
13
+ </div>
14
+ </div>
@@ -1,6 +1,6 @@
1
1
  <td>
2
2
  <%= link_to failed_job_error(job), application_job_path(@application, job.job_id, anchor: "error") %>
3
- <div class="has-text-grey"><%= time_ago_in_words_with_title(job.failed_at) %> ago</div>
3
+ <div class="has-text-grey"><%= formatted_time(job.failed_at) %></div>
4
4
  </td>
5
5
  <td class="pr-0">
6
6
  <%= render "mission_control/jobs/jobs/failed/actions", job: job %>
@@ -1,2 +1,2 @@
1
1
  <td><%= link_to job.queue_name, application_queue_path(@application, job.queue) %></td>
2
- <td><div class="has-text-grey"><%= time_ago_in_words_with_title(job.finished_at) %> ago</div></td>
2
+ <td><div class="has-text-grey"><%= formatted_time(job.finished_at) %></div></td>
@@ -6,4 +6,4 @@
6
6
 
7
7
  <% end %>
8
8
  </td>
9
- <td><div class="has-text-grey"><%= job.started_at ? time_distance_in_words_with_title(job.started_at) : "(Finished)" %></div></td>
9
+ <td><div class="has-text-grey"><%= job.started_at ? formatted_time(job.started_at) : "(Finished)" %></div></td>
@@ -1,6 +1,6 @@
1
1
  <td><%= link_to job.queue_name, application_queue_path(@application, job.queue) %></td>
2
2
  <td>
3
- <%= bidirectional_time_distance_in_words_with_title(job.scheduled_at) %>
3
+ <%= formatted_time(job.scheduled_at) %>
4
4
  <% if job_delayed?(job) %>
5
5
  <div class="is-danger tag ml-4">delayed</div>
6
6
  <% end %>
@@ -3,7 +3,7 @@
3
3
  <%= link_to application_job_path(@application, job.job_id, filter: { queue_name: job.queue }) do %>
4
4
  <%= job_title(job) %>
5
5
  <% end %>
6
- <div class="has-text-grey">Enqueued <%= time_ago_in_words_with_title(job.enqueued_at.to_datetime) %> ago</div>
6
+ <div class="has-text-grey">Enqueued on <%= formatted_time(job.enqueued_at.to_datetime) %></div>
7
7
  </td>
8
8
  <td>
9
9
  <% if job.serialized_arguments.present? %>
@@ -4,8 +4,8 @@
4
4
  <tbody>
5
5
  <thead>
6
6
  <tr>
7
- <th style="width: 50%;">Queue</th>
8
- <th style="width: 30%;">Pending jobs</th>
7
+ <th>Queue</th>
8
+ <th>Pending jobs</th>
9
9
  <th></th>
10
10
  </tr>
11
11
  </thead>
@@ -9,7 +9,7 @@
9
9
  <tbody>
10
10
  <thead>
11
11
  <tr>
12
- <th style="width: 30%;">Job</th>
12
+ <th class="job-header">Job</th>
13
13
  <th></th>
14
14
  </tr>
15
15
  </thead>
@@ -14,5 +14,6 @@
14
14
  <% end %>
15
15
  </td>
16
16
  <td> <%= recurring_task.schedule %> </td>
17
- <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>
17
+ <td><div class="has-text-grey"><%= recurring_task.last_enqueued_at ? formatted_time(recurring_task.last_enqueued_at) : "Never" %></div></td>
18
+ <td class="next_time"><div class="has-text-grey"><%= formatted_time(recurring_task.next_time) %></div></td>
18
19
  </tr>
@@ -8,6 +8,7 @@
8
8
  <th>Job</th>
9
9
  <th>Schedule</th>
10
10
  <th>Last enqueued at</th>
11
+ <th>Next run</th>
11
12
  </tr>
12
13
  </thead>
13
14
 
@@ -3,7 +3,7 @@
3
3
  <%= link_to application_job_path(@application, job.job_id, filter: { queue_name: job.queue }) do %>
4
4
  <%= job_title(job) %>
5
5
  <% end %>
6
- <div class="has-text-grey">Enqueued <%= time_ago_in_words_with_title(job.enqueued_at.to_datetime) %> ago</div>
6
+ <div class="has-text-grey">Enqueued on <%= formatted_time(job.enqueued_at.to_datetime) %></div>
7
7
  </td>
8
8
  <td>
9
9
  <% if job.serialized_arguments.present? %>
@@ -16,9 +16,9 @@
16
16
  <td>
17
17
  <div class="has-text-grey">
18
18
  <% if job.started_at %>
19
- Running for <%= time_distance_in_words_with_title(job.started_at) %>
19
+ Running since <%= formatted_time(job.started_at) %>
20
20
  <% elsif job.finished_at %>
21
- Finished <%= time_ago_in_words_with_title(job.finished_at) %> ago
21
+ Finished on <%= formatted_time(job.finished_at) %>
22
22
  <% else %>
23
23
  Pending
24
24
  <% end %>
@@ -2,9 +2,9 @@
2
2
  <tbody>
3
3
  <thead>
4
4
  <tr>
5
- <th style="width: 30%;">Job</th>
5
+ <th class="job-header">Job</th>
6
6
  <th></th>
7
- <th style="width: 20%;"></th>
7
+ <th class="duration-header"></th>
8
8
  </tr>
9
9
  </thead>
10
10
 
@@ -17,5 +17,5 @@
17
17
  <% end %>
18
18
  </td>
19
19
 
20
- <td><div class="has-text-grey"><%= time_ago_in_words_with_title(worker.last_heartbeat_at) %> ago</div></td>
20
+ <td><div class="has-text-grey"><%= formatted_time(worker.last_heartbeat_at) %></div></td>
21
21
  </tr>
@@ -4,7 +4,7 @@
4
4
  <tr>
5
5
  <th>Worker</th>
6
6
  <th>Hostname</th>
7
- <th style="width: 35%;">Jobs</th>
7
+ <th class="job-header">Jobs</th>
8
8
  <th>Last heartbeat</th>
9
9
  </tr>
10
10
  </thead>
data/config/importmap.rb CHANGED
@@ -1,4 +1,4 @@
1
- pin "application-mcj", to: "mission_control/jobs/application.js", preload: true
1
+ pin "application", to: "mission_control/jobs/application.js", preload: true
2
2
  pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
3
3
  pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
4
4
  pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
@@ -0,0 +1,49 @@
1
+ module ActiveJob::QueueAdapters::AsyncExt
2
+ include MissionControl::Jobs::Adapter
3
+
4
+ # List of filters supported natively. Non-supported filters are done in memory.
5
+ def supported_job_filters(jobs_relation)
6
+ []
7
+ end
8
+
9
+ def supports_queue_pausing?
10
+ false
11
+ end
12
+
13
+ def queues
14
+ []
15
+ end
16
+
17
+ def queue_size(*)
18
+ 0
19
+ end
20
+
21
+ def clear_queue(*)
22
+ end
23
+
24
+ def jobs_count(*)
25
+ 0
26
+ end
27
+
28
+ def fetch_jobs(*)
29
+ []
30
+ end
31
+
32
+ def retry_all_jobs(*)
33
+ end
34
+
35
+ def retry_job(job, *)
36
+ end
37
+
38
+ def discard_all_jobs(*)
39
+ end
40
+
41
+ def discard_job(*)
42
+ end
43
+
44
+ def dispatch_job(*)
45
+ end
46
+
47
+ def find_job(*)
48
+ end
49
+ end
@@ -190,7 +190,7 @@ module ActiveJob::QueueAdapters::ResqueExt
190
190
  job.last_execution_error = execution_error_from_resque_job(resque_job_hash)
191
191
  job.raw_data = resque_job_hash
192
192
  job.position = jobs_relation.offset_value + index
193
- job.failed_at = resque_job_hash["failed_at"]&.to_datetime
193
+ job.failed_at = resque_job_hash["failed_at"]&.to_datetime&.utc
194
194
  job.status = job.failed_at.present? ? :failed : :pending
195
195
  end
196
196
  end
@@ -263,7 +263,7 @@ module ActiveJob::QueueAdapters::ResqueExt
263
263
 
264
264
  def requeue(job)
265
265
  resque_job = job.raw_data
266
- resque_job["retried_at"] = Time.now.strftime("%Y/%m/%d %H:%M:%S")
266
+ resque_job["retried_at"] = Time.now.utc.strftime("%Y/%m/%d %H:%M:%S")
267
267
 
268
268
  redis.lset(queue_redis_key, job.position, Resque.encode(resque_job))
269
269
  Resque::Job.create(resque_job["queue"], resque_job["payload"]["class"], *resque_job["payload"]["args"])
@@ -28,6 +28,7 @@ module ActiveJob::QueueAdapters::SolidQueueExt::RecurringTasks
28
28
  command: task.command,
29
29
  arguments: task.arguments,
30
30
  schedule: task.schedule,
31
+ next_time: task.next_time,
31
32
  queue_name: task.queue_name,
32
33
  priority: task.priority
33
34
  }
@@ -11,7 +11,10 @@ class MissionControl::Jobs::Application
11
11
 
12
12
  def add_servers(queue_adapters_by_name)
13
13
  queue_adapters_by_name.each do |name, queue_adapter|
14
- servers << MissionControl::Jobs::Server.new(name: name.to_s, queue_adapter: queue_adapter, application: self)
14
+ adapter, cleaner = queue_adapter
15
+
16
+ servers << MissionControl::Jobs::Server.new(name: name.to_s, queue_adapter: adapter,
17
+ backtrace_cleaner: cleaner, application: self)
15
18
  end
16
19
  end
17
20
  end
@@ -1,23 +1,18 @@
1
- require "mission_control/jobs/version"
2
- require "mission_control/jobs/engine"
3
-
4
1
  require "importmap-rails"
5
2
  require "turbo-rails"
6
3
  require "stimulus-rails"
7
- require "propshaft"
8
4
 
9
5
  module MissionControl
10
6
  module Jobs
11
7
  class Engine < ::Rails::Engine
12
8
  isolate_namespace MissionControl::Jobs
13
9
 
14
- config.middleware.use ActionDispatch::Flash unless config.action_dispatch.flash
15
-
16
10
  config.mission_control = ActiveSupport::OrderedOptions.new unless config.try(:mission_control)
17
11
  config.mission_control.jobs = ActiveSupport::OrderedOptions.new
18
12
 
19
13
  config.before_initialize do
20
14
  config.mission_control.jobs.applications = MissionControl::Jobs::Applications.new
15
+ config.mission_control.jobs.backtrace_cleaner ||= Rails::BacktraceCleaner.new
21
16
 
22
17
  config.mission_control.jobs.each do |key, value|
23
18
  MissionControl::Jobs.public_send("#{key}=", value)
@@ -48,7 +43,7 @@ module MissionControl
48
43
  ActiveJob::QueueAdapters::SolidQueueAdapter.prepend ActiveJob::QueueAdapters::SolidQueueExt
49
44
  end
50
45
 
51
- ActiveJob::QueueAdapters::AsyncAdapter.include MissionControl::Jobs::Adapter
46
+ ActiveJob::QueueAdapters::AsyncAdapter.include ActiveJob::QueueAdapters::AsyncExt
52
47
  end
53
48
 
54
49
  config.after_initialize do |app|
@@ -79,25 +74,23 @@ module MissionControl
79
74
  MissionControl::Jobs.delay_between_bulk_operation_batches = 2
80
75
  MissionControl::Jobs.logger = ActiveSupport::Logger.new(STDOUT)
81
76
 
82
- if MissionControl::Jobs.applications.one? && (application = MissionControl::Jobs.applications.first) && application.servers.one?
83
- MissionControl::Jobs::Current.application = application
84
- MissionControl::Jobs::Current.server = application.servers.first
85
- end
86
-
87
77
  if MissionControl::Jobs.show_console_help
88
78
  puts "\n\nType 'jobs_help' to see how to connect to the available job servers to manage jobs\n\n"
89
79
  end
90
80
  end
91
81
 
92
82
  initializer "mission_control-jobs.assets" do |app|
93
- app.config.assets.paths << root.join("app/assets/stylesheets")
94
83
  app.config.assets.paths << root.join("app/javascript")
95
84
  app.config.assets.precompile += %w[ mission_control_jobs_manifest ]
96
85
  end
97
86
 
98
- initializer "mission_control-jobs.importmap", before: "importmap" do |app|
99
- app.config.importmap.paths << root.join("config/importmap.rb")
100
- app.config.importmap.cache_sweepers << root.join("app/javascript")
87
+ initializer "mission_control-jobs.importmap", after: "importmap" do |app|
88
+ MissionControl::Jobs.importmap.draw(root.join("config/importmap.rb"))
89
+ MissionControl::Jobs.importmap.cache_sweeper(watches: root.join("app/javascript"))
90
+
91
+ ActiveSupport.on_load(:action_controller_base) do
92
+ before_action { MissionControl::Jobs.importmap.cache_sweeper.execute_if_updated }
93
+ end
101
94
  end
102
95
  end
103
96
  end
@@ -4,12 +4,13 @@ class MissionControl::Jobs::Server
4
4
  include MissionControl::Jobs::IdentifiedByName
5
5
  include Serializable, RecurringTasks, Workers
6
6
 
7
- attr_reader :name, :queue_adapter, :application
7
+ attr_reader :name, :queue_adapter, :application, :backtrace_cleaner
8
8
 
9
- def initialize(name:, queue_adapter:, application:)
9
+ def initialize(name:, queue_adapter:, application:, backtrace_cleaner: nil)
10
10
  super(name: name)
11
11
  @queue_adapter = queue_adapter
12
12
  @application = application
13
+ @backtrace_cleaner = backtrace_cleaner || MissionControl::Jobs.backtrace_cleaner
13
14
  end
14
15
 
15
16
  def activating(&block)
@@ -1,5 +1,5 @@
1
1
  module MissionControl
2
2
  module Jobs
3
- VERSION = "0.3.2"
3
+ VERSION = "0.4.0"
4
4
  end
5
5
  end
@@ -19,5 +19,7 @@ module MissionControl
19
19
  mattr_accessor :internal_query_count_limit, default: 500_000 # Hard limit to keep unlimited count queries fast enough
20
20
  mattr_accessor :show_console_help, default: true
21
21
  mattr_accessor :scheduled_job_delay_threshold, default: 1.minute
22
+ mattr_accessor :importmap, default: Importmap::Map.new
23
+ mattr_accessor :backtrace_cleaner
22
24
  end
23
25
  end
metadata CHANGED
@@ -1,17 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mission_control-jobs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jorge Manrubia
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-05 00:00:00.000000000 Z
11
+ date: 2024-11-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: rails
14
+ name: activerecord
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
@@ -25,33 +25,75 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '7.1'
27
27
  - !ruby/object:Gem::Dependency
28
- name: propshaft
28
+ name: activejob
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: '7.1'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: '7.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: actionpack
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '7.1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '7.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: actioncable
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '7.1'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '7.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: railties
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '7.1'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '7.1'
41
83
  - !ruby/object:Gem::Dependency
42
84
  name: importmap-rails
43
85
  requirement: !ruby/object:Gem::Requirement
44
86
  requirements:
45
87
  - - ">="
46
88
  - !ruby/object:Gem::Version
47
- version: '0'
89
+ version: 1.2.1
48
90
  type: :runtime
49
91
  prerelease: false
50
92
  version_requirements: !ruby/object:Gem::Requirement
51
93
  requirements:
52
94
  - - ">="
53
95
  - !ruby/object:Gem::Version
54
- version: '0'
96
+ version: 1.2.1
55
97
  - !ruby/object:Gem::Dependency
56
98
  name: turbo-rails
57
99
  requirement: !ruby/object:Gem::Requirement
@@ -112,16 +154,16 @@ dependencies:
112
154
  name: solid_queue
113
155
  requirement: !ruby/object:Gem::Requirement
114
156
  requirements:
115
- - - ">="
157
+ - - "~>"
116
158
  - !ruby/object:Gem::Version
117
- version: '0.9'
159
+ version: '1.0'
118
160
  type: :development
119
161
  prerelease: false
120
162
  version_requirements: !ruby/object:Gem::Requirement
121
163
  requirements:
122
- - - ">="
164
+ - - "~>"
123
165
  - !ruby/object:Gem::Version
124
- version: '0.9'
166
+ version: '1.0'
125
167
  - !ruby/object:Gem::Dependency
126
168
  name: selenium-webdriver
127
169
  requirement: !ruby/object:Gem::Requirement
@@ -248,6 +290,20 @@ dependencies:
248
290
  - - ">="
249
291
  - !ruby/object:Gem::Version
250
292
  version: '0'
293
+ - !ruby/object:Gem::Dependency
294
+ name: better_html
295
+ requirement: !ruby/object:Gem::Requirement
296
+ requirements:
297
+ - - ">="
298
+ - !ruby/object:Gem::Version
299
+ version: '0'
300
+ type: :development
301
+ prerelease: false
302
+ version_requirements: !ruby/object:Gem::Requirement
303
+ requirements:
304
+ - - ">="
305
+ - !ruby/object:Gem::Version
306
+ version: '0'
251
307
  - !ruby/object:Gem::Dependency
252
308
  name: sprockets-rails
253
309
  requirement: !ruby/object:Gem::Requirement
@@ -300,6 +356,7 @@ files:
300
356
  - MIT-LICENSE
301
357
  - README.md
302
358
  - Rakefile
359
+ - app/assets/config/mission_control_jobs_manifest.js
303
360
  - app/assets/stylesheets/mission_control/jobs/application.css
304
361
  - app/assets/stylesheets/mission_control/jobs/forms.css
305
362
  - app/assets/stylesheets/mission_control/jobs/jobs.css
@@ -354,6 +411,7 @@ files:
354
411
  - app/views/mission_control/jobs/jobs/blocked/_actions.html.erb
355
412
  - app/views/mission_control/jobs/jobs/blocked/_job.html.erb
356
413
  - app/views/mission_control/jobs/jobs/failed/_actions.html.erb
414
+ - app/views/mission_control/jobs/jobs/failed/_backtrace_toggle.html.erb
357
415
  - app/views/mission_control/jobs/jobs/failed/_job.html.erb
358
416
  - app/views/mission_control/jobs/jobs/finished/_job.html.erb
359
417
  - app/views/mission_control/jobs/jobs/in_progress/_job.html.erb
@@ -394,6 +452,7 @@ files:
394
452
  - lib/active_job/jobs_relation.rb
395
453
  - lib/active_job/querying.rb
396
454
  - lib/active_job/queue.rb
455
+ - lib/active_job/queue_adapters/async_ext.rb
397
456
  - lib/active_job/queue_adapters/resque_ext.rb
398
457
  - lib/active_job/queue_adapters/solid_queue_ext.rb
399
458
  - lib/active_job/queue_adapters/solid_queue_ext/recurring_tasks.rb