mission_control-jobs 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +244 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/config/mission_control_jobs_manifest.js +4 -0
  6. data/app/assets/stylesheets/mission_control/jobs/application.css +16 -0
  7. data/app/assets/stylesheets/mission_control/jobs/forms.css +8 -0
  8. data/app/assets/stylesheets/mission_control/jobs/jobs.css +7 -0
  9. data/app/controllers/concerns/mission_control/jobs/adapter_features.rb +20 -0
  10. data/app/controllers/concerns/mission_control/jobs/application_scoped.rb +38 -0
  11. data/app/controllers/concerns/mission_control/jobs/failed_jobs_bulk_operations.rb +17 -0
  12. data/app/controllers/concerns/mission_control/jobs/job_filters.rb +18 -0
  13. data/app/controllers/concerns/mission_control/jobs/job_scoped.rb +16 -0
  14. data/app/controllers/concerns/mission_control/jobs/not_found_redirections.rb +25 -0
  15. data/app/controllers/concerns/mission_control/jobs/queue_scoped.rb +12 -0
  16. data/app/controllers/mission_control/jobs/application_controller.rb +11 -0
  17. data/app/controllers/mission_control/jobs/bulk_discards_controller.rb +20 -0
  18. data/app/controllers/mission_control/jobs/bulk_retries_controller.rb +10 -0
  19. data/app/controllers/mission_control/jobs/discards_controller.rb +13 -0
  20. data/app/controllers/mission_control/jobs/jobs_controller.rb +37 -0
  21. data/app/controllers/mission_control/jobs/queues/pauses_controller.rb +15 -0
  22. data/app/controllers/mission_control/jobs/queues_controller.rb +24 -0
  23. data/app/controllers/mission_control/jobs/retries_controller.rb +13 -0
  24. data/app/controllers/mission_control/jobs/workers_controller.rb +18 -0
  25. data/app/helpers/mission_control/jobs/application_helper.rb +8 -0
  26. data/app/helpers/mission_control/jobs/dates_helper.rb +19 -0
  27. data/app/helpers/mission_control/jobs/jobs_helper.rb +63 -0
  28. data/app/helpers/mission_control/jobs/navigation_helper.rb +51 -0
  29. data/app/helpers/mission_control/jobs/ui_helper.rb +23 -0
  30. data/app/javascript/mission_control/jobs/application.js +4 -0
  31. data/app/javascript/mission_control/jobs/controllers/application.js +9 -0
  32. data/app/javascript/mission_control/jobs/controllers/form_controller.js +21 -0
  33. data/app/javascript/mission_control/jobs/controllers/index.js +11 -0
  34. data/app/javascript/mission_control/jobs/helpers/debounce_helpers.js +9 -0
  35. data/app/javascript/mission_control/jobs/helpers/index.js +1 -0
  36. data/app/jobs/mission_control/jobs/application_job.rb +6 -0
  37. data/app/mailers/mission_control/jobs/application_mailer.rb +8 -0
  38. data/app/models/mission_control/jobs/application_record.rb +7 -0
  39. data/app/models/mission_control/jobs/current.rb +3 -0
  40. data/app/models/mission_control/jobs/page.rb +48 -0
  41. data/app/models/mission_control/jobs/worker.rb +17 -0
  42. data/app/views/layouts/mission_control/jobs/_application_selection.html.erb +11 -0
  43. data/app/views/layouts/mission_control/jobs/_flash.html.erb +9 -0
  44. data/app/views/layouts/mission_control/jobs/_navigation.html.erb +9 -0
  45. data/app/views/layouts/mission_control/jobs/application.html.erb +25 -0
  46. data/app/views/layouts/mission_control/jobs/application_selection/_applications.html.erb +13 -0
  47. data/app/views/layouts/mission_control/jobs/application_selection/_servers.html.erb +15 -0
  48. data/app/views/mission_control/jobs/jobs/_error_information.html.erb +19 -0
  49. data/app/views/mission_control/jobs/jobs/_filters.html.erb +35 -0
  50. data/app/views/mission_control/jobs/jobs/_general_information.html.erb +54 -0
  51. data/app/views/mission_control/jobs/jobs/_job.html.erb +13 -0
  52. data/app/views/mission_control/jobs/jobs/_jobs_page.html.erb +15 -0
  53. data/app/views/mission_control/jobs/jobs/_raw_data.html.erb +4 -0
  54. data/app/views/mission_control/jobs/jobs/_title.html.erb +13 -0
  55. data/app/views/mission_control/jobs/jobs/_toolbar.html.erb +18 -0
  56. data/app/views/mission_control/jobs/jobs/blocked/_job.html.erb +3 -0
  57. data/app/views/mission_control/jobs/jobs/failed/_actions.html.erb +5 -0
  58. data/app/views/mission_control/jobs/jobs/failed/_job.html.erb +7 -0
  59. data/app/views/mission_control/jobs/jobs/finished/_job.html.erb +2 -0
  60. data/app/views/mission_control/jobs/jobs/in_progress/_job.html.erb +9 -0
  61. data/app/views/mission_control/jobs/jobs/index.html.erb +19 -0
  62. data/app/views/mission_control/jobs/jobs/scheduled/_job.html.erb +7 -0
  63. data/app/views/mission_control/jobs/jobs/show.html.erb +6 -0
  64. data/app/views/mission_control/jobs/queues/_actions.html.erb +7 -0
  65. data/app/views/mission_control/jobs/queues/_job.html.erb +15 -0
  66. data/app/views/mission_control/jobs/queues/_queue.html.erb +16 -0
  67. data/app/views/mission_control/jobs/queues/_queue_title.html.erb +17 -0
  68. data/app/views/mission_control/jobs/queues/index.html.erb +16 -0
  69. data/app/views/mission_control/jobs/queues/show.html.erb +25 -0
  70. data/app/views/mission_control/jobs/shared/_pagination_toolbar.html.erb +5 -0
  71. data/app/views/mission_control/jobs/workers/_configuration.html.erb +6 -0
  72. data/app/views/mission_control/jobs/workers/_job.html.erb +19 -0
  73. data/app/views/mission_control/jobs/workers/_jobs.html.erb +20 -0
  74. data/app/views/mission_control/jobs/workers/_raw_data.html.erb +6 -0
  75. data/app/views/mission_control/jobs/workers/_title.html.erb +11 -0
  76. data/app/views/mission_control/jobs/workers/_worker.html.erb +21 -0
  77. data/app/views/mission_control/jobs/workers/index.html.erb +17 -0
  78. data/app/views/mission_control/jobs/workers/show.html.erb +7 -0
  79. data/config/importmap.rb +6 -0
  80. data/config/routes.rb +33 -0
  81. data/lib/active_job/errors/invalid_operation.rb +5 -0
  82. data/lib/active_job/errors/job_not_found_error.rb +14 -0
  83. data/lib/active_job/errors/query_error.rb +5 -0
  84. data/lib/active_job/executing.rb +43 -0
  85. data/lib/active_job/execution_error.rb +8 -0
  86. data/lib/active_job/failed.rb +11 -0
  87. data/lib/active_job/job_proxy.rb +26 -0
  88. data/lib/active_job/jobs_relation.rb +300 -0
  89. data/lib/active_job/querying.rb +44 -0
  90. data/lib/active_job/queue.rb +62 -0
  91. data/lib/active_job/queue_adapters/resque_ext.rb +300 -0
  92. data/lib/active_job/queue_adapters/solid_queue_ext.rb +294 -0
  93. data/lib/active_job/queues.rb +29 -0
  94. data/lib/mission_control/jobs/adapter.rb +108 -0
  95. data/lib/mission_control/jobs/application.rb +17 -0
  96. data/lib/mission_control/jobs/applications.rb +8 -0
  97. data/lib/mission_control/jobs/console/context.rb +11 -0
  98. data/lib/mission_control/jobs/console/helpers.rb +26 -0
  99. data/lib/mission_control/jobs/engine.rb +88 -0
  100. data/lib/mission_control/jobs/errors/incompatible_adapter.rb +2 -0
  101. data/lib/mission_control/jobs/errors/resource_not_found.rb +2 -0
  102. data/lib/mission_control/jobs/identified_by_name.rb +18 -0
  103. data/lib/mission_control/jobs/identified_elements.rb +23 -0
  104. data/lib/mission_control/jobs/server/serializable.rb +24 -0
  105. data/lib/mission_control/jobs/server/workers.rb +15 -0
  106. data/lib/mission_control/jobs/server.rb +26 -0
  107. data/lib/mission_control/jobs/version.rb +5 -0
  108. data/lib/mission_control/jobs.rb +19 -0
  109. data/lib/resque/thread_safe_redis.rb +34 -0
  110. data/lib/tasks/mission_control/jobs_tasks.rake +4 -0
  111. metadata +364 -0
