Skip to content

Commit

Permalink
Merge pull request #217 from rails/back-to-relative-times
Browse files Browse the repository at this point in the history
Go back to relative times and a better locale management
  • Loading branch information
rosa authored Dec 3, 2024
2 parents 0c22a02 + b449afe commit fca856d
Show file tree
Hide file tree
Showing 18 changed files with 109 additions and 103 deletions.
11 changes: 11 additions & 0 deletions app/controllers/mission_control/jobs/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,19 @@ class MissionControl::Jobs::ApplicationController < MissionControl::Jobs.base_co
include MissionControl::Jobs::ApplicationScoped, MissionControl::Jobs::NotFoundRedirections
include MissionControl::Jobs::AdapterFeatures

around_action :set_current_locale

private
def default_url_options
{ server_id: MissionControl::Jobs::Current.server }
end

def set_current_locale(&block)
@previous_config = I18n.config
I18n.config = MissionControl::Jobs::I18nConfig.new
I18n.with_locale(:en, &block)
ensure
I18n.config = @previous_config
@previous_config = nil
end
end
18 changes: 16 additions & 2 deletions app/helpers/mission_control/jobs/dates_helper.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
module MissionControl::Jobs::DatesHelper
def formatted_time(time)
time.in_time_zone.strftime("%Y-%m-%d %H:%M:%S.%3N %Z")
def time_distance_in_words_with_title(time)
tag.span time_ago_in_words_with_default_options(time), title: "Since #{time.to_fs(:long)}"
end

def bidirectional_time_distance_in_words_with_title(time)
time_distance = if time.past?
"#{time_ago_in_words_with_default_options(time)} ago"
else
"in #{time_ago_in_words_with_default_options(time)}"
end

tag.span time_distance, title: time.to_fs(:long)
end

def time_ago_in_words_with_default_options(time)
time_ago_in_words(time, include_seconds: true, locale: :en)
end
end
2 changes: 1 addition & 1 deletion app/helpers/mission_control/jobs/jobs_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def attribute_names_for_job_status(status)
when "blocked" then [ "Queue", "Blocked by", "" ]
when "finished" then [ "Queue", "Finished" ]
when "scheduled" then [ "Queue", "Scheduled", "" ]
when "in_progress" then [ "Queue", "Run by", "Running since" ]
when "in_progress" then [ "Queue", "Run by", "Running for" ]
else []
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@
<tr>
<th>Enqueued</th>
<td>
<%= formatted_time(job.enqueued_at.to_datetime) %>
<%= time_distance_in_words_with_title(job.enqueued_at.to_datetime) %> ago
</td>
</tr>
<% if job.scheduled? %>
<tr>
<th>Scheduled</th>
<td>
<%= formatted_time(job.scheduled_at) %>
<%= bidirectional_time_distance_in_words_with_title(job.scheduled_at) %>
<% if job_delayed?(job) %>
<div class="is-danger tag ml-4">delayed</div>
<% end %>
Expand All @@ -41,15 +41,15 @@
<tr>
<th>Failed</th>
<td>
<%= formatted_time(job.failed_at) %>
<%= time_distance_in_words_with_title(job.failed_at) %> ago
</td>
</tr>
<% end %>
<% if job.finished_at.present? %>
<tr>
<th>Finished at</th>
<td>
<%= formatted_time(job.finished_at) %>
<%= time_distance_in_words_with_title(job.finished_at) %> ago
</td>
</tr>
<% end %>
Expand Down
2 changes: 1 addition & 1 deletion app/views/mission_control/jobs/jobs/_job.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<div class="is-family-monospace"><%= job_arguments(job) %></div>
<% end %>

<div class="has-text-grey is-size-7">Enqueued <%= formatted_time(job.enqueued_at.to_datetime) %></div>
<div class="has-text-grey is-size-7">Enqueued <%= time_distance_in_words_with_title(job.enqueued_at.to_datetime) %> ago</div>
</td>

