neiam /gitgud
Git Gud
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Packages
⭐
Log in to mark this repository.
Show repository tags on /repositories, and filter by them
83a7db3 · Gabriel Morell · 2026-09-11 01:43
Message
{commit_body(@commit)}
Files changed
modified
lib/git_gud/tags.ex
+15
−0
@@ -48,6 +48,21 @@ defmodule GitGud.Tags do
| 48 | 48 | @doc "Just the names, for rendering." |
| 49 | 49 | def tag_names(%Repository{} = repo), do: repo |> list_tags() |> Enum.map(& &1.name) |
| 50 | 50 | |
| 51 | + @doc """ | |
| 52 | + Tag names for many repositories at once, keyed by repo id. | |
| 53 | + | |
| 54 | + One query for a whole listing rather than one per row. | |
| 55 | + """ | |
| 56 | + def names_by_repository(repo_ids) when is_list(repo_ids) do | |
| 57 | + from(t in RepositoryTag, | |
| 58 | + where: t.repository_id in ^repo_ids, | |
| 59 | + order_by: [asc: t.name], | |
| 60 | + select: {t.repository_id, t.name} | |
| 61 | + ) | |
| 62 | + |> Repo.all() | |
| 63 | + |> Enum.group_by(fn {rid, _} -> rid end, fn {_, name} -> name end) | |
| 64 | + end | |
| 65 | + | |
| 51 | 66 | @doc """ |
| 52 | 67 | Replace a repository's tags with `names`. |
| 53 | 68 |
modified
lib/git_gud_web/live/repo_live/index.ex
+17
−5
@@ -29,6 +29,7 @@ defmodule GitGudWeb.RepoLive.Index do
| 29 | 29 | |> assign(:page_title, "Repositories") |
| 30 | 30 | |> assign(:repos, repos) |
| 31 | 31 | |> assign(:languages, Repositories.top_languages_for(ids)) |
| 32 | + |> assign(:tags, GitGud.Tags.names_by_repository(ids)) | |
| 32 | 33 | |> assign(:ci_status, Workflows.latest_run_status_by_repo(ids)) |
| 33 | 34 | |> assign(:query, "") |
| 34 | 35 | |> assign(:scope_filter, "all") |
@@ -67,18 +68,19 @@ defmodule GitGudWeb.RepoLive.Index do
| 67 | 68 | # add round-trips. |
| 68 | 69 | defp visible(assigns) do |
| 69 | 70 | assigns.repos |
| 70 | − |> Enum.filter(&matches?(&1, assigns.query)) | |
| 71 | + |> Enum.filter(&matches?(&1, assigns.query, assigns.tags)) | |
| 71 | 72 | |> Enum.filter(&in_scope?(&1, assigns.scope_filter, assigns.current_scope)) |
| 72 | 73 | |> sort_by(assigns.sort) |
| 73 | 74 | end |
| 74 | 75 | |
| 75 | − defp matches?(_repo, ""), do: true | |
| 76 | + defp matches?(_repo, "", _tags), do: true | |
| 76 | 77 | |
| 77 | − defp matches?(repo, query) do | |
| 78 | + defp matches?(repo, query, tags) do | |
| 78 | 79 | needle = String.downcase(query) |
| 79 | 80 | |
| 80 | 81 | String.contains?(String.downcase("#{repo.handle}/#{repo.name}"), needle) or |
| 81 | − String.contains?(String.downcase(repo.description || ""), needle) | |
| 82 | + String.contains?(String.downcase(repo.description || ""), needle) or | |
| 83 | + Enum.any?(Map.get(tags, repo.id, []), &String.contains?(&1, needle)) | |
| 82 | 84 | end |
| 83 | 85 | |
| 84 | 86 | defp in_scope?(_repo, "all", _scope), do: true |
@@ -157,7 +159,7 @@ defmodule GitGudWeb.RepoLive.Index do
| 157 | 159 | type="search" |
| 158 | 160 | name="query" |
| 159 | 161 | value={@query} |
| 160 | − placeholder="Filter by name or description…" | |
| 162 | + placeholder="Filter by name, description or tag…" | |
| 161 | 163 | class="input input-bordered input-sm flex-1 min-w-48" |
| 162 | 164 | /> |
| 163 | 165 | <select name="sort" class="select select-bordered select-sm"> |
@@ -249,6 +251,16 @@ defmodule GitGudWeb.RepoLive.Index do
| 249 | 251 | {repo.description} |
| 250 | 252 | </p> |
| 251 | 253 | |
| 254 | + <div :if={@tags[repo.id]} class="flex flex-wrap gap-1 mt-1"> | |
| 255 | + <.link | |
| 256 | + :for={tag <- @tags[repo.id]} | |
| 257 | + navigate={~p"/tags/#{tag}"} | |
| 258 | + class="badge badge-xs badge-outline hover:badge-primary" | |
| 259 | + > | |
| 260 | + {tag} | |
| 261 | + </.link> | |
| 262 | + </div> | |
| 263 | + | |
| 252 | 264 | <div class="text-xs opacity-60 mt-2 flex items-center gap-3 flex-wrap"> |
| 253 | 265 | <span :if={@languages[repo.id]} class="flex items-center gap-1.5"> |
| 254 | 266 | <span |
modified
test/git_gud_web/live/repo_live/index_test.exs
+48
−0
@@ -128,4 +128,52 @@ defmodule GitGudWeb.RepoLive.IndexTest do
| 128 | 128 | |
| 129 | 129 | assert html =~ "1 of 2 shown" |
| 130 | 130 | end |
| 131 | + | |
| 132 | + describe "tags" do | |
| 133 | + test "a repo's tags show on its card and link to the listing", %{conn: conn, user: user} do | |
| 134 | + {_user, repo} = repository_fixture(%{owner: user, name: "tagged-repo"}) | |
| 135 | + {:ok, _} = GitGud.Tags.set_tags(repo, ["elixir", "git"]) | |
| 136 | + | |
| 137 | + {:ok, _lv, html} = live(log_in_user(conn, user), ~p"/repositories") | |
| 138 | + | |
| 139 | + assert html =~ "tagged-repo" | |
| 140 | + assert html =~ ~s(href="/tags/elixir") | |
| 141 | + assert html =~ ~s(href="/tags/git") | |
| 142 | + end | |
| 143 | + | |
| 144 | + test "an untagged repo shows no tag row", %{conn: conn, user: user} do | |
| 145 | + {_user, _repo} = repository_fixture(%{owner: user, name: "plain-repo"}) | |
| 146 | + | |
| 147 | + {:ok, _lv, html} = live(log_in_user(conn, user), ~p"/repositories") | |
| 148 | + | |
| 149 | + assert html =~ "plain-repo" | |
| 150 | + refute html =~ ~s(href="/tags/) | |
| 151 | + end | |
| 152 | + | |
| 153 | + test "the filter matches on tag as well as name", %{conn: conn, user: user} do | |
| 154 | + {_user, tagged} = repository_fixture(%{owner: user, name: "alpha"}) | |
| 155 | + {_user, _other} = repository_fixture(%{owner: user, name: "beta"}) | |
| 156 | + {:ok, _} = GitGud.Tags.set_tags(tagged, ["elixir"]) | |
| 157 | + | |
| 158 | + {:ok, lv, _html} = live(log_in_user(conn, user), ~p"/repositories") | |
| 159 | + | |
| 160 | + html = lv |> form("form[phx-change=filter]", %{query: "elixir"}) |> render_change() | |
| 161 | + | |
| 162 | + assert html =~ "alpha" | |
| 163 | + refute html =~ "beta" | |
| 164 | + end | |
| 165 | + | |
| 166 | + test "filtering by name still works alongside", %{conn: conn, user: user} do | |
| 167 | + {_user, tagged} = repository_fixture(%{owner: user, name: "alpha"}) | |
| 168 | + {_user, _other} = repository_fixture(%{owner: user, name: "beta"}) | |
| 169 | + {:ok, _} = GitGud.Tags.set_tags(tagged, ["elixir"]) | |
| 170 | + | |
| 171 | + {:ok, lv, _html} = live(log_in_user(conn, user), ~p"/repositories") | |
| 172 | + | |
| 173 | + html = lv |> form("form[phx-change=filter]", %{query: "beta"}) |> render_change() | |
| 174 | + | |
| 175 | + assert html =~ "beta" | |
| 176 | + refute html =~ ">alpha<" | |
| 177 | + end | |
| 178 | + end | |
| 131 | 179 | end |
Parents: 5c4496e