Add reaction to one-comment PRs
This commit is contained in:
committed by
pullfrog[bot]
parent
c3ac7d9ff0
commit
1edeaa0f4c
@@ -121270,11 +121270,20 @@ query ($owner: String!, $repo: String!, $pullNumber: Int!) {
|
||||
}
|
||||
`;
|
||||
var MAX_BODY_PREVIEW = 80;
|
||||
var MAX_THREAD_DEPTH = 10;
|
||||
function truncateBody(body) {
|
||||
const oneLine = body.replace(/\n/g, " ").trim();
|
||||
if (oneLine.length <= MAX_BODY_PREVIEW) return oneLine;
|
||||
return oneLine.slice(0, MAX_BODY_PREVIEW - 3) + "...";
|
||||
}
|
||||
function getReplyChain(comment, commentMap, depth = 0) {
|
||||
if (depth >= MAX_THREAD_DEPTH) return [];
|
||||
const parentId = comment.replyTo?.databaseId;
|
||||
if (!parentId) return [];
|
||||
const parent = commentMap.get(parentId);
|
||||
if (!parent) return [];
|
||||
return [...getReplyChain(parent, commentMap, depth + 1), parent];
|
||||
}
|
||||
function escapeXml(text) {
|
||||
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
||||
}
|
||||
@@ -121295,13 +121304,6 @@ function GetReviewCommentsTool(ctx) {
|
||||
description: "Get review comments for a pull request review, including thread context. When approved_by is provided, only returns comments that user approved with \u{1F44D}. Returns commentsPath pointing to a file with full comment details in XML format.",
|
||||
parameters: GetReviewComments,
|
||||
execute: execute(async ({ pull_number, review_id, approved_by }) => {
|
||||
const review = await ctx.octokit.rest.pulls.getReview({
|
||||
owner: ctx.repo.owner,
|
||||
repo: ctx.repo.name,
|
||||
pull_number,
|
||||
review_id
|
||||
});
|
||||
const reviewer = review.data.user?.login ?? "unknown";
|
||||
const response = await ctx.octokit.graphql(REVIEW_THREADS_QUERY, {
|
||||
owner: ctx.repo.owner,
|
||||
repo: ctx.repo.name,
|
||||
@@ -121312,12 +121314,19 @@ function GetReviewCommentsTool(ctx) {
|
||||
return {
|
||||
review_id,
|
||||
pull_number,
|
||||
reviewer,
|
||||
reviewer: "unknown",
|
||||
count: 0,
|
||||
commentsPath: null,
|
||||
message: "No review threads found"
|
||||
};
|
||||
}
|
||||
const commentMap = /* @__PURE__ */ new Map();
|
||||
for (const thread of pullRequest.reviewThreads.nodes) {
|
||||
if (!thread?.comments?.nodes) continue;
|
||||
for (const comment of thread.comments.nodes) {
|
||||
if (comment) commentMap.set(comment.databaseId, comment);
|
||||
}
|
||||
}
|
||||
const leafComments = [];
|
||||
for (const thread of pullRequest.reviewThreads.nodes) {
|
||||
if (!thread?.comments?.nodes) continue;
|
||||
@@ -121328,12 +121337,10 @@ function GetReviewCommentsTool(ctx) {
|
||||
for (const comment of threadComments) {
|
||||
if (comment.pullRequestReview?.databaseId !== review_id) continue;
|
||||
if (approved_by && !hasThumbsUpFrom(comment, approved_by)) continue;
|
||||
const threadContext = threadComments.filter(
|
||||
(c) => c.createdAt < comment.createdAt || c.createdAt === comment.createdAt && c.databaseId < comment.databaseId
|
||||
);
|
||||
const replyChain = getReplyChain(comment, commentMap);
|
||||
leafComments.push({
|
||||
comment,
|
||||
thread: threadContext,
|
||||
thread: replyChain,
|
||||
side: thread.diffSide
|
||||
});
|
||||
}
|
||||
@@ -121342,12 +121349,13 @@ function GetReviewCommentsTool(ctx) {
|
||||
return {
|
||||
review_id,
|
||||
pull_number,
|
||||
reviewer,
|
||||
reviewer: "unknown",
|
||||
count: 0,
|
||||
commentsPath: null,
|
||||
message: approved_by ? `No comments with \u{1F44D} from ${approved_by}` : "No comments found for this review"
|
||||
};
|
||||
}
|
||||
const reviewer = leafComments[0].comment.author?.login ?? "unknown";
|
||||
const lines = [];
|
||||
lines.push(
|
||||
`<review_comments count="${leafComments.length}" reviewer="${escapeXml(reviewer)}">`
|
||||
|
||||
+32
-18
@@ -101,6 +101,7 @@ type GraphQLResponse = {
|
||||
};
|
||||
|
||||
const MAX_BODY_PREVIEW = 80;
|
||||
const MAX_THREAD_DEPTH = 10;
|
||||
|
||||
function truncateBody(body: string): string {
|
||||
const oneLine = body.replace(/\n/g, " ").trim();
|
||||
@@ -108,6 +109,20 @@ function truncateBody(body: string): string {
|
||||
return oneLine.slice(0, MAX_BODY_PREVIEW - 3) + "...";
|
||||
}
|
||||
|
||||
// walk up the replyTo chain to get actual conversation (oldest first)
|
||||
function getReplyChain(
|
||||
comment: GraphQLReviewComment,
|
||||
commentMap: Map<number, GraphQLReviewComment>,
|
||||
depth = 0
|
||||
): GraphQLReviewComment[] {
|
||||
if (depth >= MAX_THREAD_DEPTH) return [];
|
||||
const parentId = comment.replyTo?.databaseId;
|
||||
if (!parentId) return [];
|
||||
const parent = commentMap.get(parentId);
|
||||
if (!parent) return [];
|
||||
return [...getReplyChain(parent, commentMap, depth + 1), parent];
|
||||
}
|
||||
|
||||
function escapeXml(text: string): string {
|
||||
return text
|
||||
.replace(/&/g, "&")
|
||||
@@ -140,15 +155,6 @@ export function GetReviewCommentsTool(ctx: ToolContext) {
|
||||
"Returns commentsPath pointing to a file with full comment details in XML format.",
|
||||
parameters: GetReviewComments,
|
||||
execute: execute(async ({ pull_number, review_id, approved_by }) => {
|
||||
// fetch the review to get reviewer username
|
||||
const review = await ctx.octokit.rest.pulls.getReview({
|
||||
owner: ctx.repo.owner,
|
||||
repo: ctx.repo.name,
|
||||
pull_number,
|
||||
review_id,
|
||||
});
|
||||
const reviewer = review.data.user?.login ?? "unknown";
|
||||
|
||||
// fetch all review threads using graphql (single API call)
|
||||
const response = await ctx.octokit.graphql<GraphQLResponse>(REVIEW_THREADS_QUERY, {
|
||||
owner: ctx.repo.owner,
|
||||
@@ -161,13 +167,22 @@ export function GetReviewCommentsTool(ctx: ToolContext) {
|
||||
return {
|
||||
review_id,
|
||||
pull_number,
|
||||
reviewer,
|
||||
reviewer: "unknown",
|
||||
count: 0,
|
||||
commentsPath: null,
|
||||
message: "No review threads found",
|
||||
};
|
||||
}
|
||||
|
||||
// build a map of all comments for O(1) lookup when walking replyTo chains
|
||||
const commentMap = new Map<number, GraphQLReviewComment>();
|
||||
for (const thread of pullRequest.reviewThreads.nodes) {
|
||||
if (!thread?.comments?.nodes) continue;
|
||||
for (const comment of thread.comments.nodes) {
|
||||
if (comment) commentMap.set(comment.databaseId, comment);
|
||||
}
|
||||
}
|
||||
|
||||
// collect leaf comments (from target review) with their thread context
|
||||
type LeafComment = {
|
||||
comment: GraphQLReviewComment;
|
||||
@@ -191,16 +206,12 @@ export function GetReviewCommentsTool(ctx: ToolContext) {
|
||||
// filter by approved_by if specified
|
||||
if (approved_by && !hasThumbsUpFrom(comment, approved_by)) continue;
|
||||
|
||||
// get thread context (all comments before this one in the thread)
|
||||
const threadContext = threadComments.filter(
|
||||
(c) =>
|
||||
c.createdAt < comment.createdAt ||
|
||||
(c.createdAt === comment.createdAt && c.databaseId < comment.databaseId)
|
||||
);
|
||||
// get thread context by walking up the replyTo chain (not just chronological)
|
||||
const replyChain = getReplyChain(comment, commentMap);
|
||||
|
||||
leafComments.push({
|
||||
comment,
|
||||
thread: threadContext,
|
||||
thread: replyChain,
|
||||
side: thread.diffSide,
|
||||
});
|
||||
}
|
||||
@@ -210,7 +221,7 @@ export function GetReviewCommentsTool(ctx: ToolContext) {
|
||||
return {
|
||||
review_id,
|
||||
pull_number,
|
||||
reviewer,
|
||||
reviewer: "unknown",
|
||||
count: 0,
|
||||
commentsPath: null,
|
||||
message: approved_by
|
||||
@@ -219,6 +230,9 @@ export function GetReviewCommentsTool(ctx: ToolContext) {
|
||||
};
|
||||
}
|
||||
|
||||
// derive reviewer from first comment (all comments in a review are from the same user)
|
||||
const reviewer = leafComments[0].comment.author?.login ?? "unknown";
|
||||
|
||||
// build XML output
|
||||
const lines: string[] = [];
|
||||
lines.push(
|
||||
|
||||
Reference in New Issue
Block a user