maintenance_tasks 1.0.0 → 1.2.1
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 +180 -22
- data/Rakefile +16 -16
- data/app/controllers/maintenance_tasks/application_controller.rb +3 -6
- data/app/controllers/maintenance_tasks/runs_controller.rb +1 -12
- data/app/controllers/maintenance_tasks/tasks_controller.rb +7 -4
- data/app/helpers/maintenance_tasks/application_helper.rb +2 -48
- data/app/helpers/maintenance_tasks/{task_helper.rb → tasks_helper.rb} +26 -18
- data/app/jobs/maintenance_tasks/task_job.rb +49 -6
- data/app/models/maintenance_tasks/application_record.rb +2 -2
- data/app/models/maintenance_tasks/csv_collection.rb +33 -0
- data/app/models/maintenance_tasks/progress.rb +19 -14
- data/app/models/maintenance_tasks/run.rb +39 -1
- data/app/models/maintenance_tasks/runner.rb +20 -5
- data/app/models/maintenance_tasks/runs_page.rb +55 -0
- data/app/models/maintenance_tasks/task_data.rb +9 -5
- data/app/models/maintenance_tasks/ticker.rb +0 -1
- data/app/tasks/maintenance_tasks/task.rb +43 -16
- data/app/validators/maintenance_tasks/run_status_validator.rb +15 -13
- data/app/views/layouts/maintenance_tasks/_navbar.html.erb +0 -6
- data/app/views/maintenance_tasks/runs/_info.html.erb +6 -0
- data/app/views/maintenance_tasks/runs/_run.html.erb +1 -9
- data/app/views/maintenance_tasks/runs/info/_running.html.erb +0 -2
- data/app/views/maintenance_tasks/tasks/show.html.erb +31 -4
- data/config/routes.rb +4 -6
- data/db/migrate/20210225152418_remove_index_on_task_name.rb +14 -0
- data/exe/maintenance_tasks +2 -2
- data/lib/generators/maintenance_tasks/install_generator.rb +4 -5
- data/lib/generators/maintenance_tasks/task_generator.rb +15 -9
- 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 +34 -30
- data/lib/maintenance_tasks/cli.rb +18 -4
- data/lib/maintenance_tasks/engine.rb +6 -3
- metadata +9 -31
- 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
@@ -41,7 +41,7 @@ module MaintenanceTasks
|
|
41
41
|
task_names = Task.available_tasks.map(&:name)
|
42
42
|
available_task_runs = Run.where(task_name: task_names)
|
43
43
|
last_runs = Run.where(
|
44
|
-
id: available_task_runs.select(
|
44
|
+
id: available_task_runs.select("MAX(id) as id").group(:task_name)
|
45
45
|
)
|
46
46
|
|
47
47
|
task_names.map do |task_name|
|
@@ -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
|
|
@@ -111,7 +111,7 @@ module MaintenanceTasks
|
|
111
111
|
#
|
112
112
|
# @return [String] the Task status.
|
113
113
|
def status
|
114
|
-
last_run&.status ||
|
114
|
+
last_run&.status || "new"
|
115
115
|
end
|
116
116
|
|
117
117
|
# Retrieves the Task's category, which is one of active, new, or completed.
|
@@ -127,11 +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.order(created_at: :desc)
|
134
139
|
end
|
135
140
|
end
|
136
|
-
private_constant :TaskData
|
137
141
|
end
|
@@ -16,9 +16,12 @@ module MaintenanceTasks
|
|
16
16
|
#
|
17
17
|
# @raise [NotFoundError] if a Task with the given name does not exist.
|
18
18
|
def named(name)
|
19
|
-
name.
|
20
|
-
|
21
|
-
|
19
|
+
task = name.safe_constantize
|
20
|
+
raise NotFoundError.new("Task #{name} not found.", name) unless task
|
21
|
+
unless task.is_a?(Class) && task < Task
|
22
|
+
raise NotFoundError.new("#{name} is not a Task.", name)
|
23
|
+
end
|
24
|
+
task
|
22
25
|
end
|
23
26
|
|
24
27
|
# Returns a list of concrete classes that inherit from the Task
|
@@ -30,6 +33,41 @@ module MaintenanceTasks
|
|
30
33
|
descendants
|
31
34
|
end
|
32
35
|
|
36
|
+
# Make this Task a task that handles CSV.
|
37
|
+
#
|
38
|
+
# An input to upload a CSV will be added in the form to start a Run. The
|
39
|
+
# collection and count method are implemented.
|
40
|
+
def csv_collection
|
41
|
+
include(CsvCollection)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Processes one item.
|
45
|
+
#
|
46
|
+
# Especially useful for tests.
|
47
|
+
#
|
48
|
+
# @param item the item to process.
|
49
|
+
def process(item)
|
50
|
+
new.process(item)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns the collection for this Task.
|
54
|
+
#
|
55
|
+
# Especially useful for tests.
|
56
|
+
#
|
57
|
+
# @return the collection.
|
58
|
+
def collection
|
59
|
+
new.collection
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns the count of items for this Task.
|
63
|
+
#
|
64
|
+
# Especially useful for tests.
|
65
|
+
#
|
66
|
+
# @return the count of items.
|
67
|
+
def count
|
68
|
+
new.count
|
69
|
+
end
|
70
|
+
|
33
71
|
private
|
34
72
|
|
35
73
|
def load_constants
|
@@ -45,8 +83,7 @@ module MaintenanceTasks
|
|
45
83
|
# @raise [NotImplementedError] with a message advising subclasses to
|
46
84
|
# implement an override for this method.
|
47
85
|
def collection
|
48
|
-
raise
|
49
|
-
"#{self.class.name} must implement `collection`."
|
86
|
+
raise NoMethodError, "#{self.class.name} must implement `collection`."
|
50
87
|
end
|
51
88
|
|
52
89
|
# Placeholder method to raise in case a subclass fails to implement the
|
@@ -57,8 +94,7 @@ module MaintenanceTasks
|
|
57
94
|
# @raise [NotImplementedError] with a message advising subclasses to
|
58
95
|
# implement an override for this method.
|
59
96
|
def process(_item)
|
60
|
-
raise
|
61
|
-
"#{self.class.name} must implement `process`."
|
97
|
+
raise NoMethodError, "#{self.class.name} must implement `process`."
|
62
98
|
end
|
63
99
|
|
64
100
|
# Total count of iterations to be performed.
|
@@ -70,14 +106,5 @@ module MaintenanceTasks
|
|
70
106
|
# @return [Integer, nil]
|
71
107
|
def count
|
72
108
|
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
109
|
end
|
83
110
|
end
|
@@ -11,8 +11,9 @@ 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
|
15
|
-
|
14
|
+
# enqueued -> errored occurs when the task job fails to be enqueued, or
|
15
|
+
# if the Task is deleted before is starts running.
|
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.
|
18
19
|
# pausing -> cancelling occurs when the user cancels a task immediately
|
@@ -23,14 +24,14 @@ module MaintenanceTasks
|
|
23
24
|
# nothing in its collection to process.
|
24
25
|
# pausing -> errored occurs when the job raises an exception after the
|
25
26
|
# user has paused it.
|
26
|
-
|
27
|
+
"pausing" => ["paused", "cancelling", "succeeded", "errored"],
|
27
28
|
# cancelling -> cancelled occurs when the task actually halts performing
|
28
29
|
# and occupies a status of cancelled.
|
29
30
|
# cancelling -> succeeded occurs when the task completes immediately after
|
30
31
|
# being cancelled. See description for pausing -> succeeded.
|
31
32
|
# cancelling -> errored occurs when the job raises an exception after the
|
32
33
|
# user has cancelled it.
|
33
|
-
|
34
|
+
"cancelling" => ["cancelled", "succeeded", "errored"],
|
34
35
|
# running -> succeeded occurs when the task completes successfully.
|
35
36
|
# running -> pausing occurs when a user pauses the task as
|
36
37
|
# it's performing.
|
@@ -39,24 +40,26 @@ module MaintenanceTasks
|
|
39
40
|
# running -> interrupted occurs when the job infra shuts down the task as
|
40
41
|
# it's performing.
|
41
42
|
# running -> errored occurs when the job raises an exception when running.
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
43
|
+
"running" => [
|
44
|
+
"succeeded",
|
45
|
+
"pausing",
|
46
|
+
"cancelling",
|
47
|
+
"interrupted",
|
48
|
+
"errored",
|
48
49
|
],
|
49
50
|
# paused -> enqueued occurs when the task is resumed after being paused.
|
50
51
|
# paused -> cancelling when the user cancels the task after it is paused.
|
51
52
|
# paused -> cancelled when the user cancels the task after it is paused.
|
52
|
-
|
53
|
+
"paused" => ["enqueued", "cancelling", "cancelled"],
|
53
54
|
# interrupted -> running occurs when the task is resumed after being
|
54
55
|
# interrupted by the job infrastructure.
|
55
56
|
# interrupted -> pausing occurs when the task is paused by the user while
|
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
|
@@ -82,5 +85,4 @@ module MaintenanceTasks
|
|
82
85
|
)
|
83
86
|
end
|
84
87
|
end
|
85
|
-
private_constant :RunStatusValidator
|
86
88
|
end
|
@@ -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,11 +1,3 @@
|
|
1
1
|
<div class="box">
|
2
|
-
|
3
|
-
<h3 class="title is-3">
|
4
|
-
<%= link_to run.task_name, task_path(run.task_name) %>
|
5
|
-
<%= status_tag run.status %>
|
6
|
-
</h3>
|
7
|
-
<%= render 'maintenance_tasks/runs/info', run: run, with_status: false %>
|
8
|
-
<% else %>
|
9
|
-
<%= render 'maintenance_tasks/runs/info', run: run, with_status: true %>
|
10
|
-
<% end %>
|
2
|
+
<%= render 'maintenance_tasks/runs/info', run: run, with_status: true %>
|
11
3
|
</div>
|
@@ -7,19 +7,46 @@
|
|
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) %>
|
14
41
|
<pre><code><%= highlight_code(code) %></code></pre>
|
15
42
|
<% end %>
|
16
43
|
|
17
|
-
<% if @
|
44
|
+
<% if @runs_page.records.present? %>
|
18
45
|
<hr/>
|
19
46
|
|
20
47
|
<h4 class="title is-4">Previous Runs</h4>
|
21
48
|
|
22
|
-
<%= render @
|
49
|
+
<%= render @runs_page.records %>
|
23
50
|
|
24
|
-
<%=
|
51
|
+
<%= link_to "Next page", task_path(@task, cursor: @runs_page.next_cursor) unless @runs_page.last? %>
|
25
52
|
<% end %>
|
data/config/routes.rb
CHANGED
@@ -1,19 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
MaintenanceTasks::Engine.routes.draw do
|
3
|
-
resources :runs, only: [:index], format: false
|
4
|
-
|
5
3
|
resources :tasks, only: [:index, :show], format: false do
|
6
4
|
member do
|
7
|
-
put
|
5
|
+
put "run"
|
8
6
|
end
|
9
7
|
|
10
8
|
resources :runs, only: [], format: false do
|
11
9
|
member do
|
12
|
-
put
|
13
|
-
put
|
10
|
+
put "pause"
|
11
|
+
put "cancel"
|
14
12
|
end
|
15
13
|
end
|
16
14
|
end
|
17
15
|
|
18
|
-
root to:
|
16
|
+
root to: "tasks#index"
|
19
17
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class RemoveIndexOnTaskName < ActiveRecord::Migration[6.0]
|
3
|
+
def up
|
4
|
+
change_table(:maintenance_tasks_runs) do |t|
|
5
|
+
t.remove_index(:task_name)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def down
|
10
|
+
change_table(:maintenance_tasks_runs) do |t|
|
11
|
+
t.index(:task_name)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/exe/maintenance_tasks
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
# frozen_string_literal: true
|
4
4
|
|
5
|
-
require File.expand_path(
|
5
|
+
require File.expand_path("config/application", Dir.pwd)
|
6
6
|
|
7
7
|
Rails.application.require_environment!
|
8
8
|
|
9
|
-
require
|
9
|
+
require "maintenance_tasks/cli"
|
10
10
|
|
11
11
|
module MaintenanceTasks
|
12
12
|
CLI.start(ARGV)
|
@@ -5,18 +5,17 @@ module MaintenanceTasks
|
|
5
5
|
#
|
6
6
|
# @api private
|
7
7
|
class InstallGenerator < Rails::Generators::Base
|
8
|
-
source_root File.expand_path(
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
9
9
|
|
10
10
|
# Mounts the engine in the host application's config/routes.rb
|
11
11
|
def mount_engine
|
12
|
-
route("mount MaintenanceTasks::Engine =>
|
12
|
+
route("mount MaintenanceTasks::Engine => \"/maintenance_tasks\"")
|
13
13
|
end
|
14
14
|
|
15
15
|
# Copies engine migrations to host application and migrates the database
|
16
16
|
def install_migrations
|
17
|
-
rake(
|
18
|
-
rake(
|
17
|
+
rake("maintenance_tasks:install:migrations")
|
18
|
+
rake("db:migrate")
|
19
19
|
end
|
20
20
|
end
|
21
|
-
private_constant :InstallGenerator
|
22
21
|
end
|
@@ -5,11 +5,14 @@ module MaintenanceTasks
|
|
5
5
|
#
|
6
6
|
# @api private
|
7
7
|
class TaskGenerator < Rails::Generators::NamedBase
|
8
|
-
source_root File.expand_path(
|
9
|
-
desc
|
10
|
-
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
9
|
+
desc "This generator creates a task file at app/tasks and a corresponding "\
|
10
|
+
"test."
|
11
11
|
|
12
|
-
|
12
|
+
class_option :csv, type: :boolean, default: false,
|
13
|
+
desc: "Generate a CSV Task."
|
14
|
+
|
15
|
+
check_class_collision suffix: "Task"
|
13
16
|
|
14
17
|
# Creates the Task file.
|
15
18
|
def create_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.
|
@@ -42,7 +49,7 @@ module MaintenanceTasks
|
|
42
49
|
class_path,
|
43
50
|
"#{file_name}_task_test.rb"
|
44
51
|
)
|
45
|
-
template(
|
52
|
+
template("task_test.rb", template_file)
|
46
53
|
end
|
47
54
|
|
48
55
|
def create_task_spec_file
|
@@ -51,11 +58,11 @@ module MaintenanceTasks
|
|
51
58
|
class_path,
|
52
59
|
"#{file_name}_task_spec.rb"
|
53
60
|
)
|
54
|
-
template(
|
61
|
+
template("task_spec.rb", template_file)
|
55
62
|
end
|
56
63
|
|
57
64
|
def file_name
|
58
|
-
super.sub(/_task\z/i,
|
65
|
+
super.sub(/_task\z/i, "")
|
59
66
|
end
|
60
67
|
|
61
68
|
def tasks_module
|
@@ -70,5 +77,4 @@ module MaintenanceTasks
|
|
70
77
|
Rails.application.config.generators.options[:rails][:test_framework]
|
71
78
|
end
|
72
79
|
end
|
73
|
-
private_constant :TaskGenerator
|
74
80
|
end
|