Skip to content
Merged
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
7 changes: 6 additions & 1 deletion packages/ui/src/features/auth/useAuthMutations.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useService } from "@posthog/di/react";
import { useHostTRPCClient } from "@posthog/host-router/react";
import type { CloudRegion } from "@posthog/shared";
import { clearCapturedLogs } from "@posthog/ui/shell/logCapture";
import { useMutation } from "@tanstack/react-query";
import { clearAuthScopedQueries, refreshAuthStateQuery } from "./authQueries";
import { AUTH_SIDE_EFFECTS, type IAuthSideEffects } from "./identifiers";
Expand Down Expand Up @@ -71,6 +72,10 @@ export function useLogoutMutation() {
await hostClient.auth.logout.mutate();
return previous;
},
onSuccess: (previous) => fx.onLogout(previous.cloudRegion),
onSuccess: (previous) => {
// Privacy boundary: error bundles must never export another account's logs.
clearCapturedLogs();
fx.onLogout(previous.cloudRegion);
},
});
}
6 changes: 2 additions & 4 deletions packages/ui/src/features/canvas/hooks/useGenerateContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import type { WorkspaceMode } from "@posthog/shared";
import { buildContextGenerationPrompt } from "@posthog/ui/features/canvas/contextPrompt";
import { useChannelTaskMutations } from "@posthog/ui/features/canvas/hooks/useChannelTasks";
import { useFolderGenerationTaskMutation } from "@posthog/ui/features/canvas/hooks/useFolderGenerationTask";
import { toastError } from "@posthog/ui/features/notifications/errorDetails";
import { useCreateTask } from "@posthog/ui/features/tasks/useTaskCrudMutations";
import { toast } from "@posthog/ui/primitives/toast";
import { useQueryClient } from "@tanstack/react-query";
import { useCallback, useState } from "react";

Expand Down Expand Up @@ -47,9 +47,7 @@ export function useGenerateContext(channelId: string, channelName: string) {
);

