Skip to content

Conversation

@drifter089
Copy link
Owner

@drifter089 drifter089 commented Nov 22, 2025

Summary

  • Simplified PostHog metrics UI with dialog-based workflow
  • Removed Active Users template, focusing only on Event Count (Time Series) metrics
  • Button is now enabled instantly - no more loading state blocking user interaction
  • Data loads on-demand: projects fetch when dialog opens, events fetch when project is selected
  • Auto-generates metric name from project and event selection (no manual name input needed)

Changes

  • src/app/metric/posthog/page.tsx:
    • Replaced multi-step form with simple dialog (just project + event dropdowns)
    • Removed background prefetching logic
    • Added on-demand loading when dialog opens and project is selected
    • Auto-generate metric names instead of manual input
  • src/lib/integrations/posthog.ts: Removed Active Users template
  • src/app/api-test/_components/integration-tester.tsx: (existing changes preserved)

Test plan

  • Navigate to PostHog metrics page - button is enabled immediately
  • Open dialog and verify projects load
  • Select a project and verify events load
  • Create a metric and verify it works with auto-generated name
  • Check that event dropdown updates when project changes

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • API tests now capture and show richer error details with a structured, collapsible error view.
    • PostHog metric creation redesigned into a dialog-driven flow with project and event selection and improved loading/disabled states.
  • Chores

    • Removed the PostHog "active-users" metric template.

✏️ Tip: You can customize this high-level summary in your review settings.

…background prefetching

- Replace multi-step form with simple dialog containing metric name, project, and event dropdowns
- Remove Active Users template, focusing only on Event Count (Time Series) metrics
- Add background prefetching of all projects and events on component mount
- Fix loading state issues by using separate mutation instances for concurrent API calls
- Improve UX with cached data ready before user opens dialog

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@vercel
Copy link

vercel bot commented Nov 22, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
org-os Ready Ready Preview Comment Nov 22, 2025 7:32pm

@coderabbitai
Copy link

coderabbitai bot commented Nov 22, 2025

Walkthrough

Three changes: API test runner now captures, parses, logs, and displays structured error details; PostHog metrics UI refactored from template-driven to a dialog with project/event prefetching; and the "posthog-active-users" metric template was removed.

Changes

Cohort / File(s) Change Summary
API Test Error Handling
src/app/api-test/_components/integration-tester.tsx
Added errorDetails?: any to TestResult; parse enriched errorMessage and errorDetails from non-OK responses and caught exceptions (including regex extraction); log enhanced context; render a structured error block with collapsible JsonViewer when tests fail.
PostHog Metric Creation UI
src/app/metric/posthog/page.tsx
Replaced template-driven creation with a dialog-based flow: added project/event types and transforms, prefetches projects and events, uses selectedProject/selectedEvent and in-memory collections (projects, events), updates mutation to posthog-event-count, and updates UI to dialog with loading/disabled states.
PostHog Template Management
src/lib/integrations/posthog.ts
Removed template with templateId: "posthog-active-users" from exported templates array.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant IT as IntegrationTester
    participant API as Remote API
    participant UI as JsonViewer

    User->>IT: Run API Test
    activate IT
    IT->>API: Send request
    activate API
    API-->>IT: non-OK response (body with error)
    deactivate API
    IT->>IT: Parse response -> errorMessage, errorDetails
    IT-->>UI: Render structured error block (collapsible)
    deactivate IT
Loading
sequenceDiagram
    participant User
    participant Page as PostHog Page
    participant API as Backend

    User->>Page: Open Create Metric Dialog
    activate Page
    Page->>API: Fetch Projects
    API-->>Page: Projects[]
    Page->>API: Fetch Events for selected project
    API-->>Page: Events[]
    User->>Page: Select Project & Event, Submit
    Page->>API: Create Metric (templateId: posthog-event-count)
    API-->>Page: Success
    Page->>User: Close Dialog, Clear State
    deactivate Page
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Pay attention to:
    • src/app/metric/posthog/page.tsx — state transitions, prefetch logic, event-fetch sequencing, dialog lifecycle, mutation payload changes.
    • src/app/api-test/_components/integration-tester.tsx — error parsing (regex), extraction from varied response shapes, logging, and JsonViewer rendering correctness.
    • src/lib/integrations/posthog.ts — ensure removal of the template doesn't leave dangling references.

