Skip to content
Closed
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/code/src/renderer/assets/images/mail-hog.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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
Loading
Loading