@@ -0,0 +1,19 @@
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)
18
+ end
19
+ end
@@ -0,0 +1,63 @@
1
+ module MissionControl::Jobs::JobsHelper
2
+ def job_title(job)
3
+ job.job_class_name
4
+ end
5
+
6
+ def job_arguments(job)
7
+ renderable_job_arguments_for(job).join(", ")
8
+ end
9
+
10
+ def failed_job_error(job)
11
+ "#{job.last_execution_error.error_class}: #{job.last_execution_error.message}"
12
+ end
13
+
14
+ def failed_job_backtrace(job)
15
+ job.last_execution_error.backtrace.join("\n")
16
+ end
17
+
18
+ def attribute_names_for_job_status(status)
19
+ case status.to_s
20
+ when "failed" then [ "Error", "" ]
21
+ when "blocked" then [ "Queue", "Blocked by", "Block expiry" ]
22
+ when "finished" then [ "Queue", "Finished" ]
23
+ when "scheduled" then [ "Queue", "Scheduled" ]
24
+ when "in_progress" then [ "Queue", "Run by", "Running for" ]
25
+ else []
26
+ end
27
+ end
28
+
29
+ private
30
+ def renderable_job_arguments_for(job)
31
+ job.serialized_arguments.collect do |argument|
32
+ as_renderable_argument(argument)
33
+ end
34
+ end
35
+
36
+ def as_renderable_argument(argument)
37
+ case argument
38
+ when Hash
39
+ as_renderable_hash(argument)
40
+ when Array
41
+ as_renderable_array(argument)
42
+ else
43
+ ActiveJob::Arguments.deserialize([ argument ])
44
+ end
45
+ rescue ActiveJob::DeserializationError
46
+ argument.to_s
47
+ end
48
+
49
+ def as_renderable_hash(argument)
50
+ if argument["_aj_globalid"]
51
+ # don't deserialize as the class might not exist in the host app running the engine
52
+ argument["_aj_globalid"]
53
+ elsif argument["_aj_serialized"] == "ActiveJob::Serializers::ModuleSerializer"
54
+ argument["value"]
55
+ else
56
+ ActiveJob::Arguments.deserialize([ argument ])
57
+ end
58
+ end
59
+
60
+ def as_renderable_array(argument)
61
+ "(#{argument.collect { |part| as_renderable_argument(part) }.join(", ")})"
62
+ end
63
+ end
@@ -0,0 +1,51 @@
1
+ module MissionControl::Jobs::NavigationHelper
2
+ attr_reader :page_title, :current_section
3
+
4
+ def navigation_sections
5
+ { queues: [ "Queues", application_queues_path(@application) ] }.tap do |sections|
6
+ supported_job_statuses.without(:pending).each do |status|
7
+ sections[navigation_section_for_status(status)] = [ "#{status.to_s.titleize} jobs (#{jobs_count_with_status(status)})", application_jobs_path(@application, status) ]
8
+ end
9
+
10
+ sections[:workers] = [ "Workers", application_workers_path(@application) ] if workers_exposed?
11
+ end
12
+ end
13
+
14
+ def navigation_section_for_status(status)
15
+ if status.nil? || status == :pending
16
+ :queues
17
+ else
18
+ "#{status}_jobs".to_sym
19
+ end
20
+ end
21
+
22
+ def navigation(title: nil, section: nil)
23
+ @page_title = title
24
+ @current_section = section
25
+ end
26
+
27
+ def selected_application?(application)
28
+ MissionControl::Jobs::Current.application.name == application.name
29
+ end
30
+
31
+ def selectable_applications
32
+ MissionControl::Jobs.applications.reject { |app| selected_application?(app) }
33
+ end
34
+
35
+ def selected_server?(server)
36
+ MissionControl::Jobs::Current.server.name == server.name
37
+ end
38
+
39
+ def jobs_filter_param
40
+ if @job_filters&.any?
41
+ { filter: @job_filters }
42
+ else
43
+ {}
44
+ end
45
+ end
46
+
47
+ def jobs_count_with_status(status)
48
+ count = ApplicationJob.jobs.with_status(status).count
49
+ count.infinite? ? "..." : number_to_human(count)
50
+ end
51
+ end
@@ -0,0 +1,23 @@
1
+ module MissionControl::Jobs::UiHelper
2
+ def blank_status_notice(message)
3
+ tag.div message, class: "mt-6 has-text-centered is-size-3 has-text-grey"
4
+ end
5
+
6
+ def blank_status_emoji(status)
7
+ case status.to_s
8
+ when "failed", "blocked" then "😌"
9
+ else ""
10
+ end
11
+ end
12
+
13
+ def modifier_for_status(status)
14
+ case status.to_s
15
+ when "failed" then "is-danger"
16
+ when "blocked" then "is-warning"
17
+ when "finished" then "is-success"
18
+ when "scheduled" then "is-info"
19
+ when "in_progress" then "is-primary"
20
+ else "is-primary is-light"
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,4 @@
1
+ // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
2
+ import "@hotwired/turbo-rails"
3
+ import "controllers"
4
+ import "helpers"
@@ -0,0 +1,9 @@
1
+ import { Application } from "@hotwired/stimulus"
2
+
3
+ const application = Application.start()
4
+
5
+ // Configure Stimulus development experience
6
+ application.debug = false
7
+ window.Stimulus = application
8
+
9
+ export { application }
@@ -0,0 +1,21 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import { debounce } from "helpers"
3
+
4
+ export default class extends Controller {
5
+ static values = {
6
+ debounceTimeout: { type: Number, default: 300 }
7
+ }
8
+
9
+ initialize() {
10
+ this.debouncedSubmit = debounce(this.debouncedSubmit.bind(this), this.debounceTimeoutValue)
11
+ }
12
+
13
+ submit(event) {
14
+ const form = event.target.form || event.target.closest("form")
15
+ if (form) form.requestSubmit()
16
+ }
17
+
18
+ debouncedSubmit(event) {
19
+ this.submit(event)
20
+ }
21
+ }
@@ -0,0 +1,11 @@
1
+ // Import and register all your controllers from the importmap under controllers/*
2
+
3
+ import { application } from "controllers/application"
4
+
5
+ // Eager load all controllers defined in the import map under controllers/**/*_controller
6
+ import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
7
+ eagerLoadControllersFrom("controllers", application)
8
+
9
+ // Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!)
10
+ // import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading"
11
+ // lazyLoadControllersFrom("controllers", application)
@@ -0,0 +1,9 @@
1
+ export function debounce(fn, delay = 10) {
2
+ let timeoutId = null
3
+
4
+ return (...args) => {
5
+ const callback = () => fn.apply(this, args)
6
+ clearTimeout(timeoutId)
7
+ timeoutId = setTimeout(callback, delay)
8
+ }
9
+ }
@@ -0,0 +1 @@
1
+ export * from "helpers/debounce_helpers"
@@ -0,0 +1,6 @@
1
+ module MissionControl
2
+ module Jobs
3
+ class ApplicationJob < ActiveJob::Base
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,8 @@
1
+ module MissionControl
2
+ module Jobs
3
+ class ApplicationMailer < ActionMailer::Base
4
+ default from: "from@example.com"
5
+ layout "mailer"
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ module MissionControl
2
+ module Jobs
3
+ class ApplicationRecord < ActiveRecord::Base
4
+ self.abstract_class = true
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ class MissionControl::Jobs::Current < ActiveSupport::CurrentAttributes
2
+ attribute :application, :server
3
+ end
@@ -0,0 +1,48 @@
1
+ class MissionControl::Jobs::Page
2
+ DEFAULT_PAGE_SIZE = 10
3
+
4
+ attr_reader :jobs_relation, :index, :page_size
5
+
6
+ def initialize(jobs_relation, page: 1, page_size: DEFAULT_PAGE_SIZE)
7
+ @jobs_relation = jobs_relation
8
+ @page_size = page_size
9
+ @index = [ page, 1 ].max
10
+ end
11
+
12
+ def jobs
13
+ jobs_relation.limit(page_size).offset(offset)
14
+ end
15
+
16
+ def first?
17
+ index == 1
18
+ end
19
+
20
+ def last?
21
+ index == pages_count || empty? || jobs.empty?
22
+ end
23
+
24
+ def empty?
25
+ total_count == 0
26
+ end
27
+
28
+ def previous_index
29
+ [ index - 1, 1 ].max
30
+ end
31
+
32
+ def next_index
33
+ pages_count ? [ index + 1, pages_count ].min : index + 1
34
+ end
35
+
36
+ def pages_count
37
+ (total_count.to_f / 10).ceil unless total_count.infinite?
38
+ end
39
+
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
42
+ end
43
+
44
+ private
45
+ def offset
46
+ (index - 1) * page_size
47
+ end
48
+ end
@@ -0,0 +1,17 @@
1
+ class MissionControl::Jobs::Worker
2
+ include ActiveModel::Model
3
+
4
+ attr_accessor :id, :name, :hostname, :last_heartbeat_at, :configuration, :raw_data
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
+ @jobs ||= ActiveJob::JobsRelation.new(queue_adapter: queue_adapter).in_progress.where(worker_id: id)
13
+ end
14
+
15
+ private
16
+ attr_reader :queue_adapter
17
+ end
@@ -0,0 +1,11 @@
1
+ <nav class="navbar" role="navigation" aria-label="main navigation">
2
+ <div class="navbar-menu is-active">
3
+ <div class="navbar-start">
4
+ </div>
5
+
6
+ <div class="navbar-end">
7
+ <%= render "layouts/mission_control/jobs/application_selection/servers", application: @application %>
8
+ <%= render "layouts/mission_control/jobs/application_selection/applications" if selectable_applications.any? %>
9
+ </div>
10
+ </div>
11
+ </nav>
@@ -0,0 +1,9 @@
1
+ <% flash.each do |name, message| -%>
2
+ <% message_class = { notice: "is-success", alert: "is-danger" }[name.to_sym] %>
3
+
4
+ <article class="message <%= message_class %>">
5
+ <div class="message-body">
6
+ <%= message %>
7
+ </div>
8
+ </article>
9
+ <% end -%>
@@ -0,0 +1,9 @@
1
+ <div class="tabs is-boxed">
2
+ <ul>
3
+ <% navigation_sections.each do |key, (label, url)| %>
4
+ <li class="<%= "is-active" if key == current_section %>">
5
+ <%= link_to label, url %>
6
+ </li>
7
+ <% end %>
8
+ </ul>
9
+ </div>
@@ -0,0 +1,25 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Mission control - <%= page_title %></title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <meta name="viewport" content="width=device-width,initial-scale=1">
9
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
10
+ <%= stylesheet_link_tag "mission_control/jobs/application", "data-turbo-track": "reload" %>
11
+ <%= javascript_importmap_tags "application-mcj" %>
12
+ </head>
13
+ <body>
14
+
15
+ <section class="section">
16
+ <div class="container">
17
+ <%= render "layouts/mission_control/jobs/application_selection" %>
18
+ <%= render "layouts/mission_control/jobs/flash" %>
19
+ <%= render "layouts/mission_control/jobs/navigation" %>
20
+ <%= yield %>
21
+ </div>
22
+ </section>
23
+
24
+ </body>
25
+ </html>
@@ -0,0 +1,13 @@
1
+ <div class="application-selector navbar-item has-dropdown is-hoverable">
2
+ <a class="navbar-link">
3
+ <%= MissionControl::Jobs::Current.application.name %>
4
+ </a>
5
+
6
+ <div class="navbar-dropdown">
7
+ <% selectable_applications.each do |application| %>
8
+ <%= link_to application_queues_path(application, server_id: nil), class: "navbar-item" do %>
9
+ <span><%= application.name %></span>
10
+ <% end %>
11
+ <% end %>
12
+ </div>
13
+ </div>
@@ -0,0 +1,15 @@
1
+ <% if application.servers.many? %>
2
+ <div class="server-selector navbar-item">
3
+ <div class="tabs is-toggle is-toggle-rounded is-small">
4
+ <ul>
5
+ <% application.servers.each do |server| %>
6
+ <li class="<%= "is-active" if selected_server?(server) %>">
7
+ <%= link_to application_queues_path(application, server_id: server) do %>
8
+ <span><%= server.name %></span>
9
+ <% end %>
10
+ </li>
11
+ <% end %>
12
+ </ul>
13
+ </div>
14
+ </div>
15
+ <% end %>
@@ -0,0 +1,19 @@
1
+ <% if job.failed? %>
2
+ <a id="error"></a>
3
+ <h2 class="subtitle">Error information</h2>
4
+
5
+ <table class="table">
6
+ <tbody>
7
+ <tr>
8
+ <th>Type</th>
9
+ <td> <%= job.last_execution_error.error_class %> </td>
10
+ </tr>
11
+ <tr>
12
+ <th>Message</th>
13
+ <td> <%= job.last_execution_error.try(:message) || job.last_execution_error.inspect %> </td>
14
+ </tr>
15
+ </tbody>
16
+ </table>
17
+
18
+ <pre class="is-family-monospace mb-4"><%= failed_job_backtrace(job) %></pre>
19
+ <% end %>
@@ -0,0 +1,35 @@
1
+ <div class="filter level-left">
2
+ <div class="field is-grouped">
3
+ <div class="control">
4
+ <%= form_for :filter, url: application_jobs_path(MissionControl::Jobs::Current.application, jobs_status), method: :get,
5
+ data: { controller: "form", action: "input->form#debouncedSubmit" } do |form| %>
6
+
7
+ <div class="select is-rounded">
8
+ <%= form.text_field :job_class_name, value: @job_filters[:job_class_name], class: "input", list: "job-classes", placeholder: "Filter by job class..." %>
9
+ </div>
10
+
11
+ <div class="select is-rounded">
12
+ <%= form.text_field :queue_name, value: @job_filters[:queue_name], class: "input", list: "queue-names", placeholder: "Filter by queue name..." %>
13
+ </div>
14
+
15
+ <%= hidden_field_tag :server_id, MissionControl::Jobs::Current.server.id %>
16
+
17
+ <datalist id="job-classes" style="display: none;">
18
+ <% job_class_names.each do |job_class_name| %>
19
+ <option value="<%= job_class_name %>" />
20
+ <% end %>
21
+ </datalist>
22
+
23
+ <datalist id="queue-names" style="display: none;">
24
+ <% queue_names.each do |queue_name| %>
25
+ <option value="<%= queue_name %>" />
26
+ <% end %>
27
+ </datalist>
28
+ <% end %>
29
+ </div>
30
+
31
+ <div class="control">
32
+ <%= link_to "Clear", application_jobs_path(MissionControl::Jobs::Current.application, jobs_status, job_class_name: nil, queue_name: nil), class: "button" %>
33
+ </div>
34
+ </div>
35
+ </div>
@@ -0,0 +1,54 @@
1
+ <table class="table">
2
+ <tbody>
3
+ <tr>
4
+ <th>Arguments</th>
5
+ <td>
6
+ <div class="is-family-monospace">
7
+ <%= job_arguments(job) %>
8
+ </div>
9
+ </td>
10
+ </tr>
11
+ <tr>
12
+ <th>Job id</th>
13
+ <td><%= job.job_id %> </td>
14
+ </tr>
15
+ <tr>
16
+ <th>Queue</th>
17
+ <td>
18
+ <div class="tag">
19
+ <%= link_to job.queue_name, application_queue_path(@application, job.queue) %>
20
+ </div>
21
+ </td>
22
+ </tr>
23
+ <tr>
24
+ <th>Enqueued</th>
25
+ <td>
26
+ <%= time_ago_in_words_with_title(job.enqueued_at.to_datetime) %> ago
27
+ </td>
28
+ </tr>
29
+ <% if job.failed? %>
30
+ <tr>
31
+ <th>Failed</th>
32
+ <td>
33
+ <%= time_ago_in_words_with_title(job.failed_at) %> ago
34
+ </td>
35
+ </tr>
36
+ <% end %>
37
+ <% if job.finished_at.present? %>
38
+ <tr>
39
+ <th>Finished at</th>
40
+ <td>
41
+ <%= time_ago_in_words_with_title(job.finished_at) %> ago
42
+ </td>
43
+ </tr>
44
+ <% end %>
45
+ <% if job.worker_id.present? %>
46
+ <tr>
47
+ <th>Processed by</th>
48
+ <td>
49
+ <%= link_to "worker #{job.worker_id}", application_worker_path(@application, job.worker_id) %>
50
+ </td>
51
+ </tr>
52
+ <% end %>
53
+ </tbody>
54
+ </table>
@@ -0,0 +1,13 @@
1
+ <tr class="job">
2
+ <td>
3
+ <%= link_to job_title(job), application_job_path(@application, job.job_id) %>
4
+
5
+ <% if job.serialized_arguments.present? %>
6
+ <div class="is-family-monospace"><%= job_arguments(job) %></div>
7
+ <% end %>
8
+
9
+ <div class="has-text-grey is-size-7">Enqueued <%= time_ago_in_words_with_title(job.enqueued_at.to_datetime) %> ago</div>
10
+ </td>
11
+
12
+ <%= render "mission_control/jobs/jobs/#{jobs_status}/job", job: job %>
13
+ </tr>
@@ -0,0 +1,15 @@
1
+ <table class="jobs <%= jobs_status %> table queues is-hoverable is-fullwidth">
2
+ <tbody>
3
+ <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>
10
+ </thead>
11
+
12
+ <%= render partial: "mission_control/jobs/jobs/job", collection: jobs_page.jobs %>
13
+
14
+ </tbody>
15
+ </table>
@@ -0,0 +1,4 @@
1
+ <h2 class="subtitle">Raw data</h2>
2
+ <pre>
3
+ <%= JSON.pretty_generate(job.raw_data.without("backtrace")) %>
4
+ </pre>
@@ -0,0 +1,13 @@
1
+ <h1 class="title">
2
+ <div class="level">
3
+ <div class="level-left">
4
+ <%= job_title(job) %>
5
+ <div class="<%= modifier_for_status(job.status) %> tag ml-4"><%= job.status %></div>
6
+ </div>
7
+ <div class="level-right">
8
+ <% if job.failed? %>
9
+ <%= render "mission_control/jobs/jobs/failed/actions", job: job %>
10
+ <% end %>
11
+ </div>
12
+ </div>
13
+ </h1>
@@ -0,0 +1,18 @@
1
+ <div class="buttons is-right level-right">
2
+ <% if active_filters? %>
3
+ <span class="mr-5">
4
+ <%= jobs_count %> jobs found
5
+ </span>
6
+ <% end %>
7
+
8
+ <% if jobs_status.failed? %>
9
+ <% target = active_filters? ? "selection" : "all" %>
10
+
11
+ <%= button_to "Discard #{target}", application_bulk_discards_path(@application, **jobs_filter_param),
12
+ method: :post, disabled: jobs_count == 0, class: "button is-danger is-light",
13
+ form: { data: { turbo_confirm: "This will delete #{jobs_count} jobs and can't be undone. Are you sure?" } } %>
14
+ <%= button_to "Retry #{target}", application_bulk_retries_path(@application, **jobs_filter_param),
15
+ method: :post, disabled: jobs_count == 0,
16
+ class: "button is-warning is-light mr-0" %>
17
+ <% end %>
18
+ </div>
@@ -0,0 +1,3 @@
1
+ <td><%= link_to job.queue_name, application_queue_path(@application, job.queue) %></td>
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>
@@ -0,0 +1,5 @@
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
+ <%= button_to "Retry", application_job_retry_path(@application, job.job_id), class: "button is-warning is-light mr-0" %>
5
+ </div>
@@ -0,0 +1,7 @@
1
+ <td>
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>
4
+ </td>
5
+ <td class="pr-0">
6
+ <%= render "mission_control/jobs/jobs/failed/actions", job: job %>
7
+ </td>
@@ -0,0 +1,2 @@
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>
@@ -0,0 +1,9 @@
1
+ <td><%= link_to job.queue_name, application_queue_path(@application, job.queue) %></td>
2
+ <td>
3
+ <% if job.worker_id %>
4
+ <%= link_to "worker #{job.worker_id}", application_worker_path(@application, job.worker_id) %>
5
+ <% else %>
6
+
7
+ <% end %>
8
+ </td>
9
+ <td><div class="has-text-grey"><%= job.started_at ? time_distance_in_words_with_title(job.started_at) : "(Finished)" %></div></td>
@@ -0,0 +1,19 @@
1
+ <% navigation(title: "#{jobs_status.titleize} jobs", section: "#{jobs_status}_jobs".to_sym) %>
2
+
3
+ <% if @jobs_page.empty? && !active_filters? %>
4
+ <%= blank_status_notice "There are no #{jobs_status.dasherize} jobs #{blank_status_emoji(jobs_status)}" %>
5
+ <% else %>
6
+ <div class="level">
7
+ <%= render "mission_control/jobs/jobs/filters", job_class_names: @job_class_names, queue_names: @queue_names %>
8
+ <%= render "mission_control/jobs/jobs/toolbar", jobs_count: @jobs_count %>
9
+ </div>
10
+
11
+ <% if @jobs_page.empty? %>
12
+ <%= blank_status_notice "No #{jobs_status.dasherize} jobs found with the given filters" %>
13
+ <% else %>
14
+ <%= render "mission_control/jobs/jobs/jobs_page", jobs_page: @jobs_page %>
15
+
16
+ <%= render "mission_control/jobs/shared/pagination_toolbar", jobs_page: @jobs_page %>
17
+ <% end %>
18
+ <% end %>
19
+