Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 42 additions & 13 deletions apps/code/src/renderer/api/posthogClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,25 @@ export type McpRecommendedServer = Schemas.RecommendedServer;

export type McpServerInstallation = Schemas.MCPServerInstallation;

export type Evaluation = Schemas.Evaluation;

export interface SignalSourceConfig {
id: string;
source_product:
| "session_replay"
| "llm_analytics"
| "github"
| "linear"
| "zendesk";
source_type: "session_analysis_cluster" | "evaluation" | "issue" | "ticket";
| "zendesk"
| "error_tracking";
source_type:
| "session_analysis_cluster"
| "evaluation"
| "issue"
| "ticket"
| "issue_created"
| "issue_reopened"
| "issue_spiking";
enabled: boolean;
config: Record<string, unknown>;
created_at: string;
Expand Down Expand Up @@ -223,17 +233,8 @@ export class PostHogAPIClient {
async createSignalSourceConfig(
projectId: number,
options: {
source_product:
| "session_replay"
| "llm_analytics"
| "github"
| "linear"
| "zendesk";
source_type:
| "session_analysis_cluster"
| "evaluation"
| "issue"
| "ticket";
source_product: SignalSourceConfig["source_product"];
source_type: SignalSourceConfig["source_type"];
enabled: boolean;
config?: Record<string, unknown>;
},
Expand Down Expand Up @@ -287,6 +288,34 @@ export class PostHogAPIClient {
return (await response.json()) as SignalSourceConfig;
}

async listEvaluations(projectId: number): Promise<Evaluation[]> {
const data = await this.api.get(
"/api/environments/{project_id}/evaluations/",
{
path: { project_id: projectId.toString() },
query: { limit: 200 },
},
);
return data.results ?? [];
}

async updateEvaluation(
projectId: number,
evaluationId: string,
updates: { enabled: boolean },
): Promise<Evaluation> {
return await this.api.patch(
"/api/environments/{project_id}/evaluations/{id}/",
{
path: {
project_id: projectId.toString(),
id: evaluationId,
},
body: updates,
},
);
}

async listExternalDataSources(
projectId: number,
): Promise<ExternalDataSource[]> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,41 @@ interface SetupFormProps {
onCancel: () => void;
}

const POLL_INTERVAL_GITHUB_MS = 3_000;
const POLL_TIMEOUT_GITHUB_MS = 300_000;

function GitHubSetup({ onComplete, onCancel }: SetupFormProps) {
const projectId = useAuthStore((s) => s.projectId);
const cloudRegion = useAuthStore((s) => s.cloudRegion);
const client = useAuthStore((s) => s.client);
const { githubIntegration, repositories, isLoadingRepos } =
useRepositoryIntegration();
const [repo, setRepo] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const [connecting, setConnecting] = useState(false);
const pollTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
const pollTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);

const stopPolling = useCallback(() => {
if (pollTimerRef.current) {
clearInterval(pollTimerRef.current);
pollTimerRef.current = null;
}
if (pollTimeoutRef.current) {
clearTimeout(pollTimeoutRef.current);
pollTimeoutRef.current = null;
}
}, []);

useEffect(() => stopPolling, [stopPolling]);

// Stop polling once integration appears
useEffect(() => {
if (githubIntegration && connecting) {
stopPolling();
setConnecting(false);
}
}, [githubIntegration, connecting, stopPolling]);

// Auto-select the first repo once loaded
useEffect(() => {
Expand All @@ -66,6 +94,47 @@ function GitHubSetup({ onComplete, onCancel }: SetupFormProps) {
}
}, [repo, repositories]);

const handleConnectGitHub = useCallback(async () => {
if (!cloudRegion || !projectId) return;
setConnecting(true);
try {
await trpcClient.githubIntegration.startFlow.mutate({
region: cloudRegion,
projectId,
});

pollTimerRef.current = setInterval(async () => {
try {
if (!client) return;
// Trigger a refetch of integrations
const integrations =
await client.getIntegrationsForProject(projectId);
const hasGithub = integrations.some(
(i: { kind: string }) => i.kind === "github",
);
if (hasGithub) {
stopPolling();
setConnecting(false);
toast.success("GitHub connected");
}
} catch {
// Ignore individual poll failures
}
}, POLL_INTERVAL_GITHUB_MS);

pollTimeoutRef.current = setTimeout(() => {
stopPolling();
setConnecting(false);
toast.error("Connection timed out. Please try again.");
}, POLL_TIMEOUT_GITHUB_MS);
} catch (error) {
setConnecting(false);
toast.error(
error instanceof Error ? error.message : "Failed to start GitHub flow",
);
}
}, [cloudRegion, projectId, client, stopPolling]);

const handleSubmit = useCallback(async () => {
if (!projectId || !client || !repo || !githubIntegration) return;

Expand Down Expand Up @@ -96,10 +165,28 @@ function GitHubSetup({ onComplete, onCancel }: SetupFormProps) {
if (!githubIntegration) {
return (
<SetupFormContainer title="Connect GitHub">
<Text size="2" style={{ color: "var(--gray-11)" }}>
No GitHub integration found. Please connect GitHub during onboarding
first.
</Text>
<Flex direction="column" gap="3">
<Text size="2" style={{ color: "var(--gray-11)" }}>
Connect your GitHub account to import issues as signals.
</Text>
<Flex gap="2" justify="end">
<Button
size="2"
variant="soft"
onClick={onCancel}
disabled={connecting}
>
Cancel
</Button>
<Button
size="2"
onClick={() => void handleConnectGitHub()}
disabled={connecting}
>
{connecting ? "Waiting for authorization..." : "Connect GitHub"}
</Button>
</Flex>
</Flex>
</SetupFormContainer>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
} from "@radix-ui/themes";
import { getCloudUrlFromRegion } from "@shared/constants/oauth";
import type {
SignalReportArtefact,
SignalReportArtefactsResponse,
SignalReportsQueryParams,
} from "@shared/types";
Expand Down Expand Up @@ -201,7 +202,9 @@ export function InboxSignalsTab() {
const artefactsQuery = useInboxReportArtefacts(selectedReport?.id ?? "", {
enabled: !!selectedReport,
});
const visibleArtefacts = artefactsQuery.data?.results ?? [];
const visibleArtefacts = (artefactsQuery.data?.results ?? []).filter(
(a): a is SignalReportArtefact => a.type !== "suggested_reviewers",
);
const artefactsUnavailableReason = artefactsQuery.data?.unavailableReason;
const showArtefactsUnavailable =
!artefactsQuery.isLoading &&
Expand Down
Loading
Loading