<%= render "mission_control/jobs/jobs/#{jobs_status}/job", job: job %>
Expand Down
2 changes: 1 addition & 1 deletion app/views/mission_control/jobs/jobs/blocked/_job.html.erb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<td><%= link_to job.queue_name, application_queue_path(@application, job.queue) %></td>
<td><div class="is-family-monospace is-size-7"><%= job.blocked_by %></div>
<div class="has-text-grey is-size-7">Until <%= formatted_time(job.blocked_until) %></div>
<div class="has-text-grey is-size-7">Expires <%= bidirectional_time_distance_in_words_with_title(job.blocked_until) %></div>
</td>
<td class="pr-0">
<%= render "mission_control/jobs/jobs/blocked/actions", job: job %>
Expand Down
2 changes: 1 addition & 1 deletion app/views/mission_control/jobs/jobs/failed/_job.html.erb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<td>
<%= link_to failed_job_error(job), application_job_path(@application, job.job_id, anchor: "error") %>
<div class="has-text-grey"><%= formatted_time(job.failed_at) %></div>
<div class="has-text-grey"><%= time_distance_in_words_with_title(job.failed_at) %> ago</div>
</td>
<td class="pr-0">
<%= render "mission_control/jobs/jobs/failed/actions", job: job %>
Expand Down
2 changes: 1 addition & 1 deletion app/views/mission_control/jobs/jobs/finished/_job.html.erb
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
<td><%= link_to job.queue_name, application_queue_path(@application, job.queue) %></td>
<td><div class="has-text-grey"><%= formatted_time(job.finished_at) %></div></td>
<td><div class="has-text-grey"><%= time_distance_in_words_with_title(job.finished_at) %> ago</div></td>
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
<% end %>
</td>
<td><div class="has-text-grey"><%= job.started_at ? formatted_time(job.started_at) : "(Finished)" %></div></td>
<td><div class="has-text-grey"><%= job.started_at ? time_distance_in_words_with_title(job.started_at) : "(Finished)" %></div></td>
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<td><%= link_to job.queue_name, application_queue_path(@application, job.queue) %></td>
<td>
<%= formatted_time(job.scheduled_at) %>
<%= bidirectional_time_distance_in_words_with_title(job.scheduled_at) %>
<% if job_delayed?(job) %>
<div class="is-danger tag ml-4">delayed</div>
<% end %>
Expand Down
2 changes: 1 addition & 1 deletion app/views/mission_control/jobs/queues/_job.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<%= link_to application_job_path(@application, job.job_id, filter: { queue_name: job.queue }) do %>
<%= job_title(job) %>
<% end %>
<div class="has-text-grey">Enqueued on <%= formatted_time(job.enqueued_at.to_datetime) %></div>
<div class="has-text-grey">Enqueued <%= time_distance_in_words_with_title(job.enqueued_at.to_datetime) %> ago</div>
</td>
<td>
<% if job.serialized_arguments.present? %>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
<% end %>
</td>
<td> <%= recurring_task.schedule %> </td>
<td><div class="has-text-grey"><%= recurring_task.last_enqueued_at ? formatted_time(recurring_task.last_enqueued_at) : "Never" %></div></td>
<td class="next_time"><div class="has-text-grey"><%= formatted_time(recurring_task.next_time) %></div></td>
<td><div class="has-text-grey"><%= recurring_task.last_enqueued_at ? bidirectional_time_distance_in_words_with_title(recurring_task.last_enqueued_at) : "Never" %></div></td>
<td class="next_time"><div class="has-text-grey"><%= bidirectional_time_distance_in_words_with_title(recurring_task.next_time) %></div></td>
<td class="pr-0">
<%= render "mission_control/jobs/recurring_tasks/actions", recurring_task: recurring_task %>
</td>
Expand Down
6 changes: 3 additions & 3 deletions app/views/mission_control/jobs/shared/_job.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<%= link_to application_job_path(@application, job.job_id, filter: { queue_name: job.queue }) do %>
<%= job_title(job) %>
<% end %>
<div class="has-text-grey">Enqueued on <%= formatted_time(job.enqueued_at.to_datetime) %></div>
<div class="has-text-grey">Enqueued <%= time_distance_in_words_with_title(job.enqueued_at.to_datetime) %> ago</div>
</td>
<td>
<% if job.serialized_arguments.present? %>
Expand All @@ -16,9 +16,9 @@
<td>
<div class="has-text-grey">
<% if job.started_at %>
Running since <%= formatted_time(job.started_at) %>
Running for <%= time_distance_in_words_with_title(job.started_at) %>
<% elsif job.finished_at %>
Finished on <%= formatted_time(job.finished_at) %>
Finished <%= time_distance_in_words_with_title(job.finished_at) %> ago
<% else %>
Pending
<% end %>
Expand Down
2 changes: 1 addition & 1 deletion app/views/mission_control/jobs/workers/_worker.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@
<% end %>
</td>

