diff --git a/lib/oban/live_dashboard.ex b/lib/oban/live_dashboard.ex
index 04665f0..07e86e0 100644
--- a/lib/oban/live_dashboard.ex
+++ b/lib/oban/live_dashboard.ex
@@ -18,6 +18,8 @@ defmodule Oban.LiveDashboard do
@impl true
def render(assigns) do
+ assigns = assign(assigns, :now, DateTime.utc_now())
+
~H"""
Oban
<.live_nav_bar id="oban_states" page={@page} nav_param="job_state" style={:bar} extra_params={["nav"]}>
@@ -33,7 +35,7 @@ defmodule Oban.LiveDashboard do
<:col field={:queue} header="Queue" sortable={:desc} />
<:col :let={job} field={@timestamp_field} sortable={:desc}>
- <%= format_value(timestamp(job, @timestamp_field)) %>
+ <%= render_time(timestamp(job, @timestamp_field), @now) %>
@@ -61,17 +63,30 @@ defmodule Oban.LiveDashboard do
<:elem label="Attempts"><%= @job.attempt %>/<%= @job.max_attempts %>
<:elem label="Priority"><%= @job.priority %>
<:elem label="Attempted at"><%= format_value(@job.attempted_at) %>
- <:elem :if={@job.cancelled_at} label="Cancelled at"><%= format_value(@job.cancelled_at) %>
- <:elem :if={@job.completed_at} label="Completed at"><%= format_value(@job.completed_at) %>
- <:elem :if={@job.discarded_at} label="Discarded at"><%= format_value(@job.discarded_at) %>
- <:elem label="Inserted at"><%= format_value(@job.inserted_at) %>
- <:elem label="Scheduled at"><%= format_value(@job.scheduled_at) %>
+ <:elem :if={@job.cancelled_at} label="Cancelled at"><%= render_time(@job.cancelled_at, @now) %>
+ <:elem :if={@job.completed_at} label="Completed at"><%= render_time(@job.completed_at, @now) %>
+ <:elem :if={@job.discarded_at} label="Discarded at"><%= render_time(@job.discarded_at, @now) %>
+ <:elem label="Inserted at"><%= render_time(@job.inserted_at, @now) %>
+ <:elem label="Scheduled at"><%= render_time(@job.scheduled_at, @now) %>
"""
end
+ defp render_time(nil, _), do: nil
+
+ defp render_time(datetime, now) do
+ assigns = %{
+ datetime: datetime,
+ now: now,
+ iso8601: DateTime.to_iso8601(datetime),
+ title: DateTime.to_string(datetime)
+ }
+
+ ~H||
+ end
+
@impl true
def mount(_params, _, socket) do
{:ok, socket}
@@ -258,6 +273,30 @@ defmodule Oban.LiveDashboard do
defp format_value(value), do: value
+ defp format_relative(%DateTime{} = a, %DateTime{} = b) do
+ delta = DateTime.diff(a, b, :second)
+ {prefix, suffix} = if delta < 0, do: {"", " ago"}, else: {"in ", ""}
+ {d, {h, m, s}} = :calendar.seconds_to_daystime(div(delta, 1000))
+
+ cond do
+ d > 1 -> "#{d} days"
+ d == 1 -> "1 day"
+ h > 1 -> "#{h} hours"
+ h == 1 -> "1 hour"
+ m > 1 -> "#{m} minutes"
+ m == 1 -> "1 minute"
+ s == 1 -> "1 second"
+ true -> "#{s} seconds"
+ end
+ |> then(fn x -> "#{prefix}#{x}#{suffix}" end)
+ end
+
+ defp format_relative(%DateTime{} = a, nil) do
+ format_relative(a, DateTime.utc_now())
+ end
+
+ defp format_relative(nil, _), do: nil
+
defp timestamp(job, timestamp_field) do
Map.get(job, timestamp_field)
end