Skip to content

Commit d287421

Browse files
committed
feat: Gate Inbox behind GitHub integration and improve GH connection UX
- Show Welcome pane instead of "warming up" when no GitHub integration exists - Add GitHub connection card to signal sources config modal - Disable signal source toggles until GitHub is connected - Fix redirect URL after GitHub OAuth (was 404ing with double-concatenated URL) - Make onboarding Connect GitHub button retryable during auth flow - Show GitHub profile pictures for suggested reviewers - Add github_login to available_reviewers API response
1 parent 9418447 commit d287421

12 files changed

Lines changed: 254 additions & 47 deletions

File tree

apps/code/src/renderer/api/posthogClient.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@ function normalizeAvailableSuggestedReviewer(
305305
uuid: normalizedUuid,
306306
name: optionalString(value.name) ?? "",
307307
email: optionalString(value.email) ?? "",
308+
github_login: optionalString(value.github_login) ?? "",
308309
};
309310
}
310311

apps/code/src/renderer/features/inbox/components/InboxSignalsTab.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
filterReportsBySearch,
2323
} from "@features/inbox/utils/filterReports";
2424
import { INBOX_REFETCH_INTERVAL_MS } from "@features/inbox/utils/inboxConstants";
25+
import { useRepositoryIntegration } from "@hooks/useIntegrations";
2526
import { Box, Flex, ScrollArea } from "@radix-ui/themes";
2627
import type { SignalReportsQueryParams } from "@shared/types";
2728
import { useNavigationStore } from "@stores/navigationStore";
@@ -48,6 +49,9 @@ export function InboxSignalsTab() {
4849
(s) => s.suggestedReviewerFilter,
4950
);
5051