<td><div class="has-text-grey"><%= formatted_time(worker.last_heartbeat_at) %></div></td>
<td><div class="has-text-grey"><%= time_distance_in_words_with_title(worker.last_heartbeat_at) %> ago</div></td>
</tr>
5 changes: 5 additions & 0 deletions lib/mission_control/jobs/i18n_config.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class MissionControl::Jobs::I18nConfig < ::I18n::Config
def available_locales
[ :en ]
end
end
39 changes: 31 additions & 8 deletions test/controllers/jobs_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ class MissionControl::Jobs::JobsControllerTest < ActionDispatch::IntegrationTest
end

test "get jobs and job details when there are multiple instances of the same job due to automatic retries" do
time = Time.now
job = AutoRetryingJob.perform_later

perform_enqueued_jobs_async
Expand All @@ -33,7 +32,7 @@ class MissionControl::Jobs::JobsControllerTest < ActionDispatch::IntegrationTest
assert_response :ok

assert_select "tr.job", 2
assert_select "tr.job", /AutoRetryingJob\s+Enqueued #{time_pattern(time)}\s+default/
assert_select "tr.job", /AutoRetryingJob\s+Enqueued less than 5 seconds ago\s+default/

get mission_control_jobs.application_job_url(@application, job.job_id)
assert_response :ok
Expand Down Expand Up @@ -72,16 +71,17 @@ class MissionControl::Jobs::JobsControllerTest < ActionDispatch::IntegrationTest
end

test "get scheduled jobs" do
time = Time.now
DummyJob.set(wait: 3.minutes).perform_later
DummyJob.set(wait: 1.minute).perform_later

travel_to 2.minutes.from_now

get mission_control_jobs.application_jobs_url(@application, :scheduled)
assert_response :ok

assert_select "tr.job", 2
assert_select "tr.job", /DummyJob\s+Enqueued #{time_pattern(time)}\s+queue_1\s+#{time_pattern(time + 3.minute)}/
assert_select "tr.job", /DummyJob\s+Enqueued #{time_pattern(time)}\s+queue_1\s+#{time_pattern(time + 1.minute)}/
assert_select "tr.job", /DummyJob\s+Enqueued 2 minutes ago\s+queue_1\s+in 1 minute/
assert_select "tr.job", /DummyJob\s+Enqueued 2 minutes ago\s+queue_1\s+(1 minute ago|less than a minute ago)/
assert_select "tr.job", /Discard/
end

Expand All @@ -104,8 +104,31 @@ class MissionControl::Jobs::JobsControllerTest < ActionDispatch::IntegrationTest
end
end

private
def time_pattern(time)
/#{time.utc.strftime("%Y-%m-%d %H:%M")}:\d{2}\.\d{3} UTC/
test "get jobs and job details when the default locale is set to another language than English" do
previous_locales, I18n.available_locales = I18n.available_locales, %i[ en nl ]

DummyJob.set(wait: 3.minutes).perform_later

I18n.with_locale(:nl) do
get mission_control_jobs.application_jobs_url(@application, :scheduled)
assert_response :ok

assert_select "tr.job", /DummyJob\s+Enqueued less than 5 seconds ago\s+queue_1\s+in 3 minutes/
end
ensure
I18n.available_locales = previous_locales
end

test "get jobs and job details when English is not included among the locales" do
previous_locales, I18n.available_locales = I18n.available_locales, %i[ es nl ]

DummyJob.set(wait: 3.minutes).perform_later

get mission_control_jobs.application_jobs_url(@application, :scheduled)
assert_response :ok

assert_select "tr.job", /DummyJob\s+Enqueued less than 5 seconds ago\s+queue_1\s+in 3 minutes/
ensure
I18n.available_locales = previous_locales
end
end
79 changes: 17 additions & 62 deletions test/controllers/recurring_tasks_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,33 +13,27 @@ class MissionControl::Jobs::RecurringTasksControllerTest < ActionDispatch::Integ
end

test "get recurring task list" do
travel_to Time.parse("2024-10-30 19:07:10 UTC") do
schedule_recurring_tasks_async(wait: 2.seconds) do
get mission_control_jobs.application_recurring_tasks_url(@application)
assert_response :ok

assert_select "tr.recurring_task", 1
assert_select "td a", "periodic_pause_job"
assert_select "td", "PauseJob"
assert_select "td", "every second"
assert_select "td", /2024-10-30 19:07:1\d\.\d{3}/
assert_select "button", "Run now"
end
schedule_recurring_tasks_async(wait: 2.seconds) do
get mission_control_jobs.application_recurring_tasks_url(@application)
assert_response :ok

