defmodule GitGudWeb.WorkflowLive.Index do
use GitGudWeb, :live_view
alias GitGud.Git
alias GitGud.Repositories
alias GitGud.Repositories.Storage
alias GitGud.Workflows
@impl true
def mount(%{"owner" => owner, "name" => name}, _session, socket) do
repo =
Repositories.get_repository_by_path!(owner, name)
|> GitGud.Repo.preload([:owner, :organization])
# Live-update the run list + header badge as runs change (a job
# starting flips a run to `running`).
if connected?(socket),
do: Phoenix.PubSub.subscribe(GitGud.PubSub, Workflows.ci_topic())
runs = Workflows.list_runs(repo)
{:ok,
socket
|> assign(:repo, repo)
|> assign(:handle, Storage.repo_handle(repo))
|> GitGudWeb.RepoLive.Header.assign_chrome(repo)
|> stream(:runs, runs)
|> assign(:page_title, "Actions — #{owner}/#{name}")}
end
@impl true
def handle_info({:ci_run_changed, _run_id}, socket) do
repo = socket.assigns.repo
{:noreply,
socket
|> stream(:runs, Workflows.list_runs(repo), reset: true)
|> assign(:latest_run, Workflows.latest_run(repo))}
end
def handle_info(_msg, socket), do: {:noreply, socket}
@impl true
def render(assigns) do
~H"""
<Layouts.app flash={@flash} current_scope={@current_scope}>
<div class="space-y-4">
<GitGudWeb.RepoLive.Header.header
repo={@repo}
handle={@handle}
current_scope={@current_scope}
has_packages?={@has_packages?}
can_admin?={@can_admin?}
latest_run={@latest_run}
/>
<h1 class="text-xl font-semibold">Actions</h1>
<ul id="runs" phx-update="stream" class="divide-y divide-base-300">
<li class="hidden only:block py-6 opacity-60 text-sm">No workflow runs yet.</li>
<li :for={{id, run} <- @streams.runs} id={id} class="py-3">
<div class="flex items-baseline justify-between gap-3">
<.link
navigate={~p"/r/#{@handle}/#{@repo.name}/actions/#{run.number}"}
class="font-medium link link-hover"
>
#{run.number} — {run.workflow_name || run.workflow_path}
</.link>
<span class={["badge badge-sm", status_class(run.status)]}>{run.status}</span>
</div>
<p class="text-xs opacity-60 mt-1 font-mono">
{run.trigger} · {run.head_ref} · {Git.short(run.head_sha)}
</p>
<p class="text-xs opacity-50 mt-0.5">
<span :if={run.started_at}>started {format_dt(run.started_at)}</span>
<span :if={run.started_at} class="ml-2">
· {duration(run.started_at, run.completed_at)}
</span>
</p>
</li>
</ul>
</div>
</Layouts.app>
"""
end
defp status_class("success"), do: "badge-success"
defp status_class("running"), do: "badge-primary"
defp status_class("queued"), do: "badge-warning"
defp status_class("failure"), do: "badge-error"
defp status_class("cancelled"), do: "badge-error"
defp status_class("skipped"), do: "badge-ghost"
defp status_class("blocked"), do: "badge-ghost"
defp status_class(_), do: "badge-neutral"
defp format_dt(%DateTime{} = dt), do: Calendar.strftime(dt, "%Y-%m-%d %H:%M")
defp format_dt(_), do: ""
defp duration(nil, _), do: ""
defp duration(start, nil), do: "for #{secs(DateTime.diff(DateTime.utc_now(), start))}"
defp duration(start, %DateTime{} = finish), do: "in #{secs(DateTime.diff(finish, start))}"
defp secs(s) when s < 0, do: "0s"
defp secs(s) when s < 60, do: "#{s}s"
defp secs(s) when s < 3600, do: "#{div(s, 60)}m #{rem(s, 60)}s"
defp secs(s), do: "#{div(s, 3600)}h #{div(rem(s, 3600), 60)}m"
end
3.7 KiB · text
5af55d9