mission_control-jobs 0.3.3 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +20 -2
- data/app/assets/stylesheets/mission_control/jobs/jobs.css +31 -2
- data/app/controllers/mission_control/jobs/application_controller.rb +0 -6
- data/app/controllers/mission_control/jobs/discards_controller.rb +7 -2
- data/app/controllers/mission_control/jobs/dispatches_controller.rb +9 -4
- data/app/controllers/mission_control/jobs/jobs_controller.rb +1 -0
- data/app/controllers/mission_control/jobs/recurring_tasks_controller.rb +9 -1
- data/app/helpers/mission_control/jobs/dates_helper.rb +2 -16
- data/app/helpers/mission_control/jobs/jobs_helper.rb +13 -4
- data/app/helpers/mission_control/jobs/navigation_helper.rb +13 -1
- data/app/models/mission_control/jobs/recurring_task.rb +5 -1
- data/app/views/layouts/mission_control/jobs/application.html.erb +1 -1
- data/app/views/mission_control/jobs/jobs/_error_information.html.erb +5 -1
- data/app/views/mission_control/jobs/jobs/_filters.html.erb +4 -4
- data/app/views/mission_control/jobs/jobs/_general_information.html.erb +14 -3
- data/app/views/mission_control/jobs/jobs/_job.html.erb +1 -1
- data/app/views/mission_control/jobs/jobs/_jobs_page.html.erb +1 -1
- data/app/views/mission_control/jobs/jobs/_title.html.erb +3 -0
- data/app/views/mission_control/jobs/jobs/blocked/_actions.html.erb +1 -1
- data/app/views/mission_control/jobs/jobs/blocked/_job.html.erb +3 -2
- data/app/views/mission_control/jobs/jobs/failed/_backtrace_toggle.html.erb +14 -0
- data/app/views/mission_control/jobs/jobs/failed/_job.html.erb +1 -1
- data/app/views/mission_control/jobs/jobs/finished/_job.html.erb +1 -1
- data/app/views/mission_control/jobs/jobs/in_progress/_job.html.erb +1 -1
- data/app/views/mission_control/jobs/jobs/scheduled/_actions.html.erb +1 -0
- data/app/views/mission_control/jobs/jobs/scheduled/_job.html.erb +1 -1
- data/app/views/mission_control/jobs/queues/_job.html.erb +1 -1
- data/app/views/mission_control/jobs/queues/index.html.erb +2 -2
- data/app/views/mission_control/jobs/queues/show.html.erb +1 -1
- data/app/views/mission_control/jobs/recurring_tasks/_actions.html.erb +3 -0
- data/app/views/mission_control/jobs/recurring_tasks/_recurring_task.html.erb +5 -1
- data/app/views/mission_control/jobs/recurring_tasks/_title.html.erb +3 -0
- data/app/views/mission_control/jobs/recurring_tasks/index.html.erb +3 -1
- data/app/views/mission_control/jobs/shared/_job.html.erb +3 -3
- data/app/views/mission_control/jobs/shared/_jobs.html.erb +2 -2
- data/app/views/mission_control/jobs/workers/_worker.html.erb +1 -1
- data/app/views/mission_control/jobs/workers/_workers_page.html.erb +1 -1
- data/config/importmap.rb +1 -1
- data/config/routes.rb +1 -1
- data/lib/active_job/executing.rb +1 -1
- data/lib/active_job/jobs_relation.rb +5 -0
- data/lib/active_job/queue_adapters/async_ext.rb +49 -0
- data/lib/active_job/queue_adapters/resque_ext.rb +2 -2
- data/lib/active_job/queue_adapters/solid_queue_ext/recurring_tasks.rb +7 -0
- data/lib/active_job/queue_adapters/solid_queue_ext.rb +7 -3
- data/lib/mission_control/jobs/application.rb +4 -1
- data/lib/mission_control/jobs/engine.rb +9 -12
- data/lib/mission_control/jobs/server.rb +3 -2
- data/lib/mission_control/jobs/version.rb +1 -1
- data/lib/mission_control/jobs.rb +2 -0
- metadata +82 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5cae6550d727f9f26c6b5c0f1259a03c7a3f802abe8eb025973fab559b8a8f73
|
4
|
+
data.tar.gz: 98a0bea82a4f3b9c3a3d338719f419508eef9ae05dbfc356314f142d053a4d11
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 223fb6d226ef1511e88cac89f0b1176e5db6bd01899b49279012d1ed94e607c28491f4ec770c96848291d84748e6f2b3956812ee0a4f25a3b7a814557f3de259
|
7
|
+
data.tar.gz: 52e763ce4e3b3bb7d59881d284f1a136474b46b3cffba722db96f333e63538e7647f638fe5b67c427e5fb6bbdf96452f0dc7b216e04300ffd690d8197302161b
|
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`.
|
@@ -65,7 +66,7 @@ This library extends Active Job with a querying interface and the following sett
|
|
65
66
|
## Adapter Specifics
|
66
67
|
|
67
68
|
- **Resque**: Queue pausing is supported only if you have `resque-pause` installed in your project
|
68
|
-
- **Solid Queue**: Requires version >= 0.
|
69
|
+
- **Solid Queue**: Requires version >= 1.0.1.
|
69
70
|
|
70
71
|
## Advanced configuration
|
71
72
|
|
@@ -107,7 +108,24 @@ SERVERS_BY_APP.each do |app, servers|
|
|
107
108
|
ActiveJob::QueueAdapters::SolidQueueAdapter.new
|
108
109
|
end
|
109
110
|
|
110
|
-
|
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)
|
@@ -2,6 +2,35 @@
|
|
2
2
|
width: 15rem;
|
3
3
|
}
|
4
4
|
|
5
|
-
.jobs
|
6
|
-
|
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: 40%;
|
29
|
+
}
|
30
|
+
&.finished th.job-header {
|
31
|
+
width: 65%;
|
32
|
+
}
|
33
|
+
&.workers th.job-header {
|
34
|
+
width: 40%;
|
35
|
+
}
|
7
36
|
}
|
@@ -4,14 +4,8 @@ class MissionControl::Jobs::ApplicationController < MissionControl::Jobs.base_co
|
|
4
4
|
include MissionControl::Jobs::ApplicationScoped, MissionControl::Jobs::NotFoundRedirections
|
5
5
|
include MissionControl::Jobs::AdapterFeatures
|
6
6
|
|
7
|
-
around_action :set_current_locale
|
8
|
-
|
9
7
|
private
|
10
8
|
def default_url_options
|
11
9
|
{ server_id: MissionControl::Jobs::Current.server }
|
12
10
|
end
|
13
|
-
|
14
|
-
def set_current_locale(&block)
|
15
|
-
I18n.with_locale(:en, &block)
|
16
|
-
end
|
17
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
|
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
|
-
ActiveJob.jobs
|
11
|
+
ActiveJob.jobs
|
12
|
+
end
|
13
|
+
|
14
|
+
def redirect_location
|
15
|
+
status = @job.status.presence_in(supported_job_statuses) || :failed
|
16
|
+
application_jobs_url(@application, status)
|
12
17
|
end
|
13
18
|
end
|
@@ -3,11 +3,16 @@ class MissionControl::Jobs::DispatchesController < MissionControl::Jobs::Applica
|
|
3
3
|
|
4
4
|
def create
|
5
5
|
@job.dispatch
|
6
|
-
redirect_to
|
6
|
+
redirect_to redirect_location, notice: "Dispatched job with id #{@job.job_id}"
|
7
7
|
end
|
8
8
|
|
9
9
|
private
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
def jobs_relation
|
11
|
+
ActiveJob.jobs
|
12
|
+
end
|
13
|
+
|
14
|
+
def redirect_location
|
15
|
+
status = @job.status.presence_in(supported_job_statuses) || :blocked
|
16
|
+
application_jobs_url(@application, status)
|
17
|
+
end
|
13
18
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
class MissionControl::Jobs::RecurringTasksController < MissionControl::Jobs::ApplicationController
|
2
2
|
before_action :ensure_supported_recurring_tasks
|
3
|
-
before_action :set_recurring_task, only: :show
|
3
|
+
before_action :set_recurring_task, only: [ :show, :update ]
|
4
4
|
|
5
5
|
def index
|
6
6
|
@recurring_tasks = MissionControl::Jobs::Current.server.recurring_tasks
|
@@ -10,6 +10,14 @@ class MissionControl::Jobs::RecurringTasksController < MissionControl::Jobs::App
|
|
10
10
|
@jobs_page = MissionControl::Jobs::Page.new(@recurring_task.jobs, page: params[:page].to_i)
|
11
11
|
end
|
12
12
|
|
13
|
+
def update
|
14
|
+
if (job = @recurring_task.enqueue) && job.successfully_enqueued?
|
15
|
+
redirect_to application_job_path(@application, job.job_id), notice: "Enqueued recurring task #{@recurring_task.id}"
|
16
|
+
else
|
17
|
+
redirect_to application_recurring_task_path(@application, @recurring_task), alert: "Something went wrong enqueuing this recurring task"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
13
21
|
private
|
14
22
|
def ensure_supported_recurring_tasks
|
15
23
|
unless recurring_tasks_supported?
|
@@ -1,19 +1,5 @@
|
|
1
1
|
module MissionControl::Jobs::DatesHelper
|
2
|
-
def
|
3
|
-
|
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,17 +11,25 @@ module MissionControl::Jobs::JobsHelper
|
|
11
11
|
"#{job.last_execution_error.error_class}: #{job.last_execution_error.message}"
|
12
12
|
end
|
13
13
|
|
14
|
-
def
|
15
|
-
|
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)
|
19
27
|
case status.to_s
|
20
28
|
when "failed" then [ "Error", "" ]
|
21
|
-
when "blocked" then [ "Queue", "Blocked by", "
|
29
|
+
when "blocked" then [ "Queue", "Blocked by", "" ]
|
22
30
|
when "finished" then [ "Queue", "Finished" ]
|
23
31
|
when "scheduled" then [ "Queue", "Scheduled", "" ]
|
24
|
-
when "in_progress" then [ "Queue", "Run by", "Running
|
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?
|
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
|
@@ -12,6 +12,10 @@ class MissionControl::Jobs::RecurringTask
|
|
12
12
|
ActiveJob::JobsRelation.new(queue_adapter: queue_adapter).where(recurring_task_id: id)
|
13
13
|
end
|
14
14
|
|
15
|
+
def enqueue
|
16
|
+
queue_adapter.enqueue_recurring_task(id)
|
17
|
+
end
|
18
|
+
|
15
19
|
private
|
16
20
|
attr_reader :queue_adapter
|
17
21
|
end
|
@@ -9,7 +9,7 @@
|
|
9
9
|
<meta name="turbo-cache-control" content="no-cache">
|
10
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
|
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
|
-
|
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"
|
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"
|
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,25 @@
|
|
23
23
|
<tr>
|
24
24
|
<th>Enqueued</th>
|
25
25
|
<td>
|
26
|
-
<%=
|
26
|
+
<%= formatted_time(job.enqueued_at.to_datetime) %>
|
27
27
|
</td>
|
28
28
|
</tr>
|
29
|
+
<% if job.scheduled? %>
|
30
|
+
<tr>
|
31
|
+
<th>Scheduled</th>
|
32
|
+
<td>
|
33
|
+
<%= formatted_time(job.scheduled_at) %>
|
34
|
+
<% if job_delayed?(job) %>
|
35
|
+
<div class="is-danger tag ml-4">delayed</div>
|
36
|
+
<% end %>
|
37
|
+
</td>
|
38
|
+
</tr>
|
39
|
+
<% end %>
|
29
40
|
<% if job.failed? %>
|
30
41
|
<tr>
|
31
42
|
<th>Failed</th>
|
32
43
|
<td>
|
33
|
-
<%=
|
44
|
+
<%= formatted_time(job.failed_at) %>
|
34
45
|
</td>
|
35
46
|
</tr>
|
36
47
|
<% end %>
|
@@ -38,7 +49,7 @@
|
|
38
49
|
<tr>
|
39
50
|
<th>Finished at</th>
|
40
51
|
<td>
|
41
|
-
<%=
|
52
|
+
<%= formatted_time(job.finished_at) %>
|
42
53
|
</td>
|
43
54
|
</tr>
|
44
55
|
<% 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 <%=
|
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
|
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 %>
|
@@ -1,3 +1,3 @@
|
|
1
1
|
<div class="buttons is-right">
|
2
|
-
<%= button_to "
|
2
|
+
<%= button_to "Run now", application_job_dispatch_path(@application, job.job_id), class: "button is-warning is-light mr-0" %>
|
3
3
|
</div>
|
@@ -1,6 +1,7 @@
|
|
1
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
|
3
|
-
<
|
2
|
+
<td><div class="is-family-monospace is-size-7"><%= job.blocked_by %></div>
|
3
|
+
<div class="has-text-grey is-size-7">Until <%= formatted_time(job.blocked_until) %></div>
|
4
|
+
</td>
|
4
5
|
<td class="pr-0">
|
5
6
|
<%= render "mission_control/jobs/jobs/blocked/actions", job: job %>
|
6
7
|
</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"><%=
|
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"><%=
|
2
|
+
<td><div class="has-text-grey"><%= formatted_time(job.finished_at) %></div></td>
|
@@ -1,4 +1,5 @@
|
|
1
1
|
<div class="buttons is-right">
|
2
|
+
<%= button_to "Run now", application_job_dispatch_path(@application, job.job_id), class: "button is-warning is-light mr-0" %>
|
2
3
|
<%= button_to "Discard", application_job_discard_path(@application, job.job_id), class: "button is-danger is-light mr-0",
|
3
4
|
form: { data: { turbo_confirm: "This will delete the job and can't be undone. Are you sure?" } } %>
|
4
5
|
</div>
|
@@ -1,6 +1,6 @@
|
|
1
1
|
<td><%= link_to job.queue_name, application_queue_path(@application, job.queue) %></td>
|
2
2
|
<td>
|
3
|
-
<%=
|
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 <%=
|
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? %>
|
@@ -14,5 +14,9 @@
|
|
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 ?
|
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>
|
19
|
+
<td class="pr-0">
|
20
|
+
<%= render "mission_control/jobs/recurring_tasks/actions", recurring_task: recurring_task %>
|
21
|
+
</td>
|
18
22
|
</tr>
|
@@ -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 <%=
|
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
|
19
|
+
Running since <%= formatted_time(job.started_at) %>
|
20
20
|
<% elsif job.finished_at %>
|
21
|
-
Finished <%=
|
21
|
+
Finished on <%= formatted_time(job.finished_at) %>
|
22
22
|
<% else %>
|
23
23
|
Pending
|
24
24
|
<% end %>
|
data/config/importmap.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
pin "application
|
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
|
data/config/routes.rb
CHANGED
@@ -20,7 +20,7 @@ MissionControl::Jobs::Engine.routes.draw do
|
|
20
20
|
resources :jobs, only: :index, path: ":status/jobs"
|
21
21
|
|
22
22
|
resources :workers, only: [ :index, :show ]
|
23
|
-
resources :recurring_tasks, only: [ :index, :show ]
|
23
|
+
resources :recurring_tasks, only: [ :index, :show, :update ]
|
24
24
|
end
|
25
25
|
|
26
26
|
# Allow referencing urls without providing an application_id. It will default to the first one.
|
data/lib/active_job/executing.rb
CHANGED
@@ -154,7 +154,12 @@ class ActiveJob::JobsRelation
|
|
154
154
|
end
|
155
155
|
|
156
156
|
# Dispatch the provided job.
|
157
|
+
#
|
158
|
+
# This operation is only valid for blocked or scheduled jobs. It will
|
159
|
+
# raise an error +ActiveJob::Errors::InvalidOperation+ otherwise.
|
157
160
|
def dispatch_job(job)
|
161
|
+
raise ActiveJob::Errors::InvalidOperation, "This operation can only be performed on blocked or scheduled jobs, but this job is #{job.status}" unless job.blocked? || job.scheduled?
|
162
|
+
|
158
163
|
queue_adapter.dispatch_job(job, self)
|
159
164
|
end
|
160
165
|
|
@@ -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"])
|
@@ -20,6 +20,12 @@ module ActiveJob::QueueAdapters::SolidQueueExt::RecurringTasks
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
+
def enqueue_recurring_task(task_id)
|
24
|
+
if task = SolidQueue::RecurringTask.find_by(key: task_id)
|
25
|
+
task.enqueue(at: Time.now)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
23
29
|
private
|
24
30
|
def recurring_task_attributes_from_solid_queue_recurring_task(task)
|
25
31
|
{
|
@@ -28,6 +34,7 @@ module ActiveJob::QueueAdapters::SolidQueueExt::RecurringTasks
|
|
28
34
|
command: task.command,
|
29
35
|
arguments: task.arguments,
|
30
36
|
schedule: task.schedule,
|
37
|
+
next_time: task.next_time,
|
31
38
|
queue_name: task.queue_name,
|
32
39
|
priority: task.priority
|
33
40
|
}
|
@@ -123,9 +123,13 @@ module ActiveJob::QueueAdapters::SolidQueueExt
|
|
123
123
|
end
|
124
124
|
|
125
125
|
def dispatch_immediately(job)
|
126
|
-
|
127
|
-
|
128
|
-
|
126
|
+
if job.blocked?
|
127
|
+
SolidQueue::Job.transaction do
|
128
|
+
job.dispatch_bypassing_concurrency_limits
|
129
|
+
job.blocked_execution.destroy!
|
130
|
+
end
|
131
|
+
else
|
132
|
+
job.scheduled_execution.update!(scheduled_at: Time.now)
|
129
133
|
end
|
130
134
|
end
|
131
135
|
|
@@ -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
|
-
|
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,6 +1,3 @@
|
|
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"
|
@@ -15,6 +12,7 @@ module MissionControl
|
|
15
12
|
|
16
13
|
config.before_initialize do
|
17
14
|
config.mission_control.jobs.applications = MissionControl::Jobs::Applications.new
|
15
|
+
config.mission_control.jobs.backtrace_cleaner ||= Rails::BacktraceCleaner.new
|
18
16
|
|
19
17
|
config.mission_control.jobs.each do |key, value|
|
20
18
|
MissionControl::Jobs.public_send("#{key}=", value)
|
@@ -45,7 +43,7 @@ module MissionControl
|
|
45
43
|
ActiveJob::QueueAdapters::SolidQueueAdapter.prepend ActiveJob::QueueAdapters::SolidQueueExt
|
46
44
|
end
|
47
45
|
|
48
|
-
ActiveJob::QueueAdapters::AsyncAdapter.include
|
46
|
+
ActiveJob::QueueAdapters::AsyncAdapter.include ActiveJob::QueueAdapters::AsyncExt
|
49
47
|
end
|
50
48
|
|
51
49
|
config.after_initialize do |app|
|
@@ -76,11 +74,6 @@ module MissionControl
|
|
76
74
|
MissionControl::Jobs.delay_between_bulk_operation_batches = 2
|
77
75
|
MissionControl::Jobs.logger = ActiveSupport::Logger.new(STDOUT)
|
78
76
|
|
79
|
-
if MissionControl::Jobs.applications.one? && (application = MissionControl::Jobs.applications.first) && application.servers.one?
|
80
|
-
MissionControl::Jobs::Current.application = application
|
81
|
-
MissionControl::Jobs::Current.server = application.servers.first
|
82
|
-
end
|
83
|
-
|
84
77
|
if MissionControl::Jobs.show_console_help
|
85
78
|
puts "\n\nType 'jobs_help' to see how to connect to the available job servers to manage jobs\n\n"
|
86
79
|
end
|
@@ -91,9 +84,13 @@ module MissionControl
|
|
91
84
|
app.config.assets.precompile += %w[ mission_control_jobs_manifest ]
|
92
85
|
end
|
93
86
|
|
94
|
-
initializer "mission_control-jobs.importmap",
|
95
|
-
|
96
|
-
|
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
|
97
94
|
end
|
98
95
|
end
|
99
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)
|
data/lib/mission_control/jobs.rb
CHANGED
@@ -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,73 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mission_control-jobs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.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-
|
11
|
+
date: 2024-11-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '7.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '7.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activejob
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '7.1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
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
|
15
71
|
requirement: !ruby/object:Gem::Requirement
|
16
72
|
requirements:
|
17
73
|
- - ">="
|
@@ -30,14 +86,14 @@ dependencies:
|
|
30
86
|
requirements:
|
31
87
|
- - ">="
|
32
88
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
89
|
+
version: 1.2.1
|
34
90
|
type: :runtime
|
35
91
|
prerelease: false
|
36
92
|
version_requirements: !ruby/object:Gem::Requirement
|
37
93
|
requirements:
|
38
94
|
- - ">="
|
39
95
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
96
|
+
version: 1.2.1
|
41
97
|
- !ruby/object:Gem::Dependency
|
42
98
|
name: turbo-rails
|
43
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -98,16 +154,16 @@ dependencies:
|
|
98
154
|
name: solid_queue
|
99
155
|
requirement: !ruby/object:Gem::Requirement
|
100
156
|
requirements:
|
101
|
-
- - "
|
157
|
+
- - "~>"
|
102
158
|
- !ruby/object:Gem::Version
|
103
|
-
version:
|
159
|
+
version: 1.0.1
|
104
160
|
type: :development
|
105
161
|
prerelease: false
|
106
162
|
version_requirements: !ruby/object:Gem::Requirement
|
107
163
|
requirements:
|
108
|
-
- - "
|
164
|
+
- - "~>"
|
109
165
|
- !ruby/object:Gem::Version
|
110
|
-
version:
|
166
|
+
version: 1.0.1
|
111
167
|
- !ruby/object:Gem::Dependency
|
112
168
|
name: selenium-webdriver
|
113
169
|
requirement: !ruby/object:Gem::Requirement
|
@@ -234,6 +290,20 @@ dependencies:
|
|
234
290
|
- - ">="
|
235
291
|
- !ruby/object:Gem::Version
|
236
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'
|
237
307
|
- !ruby/object:Gem::Dependency
|
238
308
|
name: sprockets-rails
|
239
309
|
requirement: !ruby/object:Gem::Requirement
|
@@ -341,6 +411,7 @@ files:
|
|
341
411
|
- app/views/mission_control/jobs/jobs/blocked/_actions.html.erb
|
342
412
|
- app/views/mission_control/jobs/jobs/blocked/_job.html.erb
|
343
413
|
- app/views/mission_control/jobs/jobs/failed/_actions.html.erb
|
414
|
+
- app/views/mission_control/jobs/jobs/failed/_backtrace_toggle.html.erb
|
344
415
|
- app/views/mission_control/jobs/jobs/failed/_job.html.erb
|
345
416
|
- app/views/mission_control/jobs/jobs/finished/_job.html.erb
|
346
417
|
- app/views/mission_control/jobs/jobs/in_progress/_job.html.erb
|
@@ -354,6 +425,7 @@ files:
|
|
354
425
|
- app/views/mission_control/jobs/queues/_queue_title.html.erb
|
355
426
|
- app/views/mission_control/jobs/queues/index.html.erb
|
356
427
|
- app/views/mission_control/jobs/queues/show.html.erb
|
428
|
+
- app/views/mission_control/jobs/recurring_tasks/_actions.html.erb
|
357
429
|
- app/views/mission_control/jobs/recurring_tasks/_general_information.html.erb
|
358
430
|
- app/views/mission_control/jobs/recurring_tasks/_recurring_task.html.erb
|
359
431
|
- app/views/mission_control/jobs/recurring_tasks/_title.html.erb
|
@@ -381,6 +453,7 @@ files:
|
|
381
453
|
- lib/active_job/jobs_relation.rb
|
382
454
|
- lib/active_job/querying.rb
|
383
455
|
- lib/active_job/queue.rb
|
456
|
+
- lib/active_job/queue_adapters/async_ext.rb
|
384
457
|
- lib/active_job/queue_adapters/resque_ext.rb
|
385
458
|
- lib/active_job/queue_adapters/solid_queue_ext.rb
|
386
459
|
- lib/active_job/queue_adapters/solid_queue_ext/recurring_tasks.rb
|