assert_select "tr.recurring_task", 1
assert_select "td a", "periodic_pause_job"
assert_select "td", "PauseJob"
assert_select "td", "every second"
assert_select "td", /less than \d+ seconds ago/
end
end

test "get recurring task details and job list" do
travel_to Time.parse("2024-10-30 19:07:10 UTC") do
schedule_recurring_tasks_async(wait: 1.seconds) do
get mission_control_jobs.application_recurring_task_url(@application, "periodic_pause_job")
assert_response :ok

assert_select "h1", /periodic_pause_job/
assert_select "h2", "1 job"
assert_select "tr.job", 1
assert_select "td a", "PauseJob"
assert_select "td", /2024-10-30 19:07:1\d\.\d{3}/
end
schedule_recurring_tasks_async(wait: 1.seconds) do
get mission_control_jobs.application_recurring_task_url(@application, "periodic_pause_job")
assert_response :ok
assert_select "h1", /periodic_pause_job/
assert_select "h2", "1 job"
assert_select "tr.job", 1
assert_select "td a", "PauseJob"
assert_select "td", /less than \d+ seconds ago/
end
end

Expand All @@ -53,43 +47,4 @@ class MissionControl::Jobs::RecurringTasksControllerTest < ActionDispatch::Integ
assert_select "article.is-danger", /Recurring task with id 'invalid_key' not found/
end
end

test "get recurring task with undefined class" do
# simulate recurring task inserted from another app, no validations or callbacks
SolidQueue::RecurringTask.insert({ key: "missing_class_task", class_name: "MissingJob", schedule: "every minute" })
get mission_control_jobs.application_recurring_tasks_url(@application)
assert_response :ok

assert_select "tr.recurring_task", 1
assert_select "td a", "missing_class_task"
assert_select "td", "MissingJob"
assert_select "td", "every minute"
assert_select "button", text: "Run now", count: 0 # Can't be run because the class doesn't exist
end

test "enqueue recurring task successfully" do
schedule_recurring_tasks_async(wait: 0.1.seconds)

assert_difference -> { ActiveJob.jobs.pending.count } do
put mission_control_jobs.application_recurring_task_url(@application, "periodic_pause_job")
assert_response :redirect
end

job = ActiveJob.jobs.pending.last
assert_equal "PauseJob", job.job_class_name
assert_match /jobs\/#{job.job_id}\?server_id=solid_queue\z/, response.location
end

test "fail to enqueue recurring task with undefined class" do
# simulate recurring task inserted from another app, no validations or callbacks
SolidQueue::RecurringTask.insert({ key: "missing_class_task", class_name: "MissingJob", schedule: "every minute" })

assert_no_difference -> { ActiveJob.jobs.pending.count } do
put mission_control_jobs.application_recurring_task_url(@application, "missing_class_task")
assert_response :redirect

follow_redirect!
assert_select "article.is-danger", /This task can.t be enqueued/
end
end
end
24 changes: 11 additions & 13 deletions test/controllers/retries_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,21 @@ class MissionControl::Jobs::JobsControllerTest < ActionDispatch::IntegrationTest
end

test "retry jobs when there are multiple instances of the same job due to automatic retries" do
travel_to Time.parse("2024-10-30 19:07:10 UTC") do
job = AutoRetryingJob.perform_later
job = AutoRetryingJob.perform_later

perform_enqueued_jobs_async
perform_enqueued_jobs_async

get mission_control_jobs.application_jobs_url(@application, :failed)
assert_response :ok
get mission_control_jobs.application_jobs_url(@application, :failed)
assert_response :ok

assert_select "tr.job", 1
assert_select "tr.job", /AutoRetryingJob\s+Enqueued 2024-10-30 19:07:1\d\.\d{3} UTC\s+AutoRetryingJob::RandomError/
assert_select "tr.job", 1
assert_select "tr.job", /AutoRetryingJob\s+Enqueued less than 5 seconds ago\s+AutoRetryingJob::RandomError/

post mission_control_jobs.application_job_retry_url(@application, job.job_id)
assert_redirected_to mission_control_jobs.application_jobs_url(@application, :failed)
follow_redirect!
post mission_control_jobs.application_job_retry_url(@application, job.job_id)
assert_redirected_to mission_control_jobs.application_jobs_url(@application, :failed)
follow_redirect!

assert_select "article.is-danger", text: /Job with id '#{job.job_id}' not found/, count: 0
assert_select "tr.job", 0
end
assert_select "article.is-danger", text: /Job with id '#{job.job_id}' not found/, count: 0
assert_select "tr.job", 0
end
end

0 comments on commit fca856d

Please sign in to comment.