Link reviewer names to their profiles

a462837 · Gabriel Morell · 2026-09-10 15:18

2 files +95 -10
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