MCPcopy Create free account
hub / github.com/backnotprop/plannotator / fetchGhThreadsAndBots

Function fetchGhThreadsAndBots

packages/shared/pr-github.ts:423–482  ·  view source on GitHub ↗

* One GraphQL round-trip returning the PR's inline review threads AND the set of * author logins that are bots (`__typename === "Bot"`) across comments, reviews, * and thread comments. GraphQL is the only place GitHub exposes author type — * `gh pr view --json` gives the login only. The caller us

(
  runtime: PRRuntime,
  ref: GhPRRef,
)

Source from the content-addressed store, hash-verified

421 * the top-level comments/reviews; thread comments are tagged inline here.
422 */
423async function fetchGhThreadsAndBots(
424 runtime: PRRuntime,
425 ref: GhPRRef,
426): Promise<{ threads: PRReviewThread[]; botLogins: Set<string>; avatarByLogin: Map<string, string> }> {
427 const result = await runtime.runCommand("gh", hostnameArgs(ref.host, [
428 "api", "graphql",
429 "-f", `query=${PR_CONTEXT_GRAPHQL_QUERY}`,
430 "-f", `owner=${ref.owner}`,
431 "-f", `repo=${ref.repo}`,
432 "-F", `number=${ref.number}`,
433 ]));
434
435 const empty = { threads: [] as PRReviewThread[], botLogins: new Set<string>(), avatarByLogin: new Map<string, string>() };
436 if (result.exitCode !== 0) return empty;
437
438 const data = JSON.parse(result.stdout);
439 const pr = data?.data?.repository?.pullRequest;
440 if (!pr) return empty;
441
442 const botLogins = new Set<string>();
443 const avatarByLogin = new Map<string, string>();
444 const collect = (author: any) => {
445 if (!author?.login) return;
446 const login = String(author.login);
447 if (author.__typename === 'Bot') botLogins.add(login);
448 if (author.avatarUrl && !avatarByLogin.has(login)) avatarByLogin.set(login, String(author.avatarUrl));
449 };
450 for (const n of (pr.comments?.nodes ?? [])) collect(n?.author);
451 for (const n of (pr.reviews?.nodes ?? [])) collect(n?.author);
452
453 const threadNodes = pr.reviewThreads?.nodes;
454 const threads: PRReviewThread[] = Array.isArray(threadNodes)
455 ? threadNodes.map((t: any): PRReviewThread => ({
456 id: String(t.id ?? ''),
457 isResolved: t.isResolved === true,
458 isOutdated: t.isOutdated === true,
459 path: String(t.path ?? ''),
460 line: typeof t.line === 'number' ? t.line : null,
461 startLine: typeof t.startLine === 'number' ? t.startLine : null,
462 diffSide: t.diffSide === 'LEFT' || t.diffSide === 'RIGHT' ? t.diffSide : null,
463 comments: Array.isArray(t.comments?.nodes)
464 ? t.comments.nodes.map((c: any): PRThreadComment => {
465 collect(c?.author);
466 return {
467 id: String(c.id ?? ''),
468 author: c.author?.login ? String(c.author.login) : '',
469 ...(c.author?.__typename === 'Bot' ? { isBot: true } : {}),
470 ...(c.author?.avatarUrl ? { avatarUrl: String(c.author.avatarUrl) } : {}),
471 body: String(c.body ?? ''),
472 createdAt: String(c.createdAt ?? ''),
473 url: String(c.url ?? ''),
474 ...(c.diffHunk ? { diffHunk: String(c.diffHunk) } : {}),
475 };
476 })
477 : [],
478 }))
479 : [];
480

Callers 1

fetchGhPRContextFunction · 0.85

Calls 3

hostnameArgsFunction · 0.85
collectFunction · 0.85
parseMethod · 0.80

Tested by

no test coverage detected