neiam /gitgud
Git Gud
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Packages
⭐
Log in to mark this repository.
Link reviewer names to their profiles
a462837 · Gabriel Morell · 2026-09-10 15:18
Message
{commit_body(@commit)}
Files changed
modified
lib/git_gud_web/live/pr_live/reviews.ex
+46
−10
@@ -96,6 +96,11 @@ defmodule GitGudWeb.PrLive.Reviews do
| 96 | 96 | defp reviewer_name(%{email: e}) when is_binary(e), do: e |> String.split("@") |> hd() |
| 97 | 97 | defp reviewer_name(_), do: "anonymous" |
| 98 | 98 | |
| 99 | + # Only local users with a handle have a profile page; a federated | |
| 100 | + # reviewer has neither, so their name stays plain text. | |
| 101 | + defp profile_handle(%{handle: h}) when is_binary(h), do: h | |
| 102 | + defp profile_handle(_), do: nil | |
| 103 | + | |
| 99 | 104 | @impl true |
| 100 | 105 | def handle_event("request_review", %{"handle" => handle}, socket) do |
| 101 | 106 | pr = socket.assigns.pr |
@@ -226,7 +231,16 @@ defmodule GitGudWeb.PrLive.Reviews do
| 226 | 231 | size="xs" |
| 227 | 232 | alt={reviewer_name(req.reviewer)} |
| 228 | 233 | /> |
| 229 | − <span>{reviewer_name(req.reviewer)}</span> | |
| 234 | + <.link | |
| 235 | + :if={profile_handle(req.reviewer)} | |
| 236 | + navigate={~p"/u/#{profile_handle(req.reviewer)}"} | |
| 237 | + class="link link-hover" | |
| 238 | + > | |
| 239 | + {reviewer_name(req.reviewer)} | |
| 240 | + </.link> | |
| 241 | + <span :if={is_nil(profile_handle(req.reviewer))}> | |
| 242 | + {reviewer_name(req.reviewer)} | |
| 243 | + </span> | |
| 230 | 244 | <span :if={awaiting_review?(@pr, req)} class="badge badge-xs badge-ghost"> |
| 231 | 245 | awaiting review |
| 232 | 246 | </span> |
@@ -250,17 +264,25 @@ defmodule GitGudWeb.PrLive.Reviews do
| 250 | 264 | <div :if={can_request_review?(assigns) and @suggestions != []} class="mb-2"> |
| 251 | 265 | <p class="text-xs opacity-60 mb-1">Reviews often in this repo</p> |
| 252 | 266 | <div class="flex flex-wrap gap-1"> |
| 253 | − <button | |
| 267 | + <span | |
| 254 | 268 | :for={{user, count} <- @suggestions} |
| 255 | − type="button" | |
| 256 | − phx-click="request_review" | |
| 257 | − phx-value-handle={user.handle} | |
| 258 | − class="badge badge-outline gap-1 hover:badge-primary cursor-pointer" | |
| 269 | + class="badge badge-outline gap-1 pr-0.5" | |
| 259 | 270 | title={"#{count} review(s) in this repo"} |
| 260 | 271 | > |
| 261 | − {user.handle} | |
| 272 | + <.link navigate={~p"/u/#{user.handle}"} class="link link-hover"> | |
| 273 | + {user.handle} | |
| 274 | + </.link> | |
| 262 | 275 | <span class="opacity-60">{count}</span> |
| 263 | − </button> | |
| 276 | + <button | |
| 277 | + type="button" | |
| 278 | + phx-click="request_review" | |
| 279 | + phx-value-handle={user.handle} | |
| 280 | + class="btn btn-ghost btn-xs px-1 min-h-0 h-4" | |
| 281 | + aria-label={"Request a review from #{user.handle}"} | |
| 282 | + > | |
| 283 | + <.icon name="hero-plus" class="size-3" /> | |
| 284 | + </button> | |
| 285 | + </span> | |
| 264 | 286 | </div> |
| 265 | 287 | </div> |
| 266 | 288 |
@@ -294,7 +316,14 @@ defmodule GitGudWeb.PrLive.Reviews do
| 294 | 316 | > |
| 295 | 317 | <p class="opacity-60 text-xs mb-1 flex items-center gap-1.5"> |
| 296 | 318 | <span class={["badge badge-xs", review_badge(r.state)]}>{review_word(r.state)}</span> |
| 297 | − {reviewer_name(r.reviewer)} | |
| 319 | + <.link | |
| 320 | + :if={profile_handle(r.reviewer)} | |
| 321 | + navigate={~p"/u/#{profile_handle(r.reviewer)}"} | |
| 322 | + class="link link-hover" | |
| 323 | + > | |
| 324 | + {reviewer_name(r.reviewer)} | |
| 325 | + </.link> | |
| 326 | + <span :if={is_nil(profile_handle(r.reviewer))}>{reviewer_name(r.reviewer)}</span> | |
| 298 | 327 | </p> |
| 299 | 328 | <p :if={r.body}>{r.body}</p> |
| 300 | 329 | </li> |
@@ -310,7 +339,14 @@ defmodule GitGudWeb.PrLive.Reviews do
| 310 | 339 | <ul class="mt-2 space-y-1"> |
| 311 | 340 | <li :for={r <- Enum.reverse(@pr.reviews)} class="opacity-70"> |
| 312 | 341 | <span class={["badge badge-xs", review_badge(r.state)]}>{review_word(r.state)}</span> |
| 313 | − {reviewer_name(r.reviewer)} | |
| 342 | + <.link | |
| 343 | + :if={profile_handle(r.reviewer)} | |
| 344 | + navigate={~p"/u/#{profile_handle(r.reviewer)}"} | |
| 345 | + class="link link-hover" | |
| 346 | + > | |
| 347 | + {reviewer_name(r.reviewer)} | |
| 348 | + </.link> | |
| 349 | + <span :if={is_nil(profile_handle(r.reviewer))}>{reviewer_name(r.reviewer)}</span> | |
| 314 | 350 | </li> |
| 315 | 351 | </ul> |
| 316 | 352 | </details> |
modified
test/git_gud_web/live/pr_live/review_request_test.exs
+49
−0
@@ -212,6 +212,55 @@ defmodule GitGudWeb.PrLive.ReviewRequestTest do
| 212 | 212 | assert req.reviewer_id == frequent.id |
| 213 | 213 | end |
| 214 | 214 | |
| 215 | + test "the chip links to the suggested reviewer's profile", %{conn: conn} do | |
| 216 | + {owner, repo} = repository_fixture(%{visibility: "public"}) | |
| 217 | + old_pr = pr_with_branches(repo, owner) | |
| 218 | + frequent = user_fixture() | |
| 219 | + _ = review!(conn, repo, old_pr, frequent) | |
| 220 | + | |
| 221 | + new_pr = pull_request_fixture(repo, owner, "feature", "main") | |
| 222 | + {:ok, lv, _html} = live(log_in_user(conn, owner), pr_path(repo, new_pr)) | |
| 223 | + | |
| 224 | + assert has_element?(lv, ~s{a[href="/u/#{frequent.handle}"]}, frequent.handle) | |
| 225 | + end | |
| 226 | + | |
| 227 | + test "the chip still requests without navigating", %{conn: conn} do | |
| 228 | + {owner, repo} = repository_fixture(%{visibility: "public"}) | |
| 229 | + old_pr = pr_with_branches(repo, owner) | |
| 230 | + frequent = user_fixture() | |
| 231 | + _ = review!(conn, repo, old_pr, frequent) | |
| 232 | + | |
| 233 | + new_pr = pull_request_fixture(repo, owner, "feature", "main") | |
| 234 | + {:ok, lv, _html} = live(log_in_user(conn, owner), pr_path(repo, new_pr)) | |
| 235 | + | |
| 236 | + _ = lv |> element("button[phx-value-handle='#{frequent.handle}']") |> render_click() | |
| 237 | + | |
| 238 | + assert [_req] = PullRequests.list_review_requests(new_pr) | |
| 239 | + end | |
| 240 | + | |
| 241 | + test "a requested reviewer's name links to their profile", %{conn: conn} do | |
| 242 | + {owner, repo} = repository_fixture(%{visibility: "public"}) | |
| 243 | + pr = pr_with_branches(repo, owner) | |
| 244 | + reviewer = user_fixture() | |
| 245 | + | |
| 246 | + {:ok, lv, _html} = live(log_in_user(conn, owner), pr_path(repo, pr)) | |
| 247 | + _ = request!(lv, reviewer.handle) | |
| 248 | + | |
| 249 | + assert has_element?(lv, ~s{a[href="/u/#{reviewer.handle}"]}, reviewer.handle) | |
| 250 | + end | |
| 251 | + | |
| 252 | + test "a reviewer who left a verdict links to their profile", %{conn: conn} do | |
| 253 | + {owner, repo} = repository_fixture(%{visibility: "public"}) | |
| 254 | + pr = pr_with_branches(repo, owner) | |
| 255 | + reviewer = user_fixture() | |
| 256 | + | |
| 257 | + _ = review!(conn, repo, pr, reviewer) | |
| 258 | + | |
| 259 | + {:ok, lv, _html} = live(log_in_user(conn, owner), pr_path(repo, pr)) | |
| 260 | + | |
| 261 | + assert has_element?(lv, ~s{a[href="/u/#{reviewer.handle}"]}, reviewer.handle) | |
| 262 | + end | |
| 263 | + | |
| 215 | 264 | test "someone already asked stops being suggested", %{conn: conn} do |
| 216 | 265 | {owner, repo} = repository_fixture(%{visibility: "public"}) |
| 217 | 266 | old_pr = pr_with_branches(repo, owner) |
Parents: a23f026