Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Go back to relative times and a better locale management #217

Merged
merged 3 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
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
Loading