defmodule GitGudWeb.RepoLive.EntryTimes do
@moduledoc """
Last-touched timestamps for the entries of a tree listing.
`Repositories.last_touched/4` answers the whole directory in one
history walk, but that walk still grows with the history behind the
listing, so the live views kick it off after the connected mount
(`request/4`) and fold the result in when it lands
(`handle_result/2`). The listing paints immediately either way.
"""
use Phoenix.Component
alias GitGud.Repositories
@timeout 30_000
@doc "Kick off the walk for `entries` under `base_path`, if connected."
def request(socket, repo, head_sha, base_path, entries) do
if Phoenix.LiveView.connected?(socket) and entries != [] do
parent = self()
names = Enum.map(entries, & &1.name)
Task.Supervisor.start_child(GitGud.TaskSupervisor, fn ->
send(parent, {__MODULE__, head_sha, load(repo, head_sha, base_path, names)})
end)
end
Phoenix.Component.assign(socket, :entry_times, %{})
end
@doc """
Fold a result in, ignoring one that belongs to a commit the view has
since navigated away from.
"""
def handle_result(socket, {__MODULE__, head_sha, times}) do
if socket.assigns[:head_sha] == head_sha do
Phoenix.Component.assign(socket, :entry_times, times)
else
socket
end
end
# The walk runs in a task of its own purely so a wedged backend call
# can't pin this process forever — a listing with no times beats one
# that never arrives.
defp load(repo, head_sha, base_path, names) do
task =
Task.Supervisor.async_nolink(GitGud.TaskSupervisor, fn ->
Repositories.last_touched(repo, head_sha, base_path, names)
end)
case Task.yield(task, @timeout) || Task.shutdown(task, :brutal_kill) do
{:ok, {:ok, touches}} ->
Map.new(touches, fn {name, %{committed_at: at}} -> {name, at} end)
_ ->
%{}
end
end
@doc "The cell rendered in the listing's last-touched column."
attr :times, :map, required: true
attr :name, :string, required: true
def entry_time(assigns) do
assigns = assign(assigns, :at, Map.get(assigns.times, assigns.name))
~H"""
<span :if={@at} title={DateTime.to_iso8601(@at)} class="whitespace-nowrap">{ago(@at)}</span>
"""
end
@doc "Coarse relative age: `3d`, `2w`, `5mo`, `1y`."
def ago(%DateTime{} = dt) do
case DateTime.diff(DateTime.utc_now(), dt) do
s when s < 60 -> "just now"
s when s < 3600 -> "#{div(s, 60)}m ago"
s when s < 86_400 -> "#{div(s, 3600)}h ago"
s when s < 604_800 -> "#{div(s, 86_400)}d ago"
s when s < 2_592_000 -> "#{div(s, 604_800)}w ago"
s when s < 31_536_000 -> "#{div(s, 2_592_000)}mo ago"
s -> "#{div(s, 31_536_000)}y ago"
end
end
end
neiam
/gitgud
Git Gud
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
1
Log in to mark this repository.
2.7 KiB · text
History
b868907