Skip to content

Commit 6888cbd

Browse files
committed
feat(inbox): show live PR status on pull request list cards
Extend the batched diff-stats GraphQL request to also fetch each PR's state/isDraft, so list cards render live status (open/draft/merged/closed) with no extra API calls. ReportImplementationPrLink now reads status from the batch context when present and only falls back to the per-PR usePrDetails query on the standalone detail view.
1 parent a3ee2f8 commit 6888cbd

5 files changed

Lines changed: 59 additions & 8 deletions

File tree

packages/core/src/git/router-schemas.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,10 +352,18 @@ export const getPrChangedFilesInput = z.object({
352352
export const getPrChangedFilesOutput = z.array(changedFileSchema);
353353

354354
// getPrDiffStatsBatch schemas
355+
//
356+
// Beyond the diff numbers, the batch also carries the PR's live status
357+
// (`state`/`merged`/`draft`) so list cards can render it from the single
358+
// batched GraphQL request instead of firing one REST call per visible PR.
355359
export const prDiffStatsSchema = z.object({
356360
additions: z.number(),
357361
deletions: z.number(),
358362
changedFiles: z.number(),
363+
/** Lowercased GitHub PR state: "open" | "closed" | "merged". */
364+
state: z.string(),
365+
merged: z.boolean(),
366+
draft: z.boolean(),
359367
});
360368
export type PrDiffStats = z.infer<typeof prDiffStatsSchema>;
361369

packages/ui/src/features/inbox/components/PullRequestCard.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { InboxCardTitle } from "@posthog/ui/features/inbox/components/InboxCardT
1414
import { PrDiffStats } from "@posthog/ui/features/inbox/components/PrDiffStats";
1515
import { PriorityMonogram } from "@posthog/ui/features/inbox/components/PriorityMonogram";
1616
import { SuggestedReviewerAvatarStack } from "@posthog/ui/features/inbox/components/SuggestedReviewerAvatarStack";
17+
import { ReportImplementationPrLink } from "@posthog/ui/features/inbox/components/utils/ReportImplementationPrLink";
1718
import { useInboxReportDetailPrefetch } from "@posthog/ui/features/inbox/hooks/useInboxReportDetailPrefetch";
1819
import { useInboxReportArtefacts } from "@posthog/ui/features/inbox/hooks/useInboxReports";
1920
import { Button as UiButton } from "@posthog/ui/primitives/Button";
@@ -121,10 +122,16 @@ export function PullRequestCard({
121122
>
122123
<Flex align="center" gap="2" className="shrink-0">
123124
{report.implementation_pr_url && (
124-
<PrDiffStats
125-
prUrl={report.implementation_pr_url}
126-
hideWhileLoading
127-
/>
125+
<>
126+
<ReportImplementationPrLink
127+
prUrl={report.implementation_pr_url}
128+
size="sm"
129+
/>
130+
<PrDiffStats
131+
prUrl={report.implementation_pr_url}
132+
hideWhileLoading
133+
/>
134+
</>
128135
)}
129136
<SuggestedReviewerAvatarStack
130137
reportId={report.id}

packages/ui/src/features/inbox/components/utils/ReportImplementationPrLink.tsx

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { GitMergeIcon, GitPullRequestIcon } from "@phosphor-icons/react";
22
import { cn } from "@posthog/quill";
33
import { usePrDetails } from "@posthog/ui/features/git-interaction/usePrDetails";
4+
import { usePrDiffStatsFromBatch } from "@posthog/ui/features/inbox/context/PrDiffStatsBatchContext";
45
import { Tooltip } from "@radix-ui/themes";
56

67
export type ImplementationPrLinkSize = "sm" | "md";
@@ -45,9 +46,29 @@ export function ReportImplementationPrLink({
4546
size = "sm",
4647
onLinkClick,
4748
}: ReportImplementationPrLinkProps) {
48-
const {
49-
meta: { state, merged, draft, isLoading },
50-
} = usePrDetails(prUrl);
49+
// On list surfaces the surrounding `PrDiffStatsBatchContext` already carries
50+
// this PR's status from the single batched request, so read it from there and
51+
// skip the per-PR query. Fall back to `usePrDetails` only on the standalone
52+
// detail view, where no batch provider is mounted.
53+
const batchEntry = usePrDiffStatsFromBatch(prUrl);
54+
const fallback = usePrDetails(batchEntry.hasBatch ? null : prUrl);
55+
56+
const state = batchEntry.hasBatch
57+
? (batchEntry.stats?.state ?? null)
58+
: fallback.meta.state;
59+
const merged = batchEntry.hasBatch
60+
? (batchEntry.stats?.merged ?? false)
61+
: fallback.meta.merged;
62+
const draft = batchEntry.hasBatch
63+
? (batchEntry.stats?.draft ?? false)
64+
: fallback.meta.draft;
65+
const isLoading = batchEntry.hasBatch
66+
? batchEntry.isLoading && !batchEntry.stats
67+
: fallback.meta.isLoading;
68+
69+
// If neither source could resolve a status (PR 404s, gh failed, or the batch
70+
// had no entry for this PR), don't render a misleading "open" badge.
71+
if (!isLoading && state === null) return null;
5172

5273
const isSm = size === "sm";
5374

packages/workspace-server/src/services/git/schemas.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,10 +308,18 @@ export type PrDetailsByUrlOutput = z.infer<typeof getPrDetailsByUrlOutput>;
308308

309309
export const getPrChangedFilesInput = z.object({ prUrl: z.string() });
310310

311+
// Also carries the PR's live status (`state`/`merged`/`draft`) so list cards
312+
// can render it from the single batched GraphQL request instead of firing one
313+
// REST call per visible PR. Mirrors `prDiffStatsSchema` in
314+
// `@posthog/core/git/router-schemas`.
311315
export const prDiffStatsSchema = z.object({
312316
additions: z.number(),
313317
deletions: z.number(),
314318
changedFiles: z.number(),
319+
/** Lowercased GitHub PR state: "open" | "closed" | "merged". */
320+
state: z.string(),
321+
merged: z.boolean(),
322+
draft: z.boolean(),
315323
});
316324
export type PrDiffStats = z.infer<typeof prDiffStatsSchema>;
317325

packages/workspace-server/src/services/git/service.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1054,7 +1054,7 @@ export class GitService extends TypedEventEmitter<GitCloneEvents> {
10541054
): Promise<Record<string, PrDiffStats>> {
10551055
const aliasFragments = chunk
10561056
.map(([, { parsed }], index) => {
1057-
return `pr${index}: repository(owner: "${escapeGraphqlString(parsed.owner)}", name: "${escapeGraphqlString(parsed.repo)}") { pullRequest(number: ${parsed.number}) { additions deletions changedFiles } }`;
1057+
return `pr${index}: repository(owner: "${escapeGraphqlString(parsed.owner)}", name: "${escapeGraphqlString(parsed.repo)}") { pullRequest(number: ${parsed.number}) { additions deletions changedFiles state isDraft } }`;
10581058
})
10591059
.join("\n");
10601060
const query = `query InboxPrDiffStatsBatch {\n${aliasFragments}\n}`;
@@ -1075,6 +1075,8 @@ export class GitService extends TypedEventEmitter<GitCloneEvents> {
10751075
additions: number;
10761076
deletions: number;
10771077
changedFiles: number;
1078+
state: string;
1079+
isDraft: boolean;
10781080
} | null;
10791081
} | null
10801082
>;
@@ -1085,10 +1087,15 @@ export class GitService extends TypedEventEmitter<GitCloneEvents> {
10851087
const [, { urls }] = chunk[i];
10861088
const node = parsed.data?.[`pr${i}`]?.pullRequest;
10871089
if (!node) continue;
1090+
// GraphQL `PullRequestState` is OPEN | CLOSED | MERGED; normalise to the
1091+
// lowercase state + merged boolean shape the badge expects.
10881092
const stats: PrDiffStats = {
10891093
additions: node.additions,
10901094
deletions: node.deletions,
10911095
changedFiles: node.changedFiles,
1096+
state: node.state.toLowerCase(),
1097+
merged: node.state === "MERGED",
1098+
draft: node.isDraft,
10921099
};
10931100
for (const url of urls) {
10941101
out[url] = stats;

0 commit comments

Comments
 (0)