if (!result.success) {
toast.error("Couldn't start CONTEXT.md generation", {
description: result.error,
});
toastError("Couldn't start CONTEXT.md generation", result.error);
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
} from "@posthog/ui/features/canvas/hooks/useDashboards";
import { useFolderInstructions } from "@posthog/ui/features/canvas/hooks/useFolderInstructions";
import { useCanvasGenerationTrackerStore } from "@posthog/ui/features/canvas/stores/canvasGenerationTrackerStore";
import { toastError } from "@posthog/ui/features/notifications/errorDetails";
import { useCreateTask } from "@posthog/ui/features/tasks/useTaskCrudMutations";
import { toast } from "@posthog/ui/primitives/toast";
import { useQueryClient } from "@tanstack/react-query";
Expand Down Expand Up @@ -123,9 +124,7 @@ export function useGenerateFreeformCanvas(args: {
);

if (!result.success) {
toast.error("Couldn't start canvas generation", {
description: result.error,
});
toastError("Couldn't start canvas generation", result.error);
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useService } from "@posthog/di/react";
import { useCallback } from "react";
import { toast } from "../../primitives/toast";
import { showFocusSuccessToast } from "../focus/focusToast";
import { toastError } from "../notifications/errorDetails";

export function useExternalAppAction() {
const service = useService<ExternalAppService>(EXTERNAL_APPS_SERVICE);
Expand Down Expand Up @@ -39,14 +40,10 @@ export function useExternalAppAction() {
});
return;
case "open-failed":
toast.error("Failed to open in external app", {
description: outcome.error,
});
toastError("Failed to open in external app", outcome.error);
return;
case "focus-failed":
toast.error("Could not edit workspace", {
description: outcome.error,
});
toastError("Could not edit workspace", outcome.error);
return;
case "copied":
toast.success("Path copied to clipboard", {
Expand Down
6 changes: 2 additions & 4 deletions packages/ui/src/features/focus/focus-events.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import {
type HostTrpcClient,
} from "@posthog/host-router/client";
import { inject, injectable } from "inversify";
import { toast } from "../../primitives/toast";
import { logger } from "../../shell/logger";
import {
IMPERATIVE_QUERY_CLIENT,
type ImperativeQueryClient,
} from "../../shell/queryClient";
import { toastError } from "../notifications/errorDetails";
import { WORKSPACE_QUERY_KEY } from "../workspace/identifiers";
import { useFocusStore } from "./focusStore";

Expand Down Expand Up @@ -47,9 +47,7 @@ export class FocusEventsContribution implements Contribution {
);
const result = await useFocusStore.getState().disableFocus();
if (!result.success && result.error) {
toast.error("Could not unfocus workspace", {
description: result.error,
});
toastError("Could not unfocus workspace", result.error);
}
},
});
Expand Down
7 changes: 3 additions & 4 deletions packages/ui/src/features/home/hooks/useRunWorkstreamAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { homeKeys } from "@posthog/ui/features/home/hooks/useHomeSnapshot";
import { useQuickActionStore } from "@posthog/ui/features/home/stores/quickActionStore";
import { insertOptimisticTask } from "@posthog/ui/features/home/utils/optimisticTask";
import { useUserRepositoryIntegration } from "@posthog/ui/features/integrations/useIntegrations";
import { toastError } from "@posthog/ui/features/notifications/errorDetails";
import { useSettingsStore } from "@posthog/ui/features/settings/settingsStore";
import { useCreateTask } from "@posthog/ui/features/tasks/useTaskCrudMutations";
import { useAuthenticatedMutation } from "@posthog/ui/hooks/useAuthenticatedMutation";
Expand Down Expand Up @@ -170,16 +171,14 @@ export function useRunWorkstreamAction(): RunWorkstreamAction {
});
return;
}
toast.error("Failed to start task", { description: result.error });
toastError("Failed to start task", result.error);
log.error("Quick action task creation failed", {
failedStep: result.failedStep,
error: result.error,
});
fallbackToTaskInput();
} catch (error) {
const description =
error instanceof Error ? error.message : "Unknown error";
toast.error("Failed to start task", { description });
toastError("Failed to start task", error);
log.error("Quick action task creation threw", { error });
fallbackToTaskInput();
} finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
useRepositoryIntegration,
useUserRepositoryIntegration,
} from "@posthog/ui/features/integrations/useIntegrations";
import { toastError } from "@posthog/ui/features/notifications/errorDetails";
import { ScoutsFleetSection } from "@posthog/ui/features/scouts/components/ScoutsFleetSection";
import { GitHubIntegrationSection } from "@posthog/ui/features/settings/sections/GitHubIntegrationSection";
import { SlackInboxNotificationsSettings } from "@posthog/ui/features/settings/sections/SlackInboxNotificationsSettings";
Expand Down Expand Up @@ -365,9 +366,7 @@ function SetupTaskSection() {
adapter,
});
} else {
toast.error("Failed to start Self-driving setup", {
description: result.error,
});
toastError("Failed to start Self-driving setup", result.error);
log.error("Self-driving setup task creation failed", {
failedStep: result.failedStep,
error: result.error,
Expand All @@ -380,9 +379,7 @@ function SetupTaskSection() {
action_type: "run_setup_agent",
success: false,
});
const description =
error instanceof Error ? error.message : "Unknown error";
toast.error("Failed to start Self-driving setup", { description });
toastError("Failed to start Self-driving setup", error);
log.error("Unexpected error during Self-driving setup task creation", {
error,
repository: setupRepository,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { useAuthStateValue } from "@posthog/ui/features/auth/store";
import { showOfflineToast } from "@posthog/ui/features/connectivity/connectivityToast";
import { resolveDefaultModel } from "@posthog/ui/features/inbox/hooks/resolveDefaultModel";
import { useUserRepositoryIntegration } from "@posthog/ui/features/integrations/useIntegrations";
import { toastError } from "@posthog/ui/features/notifications/errorDetails";
import { useSettingsStore } from "@posthog/ui/features/settings/settingsStore";
import { useCreateTask } from "@posthog/ui/features/tasks/useTaskCrudMutations";
import { useConnectivity } from "@posthog/ui/hooks/useConnectivity";
Expand Down Expand Up @@ -262,7 +263,7 @@ export function useInboxCloudTaskRunner({
toast.dismiss(toastId);
// Usage-limit blocks already show the upgrade modal; don't double-toast.
if (!isUsageLimitResult(result)) {
toast.error(copy.errorTitle, { description: result.error });
toastError(copy.errorTitle, result.error);
log.error("Cloud-task creation failed", {
failedStep: result.failedStep,
error: result.error,
Expand All @@ -273,9 +274,7 @@ export function useInboxCloudTaskRunner({
}
} catch (error) {
toast.dismiss(toastId);
const description =
error instanceof Error ? error.message : "Unknown error";
toast.error(copy.errorTitle, { description });
toastError(copy.errorTitle, error);
log.error("Unexpected error during cloud-task creation", {
error,
reportId,
Expand Down
109 changes: 109 additions & 0 deletions packages/ui/src/features/notifications/ErrorDetailsDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import {
Badge,
Button,
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@posthog/quill";
import { CodeBlock } from "@posthog/ui/primitives/CodeBlock";
import { openTaskInput } from "@posthog/ui/router/useOpenTask";
import { formatCapturedLogs } from "@posthog/ui/shell/logCapture";
import { serializeError, useErrorDetailsStore } from "./errorDetails";

// Keep the create-task prompt readable: the full 500-entry buffer belongs in
// the downloaded bundle, not in a composer draft.
const TASK_PROMPT_LOG_ENTRIES = 100;

function buildBundle(
title: string,
occurredAt: number,
prettyError: string,
): string {
return [
`# ${title}`,
`Occurred at: ${new Date(occurredAt).toISOString()}`,
"",
"## Error",
prettyError,
"",
"## Recent logs",
formatCapturedLogs(),
"",
].join("\n");
}

// Global inspector behind every error toast's "Details" action: the full
// pretty-printed payload the toast had no room for, a downloadable
// error-plus-logs bundle, and (dev builds only) a one-click task prefilled
// with the same context.
export function ErrorDetailsDialog() {
const detail = useErrorDetailsStore((s) => s.detail);
const close = useErrorDetailsStore((s) => s.close);
if (!detail) return null;

const prettyError = serializeError(detail.error);

const handleDownload = () => {
const bundle = buildBundle(detail.title, detail.occurredAt, prettyError);
const blob = new Blob([bundle], { type: "text/markdown" });
const url = URL.createObjectURL(blob);
const anchor = document.createElement("a");
anchor.href = url;
anchor.download = `posthog-code-error-${detail.occurredAt}.md`;
anchor.click();
URL.revokeObjectURL(url);
};

const handleCreateTask = () => {
close();
openTaskInput({
initialPrompt: [
`Investigate this error from the PostHog Code app: ${detail.title}`,
"",
"## Error",
"```",
prettyError,
"```",
Comment thread
adamleithp marked this conversation as resolved.
"",
"## Recent logs",
"```",
formatCapturedLogs({ maxEntries: TASK_PROMPT_LOG_ENTRIES }),
"```",
].join("\n"),
});
};

return (
<Dialog open onOpenChange={(open) => !open && close()}>
<DialogContent className="w-[min(720px,calc(100vw-32px))] max-w-[720px] sm:max-w-[720px]">
<DialogHeader>
<DialogTitle>{detail.title}</DialogTitle>
</DialogHeader>
<div className="max-h-[55vh] overflow-y-auto px-1">
<CodeBlock size="1">{prettyError}</CodeBlock>
</div>
<DialogFooter>
{import.meta.env.DEV && (
<Button
variant="outline"
size="sm"
onClick={handleCreateTask}
className="gap-2 sm:mr-auto"
>
Create task from error
<Badge variant="warning">Dev</Badge>
</Button>
)}
<Button variant="outline" size="sm" onClick={handleDownload}>
Download error + logs
</Button>
<Button variant="primary" size="sm" onClick={close}>
Close
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
Loading
Loading