diff --git a/app/controllers/mission_control/jobs/application_controller.rb b/app/controllers/mission_control/jobs/application_controller.rb
index d1c56c09..6f5d1af2 100644
--- a/app/controllers/mission_control/jobs/application_controller.rb
+++ b/app/controllers/mission_control/jobs/application_controller.rb
@@ -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
diff --git a/app/helpers/mission_control/jobs/dates_helper.rb b/app/helpers/mission_control/jobs/dates_helper.rb
index f559ea59..ad8a63ad 100644
--- a/app/helpers/mission_control/jobs/dates_helper.rb
+++ b/app/helpers/mission_control/jobs/dates_helper.rb
@@ -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
diff --git a/app/helpers/mission_control/jobs/jobs_helper.rb b/app/helpers/mission_control/jobs/jobs_helper.rb
index 146504bc..f55f02d7 100644
--- a/app/helpers/mission_control/jobs/jobs_helper.rb
+++ b/app/helpers/mission_control/jobs/jobs_helper.rb
@@ -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
diff --git a/app/views/mission_control/jobs/jobs/_general_information.html.erb b/app/views/mission_control/jobs/jobs/_general_information.html.erb
index 0b15d7f5..e962e763 100644
--- a/app/views/mission_control/jobs/jobs/_general_information.html.erb
+++ b/app/views/mission_control/jobs/jobs/_general_information.html.erb
@@ -23,14 +23,14 @@
Enqueued |
- <%= formatted_time(job.enqueued_at.to_datetime) %>
+ <%= time_distance_in_words_with_title(job.enqueued_at.to_datetime) %> ago
|
<% if job.scheduled? %>
Scheduled |
- <%= formatted_time(job.scheduled_at) %>
+ <%= bidirectional_time_distance_in_words_with_title(job.scheduled_at) %>
<% if job_delayed?(job) %>
delayed
<% end %>
@@ -41,7 +41,7 @@
|
Failed |
- <%= formatted_time(job.failed_at) %>
+ <%= time_distance_in_words_with_title(job.failed_at) %> ago
|
<% end %>
@@ -49,7 +49,7 @@
Finished at |
- <%= formatted_time(job.finished_at) %>
+ <%= time_distance_in_words_with_title(job.finished_at) %> ago
|
<% end %>
diff --git a/app/views/mission_control/jobs/jobs/_job.html.erb b/app/views/mission_control/jobs/jobs/_job.html.erb
index bdf8ba54..716dfdbc 100644
--- a/app/views/mission_control/jobs/jobs/_job.html.erb
+++ b/app/views/mission_control/jobs/jobs/_job.html.erb
@@ -6,7 +6,7 @@
<%= job_arguments(job) %>
<% end %>
- Enqueued <%= formatted_time(job.enqueued_at.to_datetime) %>
+ Enqueued <%= time_distance_in_words_with_title(job.enqueued_at.to_datetime) %> ago
<%= render "mission_control/jobs/jobs/#{jobs_status}/job", job: job %>
diff --git a/app/views/mission_control/jobs/jobs/blocked/_job.html.erb b/app/views/mission_control/jobs/jobs/blocked/_job.html.erb
index 64f00af4..4521ea91 100644
--- a/app/views/mission_control/jobs/jobs/blocked/_job.html.erb
+++ b/app/views/mission_control/jobs/jobs/blocked/_job.html.erb
@@ -1,6 +1,6 @@
<%= link_to job.queue_name, application_queue_path(@application, job.queue) %> |
<%= job.blocked_by %>
- Until <%= formatted_time(job.blocked_until) %>
+ Expires <%= bidirectional_time_distance_in_words_with_title(job.blocked_until) %>
|
<%= render "mission_control/jobs/jobs/blocked/actions", job: job %>
diff --git a/app/views/mission_control/jobs/jobs/failed/_job.html.erb b/app/views/mission_control/jobs/jobs/failed/_job.html.erb
index 5291336c..22bcd146 100644
--- a/app/views/mission_control/jobs/jobs/failed/_job.html.erb
+++ b/app/views/mission_control/jobs/jobs/failed/_job.html.erb
@@ -1,6 +1,6 @@
|
<%= link_to failed_job_error(job), application_job_path(@application, job.job_id, anchor: "error") %>
- <%= formatted_time(job.failed_at) %>
+ <%= time_distance_in_words_with_title(job.failed_at) %> ago
|
<%= render "mission_control/jobs/jobs/failed/actions", job: job %>
diff --git a/app/views/mission_control/jobs/jobs/finished/_job.html.erb b/app/views/mission_control/jobs/jobs/finished/_job.html.erb
index b0e9f201..5488bed7 100644
--- a/app/views/mission_control/jobs/jobs/finished/_job.html.erb
+++ b/app/views/mission_control/jobs/jobs/finished/_job.html.erb
@@ -1,2 +1,2 @@
| <%= link_to job.queue_name, application_queue_path(@application, job.queue) %> |
-<%= formatted_time(job.finished_at) %> |
+<%= time_distance_in_words_with_title(job.finished_at) %> ago |
diff --git a/app/views/mission_control/jobs/jobs/in_progress/_job.html.erb b/app/views/mission_control/jobs/jobs/in_progress/_job.html.erb
index f428b28f..48491725 100644
--- a/app/views/mission_control/jobs/jobs/in_progress/_job.html.erb
+++ b/app/views/mission_control/jobs/jobs/in_progress/_job.html.erb
@@ -6,4 +6,4 @@
—
<% end %>
-<%= job.started_at ? formatted_time(job.started_at) : "(Finished)" %> |
+<%= job.started_at ? time_distance_in_words_with_title(job.started_at) : "(Finished)" %> |
diff --git a/app/views/mission_control/jobs/jobs/scheduled/_job.html.erb b/app/views/mission_control/jobs/jobs/scheduled/_job.html.erb
index b337a04e..78b5a366 100644
--- a/app/views/mission_control/jobs/jobs/scheduled/_job.html.erb
+++ b/app/views/mission_control/jobs/jobs/scheduled/_job.html.erb
@@ -1,6 +1,6 @@
<%= link_to job.queue_name, application_queue_path(@application, job.queue) %> |
- <%= formatted_time(job.scheduled_at) %>
+ <%= bidirectional_time_distance_in_words_with_title(job.scheduled_at) %>
<% if job_delayed?(job) %>
delayed
<% end %>
diff --git a/app/views/mission_control/jobs/queues/_job.html.erb b/app/views/mission_control/jobs/queues/_job.html.erb
index 34c0ae8e..5635abec 100644
--- a/app/views/mission_control/jobs/queues/_job.html.erb
+++ b/app/views/mission_control/jobs/queues/_job.html.erb
@@ -3,7 +3,7 @@
<%= link_to application_job_path(@application, job.job_id, filter: { queue_name: job.queue }) do %>
<%= job_title(job) %>
<% end %>
- Enqueued on <%= formatted_time(job.enqueued_at.to_datetime) %>
+ Enqueued <%= time_distance_in_words_with_title(job.enqueued_at.to_datetime) %> ago
|
<% if job.serialized_arguments.present? %>
diff --git a/app/views/mission_control/jobs/recurring_tasks/_recurring_task.html.erb b/app/views/mission_control/jobs/recurring_tasks/_recurring_task.html.erb
index f3c40e60..3a40f396 100644
--- a/app/views/mission_control/jobs/recurring_tasks/_recurring_task.html.erb
+++ b/app/views/mission_control/jobs/recurring_tasks/_recurring_task.html.erb
@@ -14,8 +14,8 @@
<% end %>
|
<%= recurring_task.schedule %> |
- <%= recurring_task.last_enqueued_at ? formatted_time(recurring_task.last_enqueued_at) : "Never" %> |
- <%= formatted_time(recurring_task.next_time) %> |
+ <%= recurring_task.last_enqueued_at ? bidirectional_time_distance_in_words_with_title(recurring_task.last_enqueued_at) : "Never" %> |
+ <%= bidirectional_time_distance_in_words_with_title(recurring_task.next_time) %> |
<%= render "mission_control/jobs/recurring_tasks/actions", recurring_task: recurring_task %>
|
diff --git a/app/views/mission_control/jobs/shared/_job.html.erb b/app/views/mission_control/jobs/shared/_job.html.erb
index 52075dba..c42a3851 100644
--- a/app/views/mission_control/jobs/shared/_job.html.erb
+++ b/app/views/mission_control/jobs/shared/_job.html.erb
@@ -3,7 +3,7 @@
<%= link_to application_job_path(@application, job.job_id, filter: { queue_name: job.queue }) do %>
<%= job_title(job) %>
<% end %>
- Enqueued on <%= formatted_time(job.enqueued_at.to_datetime) %>
+ Enqueued <%= time_distance_in_words_with_title(job.enqueued_at.to_datetime) %> ago
<% if job.serialized_arguments.present? %>
@@ -16,9 +16,9 @@
|
<% 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 %>
diff --git a/app/views/mission_control/jobs/workers/_worker.html.erb b/app/views/mission_control/jobs/workers/_worker.html.erb
index a10a3d07..3d1aee48 100644
--- a/app/views/mission_control/jobs/workers/_worker.html.erb
+++ b/app/views/mission_control/jobs/workers/_worker.html.erb
@@ -17,5 +17,5 @@
<% end %>
|
- <%= formatted_time(worker.last_heartbeat_at) %> |
+ <%= time_distance_in_words_with_title(worker.last_heartbeat_at) %> ago |
diff --git a/lib/mission_control/jobs/i18n_config.rb b/lib/mission_control/jobs/i18n_config.rb
new file mode 100644
index 00000000..442d61f6
--- /dev/null
+++ b/lib/mission_control/jobs/i18n_config.rb
@@ -0,0 +1,5 @@
+class MissionControl::Jobs::I18nConfig < ::I18n::Config
+ def available_locales
+ [ :en ]
+ end
+end
diff --git a/test/controllers/jobs_controller_test.rb b/test/controllers/jobs_controller_test.rb
index 4d79fc1d..54a36120 100644
--- a/test/controllers/jobs_controller_test.rb
+++ b/test/controllers/jobs_controller_test.rb
@@ -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
@@ -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
@@ -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
@@ -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
diff --git a/test/controllers/recurring_tasks_controller_test.rb b/test/controllers/recurring_tasks_controller_test.rb
index 77f2dcbb..21102b75 100644
--- a/test/controllers/recurring_tasks_controller_test.rb
+++ b/test/controllers/recurring_tasks_controller_test.rb
@@ -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
@@ -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
diff --git a/test/controllers/retries_controller_test.rb b/test/controllers/retries_controller_test.rb
index c7f24a21..ea02f662 100644
--- a/test/controllers/retries_controller_test.rb
+++ b/test/controllers/retries_controller_test.rb
@@ -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