maintenance_tasks 1.0.0 → 1.1.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 +138 -21
- data/app/controllers/maintenance_tasks/runs_controller.rb +1 -11
- data/app/controllers/maintenance_tasks/tasks_controller.rb +6 -2
- data/app/helpers/maintenance_tasks/application_helper.rb +0 -34
- data/app/helpers/maintenance_tasks/task_helper.rb +8 -0
- data/app/jobs/maintenance_tasks/task_job.rb +23 -4
- data/app/models/maintenance_tasks/csv_collection.rb +34 -0
- data/app/models/maintenance_tasks/run.rb +19 -0
- data/app/models/maintenance_tasks/runner.rb +19 -4
- data/app/models/maintenance_tasks/task_data.rb +7 -2
- data/app/tasks/maintenance_tasks/task.rb +37 -13
- data/app/validators/maintenance_tasks/run_status_validator.rb +5 -2
- data/app/views/layouts/maintenance_tasks/_navbar.html.erb +0 -6
- data/app/views/maintenance_tasks/runs/_info.html.erb +6 -1
- data/app/views/maintenance_tasks/runs/_run.html.erb +6 -9
- data/app/views/maintenance_tasks/tasks/show.html.erb +29 -2
- data/config/routes.rb +0 -2
- data/lib/generators/maintenance_tasks/task_generator.rb +8 -1
- data/lib/generators/maintenance_tasks/templates/csv_task.rb.tt +13 -0
- data/lib/generators/maintenance_tasks/templates/task_spec.rb.tt +1 -1
- data/lib/generators/maintenance_tasks/templates/task_test.rb.tt +1 -1
- data/lib/maintenance_tasks.rb +17 -21
- data/lib/maintenance_tasks/cli.rb +16 -1
- metadata +6 -16
- data/app/views/maintenance_tasks/runs/index.html.erb +0 -15
- data/app/views/maintenance_tasks/tasks/actions/_cancelled.html.erb +0 -1
- data/app/views/maintenance_tasks/tasks/actions/_cancelling.html.erb +0 -4
- data/app/views/maintenance_tasks/tasks/actions/_enqueued.html.erb +0 -2
- data/app/views/maintenance_tasks/tasks/actions/_errored.html.erb +0 -1
- data/app/views/maintenance_tasks/tasks/actions/_interrupted.html.erb +0 -2
- data/app/views/maintenance_tasks/tasks/actions/_new.html.erb +0 -1
- data/app/views/maintenance_tasks/tasks/actions/_paused.html.erb +0 -2
- data/app/views/maintenance_tasks/tasks/actions/_pausing.html.erb +0 -2
- data/app/views/maintenance_tasks/tasks/actions/_running.html.erb +0 -2
- data/app/views/maintenance_tasks/tasks/actions/_succeeded.html.erb +0 -1
- data/lib/maintenance_tasks/integrations/bugsnag_handler.rb +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3e274988d4a5facd653134ab4d4f2c51a71957ad8b2e6e1e16958f3726d50bb1
|
4
|
+
data.tar.gz: beae97f203e12ed5017b9a76e1ced6b8d9f3e9e8ca8bc65a85a63d781d150bcf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 67ed786c6c0281422ef4a10af1167c129cbab7422624656c3cae3eb3fe8f35bc5df00f874f2aee2a1eacf54fe957faccd5a4df12240e3f461b81817cdd5347a7
|
7
|
+
data.tar.gz: 3cf190c896f046accf647b74e6acb58680b9e7eb89d25ea58e1a58e2db48e5437c5a410f2536c440c494efc599ea635a0380fc6a554ae157b53b402efbda4211
|
data/README.md
CHANGED
@@ -3,11 +3,16 @@
|
|
3
3
|
A Rails engine for queuing and managing maintenance tasks.
|
4
4
|
|
5
5
|
## Table of Contents
|
6
|
+
|
7
|
+
* [Demo](#demo)
|
6
8
|
* [Installation](#installation)
|
9
|
+
* [Active Job Dependency](#active-job-dependency)
|
7
10
|
* [Usage](#usage)
|
8
11
|
* [Creating a Task](#creating-a-task)
|
9
|
-
|
12
|
+
* [Creating a CSV Task](#creating-a-csv-task)
|
13
|
+
* [Considerations when writing Tasks](#considerations-when-writing-tasks)
|
10
14
|
* [Writing tests for a Task](#writing-tests-for-a-task)
|
15
|
+
* [Writing tests for a CSV Task](#writing-tests-for-a-csv-task)
|
11
16
|
* [Running a Task](#running-a-task)
|
12
17
|
* [Monitoring your Task's status](#monitoring-your-tasks-status)
|
13
18
|
* [How Maintenance Tasks runs a Task](#how-maintenance-tasks-runs-a-task)
|
@@ -21,18 +26,18 @@ A Rails engine for queuing and managing maintenance tasks.
|
|
21
26
|
* [Contributing](#contributing)
|
22
27
|
* [Releasing new versions](#releasing-new-versions)
|
23
28
|
|
24
|
-
##
|
29
|
+
## Demo
|
25
30
|
|
26
|
-
|
31
|
+
Watch this demo video to see the gem in action:
|
27
32
|
|
28
|
-
|
29
|
-
gem 'maintenance_tasks'
|
30
|
-
```
|
33
|
+
[](https://www.youtube.com/watch?v=BTuvTQxlFzs)
|
31
34
|
|
32
|
-
|
35
|
+
## Installation
|
36
|
+
|
37
|
+
To install the gem and run the install generator, execute:
|
33
38
|
|
34
39
|
```bash
|
35
|
-
$ bundle
|
40
|
+
$ bundle add maintenance_tasks
|
36
41
|
$ rails generate maintenance_tasks:install
|
37
42
|
```
|
38
43
|
|
@@ -40,6 +45,22 @@ The generator creates and runs a migration to add the necessary table to your
|
|
40
45
|
database. It also mounts Maintenance Tasks in your `config/routes.rb`. By
|
41
46
|
default the web UI can be accessed in the new `/maintenance_tasks` path.
|
42
47
|
|
48
|
+
In case you use an exception reporting service (e.g. Bugsnag) you might want to
|
49
|
+
define an error handler. See [Customizing the error
|
50
|
+
handler](#customizing-the-error-handler) for more information.
|
51
|
+
|
52
|
+
### Active Job Dependency
|
53
|
+
|
54
|
+
The Maintenance Tasks framework relies on ActiveJob behind the scenes to run
|
55
|
+
Tasks. The default queuing backend for ActiveJob is
|
56
|
+
[asynchronous][async-adapter]. It is **strongly recommended** to change this to
|
57
|
+
a persistent backend so that Task progress is not lost during code or
|
58
|
+
infrastructure changes. For more information on configuring a queuing backend,
|
59
|
+
take a look at the [ActiveJob documentation][active-job-docs].
|
60
|
+
|
61
|
+
[async-adapter]: https://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/AsyncAdapter.html
|
62
|
+
[active-job-docs]: https://guides.rubyonrails.org/active_job_basics.html#setting-the-backend
|
63
|
+
|
43
64
|
## Usage
|
44
65
|
|
45
66
|
### Creating a Task
|
@@ -81,7 +102,45 @@ module Maintenance
|
|
81
102
|
end
|
82
103
|
```
|
83
104
|
|
84
|
-
|
105
|
+
### Creating a CSV Task
|
106
|
+
|
107
|
+
You can also write a Task that iterates on a CSV file. Note that writing CSV
|
108
|
+
Tasks **requires ActiveStorage to be configured**. Ensure that the dependency
|
109
|
+
is specified in your application's Gemfile, and that you've followed the
|
110
|
+
[setup instuctions][setup].
|
111
|
+
|
112
|
+
[setup]: https://edgeguides.rubyonrails.org/active_storage_overview.html#setup
|
113
|
+
|
114
|
+
Generate a CSV Task by running:
|
115
|
+
|
116
|
+
```bash
|
117
|
+
$ rails generate maintenance_tasks:task import_posts --csv
|
118
|
+
```
|
119
|
+
|
120
|
+
The generated task is a subclass of `MaintenanceTasks::Task` that implements:
|
121
|
+
|
122
|
+
* `process`: do the work of your maintenance task on a `CSV::Row`
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
# app/tasks/maintenance/import_posts_task.rb
|
126
|
+
module Maintenance
|
127
|
+
class ImportPostsTask < MaintenanceTasks::Task
|
128
|
+
csv_collection
|
129
|
+
|
130
|
+
def process(row)
|
131
|
+
Post.create!(title: row["title"], content: row["content"])
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
```
|
136
|
+
|
137
|
+
```csv
|
138
|
+
# posts.csv
|
139
|
+
title,content
|
140
|
+
My Title,Hello World!
|
141
|
+
```
|
142
|
+
|
143
|
+
### Considerations when writing Tasks
|
85
144
|
|
86
145
|
MaintenanceTasks relies on the queue adapter configured for your application to
|
87
146
|
run the job which is processing your Task. The guidelines for writing Task may
|
@@ -120,7 +179,7 @@ module Maintenance
|
|
120
179
|
test "#process performs a task iteration" do
|
121
180
|
post = Post.new
|
122
181
|
|
123
|
-
Maintenance::UpdatePostsTask.
|
182
|
+
Maintenance::UpdatePostsTask.process(post)
|
124
183
|
|
125
184
|
assert_equal 'New content!', post.content
|
126
185
|
end
|
@@ -128,6 +187,32 @@ module Maintenance
|
|
128
187
|
end
|
129
188
|
```
|
130
189
|
|
190
|
+
### Writing tests for a CSV Task
|
191
|
+
|
192
|
+
You should write tests for your `#process` method in a CSV Task as well. It
|
193
|
+
takes a `CSV::Row` as an argument. You can pass a row, or a hash with string
|
194
|
+
keys to `#process` from your test.
|
195
|
+
|
196
|
+
```ruby
|
197
|
+
# app/tasks/maintenance/import_posts_task_test.rb
|
198
|
+
module Maintenance
|
199
|
+
class ImportPostsTaskTest < ActiveSupport::TestCase
|
200
|
+
test "#process performs a task iteration" do
|
201
|
+
assert_difference -> { Post.count } do
|
202
|
+
Maintenance::UpdatePostsTask.process({
|
203
|
+
'title' => 'My Title',
|
204
|
+
'content' => 'Hello World!',
|
205
|
+
})
|
206
|
+
end
|
207
|
+
|
208
|
+
post = Post.last
|
209
|
+
assert_equal 'My Title', post.title
|
210
|
+
assert_equal 'Hello World!', post.content
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
```
|
215
|
+
|
131
216
|
### Running a Task
|
132
217
|
|
133
218
|
You can run your new Task by accessing the Web UI and clicking on "Run".
|
@@ -138,11 +223,26 @@ Alternatively, you can run your Task in the command line:
|
|
138
223
|
$ bundle exec maintenance_tasks perform Maintenance::UpdatePostsTask
|
139
224
|
```
|
140
225
|
|
141
|
-
|
142
|
-
|
226
|
+
To run a Task that processes CSVs from the command line, use the --csv option:
|
227
|
+
|
228
|
+
```bash
|
229
|
+
$ bundle exec maintenance_tasks perform Maintenance::ImportPostsTask --csv 'path/to/my_csv.csv'
|
230
|
+
```
|
231
|
+
|
232
|
+
You can also run a Task in Ruby by sending `run` with a Task name to Runner:
|
143
233
|
|
144
234
|
```ruby
|
145
|
-
MaintenanceTasks::Runner.
|
235
|
+
MaintenanceTasks::Runner.run(name: 'Maintenance::UpdatePostsTask')
|
236
|
+
```
|
237
|
+
|
238
|
+
To run a Task that processes CSVs using the Runner, provide a Hash containing an
|
239
|
+
open IO object and a filename to `run`:
|
240
|
+
|
241
|
+
```ruby
|
242
|
+
MaintenanceTasks::Runner.run(
|
243
|
+
name: 'Maintenance::ImportPostsTask'
|
244
|
+
csv_file: { io: File.open('path/to/my_csv.csv'), filename: 'my_csv.csv' }
|
245
|
+
)
|
146
246
|
```
|
147
247
|
|
148
248
|
### Monitoring your Task's status
|
@@ -224,17 +324,36 @@ be placed in a `maintenance_tasks.rb` initializer.
|
|
224
324
|
Exceptions raised while a Task is performing are rescued and information about
|
225
325
|
the error is persisted and visible in the UI.
|
226
326
|
|
227
|
-
If
|
228
|
-
|
229
|
-
|
230
|
-
If you want to integrate with another exception monitoring service or customize
|
231
|
-
error handling, a callback can be defined:
|
327
|
+
If you want to integrate with an exception monitoring service (e.g. Bugsnag),
|
328
|
+
you can define an error handler:
|
232
329
|
|
233
330
|
```ruby
|
234
331
|
# config/initializers/maintenance_tasks.rb
|
235
|
-
MaintenanceTasks.error_handler = ->(error
|
332
|
+
MaintenanceTasks.error_handler = ->(error, task_context, _errored_element) do
|
333
|
+
Bugsnag.notify(error) do |notification|
|
334
|
+
notification.add_tab(:task, task_context)
|
335
|
+
end
|
336
|
+
end
|
236
337
|
```
|
237
338
|
|
339
|
+
The error handler should be a lambda that accepts three arguments:
|
340
|
+
|
341
|
+
* `error`: The object containing the exception that was raised.
|
342
|
+
* `task_context`: A hash with additional information about the Task and the
|
343
|
+
error:
|
344
|
+
* `task_name`: The name of the Task that errored
|
345
|
+
* `started_at`: The time the Task started
|
346
|
+
* `ended_at`: The time the Task errored
|
347
|
+
* `errored_element`: The element, if any, that was being processed when the
|
348
|
+
Task raised an exception. If you would like to pass this object to your
|
349
|
+
exception monitoring service, make sure you **sanitize the object** to avoid
|
350
|
+
leaking sensitive data and **convert it to a format** that is compatible with
|
351
|
+
your bug tracker. For example, Bugsnag only sends the id and class name of
|
352
|
+
ActiveRecord objects in order to protect sensitive data. CSV rows, on the
|
353
|
+
other hand, are converted to strings and passed raw to Bugsnag, so make sure
|
354
|
+
to filter any personal data from these objects before adding them to a
|
355
|
+
report.
|
356
|
+
|
238
357
|
#### Customizing the maintenance tasks module
|
239
358
|
|
240
359
|
`MaintenanceTasks.tasks_module` can be configured to define the module in which
|
@@ -302,8 +421,6 @@ pull requests. You can find the contribution guidelines on
|
|
302
421
|
|
303
422
|
## Releasing new versions
|
304
423
|
|
305
|
-
This gem is published to packagecloud. The procedure to publish a new version:
|
306
|
-
|
307
424
|
* Update `spec.version` in `maintenance_tasks.gemspec`.
|
308
425
|
* Run `bundle install` to bump the `Gemfile.lock` version of the gem.
|
309
426
|
* Open a PR and merge on approval.
|
@@ -6,17 +6,7 @@ module MaintenanceTasks
|
|
6
6
|
#
|
7
7
|
# @api private
|
8
8
|
class RunsController < ApplicationController
|
9
|
-
before_action :set_run
|
10
|
-
|
11
|
-
# Shows a full list of Runs.
|
12
|
-
def index
|
13
|
-
query = Run.all.order(id: :desc)
|
14
|
-
if params[:task_name].present?
|
15
|
-
task_name = Run.sanitize_sql_like(params[:task_name])
|
16
|
-
query = query.where('task_name LIKE ?', "%#{task_name}%")
|
17
|
-
end
|
18
|
-
@pagy, @runs = pagy(query)
|
19
|
-
end
|
9
|
+
before_action :set_run
|
20
10
|
|
21
11
|
# Updates a Run status to paused.
|
22
12
|
def pause
|
@@ -7,7 +7,7 @@ module MaintenanceTasks
|
|
7
7
|
#
|
8
8
|
# @api private
|
9
9
|
class TasksController < ApplicationController
|
10
|
-
before_action :set_refresh, only: [:index
|
10
|
+
before_action :set_refresh, only: [:index]
|
11
11
|
|
12
12
|
# Renders the maintenance_tasks/tasks page, displaying
|
13
13
|
# available tasks to users, grouped by category.
|
@@ -19,12 +19,16 @@ module MaintenanceTasks
|
|
19
19
|
# Shows running and completed instances of the Task.
|
20
20
|
def show
|
21
21
|
@task = TaskData.find(params.fetch(:id))
|
22
|
+
set_refresh if @task.last_run&.active?
|
22
23
|
@pagy, @previous_runs = pagy(@task.previous_runs)
|
23
24
|
end
|
24
25
|
|
25
26
|
# Runs a given Task and redirects to the Task page.
|
26
27
|
def run
|
27
|
-
task = Runner.
|
28
|
+
task = Runner.run(
|
29
|
+
name: params.fetch(:id),
|
30
|
+
csv_file: params[:csv_file]
|
31
|
+
)
|
28
32
|
redirect_to(task_path(task))
|
29
33
|
rescue ActiveRecord::RecordInvalid => error
|
30
34
|
redirect_to(task_path(error.record.task_name), alert: error.message)
|
@@ -28,40 +28,6 @@ module MaintenanceTasks
|
|
28
28
|
time_ago_in_words(datetime) + ' ago'
|
29
29
|
end
|
30
30
|
end
|
31
|
-
|
32
|
-
# Fix stylesheet_link_tag to handle integrity when preloading.
|
33
|
-
# To be reverted once fixed upstream in Rails.
|
34
|
-
def stylesheet_link_tag(*sources)
|
35
|
-
return super unless respond_to?(:send_preload_links_header, true)
|
36
|
-
options = sources.extract_options!.stringify_keys
|
37
|
-
path_options = options.extract!('protocol', 'host', 'skip_pipeline')
|
38
|
-
.symbolize_keys
|
39
|
-
preload_links = []
|
40
|
-
crossorigin = options.delete('crossorigin')
|
41
|
-
crossorigin = 'anonymous' if crossorigin == true
|
42
|
-
nopush = options['nopush'].nil? ? true : options.delete('nopush')
|
43
|
-
integrity = options['integrity']
|
44
|
-
|
45
|
-
sources_tags = sources.uniq.map do |source|
|
46
|
-
href = path_to_stylesheet(source, path_options)
|
47
|
-
preload_link = "<#{href}>; rel=preload; as=style"
|
48
|
-
preload_link += "; crossorigin=#{crossorigin}" unless crossorigin.nil?
|
49
|
-
preload_link += "; integrity=#{integrity}" unless integrity.nil?
|
50
|
-
preload_link += '; nopush' if nopush
|
51
|
-
preload_links << preload_link
|
52
|
-
tag_options = {
|
53
|
-
'rel' => 'stylesheet',
|
54
|
-
'media' => 'screen',
|
55
|
-
'crossorigin' => crossorigin,
|
56
|
-
'href' => href,
|
57
|
-
}.merge!(options)
|
58
|
-
tag(:link, tag_options)
|
59
|
-
end
|
60
|
-
|
61
|
-
send_preload_links_header(preload_links)
|
62
|
-
|
63
|
-
safe_join(sources_tags)
|
64
|
-
end
|
65
31
|
end
|
66
32
|
private_constant :ApplicationHelper
|
67
33
|
end
|
@@ -105,6 +105,14 @@ module MaintenanceTasks
|
|
105
105
|
end
|
106
106
|
safe_join(tokens)
|
107
107
|
end
|
108
|
+
|
109
|
+
# Returns a download link for a Run's CSV attachment
|
110
|
+
def csv_file_download_path(run)
|
111
|
+
Rails.application.routes.url_helpers.rails_blob_path(
|
112
|
+
run.csv_file,
|
113
|
+
only_path: true
|
114
|
+
)
|
115
|
+
end
|
108
116
|
end
|
109
117
|
private_constant :TaskHelper
|
110
118
|
end
|
@@ -34,9 +34,11 @@ module MaintenanceTasks
|
|
34
34
|
enumerator_builder.active_record_on_records(collection, cursor: cursor)
|
35
35
|
when Array
|
36
36
|
enumerator_builder.build_array_enumerator(collection, cursor: cursor)
|
37
|
+
when CSV
|
38
|
+
JobIteration::CsvEnumerator.new(collection).rows(cursor: cursor)
|
37
39
|
else
|
38
40
|
raise ArgumentError, "#{@task.class.name}#collection must be either "\
|
39
|
-
'an Active Record Relation or
|
41
|
+
'an Active Record Relation, Array, or CSV.'
|
40
42
|
end
|
41
43
|
end
|
42
44
|
|
@@ -47,14 +49,24 @@ module MaintenanceTasks
|
|
47
49
|
# @param _run [Run] the current Run, passed as an argument by Job Iteration.
|
48
50
|
def each_iteration(input, _run)
|
49
51
|
throw(:abort, :skip_complete_callbacks) if @run.stopping?
|
50
|
-
|
52
|
+
task_iteration(input)
|
51
53
|
@ticker.tick
|
52
54
|
@run.reload_status
|
53
55
|
end
|
54
56
|
|
57
|
+
def task_iteration(input)
|
58
|
+
@task.process(input)
|
59
|
+
rescue => error
|
60
|
+
@errored_element = input
|
61
|
+
raise error
|
62
|
+
end
|
63
|
+
|
55
64
|
def before_perform
|
56
65
|
@run = arguments.first
|
57
66
|
@task = Task.named(@run.task_name).new
|
67
|
+
if @task.respond_to?(:csv_content=)
|
68
|
+
@task.csv_content = @run.csv_file.download
|
69
|
+
end
|
58
70
|
@run.job_id = job_id
|
59
71
|
|
60
72
|
@run.running! unless @run.stopping?
|
@@ -90,9 +102,16 @@ module MaintenanceTasks
|
|
90
102
|
end
|
91
103
|
|
92
104
|
def on_error(error)
|
93
|
-
@ticker.persist
|
105
|
+
@ticker.persist if defined?(@ticker)
|
94
106
|
@run.persist_error(error)
|
95
|
-
|
107
|
+
|
108
|
+
task_context = {
|
109
|
+
task_name: @run.task_name,
|
110
|
+
started_at: @run.started_at,
|
111
|
+
ended_at: @run.ended_at,
|
112
|
+
}
|
113
|
+
errored_element = @errored_element if defined?(@errored_element)
|
114
|
+
MaintenanceTasks.error_handler.call(error, task_context, errored_element)
|
96
115
|
end
|
97
116
|
end
|
98
117
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'csv'
|
4
|
+
|
5
|
+
module MaintenanceTasks
|
6
|
+
# Module that is included into Task classes by Task.csv_collection for
|
7
|
+
# processing CSV files.
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
module CsvCollection
|
11
|
+
# The contents of a CSV file to be processed by a Task.
|
12
|
+
#
|
13
|
+
# @return [String] the content of the CSV file to process.
|
14
|
+
attr_accessor :csv_content
|
15
|
+
|
16
|
+
# Defines the collection to be iterated over, based on the provided CSV.
|
17
|
+
#
|
18
|
+
# @return [CSV] the CSV object constructed from the specified CSV content,
|
19
|
+
# with headers.
|
20
|
+
def collection
|
21
|
+
CSV.new(csv_content, headers: true)
|
22
|
+
end
|
23
|
+
|
24
|
+
# The number of rows to be processed. Excludes the header row from the count
|
25
|
+
# and assumed a trailing new line in the CSV file. Note that this number is
|
26
|
+
# an approximation based on the number of new lines.
|
27
|
+
#
|
28
|
+
# @return [Integer] the approximate number of rows to process.
|
29
|
+
def count
|
30
|
+
csv_content.count("\n") - 1
|
31
|
+
end
|
32
|
+
end
|
33
|
+
private_constant :CsvCollection
|
34
|
+
end
|
@@ -33,6 +33,8 @@ module MaintenanceTasks
|
|
33
33
|
validates :task_name, on: :create, inclusion: { in: ->(_) {
|
34
34
|
Task.available_tasks.map(&:to_s)
|
35
35
|
} }
|
36
|
+
validate :csv_attachment_presence, on: :create
|
37
|
+
|
36
38
|
attr_readonly :task_name
|
37
39
|
|
38
40
|
serialize :backtrace
|
@@ -41,6 +43,8 @@ module MaintenanceTasks
|
|
41
43
|
|
42
44
|
validates_with RunStatusValidator, on: :update
|
43
45
|
|
46
|
+
has_one_attached :csv_file
|
47
|
+
|
44
48
|
# Sets the run status to enqueued, making sure the transition is validated
|
45
49
|
# in case it's already enqueued.
|
46
50
|
def enqueued!
|
@@ -175,6 +179,21 @@ module MaintenanceTasks
|
|
175
179
|
def stuck?
|
176
180
|
cancelling? && updated_at <= 5.minutes.ago
|
177
181
|
end
|
182
|
+
|
183
|
+
# Performs validation on the presence of a :csv_file attachment.
|
184
|
+
# A Run for a Task that uses CsvCollection must have an attached :csv_file
|
185
|
+
# to be valid. Conversely, a Run for a Task that doesn't use CsvCollection
|
186
|
+
# should not have an attachment to be valid. The appropriate error is added
|
187
|
+
# if the Run does not meet the above criteria.
|
188
|
+
def csv_attachment_presence
|
189
|
+
if Task.named(task_name) < CsvCollection && !csv_file.attached?
|
190
|
+
errors.add(:csv_file, 'must be attached to CSV Task.')
|
191
|
+
elsif !(Task.named(task_name) < CsvCollection) && csv_file.attached?
|
192
|
+
errors.add(:csv_file, 'should not be attached to non-CSV Task.')
|
193
|
+
end
|
194
|
+
rescue Task::NotFoundError
|
195
|
+
nil
|
196
|
+
end
|
178
197
|
end
|
179
198
|
private_constant :Run
|
180
199
|
end
|
@@ -2,7 +2,17 @@
|
|
2
2
|
|
3
3
|
module MaintenanceTasks
|
4
4
|
# This class is responsible for running a given Task.
|
5
|
-
|
5
|
+
module Runner
|
6
|
+
extend self
|
7
|
+
|
8
|
+
# @deprecated Use {Runner} directly instead.
|
9
|
+
def new
|
10
|
+
ActiveSupport::Deprecation.warn(
|
11
|
+
'Use Runner.run instead of Runner.new.run'
|
12
|
+
)
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
6
16
|
# Exception raised when a Task Job couldn't be enqueued.
|
7
17
|
class EnqueuingError < StandardError
|
8
18
|
# Initializes a Enqueuing Error.
|
@@ -20,17 +30,22 @@ module MaintenanceTasks
|
|
20
30
|
# Runs a Task.
|
21
31
|
#
|
22
32
|
# This method creates a Run record for the given Task name and enqueues the
|
23
|
-
# Run.
|
33
|
+
# Run. If a CSV file is provided, it is attached to the Run record.
|
24
34
|
#
|
25
35
|
# @param name [String] the name of the Task to be run.
|
36
|
+
# @param csv_file [attachable, nil] a CSV file that provides the collection
|
37
|
+
# for the Task to iterate over when running, in the form of an attachable
|
38
|
+
# (see https://edgeapi.rubyonrails.org/classes/ActiveStorage/Attached/One.html#method-i-attach).
|
39
|
+
# Value is nil if the Task does not use CSV iteration.
|
26
40
|
#
|
27
41
|
# @return [Task] the Task that was run.
|
28
42
|
#
|
29
43
|
# @raise [EnqueuingError] if an error occurs while enqueuing the Run.
|
30
44
|
# @raise [ActiveRecord::RecordInvalid] if validation errors occur while
|
31
45
|
# creating the Run.
|
32
|
-
def run(name:)
|
46
|
+
def run(name:, csv_file: nil)
|
33
47
|
run = Run.active.find_by(task_name: name) || Run.new(task_name: name)
|
48
|
+
run.csv_file.attach(csv_file) if csv_file
|
34
49
|
|
35
50
|
run.enqueued!
|
36
51
|
enqueue(run)
|
@@ -40,7 +55,7 @@ module MaintenanceTasks
|
|
40
55
|
private
|
41
56
|
|
42
57
|
def enqueue(run)
|
43
|
-
unless MaintenanceTasks.job.perform_later(run)
|
58
|
+
unless MaintenanceTasks.job.constantize.perform_later(run)
|
44
59
|
raise "The job to perform #{run.task_name} could not be enqueued. "\
|
45
60
|
'Enqueuing has been prevented by a callback.'
|
46
61
|
end
|
@@ -73,7 +73,7 @@ module MaintenanceTasks
|
|
73
73
|
def code
|
74
74
|
return if deleted?
|
75
75
|
task = Task.named(name)
|
76
|
-
file = task.instance_method(:
|
76
|
+
file = task.instance_method(:process).source_location.first
|
77
77
|
File.read(file)
|
78
78
|
end
|
79
79
|
|
@@ -127,10 +127,15 @@ module MaintenanceTasks
|
|
127
127
|
end
|
128
128
|
end
|
129
129
|
|
130
|
+
# @return [Boolean] whether the Task inherits from CsvTask.
|
131
|
+
def csv_task?
|
132
|
+
!deleted? && Task.named(name) < CsvCollection
|
133
|
+
end
|
134
|
+
|
130
135
|
private
|
131
136
|
|
132
137
|
def runs
|
133
|
-
Run.where(task_name: name).order(created_at: :desc)
|
138
|
+
Run.where(task_name: name).with_attached_csv_file.order(created_at: :desc)
|
134
139
|
end
|
135
140
|
end
|
136
141
|
private_constant :TaskData
|
@@ -30,6 +30,41 @@ module MaintenanceTasks
|
|
30
30
|
descendants
|
31
31
|
end
|
32
32
|
|
33
|
+
# Make this Task a task that handles CSV.
|
34
|
+
#
|
35
|
+
# An input to upload a CSV will be added in the form to start a Run. The
|
36
|
+
# collection and count method are implemented.
|
37
|
+
def csv_collection
|
38
|
+
include(CsvCollection)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Processes one item.
|
42
|
+
#
|
43
|
+
# Especially useful for tests.
|
44
|
+
#
|
45
|
+
# @param item the item to process.
|
46
|
+
def process(item)
|
47
|
+
new.process(item)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns the collection for this Task.
|
51
|
+
#
|
52
|
+
# Especially useful for tests.
|
53
|
+
#
|
54
|
+
# @return the collection.
|
55
|
+
def collection
|
56
|
+
new.collection
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns the count of items for this Task.
|
60
|
+
#
|
61
|
+
# Especially useful for tests.
|
62
|
+
#
|
63
|
+
# @return the count of items.
|
64
|
+
def count
|
65
|
+
new.count
|
66
|
+
end
|
67
|
+
|
33
68
|
private
|
34
69
|
|
35
70
|
def load_constants
|
@@ -45,8 +80,7 @@ module MaintenanceTasks
|
|
45
80
|
# @raise [NotImplementedError] with a message advising subclasses to
|
46
81
|
# implement an override for this method.
|
47
82
|
def collection
|
48
|
-
raise
|
49
|
-
"#{self.class.name} must implement `collection`."
|
83
|
+
raise NoMethodError, "#{self.class.name} must implement `collection`."
|
50
84
|
end
|
51
85
|
|
52
86
|
# Placeholder method to raise in case a subclass fails to implement the
|
@@ -57,8 +91,7 @@ module MaintenanceTasks
|
|
57
91
|
# @raise [NotImplementedError] with a message advising subclasses to
|
58
92
|
# implement an override for this method.
|
59
93
|
def process(_item)
|
60
|
-
raise
|
61
|
-
"#{self.class.name} must implement `process`."
|
94
|
+
raise NoMethodError, "#{self.class.name} must implement `process`."
|
62
95
|
end
|
63
96
|
|
64
97
|
# Total count of iterations to be performed.
|
@@ -70,14 +103,5 @@ module MaintenanceTasks
|
|
70
103
|
# @return [Integer, nil]
|
71
104
|
def count
|
72
105
|
end
|
73
|
-
|
74
|
-
# Convenience method to allow tasks define enumerators with cursors for
|
75
|
-
# compatibility with Job Iteration.
|
76
|
-
#
|
77
|
-
# @return [JobIteration::EnumeratorBuilder] instance of an enumerator
|
78
|
-
# builder available to tasks.
|
79
|
-
def enumerator_builder
|
80
|
-
JobIteration.enumerator_builder.new(nil)
|
81
|
-
end
|
82
106
|
end
|
83
107
|
end
|
@@ -11,7 +11,8 @@ module MaintenanceTasks
|
|
11
11
|
# enqueued -> pausing occurs when the task is paused before starting.
|
12
12
|
# enqueued -> cancelling occurs when the task is cancelled
|
13
13
|
# before starting.
|
14
|
-
# enqueued -> errored occurs when the task job fails to be enqueued
|
14
|
+
# enqueued -> errored occurs when the task job fails to be enqueued, or
|
15
|
+
# if the Task is deleted before is starts running.
|
15
16
|
'enqueued' => ['running', 'pausing', 'cancelling', 'errored'],
|
16
17
|
# pausing -> paused occurs when the task actually halts performing and
|
17
18
|
# occupies a status of paused.
|
@@ -56,7 +57,9 @@ module MaintenanceTasks
|
|
56
57
|
# it is interrupted.
|
57
58
|
# interrupted -> cancelling occurs when the task is cancelled by the user
|
58
59
|
# while it is interrupted.
|
59
|
-
|
60
|
+
# interrupted -> errored occurs when the task is deleted while it is
|
61
|
+
# interrupted.
|
62
|
+
'interrupted' => ['running', 'pausing', 'cancelling', 'errored'],
|
60
63
|
}
|
61
64
|
|
62
65
|
# Validate whether a transition from one Run status
|
@@ -2,10 +2,4 @@
|
|
2
2
|
<div class="navbar-brand">
|
3
3
|
<%= link_to 'Maintenance Tasks', root_path, class: 'navbar-item is-size-4 has-text-weight-semibold has-text-danger' %>
|
4
4
|
</div>
|
5
|
-
|
6
|
-
<div id="navbarMenu" class="navbar-menu is-active">
|
7
|
-
<div class="navbar-start">
|
8
|
-
<%= link_to "Runs", runs_path, class: 'navbar-item' %>
|
9
|
-
</div>
|
10
|
-
</div>
|
11
5
|
</nav>
|
@@ -1,6 +1,5 @@
|
|
1
1
|
<h5 class="title is-5">
|
2
2
|
<%= time_tag run.created_at, title: run.created_at %>
|
3
|
-
<%= status_tag run.status if with_status %>
|
4
3
|
</h5>
|
5
4
|
|
6
5
|
<%= progress run %>
|
@@ -8,3 +7,9 @@
|
|
8
7
|
<div class="content">
|
9
8
|
<%= render "maintenance_tasks/runs/info/#{run.status}", run: run %>
|
10
9
|
</div>
|
10
|
+
|
11
|
+
<% if run.csv_file.attached? %>
|
12
|
+
<div class="block">
|
13
|
+
<%= link_to('Download CSV', csv_file_download_path(run)) %>
|
14
|
+
</div>
|
15
|
+
<% end %>
|
@@ -1,11 +1,8 @@
|
|
1
1
|
<div class="box">
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
<% else %>
|
9
|
-
<%= render 'maintenance_tasks/runs/info', run: run, with_status: true %>
|
10
|
-
<% end %>
|
2
|
+
<h5 class="title is-5">
|
3
|
+
<%= time_tag run.created_at, title: run.created_at %>
|
4
|
+
<%= status_tag run.status %>
|
5
|
+
</h5>
|
6
|
+
|
7
|
+
<%= render 'maintenance_tasks/runs/info', run: run %>
|
11
8
|
</div>
|
@@ -7,7 +7,34 @@
|
|
7
7
|
<%= render 'maintenance_tasks/runs/info', run: @task.last_run, with_status: false if @task.last_run %>
|
8
8
|
|
9
9
|
<div class="buttons">
|
10
|
-
|
10
|
+
<% last_run = @task.last_run %>
|
11
|
+
<% if last_run.nil? || last_run.completed? %>
|
12
|
+
<%= form_with url: run_task_path(@task), method: :put do |form| %>
|
13
|
+
<% if @task.csv_task? %>
|
14
|
+
<div class="block">
|
15
|
+
<%= form.label :csv_file %>
|
16
|
+
<%= form.file_field :csv_file %>
|
17
|
+
</div>
|
18
|
+
<% end %>
|
19
|
+
<div class="block">
|
20
|
+
<%= form.submit 'Run', class: "button is-success", disabled: @task.deleted? %>
|
21
|
+
</div>
|
22
|
+
<% end %>
|
23
|
+
<% elsif last_run.cancelling? %>
|
24
|
+
<%= button_to 'Run', run_task_path(@task), method: :put, class: 'button is-success', disabled: true %>
|
25
|
+
<% if last_run.stuck? %>
|
26
|
+
<%= button_to 'Cancel', cancel_task_run_path(@task, last_run), method: :put, class: 'button is-danger', disabled: @task.deleted? %>
|
27
|
+
<% end %>
|
28
|
+
<% elsif last_run.pausing? %>
|
29
|
+
<%= button_to 'Pausing', pause_task_run_path(@task, last_run), method: :put, class: 'button is-warning', disabled: true %>
|
30
|
+
<%= button_to 'Cancel', cancel_task_run_path(@task, last_run), method: :put, class: 'button is-danger' %>
|
31
|
+
<% elsif last_run.paused? %>
|
32
|
+
<%= button_to 'Resume', run_task_path(@task), method: :put, class: 'button is-primary', disabled: @task.deleted? %>
|
33
|
+
<%= button_to 'Cancel', cancel_task_run_path(@task, last_run), method: :put, class: 'button is-danger' %>
|
34
|
+
<% else %>
|
35
|
+
<%= button_to 'Pause', pause_task_run_path(@task, last_run), method: :put, class: 'button is-warning', disabled: @task.deleted? %>
|
36
|
+
<%= button_to 'Cancel', cancel_task_run_path(@task, last_run), method: :put, class: 'button is-danger' %>
|
37
|
+
<% end%>
|
11
38
|
</div>
|
12
39
|
|
13
40
|
<% if (code = @task.code) %>
|
@@ -19,7 +46,7 @@
|
|
19
46
|
|
20
47
|
<h4 class="title is-4">Previous Runs</h4>
|
21
48
|
|
22
|
-
<%= render @previous_runs
|
49
|
+
<%= render @previous_runs %>
|
23
50
|
|
24
51
|
<%= pagination(@pagy) %>
|
25
52
|
<% end %>
|
data/config/routes.rb
CHANGED
@@ -9,6 +9,9 @@ module MaintenanceTasks
|
|
9
9
|
desc 'This generator creates a task file at app/tasks and a corresponding '\
|
10
10
|
'test.'
|
11
11
|
|
12
|
+
class_option :csv, type: :boolean, default: false,
|
13
|
+
desc: 'Generate a CSV Task.'
|
14
|
+
|
12
15
|
check_class_collision suffix: 'Task'
|
13
16
|
|
14
17
|
# Creates the Task file.
|
@@ -18,7 +21,11 @@ module MaintenanceTasks
|
|
18
21
|
class_path,
|
19
22
|
"#{file_name}_task.rb"
|
20
23
|
)
|
21
|
-
|
24
|
+
if options[:csv]
|
25
|
+
template('csv_task.rb', template_file)
|
26
|
+
else
|
27
|
+
template('task.rb', template_file)
|
28
|
+
end
|
22
29
|
end
|
23
30
|
|
24
31
|
# Creates the Task test file, according to the app's test framework.
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module <%= tasks_module %>
|
4
|
+
<% module_namespacing do -%>
|
5
|
+
class <%= class_name %>Task < MaintenanceTasks::Task
|
6
|
+
csv_collection
|
7
|
+
|
8
|
+
def process(row)
|
9
|
+
# The work to be done on a row of the CSV
|
10
|
+
end
|
11
|
+
end
|
12
|
+
<% end -%>
|
13
|
+
end
|
@@ -6,7 +6,7 @@ module <%= tasks_module %>
|
|
6
6
|
RSpec.describe <%= class_name %>Task do
|
7
7
|
# describe '#process' do
|
8
8
|
# it 'performs a task iteration' do
|
9
|
-
# <%= tasks_module %>::<%= class_name %>Task.
|
9
|
+
# <%= tasks_module %>::<%= class_name %>Task.process(element)
|
10
10
|
# end
|
11
11
|
# end
|
12
12
|
end
|
@@ -5,7 +5,7 @@ module <%= tasks_module %>
|
|
5
5
|
<% module_namespacing do -%>
|
6
6
|
class <%= class_name %>TaskTest < ActiveSupport::TestCase
|
7
7
|
# test "#process performs a task iteration" do
|
8
|
-
# <%= tasks_module %>::<%= class_name %>Task.
|
8
|
+
# <%= tasks_module %>::<%= class_name %>Task.process(element)
|
9
9
|
# end
|
10
10
|
end
|
11
11
|
<% end -%>
|
data/lib/maintenance_tasks.rb
CHANGED
@@ -21,7 +21,7 @@ module MaintenanceTasks
|
|
21
21
|
# `MaintenanceTasks::TaskJob` or a class that inherits from it.
|
22
22
|
#
|
23
23
|
# @param [String] the name of the job class.
|
24
|
-
|
24
|
+
mattr_accessor :job, default: 'MaintenanceTasks::TaskJob'
|
25
25
|
|
26
26
|
# After each iteration, the progress of the task may be updated. This duration
|
27
27
|
# in seconds limits these updates, skipping if the duration since the last
|
@@ -32,27 +32,23 @@ module MaintenanceTasks
|
|
32
32
|
# the ticker during Task iterations.
|
33
33
|
mattr_accessor :ticker_delay, default: 1.second
|
34
34
|
|
35
|
-
#
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
# perform Tasks.
|
41
|
-
#
|
42
|
-
# @return [TaskJob] the job class.
|
43
|
-
def job
|
44
|
-
@@job.constantize
|
45
|
-
end
|
35
|
+
# Retrieves the callback to be performed when an error occurs in the task.
|
36
|
+
def self.error_handler
|
37
|
+
return @error_handler if defined?(@error_handler)
|
38
|
+
@error_handler = ->(_error, _task_context, _errored_element) {}
|
39
|
+
end
|
46
40
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
41
|
+
# Defines a callback to be performed when an error occurs in the task.
|
42
|
+
def self.error_handler=(error_handler)
|
43
|
+
unless error_handler.arity == 3
|
44
|
+
ActiveSupport::Deprecation.warn(
|
45
|
+
'MaintenanceTasks.error_handler should be a lambda that takes three '\
|
46
|
+
'arguments: error, task_context, and errored_element.'
|
47
|
+
)
|
48
|
+
@error_handler = ->(error, _task_context, _errored_element) do
|
49
|
+
error_handler.call(error)
|
50
|
+
end
|
54
51
|
end
|
52
|
+
@error_handler = error_handler
|
55
53
|
end
|
56
54
|
end
|
57
|
-
|
58
|
-
MaintenanceTasks.configure_bugsnag_integration
|
@@ -24,17 +24,32 @@ module MaintenanceTasks
|
|
24
24
|
#{MaintenanceTasks::Task.available_tasks.join("\n\n")}
|
25
25
|
LONGDESC
|
26
26
|
|
27
|
+
# Specify the CSV file to process for CSV Tasks
|
28
|
+
option :csv, desc: 'Supply a CSV file to be processed by a CSV Task, '\
|
29
|
+
'--csv "path/to/csv/file.csv"'
|
30
|
+
|
27
31
|
# Command to run a Task.
|
28
32
|
#
|
29
33
|
# It instantiates a Runner and sends a run message with the given Task name.
|
34
|
+
# If a CSV file is supplied using the --csv option, an attachable with the
|
35
|
+
# File IO object is sent along with the Task name to run.
|
30
36
|
#
|
31
37
|
# @param name [String] the name of the Task to be run.
|
32
38
|
def perform(name)
|
33
|
-
task = Runner.
|
39
|
+
task = Runner.run(name: name, csv_file: csv_file)
|
34
40
|
say_status(:success, "#{task.name} was enqueued.", :green)
|
35
41
|
rescue => error
|
36
42
|
say_status(:error, error.message, :red)
|
37
43
|
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def csv_file
|
48
|
+
csv_option = options[:csv]
|
49
|
+
if csv_option
|
50
|
+
{ io: File.open(csv_option), filename: File.basename(csv_option) }
|
51
|
+
end
|
52
|
+
end
|
38
53
|
end
|
39
54
|
private_constant :CLI
|
40
55
|
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: 1.
|
4
|
+
version: 1.1.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: 2021-
|
11
|
+
date: 2021-02-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: actionpack
|
@@ -110,6 +110,7 @@ files:
|
|
110
110
|
- app/helpers/maintenance_tasks/task_helper.rb
|
111
111
|
- app/jobs/maintenance_tasks/task_job.rb
|
112
112
|
- app/models/maintenance_tasks/application_record.rb
|
113
|
+
- app/models/maintenance_tasks/csv_collection.rb
|
113
114
|
- app/models/maintenance_tasks/progress.rb
|
114
115
|
- app/models/maintenance_tasks/run.rb
|
115
116
|
- app/models/maintenance_tasks/runner.rb
|
@@ -121,7 +122,6 @@ files:
|
|
121
122
|
- app/views/layouts/maintenance_tasks/application.html.erb
|
122
123
|
- app/views/maintenance_tasks/runs/_info.html.erb
|
123
124
|
- app/views/maintenance_tasks/runs/_run.html.erb
|
124
|
-
- app/views/maintenance_tasks/runs/index.html.erb
|
125
125
|
- app/views/maintenance_tasks/runs/info/_cancelled.html.erb
|
126
126
|
- app/views/maintenance_tasks/runs/info/_cancelling.html.erb
|
127
127
|
- app/views/maintenance_tasks/runs/info/_enqueued.html.erb
|
@@ -132,16 +132,6 @@ files:
|
|
132
132
|
- app/views/maintenance_tasks/runs/info/_running.html.erb
|
133
133
|
- app/views/maintenance_tasks/runs/info/_succeeded.html.erb
|
134
134
|
- app/views/maintenance_tasks/tasks/_task.html.erb
|
135
|
-
- app/views/maintenance_tasks/tasks/actions/_cancelled.html.erb
|
136
|
-
- app/views/maintenance_tasks/tasks/actions/_cancelling.html.erb
|
137
|
-
- app/views/maintenance_tasks/tasks/actions/_enqueued.html.erb
|
138
|
-
- app/views/maintenance_tasks/tasks/actions/_errored.html.erb
|
139
|
-
- app/views/maintenance_tasks/tasks/actions/_interrupted.html.erb
|
140
|
-
- app/views/maintenance_tasks/tasks/actions/_new.html.erb
|
141
|
-
- app/views/maintenance_tasks/tasks/actions/_paused.html.erb
|
142
|
-
- app/views/maintenance_tasks/tasks/actions/_pausing.html.erb
|
143
|
-
- app/views/maintenance_tasks/tasks/actions/_running.html.erb
|
144
|
-
- app/views/maintenance_tasks/tasks/actions/_succeeded.html.erb
|
145
135
|
- app/views/maintenance_tasks/tasks/index.html.erb
|
146
136
|
- app/views/maintenance_tasks/tasks/show.html.erb
|
147
137
|
- config/routes.rb
|
@@ -149,21 +139,21 @@ files:
|
|
149
139
|
- exe/maintenance_tasks
|
150
140
|
- lib/generators/maintenance_tasks/install_generator.rb
|
151
141
|
- lib/generators/maintenance_tasks/task_generator.rb
|
142
|
+
- lib/generators/maintenance_tasks/templates/csv_task.rb.tt
|
152
143
|
- lib/generators/maintenance_tasks/templates/task.rb.tt
|
153
144
|
- lib/generators/maintenance_tasks/templates/task_spec.rb.tt
|
154
145
|
- lib/generators/maintenance_tasks/templates/task_test.rb.tt
|
155
146
|
- lib/maintenance_tasks.rb
|
156
147
|
- lib/maintenance_tasks/cli.rb
|
157
148
|
- lib/maintenance_tasks/engine.rb
|
158
|
-
- lib/maintenance_tasks/integrations/bugsnag_handler.rb
|
159
149
|
- lib/tasks/maintenance_tasks_tasks.rake
|
160
150
|
homepage: https://github.com/Shopify/maintenance_tasks
|
161
151
|
licenses: []
|
162
152
|
metadata:
|
163
|
-
source_code_uri: https://github.com/Shopify/maintenance_tasks/tree/v1.
|
153
|
+
source_code_uri: https://github.com/Shopify/maintenance_tasks/tree/v1.1.0
|
164
154
|
allowed_push_host: https://rubygems.org
|
165
155
|
post_install_message: |-
|
166
|
-
Thank you for installing Maintenance Tasks 1.
|
156
|
+
Thank you for installing Maintenance Tasks 1.1.0. To complete, please run:
|
167
157
|
|
168
158
|
rails generate maintenance_tasks:install
|
169
159
|
rdoc_options: []
|
@@ -1,15 +0,0 @@
|
|
1
|
-
<div class="block">
|
2
|
-
<%= form_with url: runs_path, method: :get do |form| %>
|
3
|
-
<div class="field has-addons">
|
4
|
-
<div class="control">
|
5
|
-
<%= form.search_field :task_name, value: params[:task_name], placeholder: "Task name", class: "input" %>
|
6
|
-
</div>
|
7
|
-
<div class="control">
|
8
|
-
<%= form.submit "Search", class: "button is-link" %>
|
9
|
-
</div>
|
10
|
-
</div>
|
11
|
-
<% end %>
|
12
|
-
</div>
|
13
|
-
|
14
|
-
<%= render @runs, with_task_name: true %>
|
15
|
-
<%= pagination(@pagy) %>
|
@@ -1 +0,0 @@
|
|
1
|
-
<%= button_to 'Run', run_task_path(task), method: :put, class: 'button is-success', disabled: task.deleted? %>
|
@@ -1,4 +0,0 @@
|
|
1
|
-
<%= button_to 'Run', run_task_path(task), method: :put, class: 'button is-success', disabled: true %>
|
2
|
-
<% if task.last_run.stuck? %>
|
3
|
-
<%= button_to 'Cancel', cancel_task_run_path(task, task.last_run), method: :put, class: 'button is-danger', disabled: task.deleted? %>
|
4
|
-
<% end %>
|
@@ -1 +0,0 @@
|
|
1
|
-
<%= button_to 'Run', run_task_path(task), method: :put, class: 'button is-success', disabled: task.deleted? %>
|
@@ -1 +0,0 @@
|
|
1
|
-
<%= button_to 'Run', run_task_path(task), method: :put, class: 'button is-success', disabled: task.deleted? %>
|
@@ -1 +0,0 @@
|
|
1
|
-
<%= button_to 'Run', run_task_path(task), method: :put, class: 'button is-success', disabled: task.deleted? %>
|