52+
// ── GitHub integration ───────────────────────────────────────────────
53+
const { hasGithubIntegration } = useRepositoryIntegration();
54+
5155
// ── Signal source configs ───────────────────────────────────────────────
5256
const { data: signalSourceConfigs } = useSignalSourceConfigs();
5357
const hasSignalSources = signalSourceConfigs?.some((c) => c.enabled) ?? false;
@@ -576,7 +580,7 @@ export function InboxSignalsTab() {
576580
}}
577581
>
578582
<Box style={{ pointerEvents: "auto" }}>
579-
{!hasSignalSources ? (
583+
{!hasSignalSources || !hasGithubIntegration ? (
580584
<WelcomePane onEnableInbox={() => setSourcesDialogOpen(true)} />
581585
) : (
582586
<WarmingUpPane
@@ -594,6 +598,7 @@ export function InboxSignalsTab() {
594598
open={sourcesDialogOpen}
595599
onOpenChange={setSourcesDialogOpen}
596600
hasSignalSources={hasSignalSources}
601+
hasGithubIntegration={hasGithubIntegration}
597602
/>
598603
</>
599604
);

apps/code/src/renderer/features/inbox/components/InboxSourcesDialog.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ interface InboxSourcesDialogProps {
66
open: boolean;
77
onOpenChange: (open: boolean) => void;
88
hasSignalSources: boolean;
9+
hasGithubIntegration: boolean;
910
}
1011

1112
export function InboxSourcesDialog({
1213
open,
1314
onOpenChange,
1415
hasSignalSources,
16+
hasGithubIntegration,
1517
}: InboxSourcesDialogProps) {
1618
return (
1719
<Dialog.Root open={open} onOpenChange={onOpenChange}>
@@ -32,12 +34,18 @@ export function InboxSourcesDialog({
3234
</Flex>
3335
<SignalSourcesSettings />
3436
<Flex justify="end" mt="4">
35-
{hasSignalSources ? (
37+
{hasSignalSources && hasGithubIntegration ? (
3638
<Dialog.Close>
3739
<Button size="2">Back to Inbox</Button>
3840
</Dialog.Close>
3941
) : (
40-
<Tooltip content="You haven't enabled any signal source yet!">
42+
<Tooltip
43+
content={
44+
!hasGithubIntegration
45+
? "Connect GitHub to get started!"
46+
: "You haven't enabled any signal source yet!"
47+
}
48+
>
4149
<Button size="2" disabled>
4250
Back to Inbox
4351
</Button>

apps/code/src/renderer/features/inbox/components/list/GitHubConnectionBanner.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,7 @@ export function GitHubConnectionBanner() {
6969
PostHog Code suggests report ownership using cutting-edge{" "}
7070
<code>git blame</code> technology.
7171
<br />
72-
For this, connect your GitHub profile (different from connecting
73-
repositories).
72+
For relevant reports, connect your GitHub profile.
7473
</div>
7574
</>
7675
}

apps/code/src/renderer/features/inbox/components/list/SuggestedReviewerFilterMenu.tsx

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -146,19 +146,32 @@ export function SuggestedReviewerFilterMenu() {
146146
className="flex w-full items-start justify-between rounded-sm px-1 py-1 text-left text-[13px] text-gray-12 transition-colors hover:bg-gray-3 focus-visible:bg-gray-3 focus-visible:outline-none"
147147
onClick={() => toggleSuggestedReviewer(reviewer.uuid)}
148148
>
149-
<Flex direction="column" gap="0" className="min-w-0">
150-
<Text size="1" className="truncate text-[12px]">
151-
{displayName}
152-
</Text>
153-
{reviewer.email ? (
154-
<Text
155-
size="1"
156-
color="gray"
157-
className="truncate text-[11px]"
158-
>
159-
{reviewer.email}
160-
</Text>
149+
<Flex align="center" gap="2" className="min-w-0">
150+
{reviewer.github_login ? (
151+
<img
152+
src={`https://github.com/${reviewer.github_login}.png?size=32`}
153+
alt=""
154+
className="github-avatar shrink-0 rounded-full"
155+
style={{ width: 20, height: 20 }}
156+
onLoad={(e) =>
157+
e.currentTarget.classList.add("loaded")
158+
}
159+
/>
161160
) : null}
161+
<Flex direction="column" gap="0" className="min-w-0">
162+
<Text size="1" className="truncate text-[12px]">
163+
{displayName}
164+
</Text>
165+
{reviewer.email ? (
166+
<Text
167+
size="1"
168+
color="gray"
169+
className="truncate text-[11px]"
170+
>
171+
{reviewer.email}
172+
</Text>
173+
) : null}
174+
</Flex>
162175
</Flex>
163176
<span
164177
className="flex h-4 w-4 shrink-0 items-center justify-center text-gray-12"

apps/code/src/renderer/features/inbox/utils/suggestedReviewerFilters.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ function makeReviewer(
1212
uuid: "reviewer-1",
1313
name: "Alice Jones",
1414
email: "alice@example.com",
15+
github_login: "alicejones",
1516
...overrides,
1617
};
1718
}

apps/code/src/renderer/features/inbox/utils/suggestedReviewerFilters.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export interface SuggestedReviewerFilterOption {
1111
uuid: string;
1212
name: string;
1313
email: string;
14+
github_login: string;
1415
isMe: boolean;
1516
showSeparatorBelow: boolean;
1617
}
@@ -71,6 +72,7 @@ export function buildSuggestedReviewerFilterOptions(
7172
uuid,
7273
name: normalizeString(reviewer.name),
7374
email: normalizeString(reviewer.email),
75+
github_login: normalizeString(reviewer.github_login),
7476
isMe: false,
7577
showSeparatorBelow: false,
7678
});
@@ -83,6 +85,7 @@ export function buildSuggestedReviewerFilterOptions(
8385
uuid: currentUserUuid,
8486
name: buildCurrentUserName(currentUser) || existing?.name || "",
8587
email: normalizeString(currentUser?.email) || existing?.email || "",
88+
github_login: existing?.github_login || "",
8689
isMe: true,
8790
showSeparatorBelow: true,
8891
});

apps/code/src/renderer/features/onboarding/components/GitIntegrationStep.tsx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ export function GitIntegrationStep({
9393

9494
const handleConnectGitHub = async () => {
9595
if (!cloudRegion || !selectedProjectId || !client) return;
96+
stopPolling();
9697
setConnectingGithub(true);
9798
try {
9899
await trpcClient.githubIntegration.startFlow.mutate({
@@ -365,12 +366,8 @@ export function GitIntegrationStep({
365366
alignItems: "center",
366367
}}
367368
>
368-
<Button
369-
size="2"
370-
onClick={handleConnectGitHub}
371-
loading={isConnecting}
372-
>
373-
Connect GitHub
369+
<Button size="2" onClick={handleConnectGitHub}>
370+
{isConnecting ? "Retry connection" : "Connect GitHub"}
374371
<ArrowSquareOut size={16} />
375372
</Button>
376373
<Text
@@ -380,7 +377,9 @@ export function GitIntegrationStep({
380377
opacity: 0.5,
381378
}}
382379
>
383-
Opens GitHub to authorize the PostHog app
380+
{isConnecting
381+
? "Waiting for authorization\u2026 Click again if the browser tab was closed."
382+
: "Opens GitHub to authorize the PostHog app"}
384383
</Text>
385384
<Button
386385
size="1"
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { useAuthStateValue } from "@features/auth/hooks/authQueries";
2+
import { useRepositoryIntegration } from "@hooks/useIntegrations";
3+
import {
4+
ArrowSquareOutIcon,
5+
CheckCircleIcon,
6+
GitBranchIcon,
7+
InfoIcon,
8+
} from "@phosphor-icons/react";
9+
import { Box, Button, Flex, Spinner, Text, Tooltip } from "@radix-ui/themes";
10+
import { trpcClient } from "@renderer/trpc/client";
11+
import { useQueryClient } from "@tanstack/react-query";
12+
import { useCallback, useEffect, useRef, useState } from "react";
13+
14+
const POLL_INTERVAL_MS = 3_000;
15+
const POLL_TIMEOUT_MS = 300_000; // 5 minutes
16+
17+
export function GitHubIntegrationSection({
18+
hasGithubIntegration,
19+
}: {
20+
hasGithubIntegration: boolean;
21+
}) {
22+
const { repositories, isLoadingRepos } = useRepositoryIntegration();
23+
const projectId = useAuthStateValue((state) => state.projectId);
24+
const cloudRegion = useAuthStateValue((state) => state.cloudRegion);
25+
const queryClient = useQueryClient();
26+
const [connecting, setConnecting] = useState(false);
27+
const pollTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
28+
const pollTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
29+
30+
const stopPolling = useCallback(() => {
31+
if (pollTimerRef.current) {
32+
clearInterval(pollTimerRef.current);
33+
pollTimerRef.current = null;
34+
}
35+
if (pollTimeoutRef.current) {
36+
clearTimeout(pollTimeoutRef.current);
37+
pollTimeoutRef.current = null;
38+
}
39+
}, []);
40+
41+
useEffect(() => stopPolling, [stopPolling]);
42+
43+
useEffect(() => {
44+
if (hasGithubIntegration && connecting) {
45+
stopPolling();
46+
setConnecting(false);
47+
}
48+
}, [hasGithubIntegration, connecting, stopPolling]);
49+
50+
const handleConnect = useCallback(async () => {
51+
if (!cloudRegion || !projectId) return;
52+
setConnecting(true);
53+
try {
54+
await trpcClient.githubIntegration.startFlow.mutate({
55+
region: cloudRegion,
56+
projectId,
57+
});
58+
59+
pollTimerRef.current = setInterval(() => {
60+
void queryClient.invalidateQueries({
61+
queryKey: ["integrations"],
62+
});
63+
}, POLL_INTERVAL_MS);
64+
65+
pollTimeoutRef.current = setTimeout(() => {
66+
stopPolling();
67+
setConnecting(false);
68+
}, POLL_TIMEOUT_MS);
69+
} catch {
70+
setConnecting(false);
71+
}
72+
}, [cloudRegion, projectId, queryClient, stopPolling]);
73+
74+
return (
75+
<Flex
76+
align="center"
77+
justify="between"
78+
gap="4"
79+
pb="3"
80+
style={{ borderBottom: "1px dashed var(--gray-5)" }}
81+
>
82+
<Flex align="center" gap="3">
83+
<Box style={{ color: "var(--gray-11)", flexShrink: 0 }}>
84+
<GitBranchIcon size={20} />
85+
</Box>
86+
<Flex direction="column">
87+
<Text size="2" weight="medium" style={{ color: "var(--gray-12)" }}>
88+
Code access
89+
</Text>
90+
{hasGithubIntegration &&
91+
!isLoadingRepos &&
92+
repositories.length > 0 ? (
93+
<Tooltip
94+
content={
95+
<Flex direction="column" gap="1">
96+
{repositories.map((repo) => (
97+
<Text key={repo} size="1">
98+
{repo}
99+
</Text>
100+
))}
101+
</Flex>
102+
}
103+
side="bottom"
104+
>
105+
<Flex align="center" gap="1" style={{ cursor: "help" }}>
106+
<Text size="1" style={{ color: "var(--gray-11)" }}>
107+
Connected and active ({repositories.length}{" "}
108+
{repositories.length === 1 ? "repo" : "repos"})
109+
</Text>
110+
<InfoIcon
111+
size={13}
112+
style={{ color: "var(--gray-9)", flexShrink: 0 }}
113+
/>
114+
</Flex>
115+
</Tooltip>
116+
) : (
117+
<Text size="1" style={{ color: "var(--gray-11)" }}>
118+
{hasGithubIntegration
119+
? "Connected and active"
120+
: "Required for the Inbox pipeline to work"}
121+
</Text>
122+
)}
123+
</Flex>
124+
</Flex>
125+
{connecting ? (
126+
<Spinner size="2" />
127+
) : hasGithubIntegration ? (
128+
<Flex align="center" gap="2">
129+
<CheckCircleIcon
130+
size={16}
131+
weight="fill"
132+
style={{ color: "var(--green-9)" }}
133+
/>
134+
<Button size="1" variant="soft" onClick={() => void handleConnect()}>
135+
Update in GitHub
136+
<ArrowSquareOutIcon size={12} />
137+
</Button>
138+
</Flex>
139+
) : (
140+
<Button size="1" onClick={() => void handleConnect()}>
141+
Connect GitHub
142+
<ArrowSquareOutIcon size={12} />
143+
</Button>
144+
)}
145+
</Flex>
146+
);
147+
}

0 commit comments

Comments
 (0)