authorgravatar for maria@kuuro.netmaria <maria@kuuro.net> 2026-09-04 14:51:18-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-04 14:51:18-04:00
loga76b898b3aa4fd4081104b06a3b2c942bd4fecb7
tree2cf08151632f6a81d89a51973001156ce156c6c7
parentcf9729d5ee9660c08556e823080d3bb19648ed28
signature Signed by PGP key B5690EEEBB952194

feat(web): link pull request authors to profiles (#9627)

Co-authored-by: maria-rcks <254055478+maria-rcks@users.noreply.github.com>

2 files changed, 39 insertions(+), 17 deletions(-)

apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx+18-15
...@@ -141,7 +141,6 @@ import {...@@ -141,7 +141,6 @@ import {
141} from "./pullRequestProjectAssignment.logic";141} from "./pullRequestProjectAssignment.logic";
142import { PullRequestChecksPopover } from "./PullRequestChecksPopover";142import { PullRequestChecksPopover } from "./PullRequestChecksPopover";
143import {143import {
144 PullRequestActorAvatar,
145 PullRequestActorLabel,144 PullRequestActorLabel,
146 PullRequestDiffStat,145 PullRequestDiffStat,
147 PullRequestMetaLine,146 PullRequestMetaLine,
...@@ -656,6 +655,13 @@ export function PullRequestDetailPanel({...@@ -656,6 +655,13 @@ export function PullRequestDetailPanel({
656 if (detail?.autoMergeMethod !== undefined) setMergeMethod(detail.autoMergeMethod);655 if (detail?.autoMergeMethod !== undefined) setMergeMethod(detail.autoMergeMethod);
657 }, [detail?.autoMergeMethod, pullRequestKey]);656 }, [detail?.autoMergeMethod, pullRequestKey]);
658 const repositoryUrl = detail === null ? null : changeRequestRepositoryUrl(detail.url);657 const repositoryUrl = detail === null ? null : changeRequestRepositoryUrl(detail.url);
658 const authorProfileUrl =
659 detail?.provider === "github" &&
660 detail.author !== null &&
661 !detail.author.login.endsWith("[bot]") &&
662 repositoryUrl !== null
663 ? new URL(`/${encodeURIComponent(detail.author.login)}`, repositoryUrl).toString()
664 : null;
659 const checkoutCommand = detail665 const checkoutCommand = detail
660 ? pullRequestCheckoutCommand(666 ? pullRequestCheckoutCommand(
661 detail.provider,667 detail.provider,
...@@ -1870,19 +1876,12 @@ export function PullRequestDetailPanel({...@@ -1870,19 +1876,12 @@ export function PullRequestDetailPanel({
1870 <div className="col-span-2 min-w-0 px-4 pb-2 pt-1">1876 <div className="col-span-2 min-w-0 px-4 pb-2 pt-1">
1871 <div className="flex min-w-0 items-center gap-2 text-xs text-muted-foreground">1877 <div className="flex min-w-0 items-center gap-2 text-xs text-muted-foreground">
1872 <span className="flex min-w-0 shrink items-center gap-1.5 overflow-hidden text-xs text-muted-foreground">1878 <span className="flex min-w-0 shrink items-center gap-1.5 overflow-hidden text-xs text-muted-foreground">
1873 <Tooltip>1879 <PullRequestActorLabel
1874 <TooltipTrigger1880 actor={detail.author}
1875 render={1881 profileUrl={authorProfileUrl}
1876 <span1882 className="shrink-0 rounded-full"
1877 className="shrink-0 rounded-full"1883 labelClassName="sr-only"
1878 aria-label={detail.author?.login ?? "ghost"}1884 />
1879 />
1880 }
1881 >
1882 <PullRequestActorAvatar actor={detail.author} />
1883 </TooltipTrigger>
1884 <TooltipPopup side="top">{detail.author?.login ?? "ghost"}</TooltipPopup>
1885 </Tooltip>
1886 <span className="shrink-0">{formatRelativeTimeLabel(detail.updatedAt)}</span>1885 <span className="shrink-0">{formatRelativeTimeLabel(detail.updatedAt)}</span>
1887 </span>1886 </span>
1888 <span aria-hidden className="h-3 w-px shrink-0 bg-border/70" />1887 <span aria-hidden className="h-3 w-px shrink-0 bg-border/70" />
...@@ -2042,7 +2041,11 @@ export function PullRequestDetailPanel({...@@ -2042,7 +2041,11 @@ export function PullRequestDetailPanel({
2042 )}2041 )}
2043 <div className="mt-2 flex min-w-0 items-center gap-2 text-xs text-muted-foreground">2042 <div className="mt-2 flex min-w-0 items-center gap-2 text-xs text-muted-foreground">
2044 <PullRequestMetaLine className="min-w-0 whitespace-nowrap">2043 <PullRequestMetaLine className="min-w-0 whitespace-nowrap">
2045 <PullRequestActorLabel actor={detail.author} className="font-medium" />2044 <PullRequestActorLabel
2045 actor={detail.author}
2046 profileUrl={authorProfileUrl}
2047 className="font-medium"
2048 />
2046 <span>updated {formatRelativeTimeLabel(detail.updatedAt)}</span>2049 <span>updated {formatRelativeTimeLabel(detail.updatedAt)}</span>
2047 </PullRequestMetaLine>2050 </PullRequestMetaLine>
2048 {checkoutCommand ? (2051 {checkoutCommand ? (
apps/web/src/components/pullRequest/pullRequestPresentation.tsx+21-2
...@@ -358,11 +358,13 @@ export function PullRequestActorLabel({...@@ -358,11 +358,13 @@ export function PullRequestActorLabel({
358 className,358 className,
359 labelClassName,359 labelClassName,
360 tooltip = true,360 tooltip = true,
361 profileUrl,
361}: {362}: {
362 actor: PullRequestActor | null;363 actor: PullRequestActor | null;
363 className?: string;364 className?: string;
364 labelClassName?: string;365 labelClassName?: string;
365 tooltip?: boolean;366 tooltip?: boolean;
367 profileUrl?: string | null;
366}) {368}) {
367 const login = actor?.login ?? "ghost";369 const login = actor?.login ?? "ghost";
368 const label = (370 const label = (
...@@ -377,11 +379,28 @@ export function PullRequestActorLabel({...@@ -377,11 +379,28 @@ export function PullRequestActorLabel({
377 return (379 return (
378 <Tooltip>380 <Tooltip>
379 <TooltipTrigger381 <TooltipTrigger
380 render={<span className={cn("flex min-w-0 items-center gap-1.5", className)} />}382 render={
383 profileUrl ? (
384 <a
385 href={profileUrl}
386 target="_blank"
387 rel="noopener noreferrer"
388 aria-label={`Open ${login}'s profile`}
389 />
390 ) : (
391 <span />
392 )
393 }
394 className={cn(
395 "flex min-w-0 items-center gap-1.5",
396 profileUrl &&
397 "cursor-pointer rounded-sm underline-offset-2 outline-none hover:text-foreground hover:underline focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",
398 className,
399 )}
381 >400 >
382 {label}401 {label}
383 </TooltipTrigger>402 </TooltipTrigger>
384 <TooltipPopup side="top">{login}</TooltipPopup>403 <TooltipPopup side="top">{profileUrl ? `Open ${login}'s profile` : login}</TooltipPopup>
385 </Tooltip>404 </Tooltip>
386 );405 );
387}406}