org page: show each repo's latest CI status at a glance
5474d55 · gmorell · 2026-06-26 00:55
Message
{commit_body(@commit)}
Files changed
modified
lib/git_gud/workflows.ex
+16
−0
@@ -302,6 +302,22 @@ defmodule GitGud.Workflows do
| 302 | 302 | |> Repo.one() |
| 303 | 303 | end |
| 304 | 304 | |
| 305 | + @doc """ | |
| 306 | + Latest CI run status for each of `repo_ids`, as | |
| 307 | + `%{repository_id => status}`. One query — for repo-list views that want | |
| 308 | + a CI status glance without an N+1. Repos with no runs are absent. | |
| 309 | + """ | |
| 310 | + def latest_run_status_by_repo(repo_ids) when is_list(repo_ids) do | |
| 311 | + from(r in WorkflowRun, | |
| 312 | + where: r.repository_id in ^repo_ids, | |
| 313 | + order_by: [asc: r.repository_id, desc: r.inserted_at], | |
| 314 | + distinct: r.repository_id, | |
| 315 | + select: {r.repository_id, r.status} | |
| 316 | + ) | |
| 317 | + |> Repo.all() | |
| 318 | + |> Map.new() | |
| 319 | + end | |
| 320 | + | |
| 305 | 321 | @doc """ |
| 306 | 322 | List artifacts attached to a run. Returns a list of |
| 307 | 323 | `%WorkflowArtifact{}` ordered by upload time. |
modified
lib/git_gud_web/live/org_live/show.ex
+19
−1
@@ -5,6 +5,7 @@ defmodule GitGudWeb.OrgLive.Show do
| 5 | 5 | alias GitGud.Organizations.Team |
| 6 | 6 | alias GitGud.Profiles |
| 7 | 7 | alias GitGud.Repositories |
| 8 | + alias GitGud.Workflows | |
| 8 | 9 | |
| 9 | 10 | @impl true |
| 10 | 11 | def mount(%{"handle" => handle}, _session, socket) do |
@@ -19,6 +20,8 @@ defmodule GitGudWeb.OrgLive.Show do
| 19 | 20 | |> push_navigate(to: ~p"/")} |
| 20 | 21 | |
| 21 | 22 | true -> |
| 23 | + repos = visible_repos(org, user) | |
| 24 | + | |
| 22 | 25 | {:ok, |
| 23 | 26 | socket |
| 24 | 27 | |> assign(:org, org) |
@@ -26,7 +29,8 @@ defmodule GitGudWeb.OrgLive.Show do
| 26 | 29 | |> assign(:is_admin?, user && Organizations.org_admin?(org, user)) |
| 27 | 30 | |> assign(:is_member?, user && Organizations.member?(org, user)) |
| 28 | 31 | |> assign(:teams, Organizations.list_teams(org)) |
| 29 | − |> assign(:repos, visible_repos(org, user)) | |
| 32 | + |> assign(:repos, repos) | |
| 33 | + |> assign(:ci_status, Workflows.latest_run_status_by_repo(Enum.map(repos, & &1.id))) | |
| 30 | 34 | |> assign(:pins, Profiles.list_pins(org)) |
| 31 | 35 | |> assign(:readme, Repositories.get_profile_readme(org, theme: socket.assigns[:editor_theme])) |
| 32 | 36 | |> assign_team_form(Team.changeset(%Team{}, %{})) |
@@ -34,6 +38,12 @@ defmodule GitGudWeb.OrgLive.Show do
| 34 | 38 | end |
| 35 | 39 | end |
| 36 | 40 | |
| 41 | + # daisyUI badge class for a repo's latest CI status. | |
| 42 | + defp ci_badge("success"), do: "badge-success" | |
| 43 | + defp ci_badge("failure"), do: "badge-error" | |
| 44 | + defp ci_badge(s) when s in ~w(running queued blocked), do: "badge-warning" | |
| 45 | + defp ci_badge(_), do: "badge-ghost" | |
| 46 | + | |
| 37 | 47 | defp can_view?(%{visibility: "public"}, _user), do: true |
| 38 | 48 | defp can_view?(%{visibility: "internal"}, %{} = _user), do: true |
| 39 | 49 | defp can_view?(%{visibility: "internal"}, nil), do: false |
@@ -178,6 +188,14 @@ defmodule GitGudWeb.OrgLive.Show do
| 178 | 188 | > |
| 179 | 189 | {repo.name} |
| 180 | 190 | </.link> |
| 191 | + <.link | |
| 192 | + :if={@ci_status[repo.id]} | |
| 193 | + navigate={~p"/r/#{@org.handle}/#{repo.name}/actions"} | |
| 194 | + class={["badge badge-xs ml-1 not-italic", ci_badge(@ci_status[repo.id])]} | |
| 195 | + title={"latest CI run: #{@ci_status[repo.id]}"} | |
| 196 | + > | |
| 197 | + {@ci_status[repo.id]} | |
| 198 | + </.link> | |
| 181 | 199 | <span |
| 182 | 200 | :if={Repositories.profile_repo?(repo, @org.handle)} |
| 183 | 201 | class="badge badge-xs badge-ghost ml-1 not-italic" |
Parents: 1fdbefa