maintenance_tasks 2.1.0 → 2.2.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 +48 -0
- data/app/controllers/maintenance_tasks/application_controller.rb +2 -7
- data/app/controllers/maintenance_tasks/runs_controller.rb +1 -1
- data/app/helpers/maintenance_tasks/tasks_helper.rb +15 -1
- data/app/jobs/concerns/maintenance_tasks/task_job_concern.rb +3 -3
- data/app/models/maintenance_tasks/run.rb +0 -1
- data/app/models/maintenance_tasks/task.rb +7 -1
- data/app/views/layouts/maintenance_tasks/application.html.erb +9 -7
- data/app/views/maintenance_tasks/runs/_arguments.html.erb +2 -2
- data/app/views/maintenance_tasks/runs/_run.html.erb +1 -1
- data/app/views/maintenance_tasks/tasks/index.html.erb +22 -20
- data/app/views/maintenance_tasks/tasks/show.html.erb +12 -10
- data/db/migrate/20210219212931_change_cursor_to_string.rb +18 -0
- data/lib/maintenance_tasks.rb +10 -0
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 04c33ddef00ee39487f42a7275fd3b11367bc16f32007a607e2d6fd7a9b3b55d
|
4
|
+
data.tar.gz: 841a336d86ce65e52d2ddee9cef746f9a448664328213056ea8c7a9f0d861b76
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cefc3a619d3d3aec10c6104cf227c33558e1d30146f0d9338706eb30c4e21f58bda704905c85a8faa87bc4735d5255b714c8690b6b733f9dcde42216231abc3d
|
7
|
+
data.tar.gz: 4f23532262622d91da4065926f19e96bb22788877acc10e1e7a0ce327e7136a2c24905e9a29aacbbdbe380c39dc58e593a0254e6bfec2b1c6d929f8e5fe13304
|
data/README.md
CHANGED
@@ -696,6 +696,26 @@ MaintenanceTasks.tasks_module = "TaskModule"
|
|
696
696
|
|
697
697
|
If no value is specified, it will default to `Maintenance`.
|
698
698
|
|
699
|
+
#### Organizing tasks using namespaces
|
700
|
+
|
701
|
+
Tasks may be nested arbitrarily deeply under `app/tasks/maintenance`, for example given a
|
702
|
+
task file `app/tasks/maintenance/team_name/service_name/update_posts_task.rb` we
|
703
|
+
can define the task as:
|
704
|
+
|
705
|
+
```ruby
|
706
|
+
module Maintenance
|
707
|
+
module TeamName
|
708
|
+
module ServiceName
|
709
|
+
class UpdatePostsTask < MaintenanceTasks::Task
|
710
|
+
def process(rows)
|
711
|
+
# ...
|
712
|
+
end
|
713
|
+
end
|
714
|
+
end
|
715
|
+
end
|
716
|
+
end
|
717
|
+
```
|
718
|
+
|
699
719
|
#### Customizing the underlying job class
|
700
720
|
|
701
721
|
`MaintenanceTasks.job` can be configured to define a Job class for your tasks to
|
@@ -784,6 +804,34 @@ MaintenanceTasks.backtrace_cleaner = cleaner
|
|
784
804
|
If none is specified, the default `Rails.backtrace_cleaner` will be used to
|
785
805
|
clean backtraces.
|
786
806
|
|
807
|
+
#### Customizing the parent controller for the web UI
|
808
|
+
|
809
|
+
`MaintenanceTasks.parent_controller` can be configured to specify a controller class for all of the web UI engine's
|
810
|
+
controllers to inherit from.
|
811
|
+
|
812
|
+
This allows applications with common logic in their `ApplicationController` (or
|
813
|
+
any other controller) to optionally configure the web UI to inherit that logic
|
814
|
+
with a simple assignment in the initializer.
|
815
|
+
|
816
|
+
```ruby
|
817
|
+
# config/initializers/maintenance_tasks.rb
|
818
|
+
|
819
|
+
MaintenanceTasks.parent_controller = "Services::CustomController"
|
820
|
+
|
821
|
+
# app/controllers/services/custom_controller.rb
|
822
|
+
|
823
|
+
class Services::CustomController < ActionController::Base
|
824
|
+
include CustomSecurityThings
|
825
|
+
include CustomLoggingThings
|
826
|
+
...
|
827
|
+
end
|
828
|
+
```
|
829
|
+
|
830
|
+
The parent controller value **must** be a string corresponding to an existing
|
831
|
+
controller class which **must inherit** from `ActionController::Base`.
|
832
|
+
|
833
|
+
If no value is specified, it will default to `"ActionController::Base"`.
|
834
|
+
|
787
835
|
## Upgrading
|
788
836
|
|
789
837
|
Use bundler to check for and upgrade to newer versions. After installing a new
|
@@ -4,7 +4,7 @@ module MaintenanceTasks
|
|
4
4
|
# Base class for all controllers used by this engine.
|
5
5
|
#
|
6
6
|
# Can be extended to add different authentication and authorization code.
|
7
|
-
class ApplicationController <
|
7
|
+
class ApplicationController < MaintenanceTasks.parent_controller.constantize
|
8
8
|
BULMA_CDN = "https://cdn.jsdelivr.net"
|
9
9
|
|
10
10
|
content_security_policy do |policy|
|
@@ -15,16 +15,11 @@ module MaintenanceTasks
|
|
15
15
|
)
|
16
16
|
policy.script_src(
|
17
17
|
# page refresh script
|
18
|
-
"'sha256-
|
18
|
+
"'sha256-NiHKryHWudRC2IteTqmY9v1VkaDUA/5jhgXkMTkgo2w='",
|
19
19
|
)
|
20
20
|
policy.frame_ancestors(:self)
|
21
21
|
end
|
22
22
|
|
23
|
-
before_action do
|
24
|
-
request.content_security_policy_nonce_generator ||= ->(_request) { SecureRandom.base64(16) }
|
25
|
-
request.content_security_policy_nonce_directives = ["style-src"]
|
26
|
-
end
|
27
|
-
|
28
23
|
protect_from_forgery with: :exception
|
29
24
|
end
|
30
25
|
end
|
@@ -20,7 +20,7 @@ module MaintenanceTasks
|
|
20
20
|
rescue ActiveRecord::RecordInvalid => error
|
21
21
|
redirect_to(task_path(error.record.task_name), alert: error.message)
|
22
22
|
rescue ActiveRecord::ValueTooLong => error
|
23
|
-
task_name = params.fetch(:
|
23
|
+
task_name = params.fetch(:task_id)
|
24
24
|
redirect_to(task_path(task_name), alert: error.message)
|
25
25
|
rescue Runner::EnqueuingError => error
|
26
26
|
redirect_to(task_path(error.run.task_name), alert: error.message)
|
@@ -109,7 +109,7 @@ module MaintenanceTasks
|
|
109
109
|
when ActiveModel::Type::Decimal, ActiveModel::Type::Float
|
110
110
|
form_builder.number_field(parameter_name, { step: "any" })
|
111
111
|
when ActiveModel::Type::DateTime
|
112
|
-
form_builder.datetime_field(parameter_name)
|
112
|
+
form_builder.datetime_field(parameter_name) + datetime_field_help_text
|
113
113
|
when ActiveModel::Type::Date
|
114
114
|
form_builder.date_field(parameter_name)
|
115
115
|
when ActiveModel::Type::Time
|
@@ -120,5 +120,19 @@ module MaintenanceTasks
|
|
120
120
|
form_builder.text_area(parameter_name, class: "textarea")
|
121
121
|
end
|
122
122
|
end
|
123
|
+
|
124
|
+
# Return helper text for the datetime-local form field.
|
125
|
+
def datetime_field_help_text
|
126
|
+
text =
|
127
|
+
if Time.zone_default.nil? || Time.zone_default.name == "UTC"
|
128
|
+
"Timezone: UTC."
|
129
|
+
else
|
130
|
+
"Timezone: #{Time.now.zone}."
|
131
|
+
end
|
132
|
+
tag.div(
|
133
|
+
tag.p(text),
|
134
|
+
class: "content is-small",
|
135
|
+
)
|
136
|
+
end
|
123
137
|
end
|
124
138
|
end
|
@@ -56,14 +56,14 @@ module MaintenanceTasks
|
|
56
56
|
batch_size: collection.batch_size,
|
57
57
|
)
|
58
58
|
when Array
|
59
|
-
enumerator_builder.build_array_enumerator(collection, cursor: cursor)
|
59
|
+
enumerator_builder.build_array_enumerator(collection, cursor: cursor&.to_i)
|
60
60
|
when BatchCsvCollectionBuilder::BatchCsv
|
61
61
|
JobIteration::CsvEnumerator.new(collection.csv).batches(
|
62
62
|
batch_size: collection.batch_size,
|
63
|
-
cursor: cursor,
|
63
|
+
cursor: cursor&.to_i,
|
64
64
|
)
|
65
65
|
when CSV
|
66
|
-
JobIteration::CsvEnumerator.new(collection).rows(cursor: cursor)
|
66
|
+
JobIteration::CsvEnumerator.new(collection).rows(cursor: cursor&.to_i)
|
67
67
|
else
|
68
68
|
raise ArgumentError, <<~MSG.squish
|
69
69
|
#{@task.class.name}#collection must be either an
|
@@ -175,7 +175,13 @@ module MaintenanceTasks
|
|
175
175
|
namespace = MaintenanceTasks.tasks_module.safe_constantize
|
176
176
|
return unless namespace
|
177
177
|
|
178
|
-
|
178
|
+
load_const = lambda do |root|
|
179
|
+
root.constants.each do |name|
|
180
|
+
object = root.const_get(name)
|
181
|
+
load_const.call(object) if object.instance_of?(Module)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
load_const.call(namespace)
|
179
185
|
end
|
180
186
|
end
|
181
187
|
|
@@ -33,17 +33,19 @@
|
|
33
33
|
|
34
34
|
<script>
|
35
35
|
function refresh() {
|
36
|
-
|
36
|
+
const target = document.querySelector("[data-refresh]")
|
37
|
+
if (!target || !target.dataset.refresh) return
|
37
38
|
window.setTimeout(() => {
|
38
39
|
document.body.style.cursor = "wait"
|
39
40
|
fetch(document.location, { headers: { "X-Requested-With": "XMLHttpRequest" } }).then(
|
40
41
|
async response => {
|
41
42
|
const text = await response.text()
|
42
43
|
const newDocument = new DOMParser().parseFromString(text, "text/html")
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
44
|
+
const newTarget = newDocument.querySelector("[data-refresh]")
|
45
|
+
if (newTarget) {
|
46
|
+
target.replaceWith(newTarget)
|
47
|
+
}
|
48
|
+
document.body.style.cursor = ""
|
47
49
|
refresh()
|
48
50
|
},
|
49
51
|
error => location.reload()
|
@@ -54,7 +56,7 @@
|
|
54
56
|
</script>
|
55
57
|
</head>
|
56
58
|
|
57
|
-
|
59
|
+
<body>
|
58
60
|
<%= render 'layouts/maintenance_tasks/navbar' %>
|
59
61
|
|
60
62
|
<section class="section">
|
@@ -68,5 +70,5 @@
|
|
68
70
|
<%= yield %>
|
69
71
|
</div>
|
70
72
|
</div>
|
71
|
-
|
73
|
+
</body>
|
72
74
|
</html>
|
@@ -1,22 +1,24 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
<
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
<%
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
1
|
+
<%= tag.div(data: { refresh: (defined?(@refresh) && @refresh) || "" }) do %>
|
2
|
+
<% if @available_tasks.empty? %>
|
3
|
+
<div class="content is-large">
|
4
|
+
<h3 class="title is-3"> The MaintenanceTasks gem has been successfully installed! </h3>
|
5
|
+
<p>
|
6
|
+
Any new Tasks will show up here. To start writing your first Task,
|
7
|
+
run <code>bin/rails generate maintenance_tasks:task my_task</code>.
|
8
|
+
</p>
|
9
|
+
</div>
|
10
|
+
<% else %>
|
11
|
+
<% if active_tasks = @available_tasks[:active] %>
|
12
|
+
<h3 class="title is-3">Active Tasks</h3>
|
13
|
+
<%= render partial: 'task', collection: active_tasks %>
|
14
|
+
<% end %>
|
15
|
+
<% if new_tasks = @available_tasks[:new] %>
|
16
|
+
<h3 class="title is-3">New Tasks</h3>
|
17
|
+
<%= render partial: 'task', collection: new_tasks %>
|
18
|
+
<% end %>
|
19
|
+
<% if completed_tasks = @available_tasks[:completed] %>
|
20
|
+
<h3 class="title is-3">Completed Tasks</h3>
|
21
|
+
<%= render partial: 'task', collection: completed_tasks %>
|
22
|
+
<% end %>
|
21
23
|
<% end %>
|
22
24
|
<% end %>
|
@@ -38,20 +38,22 @@
|
|
38
38
|
<pre><code><%= highlight_code(code) %></code></pre>
|
39
39
|
<% end %>
|
40
40
|
|
41
|
-
|
42
|
-
|
41
|
+
<%= tag.div(data: { refresh: (defined?(@refresh) && @refresh) || "" }) do %>
|
42
|
+
<% if @task.active_runs.any? %>
|
43
|
+
<hr/>
|
43
44
|
|
44
|
-
|
45
|
+
<h4 class="title is-4">Active Runs</h4>
|
45
46
|
|
46
|
-
|
47
|
-
<% end %>
|
47
|
+
<%= render @task.active_runs %>
|
48
|
+
<% end %>
|
48
49
|
|
49
|
-
<% if @runs_page.records.present? %>
|
50
|
-
|
50
|
+
<% if @runs_page.records.present? %>
|
51
|
+
<hr/>
|
51
52
|
|
52
|
-
|
53
|
+
<h4 class="title is-4">Previous Runs</h4>
|
53
54
|
|
54
|
-
|
55
|
+
<%= render @runs_page.records %>
|
55
56
|
|
56
|
-
|
57
|
+
<%= link_to "Next page", task_path(@task, cursor: @runs_page.next_cursor) unless @runs_page.last? %>
|
58
|
+
<% end %>
|
57
59
|
<% end %>
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ChangeCursorToString < ActiveRecord::Migration[6.0]
|
4
|
+
# This migration will clear all existing data in the cursor column with MySQL.
|
5
|
+
# Ensure no Tasks are paused when this migration is deployed, or they will be resumed from the start.
|
6
|
+
# Running tasks are able to gracefully handle this change, even if interrupted.
|
7
|
+
def up
|
8
|
+
change_table(:maintenance_tasks_runs) do |t|
|
9
|
+
t.change(:cursor, :string)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def down
|
14
|
+
change_table(:maintenance_tasks_runs) do |t|
|
15
|
+
t.change(:cursor, :bigint)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/maintenance_tasks.rb
CHANGED
@@ -72,4 +72,14 @@ module MaintenanceTasks
|
|
72
72
|
# @return [Proc] the callback to perform when an error occurs in the Task.
|
73
73
|
mattr_accessor :error_handler, default:
|
74
74
|
->(_error, _task_context, _errored_element) {}
|
75
|
+
|
76
|
+
# @!attribute parent_controller
|
77
|
+
# @scope class
|
78
|
+
#
|
79
|
+
# The parent controller all web UI controllers will inherit from.
|
80
|
+
# Must be a class that inherits from `ActionController::Base`.
|
81
|
+
# Defaults to `"ActionController::Base"`
|
82
|
+
#
|
83
|
+
# @return [String] the name of the parent controller for web UI.
|
84
|
+
mattr_accessor :parent_controller, default: "ActionController::Base"
|
75
85
|
end
|
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.2.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: 2023-02
|
11
|
+
date: 2023-08-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: actionpack
|
@@ -131,6 +131,7 @@ files:
|
|
131
131
|
- app/views/maintenance_tasks/tasks/show.html.erb
|
132
132
|
- config/routes.rb
|
133
133
|
- db/migrate/20201211151756_create_maintenance_tasks_runs.rb
|
134
|
+
- db/migrate/20210219212931_change_cursor_to_string.rb
|
134
135
|
- db/migrate/20210225152418_remove_index_on_task_name.rb
|
135
136
|
- db/migrate/20210517131953_add_arguments_to_maintenance_tasks_runs.rb
|
136
137
|
- db/migrate/20211210152329_add_lock_version_to_maintenance_tasks_runs.rb
|
@@ -154,7 +155,7 @@ homepage: https://github.com/Shopify/maintenance_tasks
|
|
154
155
|
licenses:
|
155
156
|
- MIT
|
156
157
|
metadata:
|
157
|
-
source_code_uri: https://github.com/Shopify/maintenance_tasks/tree/v2.
|
158
|
+
source_code_uri: https://github.com/Shopify/maintenance_tasks/tree/v2.2.0
|
158
159
|
allowed_push_host: https://rubygems.org
|
159
160
|
post_install_message:
|
160
161
|
rdoc_options: []
|
@@ -164,14 +165,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
164
165
|
requirements:
|
165
166
|
- - ">="
|
166
167
|
- !ruby/object:Gem::Version
|
167
|
-
version: '0'
|
168
|
+
version: '3.0'
|
168
169
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
169
170
|
requirements:
|
170
171
|
- - ">="
|
171
172
|
- !ruby/object:Gem::Version
|
172
173
|
version: '0'
|
173
174
|
requirements: []
|
174
|
-
rubygems_version: 3.
|
175
|
+
rubygems_version: 3.4.17
|
175
176
|
signing_key:
|
176
177
|
specification_version: 4
|
177
178
|
summary: A Rails engine for queuing and managing maintenance tasks
|