maintenance_tasks 1.0.0 → 1.1.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 +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
|
+
[![Link to demo video](static/demo.png)](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? %>
|