maintenance_tasks 2.9.0 → 2.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +44 -1
- data/app/controllers/maintenance_tasks/runs_controller.rb +4 -1
- data/app/controllers/maintenance_tasks/tasks_controller.rb +5 -8
- data/app/models/maintenance_tasks/runs_page.rb +3 -0
- data/app/models/maintenance_tasks/task_data_show.rb +53 -4
- data/app/views/maintenance_tasks/runs/_run.html.erb +9 -9
- data/app/views/maintenance_tasks/tasks/show.html.erb +4 -4
- data/config/routes.rb +4 -3
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 347d742ae5f71881fe2d54f7fff83b4129144eb72af60c2364a8069fc41412fe
|
4
|
+
data.tar.gz: 20ff959d1c2db4a003883955ef938414a462f16fa843d8f4ec526ff3333a0609
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7f9e56bd030e2a6918d7e8e136108d6097cbcdaa8b73ebb9683072878f8789cb0b7da1b67c122d7dddf97553ca3dcb37b4e1af7670cbdf931825ec965cbbf9af
|
7
|
+
data.tar.gz: 384ec37a77b25d689ff46f965fd6cddd4ddc7669f520a99db808edc76c290c21ed8925b5d791839dd0d61738da6b944267fa65535b91ebd4ee6d8913293bb824
|
data/README.md
CHANGED
@@ -90,6 +90,13 @@ take a look at the [Active Job documentation][active-job-docs].
|
|
90
90
|
[async-adapter]: https://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/AsyncAdapter.html
|
91
91
|
[active-job-docs]: https://guides.rubyonrails.org/active_job_basics.html#setting-the-backend
|
92
92
|
|
93
|
+
### Action Controller & Action View Dependency
|
94
|
+
|
95
|
+
The Maintenance Tasks framework relies on Action Controller and Action View to
|
96
|
+
render the UI. If you're using Rails in API-only mode, see [Using Maintenance
|
97
|
+
Tasks in API-only
|
98
|
+
applications](#using-maintenance-tasks-in-api-only-applications).
|
99
|
+
|
93
100
|
### Autoloading
|
94
101
|
|
95
102
|
The Maintenance Tasks framework does not support autoloading in `:classic` mode.
|
@@ -888,6 +895,42 @@ a Task can be in:
|
|
888
895
|
* **succeeded**: A Task that finished successfully.
|
889
896
|
* **errored**: A Task that encountered an unhandled exception while performing.
|
890
897
|
|
898
|
+
### Using Maintenance Tasks in API-only applications
|
899
|
+
|
900
|
+
The Maintenance Tasks engine uses Rails sessions for flash messages and storing
|
901
|
+
the CSRF token. For the engine to work in an API-only Rails application, you
|
902
|
+
need to add a [session middleware][] and the `ActionDispatch::Flash`
|
903
|
+
middleware. The engine also defines a strict [Content Security Policy][], make
|
904
|
+
sure to include `ActionDispatch::ContentSecurityPolicy::Middleware` in your
|
905
|
+
app's middleware stack to ensure the CSP is delivered to the user's browser.
|
906
|
+
|
907
|
+
[session middleware]: https://guides.rubyonrails.org/api_app.html#using-session-middlewares
|
908
|
+
[Content Security Policy]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
|
909
|
+
|
910
|
+
Configuring Rails applications is beyond the scope of this documentation, but
|
911
|
+
one way to do this is to add these lines to your application configuration:
|
912
|
+
|
913
|
+
```ruby
|
914
|
+
# config/application.rb
|
915
|
+
module YourApplication
|
916
|
+
class Application < Rails::Application
|
917
|
+
# ...
|
918
|
+
config.api_only = true
|
919
|
+
|
920
|
+
config.middleware.insert_before ::Rack::Head, ::ActionDispatch::Flash
|
921
|
+
config.middleware.insert_before ::Rack::Head, ::ActionDispatch::ContentSecurityPolicy::Middleware
|
922
|
+
config.session_store :cookie_store, key: "_#{railtie_name.chomp("_application")}_session", secure: true
|
923
|
+
config.middleware.insert_before ::ActionDispatch::Flash, config.session_store, config.session_options
|
924
|
+
config.middleware.insert_before config.session_store, ActionDispatch::Cookies
|
925
|
+
end
|
926
|
+
end
|
927
|
+
```
|
928
|
+
|
929
|
+
You can read more in the [Using Rails for API-only Applications][rails api] Rails
|
930
|
+
guide.
|
931
|
+
|
932
|
+
[rails api]: https://guides.rubyonrails.org/api_app.html
|
933
|
+
|
891
934
|
### How Maintenance Tasks runs a Task
|
892
935
|
|
893
936
|
Maintenance tasks can be running for a long time, and the purpose of the gem is
|
@@ -1145,7 +1188,7 @@ The value for `MaintenanceTasks.stuck_task_duration` must be an
|
|
1145
1188
|
`ActiveSupport::Duration`. If no value is specified, it will default to 5
|
1146
1189
|
minutes.
|
1147
1190
|
|
1148
|
-
|
1191
|
+
#### Metadata
|
1149
1192
|
|
1150
1193
|
`MaintenanceTasks.metadata` can be configured to specify a proc from which to
|
1151
1194
|
get extra information about the run. Since this proc will be ran in the context
|
@@ -6,6 +6,7 @@ module MaintenanceTasks
|
|
6
6
|
#
|
7
7
|
# @api private
|
8
8
|
class RunsController < ApplicationController
|
9
|
+
helper TasksHelper
|
9
10
|
before_action :set_run, except: :create
|
10
11
|
|
11
12
|
# Creates a Run for a given Task and redirects to the Task page.
|
@@ -19,7 +20,9 @@ module MaintenanceTasks
|
|
19
20
|
)
|
20
21
|
redirect_to(task_path(task))
|
21
22
|
rescue ActiveRecord::RecordInvalid => error
|
22
|
-
|
23
|
+
flash.now.alert = error.message
|
24
|
+
@task = TaskDataShow.prepare(error.record.task_name, arguments: error.record.arguments)
|
25
|
+
render(template: "maintenance_tasks/tasks/show")
|
23
26
|
rescue ActiveRecord::ValueTooLong => error
|
24
27
|
task_name = params.fetch(:task_id)
|
25
28
|
redirect_to(task_path(task_name), alert: error.message)
|
@@ -18,14 +18,11 @@ module MaintenanceTasks
|
|
18
18
|
# Renders the page responsible for providing Task actions to users.
|
19
19
|
# Shows running and completed instances of the Task.
|
20
20
|
def show
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
if @task.active_runs.none? && @runs_page.records.none?
|
27
|
-
Task.named(task_name)
|
28
|
-
end
|
21
|
+
@task = TaskDataShow.prepare(
|
22
|
+
params.fetch(:id),
|
23
|
+
runs_cursor: params[:cursor],
|
24
|
+
arguments: params.except(:id, :controller, :action).permit!,
|
25
|
+
)
|
29
26
|
end
|
30
27
|
|
31
28
|
private
|
@@ -21,6 +21,9 @@ module MaintenanceTasks
|
|
21
21
|
@cursor = cursor
|
22
22
|
end
|
23
23
|
|
24
|
+
# @return [String, nil] the cursor for the page of Runs.
|
25
|
+
attr_reader :cursor
|
26
|
+
|
24
27
|
# Returns the records for a Page, taking into account the cursor if one is
|
25
28
|
# present. Limits the number of records to 20.
|
26
29
|
#
|
@@ -11,17 +11,39 @@ module MaintenanceTasks
|
|
11
11
|
#
|
12
12
|
# @api private
|
13
13
|
class TaskDataShow
|
14
|
-
# Initializes a Task Data with a name
|
14
|
+
# Initializes a Task Data with a name.
|
15
15
|
#
|
16
16
|
# @param name [String] the name of the Task subclass.
|
17
|
-
|
17
|
+
# @param runs_cursor [String, nil] the cursor for the runs page.
|
18
|
+
# @param arguments [Hash, nil] the Task arguments.
|
19
|
+
def initialize(name, runs_cursor: nil, arguments: nil)
|
18
20
|
@name = name
|
21
|
+
@arguments = arguments
|
22
|
+
@runs_page = RunsPage.new(completed_runs, runs_cursor)
|
23
|
+
end
|
24
|
+
|
25
|
+
class << self
|
26
|
+
# Prepares a Task Data from a task name.
|
27
|
+
#
|
28
|
+
# @param name [String] the name of the Task subclass.
|
29
|
+
# @param runs_cursor [String, nil] the cursor for the runs page.
|
30
|
+
# @param arguments [Hash, nil] the Task arguments.
|
31
|
+
# @raise [Task::NotFoundError] if the Task doesn't have runs (for the given cursor) and doesn't exist.
|
32
|
+
def prepare(name, runs_cursor: nil, arguments: nil)
|
33
|
+
new(name, runs_cursor:, arguments:)
|
34
|
+
.load_active_runs
|
35
|
+
.ensure_task_exists
|
36
|
+
end
|
19
37
|
end
|
20
38
|
|
21
39
|
# @return [String] the name of the Task.
|
22
40
|
attr_reader :name
|
23
41
|
alias_method :to_s, :name
|
24
42
|
|
43
|
+
# @return [RunsPage] the current page of completed runs, based on the cursor
|
44
|
+
# passed in initialize.
|
45
|
+
attr_reader :runs_page
|
46
|
+
|
25
47
|
# The Task's source code.
|
26
48
|
#
|
27
49
|
# @return [String] the contents of the file which defines the Task.
|
@@ -38,6 +60,11 @@ module MaintenanceTasks
|
|
38
60
|
File.read(file)
|
39
61
|
end
|
40
62
|
|
63
|
+
# @return [Boolean] whether the task data needs to be refreshed.
|
64
|
+
def refresh?
|
65
|
+
active_runs.any?
|
66
|
+
end
|
67
|
+
|
41
68
|
# Returns the set of currently active Run records associated with the Task.
|
42
69
|
#
|
43
70
|
# @return [ActiveRecord::Relation<MaintenanceTasks::Run>] the relation of
|
@@ -79,12 +106,34 @@ module MaintenanceTasks
|
|
79
106
|
end
|
80
107
|
end
|
81
108
|
|
82
|
-
# @return [MaintenanceTasks::Task
|
109
|
+
# @return [MaintenanceTasks::Task] an instance of the Task class.
|
83
110
|
# @return [nil] if the Task file was deleted.
|
84
111
|
def new
|
85
112
|
return if deleted?
|
86
113
|
|
87
|
-
MaintenanceTasks::Task.named(name).new
|
114
|
+
task = MaintenanceTasks::Task.named(name).new
|
115
|
+
begin
|
116
|
+
task.assign_attributes(@arguments) if @arguments
|
117
|
+
rescue ActiveModel::UnknownAttributeError
|
118
|
+
# nothing to do
|
119
|
+
end
|
120
|
+
task
|
121
|
+
end
|
122
|
+
|
123
|
+
# Preloads the records from the active_runs ActiveRecord::Relation
|
124
|
+
# @return [self]
|
125
|
+
def load_active_runs
|
126
|
+
active_runs.load
|
127
|
+
self
|
128
|
+
end
|
129
|
+
|
130
|
+
# @raise [Task::NotFoundError] if the Task doesn't have Runs (for the given cursor) and doesn't exist.
|
131
|
+
# @return [self]
|
132
|
+
def ensure_task_exists
|
133
|
+
if active_runs.none? && runs_page.records.none?
|
134
|
+
Task.named(name)
|
135
|
+
end
|
136
|
+
self
|
88
137
|
end
|
89
138
|
|
90
139
|
private
|
@@ -23,23 +23,23 @@
|
|
23
23
|
|
24
24
|
<div class="buttons">
|
25
25
|
<% if run.paused? %>
|
26
|
-
<%= button_to 'Resume', resume_task_run_path(@task, run),
|
27
|
-
<%= button_to 'Cancel', cancel_task_run_path(@task, run),
|
26
|
+
<%= button_to 'Resume', resume_task_run_path(@task, run), class: 'button is-primary', disabled: @task.deleted? %>
|
27
|
+
<%= button_to 'Cancel', cancel_task_run_path(@task, run), class: 'button is-danger' %>
|
28
28
|
<% elsif run.errored? %>
|
29
|
-
<%= button_to 'Resume', resume_task_run_path(@task, run),
|
29
|
+
<%= button_to 'Resume', resume_task_run_path(@task, run), class: 'button is-primary', disabled: @task.deleted? %>
|
30
30
|
<% elsif run.cancelling? %>
|
31
31
|
<% if run.stuck? %>
|
32
|
-
<%= button_to 'Cancel', cancel_task_run_path(@task, run),
|
32
|
+
<%= button_to 'Cancel', cancel_task_run_path(@task, run), class: 'button is-danger', disabled: @task.deleted? %>
|
33
33
|
<% end %>
|
34
34
|
<% elsif run.pausing? %>
|
35
|
-
<%= button_to 'Pausing', pause_task_run_path(@task, run),
|
36
|
-
<%= button_to 'Cancel', cancel_task_run_path(@task, run),
|
35
|
+
<%= button_to 'Pausing', pause_task_run_path(@task, run), class: 'button is-warning', disabled: true %>
|
36
|
+
<%= button_to 'Cancel', cancel_task_run_path(@task, run), class: 'button is-danger' %>
|
37
37
|
<% if run.stuck? %>
|
38
|
-
<%= button_to 'Force pause', pause_task_run_path(@task, run),
|
38
|
+
<%= button_to 'Force pause', pause_task_run_path(@task, run), class: 'button is-danger', disabled: @task.deleted? %>
|
39
39
|
<% end %>
|
40
40
|
<% elsif run.active? %>
|
41
|
-
<%= button_to 'Pause', pause_task_run_path(@task, run),
|
42
|
-
<%= button_to 'Cancel', cancel_task_run_path(@task, run),
|
41
|
+
<%= button_to 'Pause', pause_task_run_path(@task, run), class: 'button is-warning', disabled: @task.deleted? %>
|
42
|
+
<%= button_to 'Cancel', cancel_task_run_path(@task, run), class: 'button is-danger' %>
|
43
43
|
<% end%>
|
44
44
|
</div>
|
45
45
|
</div>
|
@@ -38,7 +38,7 @@
|
|
38
38
|
<pre><code><%= highlight_code(code) %></code></pre>
|
39
39
|
<% end %>
|
40
40
|
|
41
|
-
<%= tag.div(data: { refresh:
|
41
|
+
<%= tag.div(data: { refresh: @task.refresh? || "" }) do %>
|
42
42
|
<% if @task.active_runs.any? %>
|
43
43
|
<hr/>
|
44
44
|
|
@@ -47,13 +47,13 @@
|
|
47
47
|
<%= render partial: "maintenance_tasks/runs/run", collection: @task.active_runs %>
|
48
48
|
<% end %>
|
49
49
|
|
50
|
-
<% if @runs_page.records.present? %>
|
50
|
+
<% if @task.runs_page.records.present? %>
|
51
51
|
<hr/>
|
52
52
|
|
53
53
|
<h4 class="title is-4">Previous Runs</h4>
|
54
54
|
|
55
|
-
<%= render partial: "maintenance_tasks/runs/run", collection: @runs_page.records %>
|
55
|
+
<%= render partial: "maintenance_tasks/runs/run", collection: @task.runs_page.records %>
|
56
56
|
|
57
|
-
<%= link_to "Next page", task_path(@task, cursor: @runs_page.next_cursor) unless @runs_page.last? %>
|
57
|
+
<%= link_to "Next page", task_path(@task, cursor: @task.runs_page.next_cursor) unless @task.runs_page.last? %>
|
58
58
|
<% end %>
|
59
59
|
<% end %>
|
data/config/routes.rb
CHANGED
@@ -4,11 +4,12 @@ MaintenanceTasks::Engine.routes.draw do
|
|
4
4
|
resources :tasks, only: [:index, :show], format: false do
|
5
5
|
resources :runs, only: [:create], format: false do
|
6
6
|
member do
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
post "pause"
|
8
|
+
post "cancel"
|
9
|
+
post "resume"
|
10
10
|
end
|
11
11
|
end
|
12
|
+
get :runs, to: redirect("tasks/%{task_id}")
|
12
13
|
end
|
13
14
|
|
14
15
|
root to: "tasks#index"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: maintenance_tasks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify Engineering
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-12-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: actionpack
|
@@ -185,7 +185,7 @@ homepage: https://github.com/Shopify/maintenance_tasks
|
|
185
185
|
licenses:
|
186
186
|
- MIT
|
187
187
|
metadata:
|
188
|
-
source_code_uri: https://github.com/Shopify/maintenance_tasks/tree/v2.
|
188
|
+
source_code_uri: https://github.com/Shopify/maintenance_tasks/tree/v2.10.0
|
189
189
|
allowed_push_host: https://rubygems.org
|
190
190
|
post_install_message:
|
191
191
|
rdoc_options: []
|