Skip to content

Commit 154149b

Browse files
committed
feat(inbox): error tracking source, LLM evaluations, source config improvements
- Add error tracking as signal source with 3 sub-types toggled together - Replace non-functional LLM analytics toggle with evaluations list (polls 5s) - Evaluations link to Cloud for management (region-aware) - Fix re-render cascade: direct API calls, per-source optimistic state - Per-source onToggle API with memoized cards - Rounded toggle cards, GitHub OAuth flow - Add suggested reviewer + artefact types
1 parent 3325bf5 commit 154149b

11 files changed

Lines changed: 665 additions & 238 deletions

File tree

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

Lines changed: 64 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type {
66
SignalReportSignalsResponse,
77
SignalReportsQueryParams,
88
SignalReportsResponse,
9+
SuggestedReviewersArtefact,
910
Task,
1011
TaskRun,
1112
} from "@shared/types";
@@ -20,15 +21,25 @@ export type McpRecommendedServer = Schemas.RecommendedServer;
2021

2122
export type McpServerInstallation = Schemas.MCPServerInstallation;
2223

24+
export type Evaluation = Schemas.Evaluation;
25+
2326
export interface SignalSourceConfig {
2427
id: string;
2528
source_product:
2629
| "session_replay"
2730
| "llm_analytics"
2831
| "github"
2932
| "linear"
30-
| "zendesk";
31-
source_type: "session_analysis_cluster" | "evaluation" | "issue" | "ticket";
33+
| "zendesk"
34+
| "error_tracking";
35+
source_type:
36+
| "session_analysis_cluster"
37+
| "evaluation"
38+
| "issue"
39+
| "ticket"
40+
| "issue_created"
41+
| "issue_reopened"
42+
| "issue_spiking";
3243
enabled: boolean;
3344
config: Record<string, unknown>;
3445
created_at: string;
@@ -60,9 +71,9 @@ function optionalString(value: unknown): string | null {
6071
return typeof value === "string" ? value : null;
6172
}
6273

63-
function normalizeSignalReportArtefact(
64-
value: unknown,
65-
): SignalReportArtefact | null {
74+
type AnyArtefact = SignalReportArtefact | SuggestedReviewersArtefact;
75+
76+
function normalizeSignalReportArtefact(value: unknown): AnyArtefact | null {
6677
if (!isObjectRecord(value)) {
6778
return null;
6879
}
@@ -72,6 +83,21 @@ function normalizeSignalReportArtefact(
7283
return null;
7384
}
7485

86+
const type = optionalString(value.type) ?? "unknown";
87+
const created_at =
88+
optionalString(value.created_at) ?? new Date(0).toISOString();
89+
90+
// suggested_reviewers: content is an array of reviewer objects
91+
if (type === "suggested_reviewers" && Array.isArray(value.content)) {
92+
return {
93+
id,
94+
type: "suggested_reviewers" as const,
95+
created_at,
96+
content: value.content as SuggestedReviewersArtefact["content"],
97+
};
98+
}
99+
100+
// video_segment and other artefacts with object content
75101
const contentValue = isObjectRecord(value.content) ? value.content : null;
76102
if (!contentValue) {
77103
return null;
@@ -87,8 +113,8 @@ function normalizeSignalReportArtefact(
87113

88114
return {
89115
id,
90-
type: optionalString(value.type) ?? "unknown",
91-
created_at: optionalString(value.created_at) ?? new Date(0).toISOString(),
116+
type,
117+
created_at,
92118
content: {
93119
session_id: sessionId ?? "",
94120
start_time: optionalString(contentValue.start_time) ?? "",
@@ -115,7 +141,7 @@ function parseSignalReportArtefactsPayload(
115141

116142
const results = rawResults
117143
.map(normalizeSignalReportArtefact)
118-
.filter((artefact): artefact is SignalReportArtefact => artefact !== null);
144+
.filter((artefact): artefact is AnyArtefact => artefact !== null);
119145
const count =
120146
typeof payload?.count === "number" ? payload.count : results.length;
121147

@@ -223,17 +249,8 @@ export class PostHogAPIClient {
223249
async createSignalSourceConfig(
224250
projectId: number,
225251
options: {
226-
source_product:
227-
| "session_replay"
228-
| "llm_analytics"
229-
| "github"
230-
| "linear"
231-
| "zendesk";
232-
source_type:
233-
| "session_analysis_cluster"
234-
| "evaluation"
235-
| "issue"
236-
| "ticket";
252+
source_product: SignalSourceConfig["source_product"];
253+
source_type: SignalSourceConfig["source_type"];
237254
enabled: boolean;
238255
config?: Record<string, unknown>;
239256
},
@@ -287,6 +304,34 @@ export class PostHogAPIClient {
287304
return (await response.json()) as SignalSourceConfig;
288305
}
289306

307+
async listEvaluations(projectId: number): Promise<Evaluation[]> {
308+
const data = await this.api.get(
309+
"/api/environments/{project_id}/evaluations/",
310+
{
311+
path: { project_id: projectId.toString() },
312+
query: { limit: 200 },
313+
},
314+
);
315+
return data.results ?? [];
316+
}
317+
318+
async updateEvaluation(
319+
projectId: number,
320+
evaluationId: string,
321+
updates: { enabled: boolean },
322+
): Promise<Evaluation> {
323+
return await this.api.patch(
324+
"/api/environments/{project_id}/evaluations/{id}/",
325+
{
326+
path: {
327+
project_id: projectId.toString(),
328+
id: evaluationId,
329+
},
330+
body: updates,
331+
},
332+
);
333+
}
334+
290335
async listExternalDataSources(
291336
projectId: number,
292337
): Promise<ExternalDataSource[]> {

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

Lines changed: 91 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,41 @@ interface SetupFormProps {
5252
onCancel: () => void;
5353
}
5454

55+
const POLL_INTERVAL_GITHUB_MS = 3_000;
56+
const POLL_TIMEOUT_GITHUB_MS = 300_000;
57+
5558
function GitHubSetup({ onComplete, onCancel }: SetupFormProps) {
5659
const projectId = useAuthStateValue((state) => state.projectId);
60+
const cloudRegion = useAuthStateValue((state) => state.cloudRegion);
5761
const client = useAuthenticatedClient();
5862
const { githubIntegration, repositories, isLoadingRepos } =
5963
useRepositoryIntegration();
6064
const [repo, setRepo] = useState<string | null>(null);
6165
const [loading, setLoading] = useState(false);
66+
const [connecting, setConnecting] = useState(false);
67+
const pollTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
68+
const pollTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
69+
70+
const stopPolling = useCallback(() => {
71+
if (pollTimerRef.current) {
72+
clearInterval(pollTimerRef.current);
73+
pollTimerRef.current = null;
74+
}
75+
if (pollTimeoutRef.current) {
76+
clearTimeout(pollTimeoutRef.current);
77+
pollTimeoutRef.current = null;
78+
}
79+
}, []);
80+
81+
useEffect(() => stopPolling, [stopPolling]);
82+
83+
// Stop polling once integration appears
84+
useEffect(() => {
85+
if (githubIntegration && connecting) {
86+
stopPolling();
87+
setConnecting(false);
88+
}
89+
}, [githubIntegration, connecting, stopPolling]);
6290

6391
// Auto-select the first repo once loaded
6492
useEffect(() => {
@@ -67,6 +95,47 @@ function GitHubSetup({ onComplete, onCancel }: SetupFormProps) {
6795
}
6896
}, [repo, repositories]);
6997

98+
const handleConnectGitHub = useCallback(async () => {
99+
if (!cloudRegion || !projectId) return;
100+
setConnecting(true);
101+
try {
102+
await trpcClient.githubIntegration.startFlow.mutate({
103+
region: cloudRegion,
104+
projectId,
105+
});
106+
107+
pollTimerRef.current = setInterval(async () => {
108+
try {
109+
if (!client) return;
110+
// Trigger a refetch of integrations
111+
const integrations =
112+
await client.getIntegrationsForProject(projectId);
113+
const hasGithub = integrations.some(
114+
(i: { kind: string }) => i.kind === "github",
115+
);
116+
if (hasGithub) {
117+
stopPolling();
118+
setConnecting(false);
119+
toast.success("GitHub connected");
120+
}
121+
} catch {
122+
// Ignore individual poll failures
123+
}
124+
}, POLL_INTERVAL_GITHUB_MS);
125+
126+
pollTimeoutRef.current = setTimeout(() => {
127+
stopPolling();
128+
setConnecting(false);
129+
toast.error("Connection timed out. Please try again.");
130+
}, POLL_TIMEOUT_GITHUB_MS);
131+
} catch (error) {
132+
setConnecting(false);
133+
toast.error(
134+
error instanceof Error ? error.message : "Failed to start GitHub flow",
135+
);
136+
}
137+
}, [cloudRegion, projectId, client, stopPolling]);
138+
70139
const handleSubmit = useCallback(async () => {
71140
if (!projectId || !client || !repo || !githubIntegration) return;
72141

@@ -97,10 +166,28 @@ function GitHubSetup({ onComplete, onCancel }: SetupFormProps) {
97166
if (!githubIntegration) {
98167
return (
99168
<SetupFormContainer title="Connect GitHub">
100-
<Text size="2" style={{ color: "var(--gray-11)" }}>
101-
No GitHub integration found. Please connect GitHub during onboarding
102-
first.
103-
</Text>
169+
<Flex direction="column" gap="3">
170+
<Text size="2" style={{ color: "var(--gray-11)" }}>
171+
Connect your GitHub account to import issues as signals.
172+
</Text>
173+
<Flex gap="2" justify="end">
174+
<Button
175+
size="2"
176+
variant="soft"
177+
onClick={onCancel}
178+
disabled={connecting}
179+
>
180+
Cancel
181+
</Button>
182+
<Button
183+
size="2"
184+
onClick={() => void handleConnectGitHub()}
185+
disabled={connecting}
186+
>
187+
{connecting ? "Waiting for authorization..." : "Connect GitHub"}
188+
</Button>
189+
</Flex>
190+
</Flex>
104191
</SetupFormContainer>
105192
);
106193
}

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import {
4242
} from "@radix-ui/themes";
4343
import { getCloudUrlFromRegion } from "@shared/constants/oauth";
4444
import type {
45+
SignalReportArtefact,
4546
SignalReportArtefactsResponse,
4647
SignalReportsQueryParams,
4748
} from "@shared/types";
@@ -201,7 +202,9 @@ export function InboxSignalsTab() {
201202
const artefactsQuery = useInboxReportArtefacts(selectedReport?.id ?? "", {
202203
enabled: !!selectedReport,
203204
});
204-
const visibleArtefacts = artefactsQuery.data?.results ?? [];
205+
const visibleArtefacts = (artefactsQuery.data?.results ?? []).filter(
206+
(a): a is SignalReportArtefact => a.type === "video_segment",
207+
);
205208
const artefactsUnavailableReason = artefactsQuery.data?.unavailableReason;
206209
const showArtefactsUnavailable =
207210
!artefactsQuery.isLoading &&
@@ -543,7 +546,7 @@ export function InboxSignalsTab() {
543546
!showArtefactsUnavailable &&
544547
visibleArtefacts.length === 0 && (
545548
<Text size="1" color="gray" className="block text-[12px]">
546-
No artefacts were returned for this signal.
549+
No session segments available for this report.
547550
</Text>
548551
)}
549552

0 commit comments

Comments
 (0)