Possibly related PRs

Poem

🐰
I hopped through traces, found errors bright and clear,
Collapsed their secrets so devs may peer,
Projects lined up, events ready to count,
Dialogs opened, metrics tallied by amount,
A rabbit cheers — errors tamed and dashboards near.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the main change: simplifying the PostHog metrics creation UI by replacing a multi-step form with a dialog-based workflow, which is the primary focus of the changes.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch new-metrics

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/app/api-test/_components/integration-tester.tsx (2)

53-61: TestResult extension is fine; consider tightening errorDetails later

Adding errorDetails to TestResult is a good hook for richer debugging. Once the error payload format stabilizes, consider replacing any with a small dedicated error-payload type so downstream consumers know what to expect.


157-171: Structured error payload is parsed then discarded; errorDetails will usually be empty

Right now the flow is:

  • On non-OK responses you parse errorJson, derive errorMessage/errorCause, log everything, then throw new Error(errorMessage).
  • In the catch, you try to recover errorDetails by regex-parsing JSON out of error.message.

This means:

  • The rich errorJson you already have for non-OK responses is never surfaced to the UI; you only expose a flat message.
  • The regex /\{.*\}/s is greedy and brittle; if the message format changes, JSON.parse will start throwing again (silently swallowed).

You can simplify and make errorDetails reliable by directly attaching the parsed payload when you throw, e.g.:

-      if (!response.ok) {
-        // Parse error response to get detailed error info
-        const errorJson = await response.json().catch(() => null);
-        const errorMessage = errorJson?.error?.message ?? response.statusText;
-        const errorCause = errorJson?.error?.cause;
-
-        console.error(`[API Test Error] ${endpoint.label}:`, {
-          status: response.status,
-          statusText: response.statusText,
-          errorMessage,
-          errorCause,
-          fullError: errorJson,
-        });
-
-        throw new Error(errorMessage);
-      }
+      if (!response.ok) {
+        // Parse error response to get detailed error info
+        const errorJson = await response.json().catch(() => null);
+        const errorMessage =
+          errorJson?.error?.message ??
+          `${response.status} ${response.statusText || "Request failed"}`;
+
+        console.error(`[API Test Error] ${endpoint.label}:`, {
+          status: response.status,
+          statusText: response.statusText,
+          errorMessage,
+          fullError: errorJson,
+        });
+
+        // Encode full error JSON into the message so the catch block
+        // can reliably parse it back out into `errorDetails`.
+        throw new Error(
+          errorJson ? `${errorMessage} ${JSON.stringify(errorJson)}` : errorMessage,
+        );
+      }

If you keep the regex-based parsing, also consider making it non-greedy to reduce the chance of malformed slices:

-          const match = /\{.*\}/s.exec(error.message);
+          const match = /\{.*?\}/s.exec(error.message);

This will ensure errorDetails is consistently populated for backend error responses instead of staying null most of the time.

Also applies to: 184-211

src/app/metric/posthog/page.tsx (1)

213-310: Dialog flow is solid; consider refining event loading UX

The dialog-based flow (metric name → project → event with a single “Create Metric” action) is clean and much easier to understand than a multi-step form. A couple of small UX refinements you might consider:

  • The event placeholder currently treats “no events yet” and “still loading/failed to load” the same:

    !selectedProject
      ? "Select project first"
      : selectedProjectEvents.length === 0
        ? "Loading events..."
        : "Select an event"

    If a project legitimately has zero events or its fetch failed, this will show “Loading events...” indefinitely. You could instead distinguish "no events found" vs. "loading" by tracking a per-project loading/error flag.

  • When the dialog closes (via overlay/ESC), the previous selections remain. That may be desired, but if you want a fresh form on reopen you could reset metricName, selectedProject, and selectedEvent in the onOpenChange handler when open becomes false.

