Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1353,6 +1353,15 @@ enabling this feature. See [upgrading](#upgrading) for more information.
MaintenanceTasks.serialize_cursors_as_json = true
```

#### Configure outdated task threshold

To specify a date threshold which will display an outdated message, you can
configure `MaintenanceTasks.outdated_task_threshold`. Tasks that have last run
successfully after this threshold will be marked outdated.

The value for `MaintenanceTasks.outdated_task_threshold` must be an
`ActiveSupport::Duration`. If no value is specified, it will default to 30 days.

## Upgrading

Use bundler to check for and upgrade to newer versions. After installing a new
Expand Down
2 changes: 1 addition & 1 deletion app/helpers/maintenance_tasks/tasks_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def progress(run)
def status_tag(status)
tag.span(
status.capitalize,
class: ["tag", "has-text-weight-medium", "pr-2", "mr-4"] + STATUS_COLOURS.fetch(status),
class: ["tag", "has-text-weight-medium", "px-2", "mx-4"] + STATUS_COLOURS.fetch(status),
)
end

Expand Down
9 changes: 9 additions & 0 deletions app/models/concerns/maintenance_tasks/run_concern.rb
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,15 @@ def configure_cursor_encoding!
self.cursor_is_json = true
end

# Returns whether the run is beyond the outdated task threshold.
#
# @return [Boolean]
def outdated?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

outdated doesn't seem like the best fit for this feature as it implies that the task should be updated while I believe we want to convey that the task was run and may not be used anymore.

How about stale ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love the clarity here. I'll make the update to stale terminology 🎉

return false unless ended_at.present?

ended_at < MaintenanceTasks.outdated_task_threshold.ago
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we only scope this to succeeded runs only?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great idea!

end

private

def instrument_status_change
Expand Down
2 changes: 2 additions & 0 deletions app/models/maintenance_tasks/task_data_index.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ def initialize(name, related_run = nil)

alias_method :to_s, :name

delegate :outdated?, to: :related_run
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
delegate :outdated?, to: :related_run
delegate :outdated?, to: :related_run, allow_nil: true

This would raise if related_run is nil. Can we also add a test for if there is no run?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great catch! I didn't catch the default arg of nil above. I wasn't able to get allow_nil: true to work how I wanted since it would return nil for the TaskDataIndex#stale? delegation where I want it to be a Boolean. That said maybe there is a default value that could be set using this (couldn't find anything in the docs).

I'm making an update to remove the delegation in favor of a full method definition which handles the above nicely.


# Returns the status of the latest active or completed Run, if present.
# If the Task does not have any Runs, the Task status is `new`.
#
Expand Down
14 changes: 13 additions & 1 deletion app/views/maintenance_tasks/tasks/_task.html.erb
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
<div class="cell box">
<h3 class="title is-5 has-text-weight-medium">
<h3 class="title is-5 has-text-weight-medium is-flex is-align-items-center">
<%= link_to task, task_path(task) %>
<%= status_tag(task.status) %>
</h3>

<% if (run = task.related_run) %>
<% if task.outdated? %>
<div class="content is-size-7 is-flex is-align-items-center has-text-warning">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="icon is-small">
<path stroke-linecap="round" stroke-linejoin="round" d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z" />
</svg>

<p class="ml-1">
This task last ran <%= MaintenanceTasks.outdated_task_threshold.inspect %> ago. Consider removing it as it may be outdated.
</p>
</div>
<% end %>

<h5 class="title is-5 has-text-weight-medium">
<%= time_tag run.created_at, title: run.created_at.utc %>
</h5>
Expand Down
8 changes: 8 additions & 0 deletions lib/maintenance_tasks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,14 @@ module MaintenanceTasks
# @return [Boolean] whether or not to store cursor values as JSON.
mattr_accessor :serialize_cursors_as_json, default: false

# @!attribute outdated_task_threshold
# @scope class
# The threshold after which a task is considered outdated.
# Defaults to 30 days.
#
# @return [ActiveSupport::Duration] the threshold after which a task is considered outdated.
mattr_accessor :outdated_task_threshold, default: 30.days
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to opt out of this feature?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another great catch. I'll build this in to allow opt-out


class << self
DEPRECATION_MESSAGE = "MaintenanceTasks.error_handler is deprecated and will be removed in the 3.0 release. " \
"Instead, reports will be sent to the Rails error reporter. Do not set a handler and subscribe " \
Expand Down
13 changes: 13 additions & 0 deletions test/dummy/app/tasks/maintenance/outdated_task.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

module Maintenance
class OutdatedTask < MaintenanceTasks::Task
def collection
[1, 2]
end

def process(number)
Rails.logger.debug("This task is outdated")
end
end
end
11 changes: 8 additions & 3 deletions test/fixtures/maintenance_tasks/runs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,13 @@ import_posts_task_succeeded_old:
task_name: Maintenance::ImportPostsTask
tick_count: 10
tick_total: 10

outdated_task:
task_name: Maintenance::OutdatedTask
tick_count: 10
tick_total: 10
job_id: '123abc'
status: 'succeeded'
created_at: '01 Jan 2020 00:20:00'
started_at: '01 Jan 2020 00:40:36'
ended_at: '01 Jan 2020 00:52:19'
created_at: '03 Mar 2026 00:20:00'
started_at: '03 Mar 2026 00:40:36'
ended_at: '03 Mar 2026 00:52:19'
2 changes: 1 addition & 1 deletion test/helpers/maintenance_tasks/tasks_helper_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class TasksHelperTest < ActionView::TestCase
end

test "#status_tag renders a span with the appropriate tag based on status" do
expected = '<span class="tag has-text-weight-medium pr-2 mr-4 is-warning is-light">Pausing</span>'
expected = '<span class="tag has-text-weight-medium px-2 mx-4 is-warning is-light">Pausing</span>'
assert_equal expected, status_tag("pausing")
end

Expand Down
21 changes: 21 additions & 0 deletions test/models/maintenance_tasks/run_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,27 @@ class RunTest < ActiveSupport::TestCase
end
end

test "#outdated? returns true when the run is beyond the outdated task threshold" do
MaintenanceTasks.with(outdated_task_threshold: 1.day) do
run = Run.create!(task_name: "Maintenance::UpdatePostsTask", ended_at: 2.days.ago)
assert run.outdated?
end
end

test "#outdated? returns false when the run is within the outdated task threshold" do
MaintenanceTasks.with(outdated_task_threshold: 2.day) do
run = Run.create!(task_name: "Maintenance::UpdatePostsTask", ended_at: 1.day.ago)
refute run.outdated?
end
end

test "#outdated? returns false when the run is nil" do
MaintenanceTasks.with(outdated_task_threshold: 1.day) do
run = Run.new
refute run.outdated?
end
end

private

def expected_notification(run)
Expand Down
8 changes: 8 additions & 0 deletions test/models/maintenance_tasks/task_data_index_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ class TaskDataIndexTest < ActiveSupport::TestCase
assert_equal "Maintenance::UpdatePostsTask", task_data.to_s
end

test "#outdated? returns true when the task is outdated from the delegated related_run" do
MaintenanceTasks.with(outdated_task_threshold: 1.day) do
run = Run.create!(task_name: "Maintenance::UpdatePostsTask", ended_at: 2.days.ago)
task_data = TaskDataIndex.new("Maintenance::UpdatePostsTask", run)
assert task_data.outdated?
end
end

test "#status is new when Task does not have any Runs" do
task_data = TaskDataIndex.new("Maintenance::UpdatePostsTask")
assert_equal "new", task_data.status
Expand Down
12 changes: 12 additions & 0 deletions test/system/maintenance_tasks/tasks_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,18 @@ class TasksTest < ApplicationSystemTestCase
assert_text(/January 01, 2020 01:00 Succeeded #\d/)
end

test "show a Task with outdated run" do
travel_to(maintenance_tasks_runs(:outdated_task).ended_at + 2.days) do
MaintenanceTasks.with(outdated_task_threshold: 1.day) do
visit maintenance_tasks_path

within page.find("a", text: "Maintenance::OutdatedTask").find(:xpath, "..").sibling(".has-text-warning") do |element|
assert_text "This task last ran 1 day ago. Consider removing it as it may be outdated."
end
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Future: Might be worthy of a follow-up PR to separate the various sections on the Task#index page within individual parent divs. That would make the lookup easier for testing as well as organizing the DOM.

end
end
end

test "task with attributes renders default values on the form" do
visit maintenance_tasks_path

Expand Down
Loading