maintenance_tasks 1.0.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 +7 -0
- data/README.md +316 -0
- data/Rakefile +29 -0
- data/app/controllers/maintenance_tasks/application_controller.rb +26 -0
- data/app/controllers/maintenance_tasks/runs_controller.rb +44 -0
- data/app/controllers/maintenance_tasks/tasks_controller.rb +42 -0
- data/app/helpers/maintenance_tasks/application_helper.rb +67 -0
- data/app/helpers/maintenance_tasks/task_helper.rb +110 -0
- data/app/jobs/maintenance_tasks/task_job.rb +98 -0
- data/app/models/maintenance_tasks/application_record.rb +10 -0
- data/app/models/maintenance_tasks/progress.rb +74 -0
- data/app/models/maintenance_tasks/run.rb +180 -0
- data/app/models/maintenance_tasks/runner.rb +52 -0
- data/app/models/maintenance_tasks/task_data.rb +137 -0
- data/app/models/maintenance_tasks/ticker.rb +58 -0
- data/app/tasks/maintenance_tasks/task.rb +83 -0
- data/app/validators/maintenance_tasks/run_status_validator.rb +86 -0
- data/app/views/layouts/maintenance_tasks/_navbar.html.erb +11 -0
- data/app/views/layouts/maintenance_tasks/application.html.erb +54 -0
- data/app/views/maintenance_tasks/runs/_info.html.erb +10 -0
- data/app/views/maintenance_tasks/runs/_run.html.erb +11 -0
- data/app/views/maintenance_tasks/runs/index.html.erb +15 -0
- data/app/views/maintenance_tasks/runs/info/_cancelled.html.erb +4 -0
- data/app/views/maintenance_tasks/runs/info/_cancelling.html.erb +1 -0
- data/app/views/maintenance_tasks/runs/info/_enqueued.html.erb +1 -0
- data/app/views/maintenance_tasks/runs/info/_errored.html.erb +25 -0
- data/app/views/maintenance_tasks/runs/info/_interrupted.html.erb +1 -0
- data/app/views/maintenance_tasks/runs/info/_paused.html.erb +8 -0
- data/app/views/maintenance_tasks/runs/info/_pausing.html.erb +1 -0
- data/app/views/maintenance_tasks/runs/info/_running.html.erb +7 -0
- data/app/views/maintenance_tasks/runs/info/_succeeded.html.erb +5 -0
- data/app/views/maintenance_tasks/tasks/_task.html.erb +8 -0
- data/app/views/maintenance_tasks/tasks/actions/_cancelled.html.erb +1 -0
- data/app/views/maintenance_tasks/tasks/actions/_cancelling.html.erb +4 -0
- data/app/views/maintenance_tasks/tasks/actions/_enqueued.html.erb +2 -0
- data/app/views/maintenance_tasks/tasks/actions/_errored.html.erb +1 -0
- data/app/views/maintenance_tasks/tasks/actions/_interrupted.html.erb +2 -0
- data/app/views/maintenance_tasks/tasks/actions/_new.html.erb +1 -0
- data/app/views/maintenance_tasks/tasks/actions/_paused.html.erb +2 -0
- data/app/views/maintenance_tasks/tasks/actions/_pausing.html.erb +2 -0
- data/app/views/maintenance_tasks/tasks/actions/_running.html.erb +2 -0
- data/app/views/maintenance_tasks/tasks/actions/_succeeded.html.erb +1 -0
- data/app/views/maintenance_tasks/tasks/index.html.erb +22 -0
- data/app/views/maintenance_tasks/tasks/show.html.erb +25 -0
- data/config/routes.rb +19 -0
- data/db/migrate/20201211151756_create_maintenance_tasks_runs.rb +22 -0
- data/exe/maintenance_tasks +13 -0
- data/lib/generators/maintenance_tasks/install_generator.rb +22 -0
- data/lib/generators/maintenance_tasks/task_generator.rb +74 -0
- data/lib/generators/maintenance_tasks/templates/task.rb.tt +21 -0
- data/lib/generators/maintenance_tasks/templates/task_spec.rb.tt +14 -0
- data/lib/generators/maintenance_tasks/templates/task_test.rb.tt +12 -0
- data/lib/maintenance_tasks.rb +58 -0
- data/lib/maintenance_tasks/cli.rb +40 -0
- data/lib/maintenance_tasks/engine.rb +28 -0
- data/lib/maintenance_tasks/integrations/bugsnag_handler.rb +4 -0
- data/lib/tasks/maintenance_tasks_tasks.rake +5 -0
- metadata +187 -0
@@ -0,0 +1,10 @@
|
|
1
|
+
<h5 class="title is-5">
|
2
|
+
<%= time_tag run.created_at, title: run.created_at %>
|
3
|
+
<%= status_tag run.status if with_status %>
|
4
|
+
</h5>
|
5
|
+
|
6
|
+
<%= progress run %>
|
7
|
+
|
8
|
+
<div class="content">
|
9
|
+
<%= render "maintenance_tasks/runs/info/#{run.status}", run: run %>
|
10
|
+
</div>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<div class="box">
|
2
|
+
<% if with_task_name %>
|
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 %>
|
11
|
+
</div>
|
@@ -0,0 +1,15 @@
|
|
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) %>
|
@@ -0,0 +1 @@
|
|
1
|
+
<p>Cancelling…</p>
|
@@ -0,0 +1 @@
|
|
1
|
+
<p>Waiting to start.</p>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<p>
|
2
|
+
Ran for <%= time_running_in_words run %> until an error happened
|
3
|
+
<%= time_ago run.ended_at %>.
|
4
|
+
</p>
|
5
|
+
|
6
|
+
</p>
|
7
|
+
|
8
|
+
<div class="card">
|
9
|
+
<header class="card-header">
|
10
|
+
<p class="card-header-title">
|
11
|
+
<%= run.error_class %>
|
12
|
+
</p>
|
13
|
+
</header>
|
14
|
+
|
15
|
+
<div class="card-content">
|
16
|
+
<div class="content">
|
17
|
+
<p><%= run.error_message %></p>
|
18
|
+
|
19
|
+
<% if run.backtrace.present? %>
|
20
|
+
<code><%= format_backtrace(run.backtrace) %></code>
|
21
|
+
<% end %>
|
22
|
+
|
23
|
+
</div>
|
24
|
+
</div>
|
25
|
+
</div>
|
@@ -0,0 +1 @@
|
|
1
|
+
<p>Interrupted momentarily, resuming shortly…</p>
|
@@ -0,0 +1 @@
|
|
1
|
+
<p>Pausing…</p>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= button_to 'Run', run_task_path(task), method: :put, class: 'button is-success', disabled: task.deleted? %>
|
@@ -0,0 +1,4 @@
|
|
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 %>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= button_to 'Run', run_task_path(task), method: :put, class: 'button is-success', disabled: task.deleted? %>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= button_to 'Run', run_task_path(task), method: :put, class: 'button is-success', disabled: task.deleted? %>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= button_to 'Run', run_task_path(task), method: :put, class: 'button is-success', disabled: task.deleted? %>
|
@@ -0,0 +1,22 @@
|
|
1
|
+
<% if @available_tasks.empty? %>
|
2
|
+
<div class="content is-large">
|
3
|
+
<h3 class="title is-3"> The MaintenanceTasks gem has been successfully installed! </h3>
|
4
|
+
<p>
|
5
|
+
Any new Tasks will show up here. To start writing your first Task,
|
6
|
+
run <code>rails generate maintenance_tasks:task my_task</code>.
|
7
|
+
</p>
|
8
|
+
</div>
|
9
|
+
<% else %>
|
10
|
+
<% if active_tasks = @available_tasks[:active] %>
|
11
|
+
<h3 class="title is-3">Active Tasks</h3>
|
12
|
+
<%= render partial: 'task', collection: active_tasks %>
|
13
|
+
<% end %>
|
14
|
+
<% if new_tasks = @available_tasks[:new] %>
|
15
|
+
<h3 class="title is-3">New Tasks</h3>
|
16
|
+
<%= render partial: 'task', collection: new_tasks %>
|
17
|
+
<% end %>
|
18
|
+
<% if completed_tasks = @available_tasks[:completed] %>
|
19
|
+
<h3 class="title is-3">Completed Tasks</h3>
|
20
|
+
<%= render partial: 'task', collection: completed_tasks %>
|
21
|
+
<% end %>
|
22
|
+
<% end %>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<% content_for :page_title, @task %>
|
2
|
+
|
3
|
+
<h1 class="title is-1">
|
4
|
+
<%= @task %> <%= status_tag(@task.status) %>
|
5
|
+
</h1>
|
6
|
+
|
7
|
+
<%= render 'maintenance_tasks/runs/info', run: @task.last_run, with_status: false if @task.last_run %>
|
8
|
+
|
9
|
+
<div class="buttons">
|
10
|
+
<%= render "maintenance_tasks/tasks/actions/#{@task.status}", task: @task %>
|
11
|
+
</div>
|
12
|
+
|
13
|
+
<% if (code = @task.code) %>
|
14
|
+
<pre><code><%= highlight_code(code) %></code></pre>
|
15
|
+
<% end %>
|
16
|
+
|
17
|
+
<% if @previous_runs.present? %>
|
18
|
+
<hr/>
|
19
|
+
|
20
|
+
<h4 class="title is-4">Previous Runs</h4>
|
21
|
+
|
22
|
+
<%= render @previous_runs, with_task_name: false %>
|
23
|
+
|
24
|
+
<%= pagination(@pagy) %>
|
25
|
+
<% end %>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
MaintenanceTasks::Engine.routes.draw do
|
3
|
+
resources :runs, only: [:index], format: false
|
4
|
+
|
5
|
+
resources :tasks, only: [:index, :show], format: false do
|
6
|
+
member do
|
7
|
+
put 'run'
|
8
|
+
end
|
9
|
+
|
10
|
+
resources :runs, only: [], format: false do
|
11
|
+
member do
|
12
|
+
put 'pause'
|
13
|
+
put 'cancel'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
root to: 'tasks#index'
|
19
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class CreateMaintenanceTasksRuns < ActiveRecord::Migration[6.0]
|
3
|
+
def change
|
4
|
+
create_table(:maintenance_tasks_runs) do |t|
|
5
|
+
t.string(:task_name, null: false)
|
6
|
+
t.datetime(:started_at)
|
7
|
+
t.datetime(:ended_at)
|
8
|
+
t.float(:time_running, default: 0.0, null: false)
|
9
|
+
t.integer(:tick_count, default: 0, null: false)
|
10
|
+
t.integer(:tick_total)
|
11
|
+
t.string(:job_id)
|
12
|
+
t.bigint(:cursor)
|
13
|
+
t.string(:status, default: :enqueued, null: false)
|
14
|
+
t.string(:error_class)
|
15
|
+
t.string(:error_message)
|
16
|
+
t.text(:backtrace)
|
17
|
+
t.timestamps
|
18
|
+
t.index(:task_name)
|
19
|
+
t.index([:task_name, :created_at], order: { created_at: :desc })
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module MaintenanceTasks
|
3
|
+
# Generator used to set up the engine in the host application. It handles
|
4
|
+
# mounting the engine and installing migrations.
|
5
|
+
#
|
6
|
+
# @api private
|
7
|
+
class InstallGenerator < Rails::Generators::Base
|
8
|
+
source_root File.expand_path('templates', __dir__)
|
9
|
+
|
10
|
+
# Mounts the engine in the host application's config/routes.rb
|
11
|
+
def mount_engine
|
12
|
+
route("mount MaintenanceTasks::Engine => '/maintenance_tasks'")
|
13
|
+
end
|
14
|
+
|
15
|
+
# Copies engine migrations to host application and migrates the database
|
16
|
+
def install_migrations
|
17
|
+
rake('maintenance_tasks:install:migrations')
|
18
|
+
rake('db:migrate')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
private_constant :InstallGenerator
|
22
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MaintenanceTasks
|
4
|
+
# Generator used for creating maintenance tasks in the host application.
|
5
|
+
#
|
6
|
+
# @api private
|
7
|
+
class TaskGenerator < Rails::Generators::NamedBase
|
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
|
+
|
12
|
+
check_class_collision suffix: 'Task'
|
13
|
+
|
14
|
+
# Creates the Task file.
|
15
|
+
def create_task_file
|
16
|
+
template_file = File.join(
|
17
|
+
"app/tasks/#{tasks_module_file_path}",
|
18
|
+
class_path,
|
19
|
+
"#{file_name}_task.rb"
|
20
|
+
)
|
21
|
+
template('task.rb', template_file)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Creates the Task test file, according to the app's test framework.
|
25
|
+
# A spec file is created if the app uses RSpec.
|
26
|
+
# Otherwise, an ActiveSupport::TestCase test is created.
|
27
|
+
def create_test_file
|
28
|
+
return unless test_framework
|
29
|
+
|
30
|
+
if test_framework == :rspec
|
31
|
+
create_task_spec_file
|
32
|
+
else
|
33
|
+
create_task_test_file
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def create_task_test_file
|
40
|
+
template_file = File.join(
|
41
|
+
"test/tasks/#{tasks_module_file_path}",
|
42
|
+
class_path,
|
43
|
+
"#{file_name}_task_test.rb"
|
44
|
+
)
|
45
|
+
template('task_test.rb', template_file)
|
46
|
+
end
|
47
|
+
|
48
|
+
def create_task_spec_file
|
49
|
+
template_file = File.join(
|
50
|
+
"spec/tasks/#{tasks_module_file_path}",
|
51
|
+
class_path,
|
52
|
+
"#{file_name}_task_spec.rb"
|
53
|
+
)
|
54
|
+
template('task_spec.rb', template_file)
|
55
|
+
end
|
56
|
+
|
57
|
+
def file_name
|
58
|
+
super.sub(/_task\z/i, '')
|
59
|
+
end
|
60
|
+
|
61
|
+
def tasks_module
|
62
|
+
MaintenanceTasks.tasks_module
|
63
|
+
end
|
64
|
+
|
65
|
+
def tasks_module_file_path
|
66
|
+
tasks_module.underscore
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_framework
|
70
|
+
Rails.application.config.generators.options[:rails][:test_framework]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
private_constant :TaskGenerator
|
74
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module <%= tasks_module %>
|
4
|
+
<% module_namespacing do -%>
|
5
|
+
class <%= class_name %>Task < MaintenanceTasks::Task
|
6
|
+
def collection
|
7
|
+
# Collection to be iterated over
|
8
|
+
# Must be Active Record Relation or Array
|
9
|
+
end
|
10
|
+
|
11
|
+
def process(element)
|
12
|
+
# The work to be done in a single iteration of the task
|
13
|
+
end
|
14
|
+
|
15
|
+
def count
|
16
|
+
# Optionally, define the number of rows that will be iterated over
|
17
|
+
# This is used to track the task's progress
|
18
|
+
end
|
19
|
+
end
|
20
|
+
<% end -%>
|
21
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'rails_helper'
|
3
|
+
|
4
|
+
module <%= tasks_module %>
|
5
|
+
<% module_namespacing do -%>
|
6
|
+
RSpec.describe <%= class_name %>Task do
|
7
|
+
# describe '#process' do
|
8
|
+
# it 'performs a task iteration' do
|
9
|
+
# <%= tasks_module %>::<%= class_name %>Task.new.process(element)
|
10
|
+
# end
|
11
|
+
# end
|
12
|
+
end
|
13
|
+
<% end -%>
|
14
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
module <%= tasks_module %>
|
5
|
+
<% module_namespacing do -%>
|
6
|
+
class <%= class_name %>TaskTest < ActiveSupport::TestCase
|
7
|
+
# test "#process performs a task iteration" do
|
8
|
+
# <%= tasks_module %>::<%= class_name %>Task.new.process(element)
|
9
|
+
# end
|
10
|
+
end
|
11
|
+
<% end -%>
|
12
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'action_controller'
|
3
|
+
require 'action_view'
|
4
|
+
require 'active_job'
|
5
|
+
require 'active_record'
|
6
|
+
|
7
|
+
require 'job-iteration'
|
8
|
+
require 'maintenance_tasks/engine'
|
9
|
+
require 'pagy'
|
10
|
+
require 'pagy/extras/bulma'
|
11
|
+
|
12
|
+
# The engine's namespace module. It provides isolation between the host
|
13
|
+
# application's code and the engine-specific code. Top-level engine constants
|
14
|
+
# and variables are defined under this module.
|
15
|
+
module MaintenanceTasks
|
16
|
+
# The module to namespace Tasks in, as a String. Defaults to 'Maintenance'.
|
17
|
+
# @param [String] the tasks_module value.
|
18
|
+
mattr_accessor :tasks_module, default: 'Maintenance'
|
19
|
+
|
20
|
+
# Defines the job to be used to perform Tasks. This job must be either
|
21
|
+
# `MaintenanceTasks::TaskJob` or a class that inherits from it.
|
22
|
+
#
|
23
|
+
# @param [String] the name of the job class.
|
24
|
+
mattr_writer :job, default: 'MaintenanceTasks::TaskJob'
|
25
|
+
|
26
|
+
# After each iteration, the progress of the task may be updated. This duration
|
27
|
+
# in seconds limits these updates, skipping if the duration since the last
|
28
|
+
# update is lower than this value, except if the job is interrupted, in which
|
29
|
+
# case the progress will always be recorded.
|
30
|
+
#
|
31
|
+
# @param [ActiveSupport::Duration, Numeric] Duration of the delay to update
|
32
|
+
# the ticker during Task iterations.
|
33
|
+
mattr_accessor :ticker_delay, default: 1.second
|
34
|
+
|
35
|
+
# Defines a callback to be performed when an error occurs in the task.
|
36
|
+
mattr_accessor :error_handler, default: ->(_error) {}
|
37
|
+
|
38
|
+
class << self
|
39
|
+
# Retrieves the class that is configured as the Task Job to be used to
|
40
|
+
# perform Tasks.
|
41
|
+
#
|
42
|
+
# @return [TaskJob] the job class.
|
43
|
+
def job
|
44
|
+
@@job.constantize
|
45
|
+
end
|
46
|
+
|
47
|
+
# Attempts to configure Bugsnag integration. If the application uses
|
48
|
+
# Bugsnag, it is automatically configured to report on errors raised while
|
49
|
+
# a Task is performing.
|
50
|
+
def configure_bugsnag_integration
|
51
|
+
load('maintenance_tasks/integrations/bugsnag_handler.rb')
|
52
|
+
rescue LoadError
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
MaintenanceTasks.configure_bugsnag_integration
|