These are polish-level, not blockers.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fc4e4fa and 396324f.

📒 Files selected for processing (3)
  • src/app/api-test/_components/integration-tester.tsx (4 hunks)
  • src/app/metric/posthog/page.tsx (5 hunks)
  • src/lib/integrations/posthog.ts (0 hunks)
💤 Files with no reviewable changes (1)
  • src/lib/integrations/posthog.ts
🧰 Additional context used
🧬 Code graph analysis (2)
src/app/api-test/_components/integration-tester.tsx (1)
src/components/json-viewer/index.tsx (1)
  • JsonViewer (21-191)
src/app/metric/posthog/page.tsx (6)
src/trpc/react.tsx (1)
  • api (28-28)
src/components/ui/dialog.tsx (7)
  • Dialog (135-135)
  • DialogTrigger (144-144)
  • DialogContent (137-137)
  • DialogHeader (140-140)
  • DialogTitle (143-143)
  • DialogDescription (138-138)
  • DialogFooter (139-139)
src/components/ui/button.tsx (1)
  • Button (62-62)
src/components/ui/label.tsx (1)
  • Label (25-25)
src/components/ui/input.tsx (1)
  • Input (21-21)
src/components/ui/select.tsx (5)
  • Select (178-178)
  • SelectTrigger (186-186)
  • SelectValue (187-187)
  • SelectContent (179-179)
  • SelectItem (181-181)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (4)
src/app/api-test/_components/integration-tester.tsx (1)

495-510: Error details UI is a solid debugging aid

The structured error block with a prominent Error: line and JsonViewer for errorDetails is a nice UX improvement for diagnosing failing endpoints. Guarding the viewer behind result.errorDetails keeps the UI clean when only a message is available.

src/app/metric/posthog/page.tsx (3)

31-67: Transform helpers are clear and defensive

transformProjects/transformEvents are straightforward, tolerate unexpected shapes by returning [], and keep the UI layer decoupled from the raw PostHog API shape. This is a good, low-ceremony boundary.


73-83: State layout for dialog + prefetched data looks sane

Separating projects, eventsByProject, and selection state (selectedProject/selectedEvent) makes the metric flow easy to follow, and using distinct mutations for projects vs. events avoids loading-state clashes between them.

Also applies to: 85-92


172-186: Metric creation handler is straightforward; verify backend param names

handleCreate’s guards plus the createMetric mutation usage look correct, and clearing dialog state on success via the mutation’s onSuccess is a nice touch. Just ensure the backend template for "posthog-event-count" expects endpointParams keyed as PROJECT_ID and EVENT_NAME; otherwise this will fail at runtime.

- Remove background prefetching that caused loading state
- Button is now always enabled and ready to click
- Projects load when dialog opens
- Events load when project is selected
- Auto-generate metric name from project and event selection
- Remove manual metric name input field for simpler UX

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/app/metric/posthog/page.tsx (2)

27-65: Tighten PostHog response typing instead of unknown + cast

transformProjects/transformEvents currently accept unknown and then cast to a loose { results?: ... } shape. This works but gives up type safety and makes refactors harder.

Consider introducing explicit response types and narrowing the function signatures, e.g.:

-type Project = { label: string; value: string };
-type Event = { label: string; value: string };
+type Project = { label: string; value: string };
+type MetricEvent = { label: string; value: string };
+
+type PosthogProjectsResponse = {
+  results?: Array<{ name: string; id: number }>;
+};
+
+type PosthogEventsResponse = {
+  results?: Array<{ name: string }>;
+};
 
