feat: add pull request link copy actions (#3179) (#3182)

This commit is contained in:
Carlos Florêncio
2026-08-31 11:25:58 +00:00
committed by GitHub
parent 03ad1a622d
commit 6074f39a32
29 changed files with 1108 additions and 41 deletions
@@ -0,0 +1,100 @@
import { describe, expect, it } from "vitest";
import type { PRComment, PRFeedback } from "@/lib/types/github";
import { mapGitHubComments } from "./pr-detail-panel";
const REVIEW_TIMESTAMP = "2026-01-05T12:00:00Z";
const ISSUE_TIMESTAMP = "2026-01-05T09:00:00Z";
const REVIEW_COMMENT_URL = "https://github.com/acme/widget/pull/42#discussion_r20";
const CONVERSATION_COMMENT_URL = "https://github.com/acme/widget/pull/42#issuecomment-10";
describe("mapGitHubComments", () => {
it("preserves the exact GitHub permalink for every comment type", () => {
const comments: PRComment[] = [
{
id: 20,
html_url: REVIEW_COMMENT_URL,
author: "alice",
author_avatar: "",
author_is_bot: false,
body: "Inline note",
path: "main.go",
line: 7,
side: "RIGHT",
comment_type: "review",
created_at: REVIEW_TIMESTAMP,
updated_at: REVIEW_TIMESTAMP,
in_reply_to: null,
},
{
id: 10,
html_url: CONVERSATION_COMMENT_URL,
author: "dependabot",
author_avatar: "",
author_is_bot: true,
body: "Conversation note",
path: "",
line: 0,
side: "",
comment_type: "issue",
created_at: ISSUE_TIMESTAMP,
updated_at: ISSUE_TIMESTAMP,
in_reply_to: null,
},
];
expect(mapGitHubComments({ comments } as unknown as PRFeedback)).toMatchObject([
{
id: "20",
url: REVIEW_COMMENT_URL,
},
{
id: "10",
url: CONVERSATION_COMMENT_URL,
},
]);
});
it("does not invent a URL when GitHub omits one", () => {
const comment = {
id: 20,
html_url: "",
author: "alice",
author_avatar: "",
author_is_bot: false,
body: "Inline note",
path: "main.go",
line: 7,
side: "RIGHT",
comment_type: "review",
created_at: REVIEW_TIMESTAMP,
updated_at: REVIEW_TIMESTAMP,
in_reply_to: null,
} satisfies PRComment;
expect(
mapGitHubComments({ comments: [comment] } as unknown as PRFeedback)[0],
).not.toHaveProperty("url");
});
it("normalizes whitespace around a provider URL", () => {
const comment = {
id: 20,
html_url: ` ${REVIEW_COMMENT_URL} `,
author: "alice",
author_avatar: "",
author_is_bot: false,
body: "Inline note",
path: "main.go",
line: 7,
side: "RIGHT",
comment_type: "review",
created_at: REVIEW_TIMESTAMP,
updated_at: REVIEW_TIMESTAMP,
in_reply_to: null,
} satisfies PRComment;
expect(mapGitHubComments({ comments: [comment] } as unknown as PRFeedback)[0]).toMatchObject({
url: REVIEW_COMMENT_URL,
});
});
});
+15 -11
View File
@@ -254,7 +254,7 @@ function mapGitHubIdentity(taskPR: TaskPR, feedback: PRFeedback | null) {
const live = feedback?.pr;
return {
title: live?.title ?? taskPR.pr_title,
url: live?.html_url || live?.url || taskPR.pr_url,
url: live?.html_url || taskPR.pr_url,
state: live?.state ?? taskPR.state,
draft: live?.draft,
author: githubPerson(live?.author_login ?? taskPR.author_login),
@@ -323,16 +323,20 @@ function mapGitHubChecks(feedback: PRFeedback | null) {
}));
}
function mapGitHubComments(feedback: PRFeedback | null) {
return (feedback?.comments ?? []).map((comment) => ({
id: String(comment.id),
parentId: comment.in_reply_to ? String(comment.in_reply_to) : undefined,
author: githubPerson(comment.author, comment.author_avatar, comment.author_is_bot),
body: comment.body,
createdAt: comment.created_at,
path: comment.path || undefined,
line: comment.line || undefined,
}));
export function mapGitHubComments(feedback: PRFeedback | null) {
return (feedback?.comments ?? []).map((comment) => {
const url = comment.html_url?.trim();
return {
id: String(comment.id),
parentId: comment.in_reply_to ? String(comment.in_reply_to) : undefined,
author: githubPerson(comment.author, comment.author_avatar, comment.author_is_bot),
body: comment.body,
createdAt: comment.created_at,
...(url ? { url } : {}),
path: comment.path || undefined,
line: comment.line || undefined,
};
});
}
function mapGitHubDetail(