Skip to content

Commit 09139ec

Browse files
committed
Feature: Add optional output to maintenance task to display output from the task
1 parent e65194a commit 09139ec

File tree

12 files changed

+223
-6
lines changed

12 files changed

+223
-6
lines changed

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,49 @@ module Maintenance
579579
end
580580
```
581581

582+
### Task Output
583+
584+
Maintenance tasks can log output during execution, which is displayed in the web UI.
585+
586+
To use this feature:
587+
588+
1. Run the migration to add the `output` column to your database:
589+
```bash
590+
rails generate migration AddOutputToMaintenanceTasksRuns output:text
591+
rails db:migrate
592+
```
593+
594+
2. Use the `log_output` method in your task's `process` method:
595+
596+
```ruby
597+
module Maintenance
598+
class DataCleanupTask < MaintenanceTasks::Task
599+
def collection
600+
User.where(last_sign_in_at: nil)
601+
end
602+
603+
def process(user)
604+
log_output("Processing user: #{user.email} (ID: #{user.id})")
605+
606+
if user.created_at < 1.year.ago
607+
log_output(" -> Marking user for cleanup")
608+
user.update!(cleanup_flag: true)
609+
log_output(" -> Successfully processed")
610+
else
611+
log_output(" -> Skipping: user created recently")
612+
end
613+
end
614+
end
615+
end
616+
```
617+
618+
The output:
619+
- Is stored in the database with the run record
620+
- Persists permanently and can be viewed anytime
621+
- Is displayed in a formatted box on the run details page
622+
- Updates in real-time as the task executes
623+
624+
582625
### Subscribing to instrumentation events
583626

584627
If you are interested in actioning a specific task event, please refer to the

app/jobs/concerns/maintenance_tasks/task_job_concern.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ def task_iteration(input)
119119
def before_perform
120120
@run = arguments.first
121121
@task = @run.task
122+
# Set the run as an instance variable on the task to enable features
123+
# like log_output that need to write data back to the run.
124+
# This creates a bidirectional reference between the Run and Task.
125+
@task.instance_variable_set(:@run, @run)
122126
if @task.has_csv_content?
123127
@task.csv_content = @run.csv_file.download
124128
end

app/models/maintenance_tasks/run.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,20 @@ def masked_arguments
443443
argument_filter.filter(arguments)
444444
end
445445

446+
# Appends output to the existing output in the database if the column exists.
447+
#
448+
# @param new_output [String] the output to append.
449+
def append_output(new_output)
450+
return unless self.class.column_names.include?("output")
451+
452+
# Reload output from database to get the latest value
453+
current_output = self.class.where(id: id).pluck(:output).first || ""
454+
separator = current_output.present? ? "\n" : ""
455+
updated_output = current_output + separator + new_output
456+
457+
self.class.where(id: id).update_all(output: updated_output)
458+
end
459+
446460
private
447461

448462
def instrument_status_change

app/models/maintenance_tasks/task.rb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class NotFoundError < NameError; end
4141

4242
define_callbacks :start, :complete, :error, :cancel, :pause, :interrupt
4343

44-
attr_accessor :metadata
44+
attr_accessor :metadata, :output
4545

4646
class << self
4747
# Finds a Task with the given name.
@@ -335,5 +335,14 @@ def count
335335
def enumerator_builder(cursor:)
336336
nil
337337
end
338+
339+
# Appends a message to the task output.
340+
#
341+
# @param message [String] the message to append to the output.
342+
def log_output(message)
343+
return unless defined?(@run) && @run
344+
345+
@run.append_output(message)
346+
end
338347
end
339348
end
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<% if output.present? %>
2+
<div class="content">
3+
<h6 class="title is-6">Task Output</h6>
4+
<pre class="box has-background-light"><%= output %></pre>
5+
</div>
6+
<hr>
7+
<% end %>

app/views/maintenance_tasks/runs/_run.html.erb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
<%= render "maintenance_tasks/runs/arguments", arguments: run.masked_arguments %>
2727
<%= tag.hr if run.csv_file.present? || run.arguments.present? && run.metadata.present? %>
2828
<%= render "maintenance_tasks/runs/metadata", metadata: run.metadata %>
29+
<%= render "maintenance_tasks/runs/output", output: run.output if run.respond_to?(:output) %>
2930

3031
<div class="buttons">
3132
<% if run.paused? %>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# frozen_string_literal: true
2+
3+
class AddOutputToMaintenanceTasksRuns < ActiveRecord::Migration[7.0]
4+
def change
5+
add_column(:maintenance_tasks_runs, :output, :text)
6+
end
7+
end
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# frozen_string_literal: true
2+
3+
module Maintenance
4+
class OutputTestTask < MaintenanceTasks::Task
5+
def collection
6+
(1..5).to_a
7+
end
8+
9+
def process(element)
10+
log_output("Processing element #{element}")
11+
log_output("Square of #{element} is #{element * element}")
12+
end
13+
end
14+
end

test/dummy/db/schema.rb

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
#
1111
# It's strongly recommended that you check this file into your version control system.
1212

13-
ActiveRecord::Schema.define(version: 2023_06_22_035229) do
13+
ActiveRecord::Schema.define(version: 2025_01_17_000000) do
1414
create_table "active_storage_attachments", force: :cascade do |t|
1515
t.string "name", null: false
1616
t.string "record_type", null: false
@@ -52,19 +52,20 @@
5252
t.string "error_class"
5353
t.string "error_message"
5454
t.text "backtrace"
55-
t.datetime "created_at", precision: 6, null: false
56-
t.datetime "updated_at", precision: 6, null: false
55+
t.datetime "created_at", null: false
56+
t.datetime "updated_at", null: false
5757
t.text "arguments"
5858
t.integer "lock_version", default: 0, null: false
5959
t.text "metadata"
60+
t.text "output"
6061
t.index ["task_name", "status", "created_at"], name: "index_maintenance_tasks_runs", order: { created_at: :desc }
6162
end
6263

6364
create_table "posts", force: :cascade do |t|
6465
t.string "title"
6566
t.string "content"
66-
t.datetime "created_at", precision: 6, null: false
67-
t.datetime "updated_at", precision: 6, null: false
67+
t.datetime "created_at", null: false
68+
t.datetime "updated_at", null: false
6869
end
6970

7071
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"

test/models/maintenance_tasks/task_data_index_test.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,15 @@ class TaskDataIndexTest < ActiveSupport::TestCase
2020
"Maintenance::NoCollectionTask",
2121
# duplicate due to fixtures containing two active runs of this task
2222
"Maintenance::NoCollectionTask",
23+
"Maintenance::OutputTestTask",
2324
"Maintenance::ParamsTask",
2425
"Maintenance::TestTask",
2526
"Maintenance::UpdatePostsInBatchesTask",
2627
"Maintenance::UpdatePostsModulePrependedTask",
2728
"Maintenance::UpdatePostsTask",
2829
"Maintenance::UpdatePostsThrottledTask",
30+
"MaintenanceTasks::TaskOutputTest::NoCollectionTaskWithOutput",
31+
"MaintenanceTasks::TaskOutputTest::TestTaskWithOutput",
2932
]
3033
assert_equal expected, TaskDataIndex.available_tasks.map(&:name)
3134
end

0 commit comments

Comments
 (0)