-function transformProjects(data: unknown): Project[] {
-  if (!data || typeof data !== "object") return [];
+function transformProjects(
+  data: PosthogProjectsResponse | null | undefined,
+): Project[] {
+  if (!data) return [];
@@
-function transformEvents(data: unknown): Event[] {
-  if (!data || typeof data !== "object") return [];
+function transformEvents(
+  data: PosthogEventsResponse | null | undefined,
+): MetricEvent[] {
+  if (!data) return [];

This keeps the UI-facing options simple while giving you compile‑time protection if the PostHog payload shape changes. Also avoids shadowing the global Event type, which can be mildly confusing in TS code.


99-108: Create metric flow is solid; consider preserving context after success

The mutation wiring and button disabled state look good: you gate handleCreate on required selections, disable the button while pending, and surface the error message.

On success you currently:

  • Close the dialog.
  • Clear selectedProject, selectedEvent, projects, and events.

This is safe but slightly unfriendly for workflows where a user wants to create several related metrics in a row, as they must re-select project and wait for projects/events to refetch each time.

If repeat creation is a common case, consider only clearing selectedEvent and leaving projects / selectedProject intact:

  const createMetric = api.metric.create.useMutation({
    onSuccess: () => {
      void utils.metric.getAll.invalidate();
      setOpen(false);
-      setSelectedProject("");
-      setSelectedEvent("");
-      setProjects([]);
-      setEvents([]);
+      setSelectedEvent("");
    },
  });

You’d still get fresh data when the connection changes (via the effects), but users won’t pay the cost on every single successful create.

Also applies to: 140-157, 249-258, 260-264

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 396324f and 04a2fec.

📒 Files selected for processing (1)
  • src/app/metric/posthog/page.tsx (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/app/metric/posthog/page.tsx (6)
src/trpc/react.tsx (1)
  • api (28-28)
src/components/ui/card.tsx (3)
  • CardTitle (93-93)
  • CardHeader (91-91)
  • CardContent (96-96)
src/components/ui/dialog.tsx (7)
  • Dialog (135-135)
  • DialogTrigger (144-144)
  • DialogContent (137-137)
  • DialogHeader (140-140)
  • DialogTitle (143-143)
  • DialogDescription (138-138)
  • DialogFooter (139-139)
src/components/ui/button.tsx (1)
  • Button (62-62)
src/components/ui/label.tsx (1)
  • Label (25-25)
src/components/ui/select.tsx (5)
  • Select (178-178)
  • SelectTrigger (186-186)
  • SelectValue (187-187)
  • SelectContent (179-179)
  • SelectItem (181-181)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (1)
src/app/metric/posthog/page.tsx (1)

71-78: Dialog-based UX and fetch-on-open behavior look good; confirm intent vs. “background prefetch”

The new dialog flow and wiring between open, the project fetch effect, and the selects are coherent:

  • Projects are fetched lazily when the dialog first opens and projects.length === 0.
  • Events are fetched on project selection.
  • Select placeholders correctly reflect loading states and disabled conditions.
  • Errors from createMetric are surfaced inline.

One thing to double-check against the PR description: the code now fetches projects only once the dialog is opened, rather than “background prefetch on mount”. That’s a perfectly reasonable choice (and avoids the earlier infinite-retry issue), but it does slightly change the UX expectations.

If “ready before the user opens the dialog” is still a requirement, you could trigger the initial fetchProjects.mutate off connection alone (once per connection), and keep the dialog open state purely for UI. Otherwise, the current on‑open fetch behavior is consistent and easy to reason about.

Also applies to: 110-123, 177-267

Comment on lines +80 to 84
const integrationQuery = api.integration.listWithStats.useQuery();
const connection = integrationQuery.data?.active.find(
(int) => int.integrationId === "posthog",
);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Differentiate “loading connection” from “no PostHog connection”

Right now connection is derived from integrationQuery.data, and the early return:

if (!connection) {
  return <Card>…No PostHog Connection…</Card>;
}

will also render while the query is still loading or if it errors for other reasons. That can briefly (or permanently, on error) mislead users into thinking there is no connection.

A small refactor to branch on loading and error state separately would improve UX:

-  const integrationQuery = api.integration.listWithStats.useQuery();
+  const integrationQuery = api.integration.listWithStats.useQuery();
   const connection = integrationQuery.data?.active.find(
     (int) => int.integrationId === "posthog",
   );
@@
-  if (!connection) {
+  if (integrationQuery.isLoading) {
+    return (
+      <Card>
+        <CardHeader>
+          <CardTitle>Loading PostHog Connection…</CardTitle>
+        </CardHeader>
+        <CardContent>
+          <p className="text-muted-foreground text-sm">
+            Fetching your PostHog integrations.
+          </p>
+        </CardContent>
+      </Card>
+    );
+  }
+
+  if (!connection) {
     return (
       <Card>
         <CardHeader>
           <CardTitle>No PostHog Connection</CardTitle>

Optionally you could also surface integrationQuery.isError with a more precise error message.

Also applies to: 159-172

🤖 Prompt for AI Agents
In src/app/metric/posthog/page.tsx around lines 80-84 (and similarly 159-172),
the code derives connection directly from integrationQuery.data and immediately
returns a "No PostHog Connection" Card which also shows during loading or on
error; change the control flow to first check integrationQuery.isLoading and
render a loading state, then check integrationQuery.isError and render an error
message (optionally include error details), and only after those checks derive
connection from integrationQuery.data and render the "No PostHog Connection"
Card when connection is definitively absent; this ensures loading and error
states are distinct from a true "no connection" result.

Comment on lines 92 to 98
const fetchEvents = api.metric.fetchIntegrationData.useMutation({
onSuccess: (data: { data: unknown }) => {
const options = transformEvents(data.data);
setEventOptions(options);
onSuccess: (data) => {
const eventList = transformEvents(data.data);
setEvents(eventList);
},
});

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Prevent stale event options when switching projects quickly

The events mutation has a global onSuccess that unconditionally updates events:

const fetchEvents = api.metric.fetchIntegrationData.useMutation({
  onSuccess: (data) => {
    const eventList = transformEvents(data.data);
    setEvents(eventList);
  },
});

and the effect fires mutate whenever selectedProject changes. If a user switches projects quickly (or network responses arrive out of order), a slower response for project A can overwrite the events for project B, leaving the dropdown desynchronized from selectedProject.

You can guard against this by tying the success handler to the project that initiated the request:

-const fetchEvents = api.metric.fetchIntegrationData.useMutation({
-  onSuccess: (data) => {
-    const eventList = transformEvents(data.data);
-    setEvents(eventList);
-  },
-});
+const fetchEvents = api.metric.fetchIntegrationData.useMutation();
@@
-  useEffect(() => {
-    if (selectedProject && connection) {
-      setEvents([]);
-      setSelectedEvent("");
-      fetchEvents.mutate({
-        connectionId: connection.connectionId,
-        integrationId: "posthog",
-        endpoint: `/api/projects/${selectedProject}/event_definitions/`,
-        method: "GET",
-        params: {},
-      });
-    }
+  useEffect(() => {
+    if (!selectedProject || !connection) return;
+
+    const projectId = selectedProject;
+    setEvents([]);
+    setSelectedEvent("");
+
+    fetchEvents.mutate(
+      {
+        connectionId: connection.connectionId,
+        integrationId: "posthog",
+        endpoint: `/api/projects/${projectId}/event_definitions/`,
+        method: "GET",
+        params: {},
+      },
+      {
+        onSuccess: (data) => {
+          // Ignore if the user switched projects while this request was in flight.
+          if (projectId !== selectedProject) return;
+          setEvents(transformEvents(data.data));
+        },
+      },
+    );
   }, [selectedProject, connection]);

This ensures the UI always shows events for the currently selected project, even under slow or flaky networks.

Also applies to: 124-138

🤖 Prompt for AI Agents
In src/app/metric/posthog/page.tsx around lines 92-98 and also 124-138, the
global onSuccess for fetchEvents unconditionally calls setEvents which allows
out-of-order network responses to overwrite the events for the
currently-selected project; fix by associating each mutate call with the project
id (pass selectedProject id as the mutation variable or a context value) and in
the onSuccess handler compare that initiating project id to the current
selectedProject before calling setEvents (if they differ, ignore the response),
ensuring only responses for the active project update the dropdown.

@drifter089 drifter089 merged commit 075d7f9 into main Nov 22, 2025
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants