Skip to content

feat(admin): enhance analytics dashboard with improved charts and UX#488

Merged
saddlepaddle merged 7 commits intomainfrom
posthog-admin-dashboard
Dec 23, 2025
Merged

feat(admin): enhance analytics dashboard with improved charts and UX#488
saddlepaddle merged 7 commits intomainfrom
posthog-admin-dashboard

Conversation

@saddlepaddle
Copy link
Copy Markdown
Collaborator

@saddlepaddle saddlepaddle commented Dec 23, 2025

Summary

  • Add WAU trend chart with workspace_created event filtering (requires 3+ active days)
  • Add traffic sources chart with breakdown by referring domain (uses TrendsQuery with breakdown)
  • Add signups trend chart with daily counts
  • Add revenue trend chart (stub data placeholder)
  • Add YC Demo Day countdown widget (June 16, 2026)
  • Add time range pickers (7d/30d/90d/180d) to all charts
  • Add week picker for leaderboard navigation
  • Add cohort size column to retention table
  • Fix sidebar active route highlighting using usePathname()
  • Improve empty states with consistent dashed border styling across all charts
  • Use Tailwind 500 colors for traffic sources bar chart

Test plan

  • Verify WAU chart shows 0 users (no one has 3+ days of workspace_created events yet)
  • Verify traffic sources chart displays breakdown by referring domain
  • Verify time range pickers work across all charts
  • Verify YC countdown displays correctly
  • Verify retention table shows Size column with cohort counts
  • Verify empty states display properly when no data available

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Full analytics dashboard: charts and tables for revenue, signups, WAU, funnel, retention, traffic sources, leaderboard, time-range & week pickers, week navigator, and a demo countdown. Updated sidebar/navigation labels.
  • Chores

    • Added charting library dependency for visualizations.
    • Integrated PostHog-backed analytics endpoints and added PostHog credentials to deployment configuration.
    • Updated site metadata and header breadcrumbs.

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

saddlepaddle and others added 4 commits December 23, 2025 09:18
Add custom event tracking for key user actions:
- Marketing: download_clicked, waitlist_clicked
- Desktop: desktop_opened, auth_started, auth_completed,
  workspace_created/opened/closed/deleted, terminal_opened
- Fix user identity to use database user ID consistently across all apps
  (was using Clerk ID in web/admin which differed from desktop)

Also adds posthog-node for reliable server-side tracking in desktop main process.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace separate activation and onboarding funnels with one full journey
funnel: site visit → download → desktop opened → auth completed → terminal opened

This provides a clearer view of where users drop off across the entire
user journey from marketing to product activation.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add WAU trend chart with workspace_created event filtering
- Add traffic sources chart with breakdown by referring domain
- Add signups trend chart with daily counts
- Add revenue trend chart (stub data for now)
- Add YC Demo Day countdown widget
- Add time range pickers (7d/30d/90d/180d) to all charts
- Add week picker for leaderboard
- Add cohort size column to retention table
- Fix sidebar active route highlighting
- Improve empty states with consistent styling across all charts
- Use Tailwind 500 colors for traffic sources bars

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 23, 2025

Walkthrough

Adds a complete admin analytics feature set: new UI components and charts, a PostHog client, TRPC analytics router, env/schema and CI wiring for PostHog keys, a small shared constant change, UI/breadcrumb updates, and a dependency on recharts.

Changes

Cohort / File(s) Summary
Package Dependencies
apps/admin/package.json
Added recharts v2.15.4
Sidebar & Header
apps/admin/src/app/(dashboard)/components/AppSidebar/AppSidebar.tsx, apps/admin/src/app/(dashboard)/components/AppSidebar/components/AppSidebarHeader/AppSidebarHeader.tsx
Reworked navigation (Home + User Management), added path-aware active state; removed "Admin Panel" label
App Layout / Metadata
apps/admin/src/app/layout.tsx, apps/admin/src/app/(dashboard)/layout.tsx
Updated page metadata and breadcrumb labels ("Admin Panel" → "Superset", "Dashboard" → "Home")
Dashboard Page
apps/admin/src/app/(dashboard)/page.tsx
Replaced placeholder with full dashboard wired to TRPC queries and composed chart/table components with time/week controls
Chart Components (recharts)
apps/admin/src/app/(dashboard)/components/RevenueTrendChart/..., .../SignupsTrendChart/..., .../WAUTrendChart/..., .../TrafficSourcesChart/..., .../FunnelChart/...
New Area/Bar/Funnel chart components with loading/error/empty states, tooltips, formatting, and barrel exports
Card / Table Components
apps/admin/src/app/(dashboard)/components/MetricCard/..., LeaderboardTable/..., RetentionCard/...
New MetricCard, LeaderboardTable, RetentionCard components with props/types, loading/error/empty UI and barrel exports
Utility / Pickers / Micro UI
apps/admin/src/app/(dashboard)/components/DemoCountdown/*, TimeRangePicker/*, WeekPicker/*
Added DemoCountdown, TimeRangePicker (and TimeRange type), WeekPicker; index re-exports
Leaderboard / Funnel / Misc Exports
apps/admin/src/app/(dashboard)/components/*/index.ts
Added barrel exports for new components
Shared Constants
packages/shared/src/constants.ts
Changed POSTHOG_COOKIE_NAME from "ph_superset" to "superset"
PostHog Client
packages/trpc/src/lib/posthog-client.ts
New PostHog query client with in-memory caching and helpers: executeQuery, executeFunnelQuery, executeHogQLQuery, executeRetentionQuery; many related types/interfaces added
TRPC Analytics Router
packages/trpc/src/router/analytics/analytics.ts, .../index.ts, packages/trpc/src/root.ts
New analytics TRPC router with endpoints (funnels, WAU, retention, leaderboard, signups, traffic, revenue) and registration on appRouter
TRPC Env Schema
packages/trpc/src/env.ts
Added server env keys POSTHOG_API_KEY and POSTHOG_PROJECT_ID to schema
CI / Deploy workflows & Turbo
.github/workflows/deploy-preview.yml, .github/workflows/deploy-production.yml, turbo.jsonc
Propagate POSTHOG_API_KEY and POSTHOG_PROJECT_ID into build/deploy steps; added NEXT_PUBLIC_ADMIN_URL, POSTHOG_API_KEY, POSTHOG_PROJECT_ID to globalEnv
Tooling config
biome.jsonc
Adjusted $schema version reference

Sequence Diagram(s)

sequenceDiagram
    participant Browser as Admin Dashboard (Browser)
    participant TRPCClient as TRPC Client
    participant TRPCServer as TRPC Server / analyticsRouter
    participant PostHogAPI as PostHog API
    participant DB as Database

    rect `#F2F8FF`
    Note over Browser,TRPCClient: Dashboard mounts and issues analytics queries
    Browser->>TRPCClient: useQuery(analytics.getWAUTrend / getSignups / ...)
    end

    rect `#FFF5F0`
    Note over TRPCClient,TRPCServer: TRPC invokes analytics procedures
    TRPCClient->>TRPCServer: invoke analytics endpoint
    TRPCServer->>PostHogAPI: executeQuery / executeHogQLQuery / executeFunnelQuery (cached)
    PostHogAPI-->>TRPCServer: results (or error)
    TRPCServer->>DB: enrich (e.g., join user metadata for leaderboard)
    DB-->>TRPCServer: user records
    TRPCServer-->>TRPCClient: formatted analytics response
    end

    rect `#F5FFF5`
    Note over TRPCClient,Browser: Render visualizations
    TRPCClient-->>Browser: responses
    Browser->>Browser: render charts/tables/cards (recharts + UI primitives)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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
Title check ✅ Passed The title accurately summarizes the main objective: enhancing the analytics dashboard with improved charts and UX. It is concise, specific, and directly related to the substantial changeset of new chart components, pickers, and dashboard improvements.
Description check ✅ Passed The PR description provides a clear summary section with detailed bullet points covering all major changes, includes a test plan section with specific verification steps, and notes the implementation tool. However, the 'Related Issues', 'Type of Change', 'Screenshots', and 'Additional Notes' sections from the template are missing or incomplete.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch posthog-admin-dashboard

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.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Dec 23, 2025

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

Service Status
Neon Database (Neon)

Thank you for your contribution! 🎉


Preview resources have been processed for cleanup

Copy link
Copy Markdown
Contributor

@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: 6

🧹 Nitpick comments (8)
apps/admin/src/app/(dashboard)/components/MetricCard/MetricCard.tsx (1)

18-18: Unused error.message property.

The error prop is typed with { message: string } but the actual message is not displayed—the component shows a hardcoded "Failed to load" instead. Consider either using the message or simplifying the error type.

Option 1: Use the error message
-				<p className="text-destructive text-sm">Failed to load</p>
+				<p className="text-destructive text-sm">{error.message || "Failed to load"}</p>
Option 2: Simplify the error type if message is intentionally hidden
-	error?: { message: string } | null;
+	error?: Error | null;

Also applies to: 44-45

apps/admin/package.json (1)

36-36: Consider evaluating compatibility with recharts 3.x.

recharts@2.15.4 is a valid, stable version with no known security vulnerabilities. However, it's significantly outdated—the latest release is 3.6.0. If your application is compatible with the 3.x series, updating would provide access to newer features and improvements.

apps/admin/src/app/(dashboard)/components/AppSidebar/AppSidebar.tsx (1)

80-83: Consider using Next.js Link for client-side navigation.

Using raw <a> tags triggers full page reloads. For smoother navigation within the admin dashboard, prefer next/link:

🔎 Proposed fix
+import Link from "next/link";
...
-										<a href={item.url}>
+										<Link href={item.url}>
 											<item.icon className="size-4" />
 											{item.title}
-										</a>
+										</Link>

Apply the same change to line 117 for section items.

apps/admin/src/app/(dashboard)/page.tsx (1)

33-37: Consider extracting the time range parsing logic.

The pattern Number.parseInt(range.slice(1, -1), 10) is repeated 5 times. A small helper would improve readability and maintainability:

🔎 Suggested helper
const parseDays = (range: TimeRange): number =>
  Number.parseInt(range.slice(1, -1), 10);

// Usage:
trpc.analytics.getWAUTrend.queryOptions({ days: parseDays(wauRange) })
apps/admin/src/app/(dashboard)/components/RetentionCard/RetentionCard.tsx (1)

17-24: Consider making week columns dynamic for future flexibility.

The current CohortRow interface hardcodes week0-week4. If retention periods need to change, this requires interface updates. A more flexible approach:

🔎 Alternative structure
interface CohortRow {
  cohort: string;
  weeks: WeekData[]; // Dynamic array instead of fixed properties
}

This would require corresponding changes to the table rendering logic. However, the current approach is acceptable if the 5-week retention view is a stable requirement.

apps/admin/src/app/(dashboard)/components/WeekPicker/WeekPicker.tsx (1)

14-31: Consider ISO 8601 week convention for international users.

The week calculation uses Sunday as the first day (US convention). For international applications, ISO 8601 specifies Monday as the week start. Consider making this configurable or documenting the US-centric behavior.

The date formatting is also hardcoded to "en-US" locale.

Optional: Support for configurable week start day
 interface WeekPickerProps {
 	/** Week offset from current week (0 = this week, -1 = last week, etc.) */
 	weekOffset: number;
 	onChange: (offset: number) => void;
 	/** Minimum offset (how far back can we go). Default -12 (12 weeks back) */
 	minOffset?: number;
+	/** First day of week (0 = Sunday, 1 = Monday). Default 0 */
+	weekStartsOn?: 0 | 1;
 }

-function getWeekLabel(offset: number): string {
+function getWeekLabel(offset: number, weekStartsOn = 0): string {
 	const now = new Date();
 	const startOfWeek = new Date(now);
-	// Go to start of current week (Sunday)
-	startOfWeek.setDate(now.getDate() - now.getDay() + offset * 7);
+	// Go to start of current week
+	const dayOfWeek = now.getDay();
+	const daysToSubtract = (dayOfWeek + 7 - weekStartsOn) % 7;
+	startOfWeek.setDate(now.getDate() - daysToSubtract + offset * 7);
apps/admin/src/app/(dashboard)/components/RevenueTrendChart/RevenueTrendChart.tsx (1)

49-122: LGTM! Well-structured chart component with good state handling.

The component correctly implements loading, error, and empty states with consistent styling. The dynamic tick interval calculation (line 58) provides reasonable spacing, and the currency formatting is properly internationalized.

Optional: Add defensive type check in formatter

Line 105 uses a type assertion that could be made safer:

 							<ChartTooltip
 								content={
 									<ChartTooltipContent
-										formatter={(value) => formatCurrency(value as number)}
+										formatter={(value) => 
+											typeof value === 'number' ? formatCurrency(value) : String(value)
+										}
 									/>
 								}
 							/>
apps/admin/src/app/(dashboard)/components/FunnelChart/FunnelChart.tsx (1)

42-126: LGTM! Clear funnel visualization with helpful conversion metrics.

The component correctly renders funnel data with:

  • Consistent loading, error, and empty states
  • Custom tooltip displaying both user counts and conversion rates
  • Locale-aware number formatting
  • Appropriate chart dimensions and margins
Optional: Add defensive null check in tooltip formatter

Line 103 accesses item.payload.conversionRate without a null check. While the TypeScript interface makes this required, adding a defensive check improves runtime safety:

 									<ChartTooltipContent
 										formatter={(value, _name, item) => (
 											<div className="flex flex-col gap-1">
 												<span>{value.toLocaleString()} users</span>
-												<span className="text-muted-foreground">
-													{item.payload.conversionRate.toFixed(1)}% conversion
-												</span>
+												{item.payload.conversionRate != null && (
+													<span className="text-muted-foreground">
+														{item.payload.conversionRate.toFixed(1)}% conversion
+													</span>
+												)}
 											</div>
 										)}
 									/>
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 35a1a80 and d8f1a5a.

⛔ Files ignored due to path filters (2)
  • apps/admin/public/yc-logo.png is excluded by !**/*.png
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (34)
  • apps/admin/package.json
  • apps/admin/src/app/(dashboard)/components/AppSidebar/AppSidebar.tsx
  • apps/admin/src/app/(dashboard)/components/AppSidebar/components/AppSidebarHeader/AppSidebarHeader.tsx
  • apps/admin/src/app/(dashboard)/components/DemoCountdown/DemoCountdown.tsx
  • apps/admin/src/app/(dashboard)/components/DemoCountdown/index.ts
  • apps/admin/src/app/(dashboard)/components/FunnelChart/FunnelChart.tsx
  • apps/admin/src/app/(dashboard)/components/FunnelChart/index.ts
  • apps/admin/src/app/(dashboard)/components/LeaderboardTable/LeaderboardTable.tsx
  • apps/admin/src/app/(dashboard)/components/LeaderboardTable/index.ts
  • apps/admin/src/app/(dashboard)/components/MetricCard/MetricCard.tsx
  • apps/admin/src/app/(dashboard)/components/MetricCard/index.ts
  • apps/admin/src/app/(dashboard)/components/RetentionCard/RetentionCard.tsx
  • apps/admin/src/app/(dashboard)/components/RetentionCard/index.ts
  • apps/admin/src/app/(dashboard)/components/RevenueTrendChart/RevenueTrendChart.tsx
  • apps/admin/src/app/(dashboard)/components/RevenueTrendChart/index.ts
  • apps/admin/src/app/(dashboard)/components/SignupsTrendChart/SignupsTrendChart.tsx
  • apps/admin/src/app/(dashboard)/components/SignupsTrendChart/index.ts
  • apps/admin/src/app/(dashboard)/components/TimeRangePicker/TimeRangePicker.tsx
  • apps/admin/src/app/(dashboard)/components/TimeRangePicker/index.ts
  • apps/admin/src/app/(dashboard)/components/TrafficSourcesChart/TrafficSourcesChart.tsx
  • apps/admin/src/app/(dashboard)/components/TrafficSourcesChart/index.ts
  • apps/admin/src/app/(dashboard)/components/WAUTrendChart/WAUTrendChart.tsx
  • apps/admin/src/app/(dashboard)/components/WAUTrendChart/index.ts
  • apps/admin/src/app/(dashboard)/components/WeekPicker/WeekPicker.tsx
  • apps/admin/src/app/(dashboard)/components/WeekPicker/index.ts
  • apps/admin/src/app/(dashboard)/layout.tsx
  • apps/admin/src/app/(dashboard)/page.tsx
  • apps/admin/src/app/layout.tsx
  • packages/shared/src/constants.ts
  • packages/trpc/src/env.ts
  • packages/trpc/src/lib/posthog-client.ts
  • packages/trpc/src/root.ts
  • packages/trpc/src/router/analytics/analytics.ts
  • packages/trpc/src/router/analytics/index.ts
💤 Files with no reviewable changes (1)
  • apps/admin/src/app/(dashboard)/components/AppSidebar/components/AppSidebarHeader/AppSidebarHeader.tsx
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Avoid using any type in TypeScript - maintain type safety unless absolutely necessary

Files:

  • apps/admin/src/app/(dashboard)/components/DemoCountdown/index.ts
  • packages/shared/src/constants.ts
  • apps/admin/src/app/(dashboard)/components/SignupsTrendChart/index.ts
  • apps/admin/src/app/(dashboard)/components/RevenueTrendChart/index.ts
  • apps/admin/src/app/(dashboard)/components/SignupsTrendChart/SignupsTrendChart.tsx
  • apps/admin/src/app/(dashboard)/components/TimeRangePicker/TimeRangePicker.tsx
  • apps/admin/src/app/(dashboard)/layout.tsx
  • apps/admin/src/app/(dashboard)/components/RevenueTrendChart/RevenueTrendChart.tsx
  • apps/admin/src/app/(dashboard)/components/TimeRangePicker/index.ts
  • apps/admin/src/app/(dashboard)/components/WeekPicker/WeekPicker.tsx
  • apps/admin/src/app/(dashboard)/page.tsx
  • apps/admin/src/app/(dashboard)/components/TrafficSourcesChart/index.ts
  • packages/trpc/src/router/analytics/index.ts
  • apps/admin/src/app/(dashboard)/components/MetricCard/index.ts
  • apps/admin/src/app/layout.tsx
  • apps/admin/src/app/(dashboard)/components/FunnelChart/FunnelChart.tsx
  • apps/admin/src/app/(dashboard)/components/LeaderboardTable/LeaderboardTable.tsx
  • apps/admin/src/app/(dashboard)/components/MetricCard/MetricCard.tsx
  • packages/trpc/src/env.ts
  • apps/admin/src/app/(dashboard)/components/RetentionCard/index.ts
  • apps/admin/src/app/(dashboard)/components/WeekPicker/index.ts
  • apps/admin/src/app/(dashboard)/components/TrafficSourcesChart/TrafficSourcesChart.tsx
  • apps/admin/src/app/(dashboard)/components/DemoCountdown/DemoCountdown.tsx
  • packages/trpc/src/router/analytics/analytics.ts
  • apps/admin/src/app/(dashboard)/components/LeaderboardTable/index.ts
  • apps/admin/src/app/(dashboard)/components/WAUTrendChart/index.ts
  • packages/trpc/src/root.ts
  • apps/admin/src/app/(dashboard)/components/WAUTrendChart/WAUTrendChart.tsx
  • apps/admin/src/app/(dashboard)/components/RetentionCard/RetentionCard.tsx
  • apps/admin/src/app/(dashboard)/components/FunnelChart/index.ts
  • apps/admin/src/app/(dashboard)/components/AppSidebar/AppSidebar.tsx
  • packages/trpc/src/lib/posthog-client.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Run Biome for formatting, linting, import organization, and safe fixes at the root level using bun run lint:fix

Files:

  • apps/admin/src/app/(dashboard)/components/DemoCountdown/index.ts
  • packages/shared/src/constants.ts
  • apps/admin/src/app/(dashboard)/components/SignupsTrendChart/index.ts
  • apps/admin/src/app/(dashboard)/components/RevenueTrendChart/index.ts
  • apps/admin/src/app/(dashboard)/components/SignupsTrendChart/SignupsTrendChart.tsx
  • apps/admin/src/app/(dashboard)/components/TimeRangePicker/TimeRangePicker.tsx
  • apps/admin/src/app/(dashboard)/layout.tsx
  • apps/admin/src/app/(dashboard)/components/RevenueTrendChart/RevenueTrendChart.tsx
  • apps/admin/src/app/(dashboard)/components/TimeRangePicker/index.ts
  • apps/admin/src/app/(dashboard)/components/WeekPicker/WeekPicker.tsx
  • apps/admin/src/app/(dashboard)/page.tsx
  • apps/admin/src/app/(dashboard)/components/TrafficSourcesChart/index.ts
  • packages/trpc/src/router/analytics/index.ts
  • apps/admin/src/app/(dashboard)/components/MetricCard/index.ts
  • apps/admin/src/app/layout.tsx
  • apps/admin/src/app/(dashboard)/components/FunnelChart/FunnelChart.tsx
  • apps/admin/src/app/(dashboard)/components/LeaderboardTable/LeaderboardTable.tsx
  • apps/admin/src/app/(dashboard)/components/MetricCard/MetricCard.tsx
  • packages/trpc/src/env.ts
  • apps/admin/src/app/(dashboard)/components/RetentionCard/index.ts
  • apps/admin/src/app/(dashboard)/components/WeekPicker/index.ts
  • apps/admin/src/app/(dashboard)/components/TrafficSourcesChart/TrafficSourcesChart.tsx
  • apps/admin/src/app/(dashboard)/components/DemoCountdown/DemoCountdown.tsx
  • packages/trpc/src/router/analytics/analytics.ts
  • apps/admin/src/app/(dashboard)/components/LeaderboardTable/index.ts
  • apps/admin/src/app/(dashboard)/components/WAUTrendChart/index.ts
  • packages/trpc/src/root.ts
  • apps/admin/src/app/(dashboard)/components/WAUTrendChart/WAUTrendChart.tsx
  • apps/admin/src/app/(dashboard)/components/RetentionCard/RetentionCard.tsx
  • apps/admin/src/app/(dashboard)/components/FunnelChart/index.ts
  • apps/admin/src/app/(dashboard)/components/AppSidebar/AppSidebar.tsx
  • packages/trpc/src/lib/posthog-client.ts
**/{components,features}/**/*.{ts,tsx,test.ts,test.tsx,stories.tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Co-locate component dependencies (utils, hooks, constants, config, tests, stories) next to the file using them

Files:

  • apps/admin/src/app/(dashboard)/components/DemoCountdown/index.ts
  • apps/admin/src/app/(dashboard)/components/SignupsTrendChart/index.ts
  • apps/admin/src/app/(dashboard)/components/RevenueTrendChart/index.ts
  • apps/admin/src/app/(dashboard)/components/SignupsTrendChart/SignupsTrendChart.tsx
  • apps/admin/src/app/(dashboard)/components/TimeRangePicker/TimeRangePicker.tsx
  • apps/admin/src/app/(dashboard)/components/RevenueTrendChart/RevenueTrendChart.tsx
  • apps/admin/src/app/(dashboard)/components/TimeRangePicker/index.ts
  • apps/admin/src/app/(dashboard)/components/WeekPicker/WeekPicker.tsx
  • apps/admin/src/app/(dashboard)/components/TrafficSourcesChart/index.ts
  • apps/admin/src/app/(dashboard)/components/MetricCard/index.ts
  • apps/admin/src/app/(dashboard)/components/FunnelChart/FunnelChart.tsx
  • apps/admin/src/app/(dashboard)/components/LeaderboardTable/LeaderboardTable.tsx
  • apps/admin/src/app/(dashboard)/components/MetricCard/MetricCard.tsx
  • apps/admin/src/app/(dashboard)/components/RetentionCard/index.ts
  • apps/admin/src/app/(dashboard)/components/WeekPicker/index.ts
  • apps/admin/src/app/(dashboard)/components/TrafficSourcesChart/TrafficSourcesChart.tsx
  • apps/admin/src/app/(dashboard)/components/DemoCountdown/DemoCountdown.tsx
  • apps/admin/src/app/(dashboard)/components/LeaderboardTable/index.ts
  • apps/admin/src/app/(dashboard)/components/WAUTrendChart/index.ts
  • apps/admin/src/app/(dashboard)/components/WAUTrendChart/WAUTrendChart.tsx
  • apps/admin/src/app/(dashboard)/components/RetentionCard/RetentionCard.tsx
  • apps/admin/src/app/(dashboard)/components/FunnelChart/index.ts
  • apps/admin/src/app/(dashboard)/components/AppSidebar/AppSidebar.tsx
**/{components,features}/**/[!.]*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Organize project structure with one folder per component: ComponentName/ComponentName.tsx with index.ts barrel export

Files:

  • apps/admin/src/app/(dashboard)/components/SignupsTrendChart/SignupsTrendChart.tsx
  • apps/admin/src/app/(dashboard)/components/TimeRangePicker/TimeRangePicker.tsx
  • apps/admin/src/app/(dashboard)/components/RevenueTrendChart/RevenueTrendChart.tsx
  • apps/admin/src/app/(dashboard)/components/WeekPicker/WeekPicker.tsx
  • apps/admin/src/app/(dashboard)/components/FunnelChart/FunnelChart.tsx
  • apps/admin/src/app/(dashboard)/components/LeaderboardTable/LeaderboardTable.tsx
  • apps/admin/src/app/(dashboard)/components/MetricCard/MetricCard.tsx
  • apps/admin/src/app/(dashboard)/components/TrafficSourcesChart/TrafficSourcesChart.tsx
  • apps/admin/src/app/(dashboard)/components/DemoCountdown/DemoCountdown.tsx
  • apps/admin/src/app/(dashboard)/components/WAUTrendChart/WAUTrendChart.tsx
  • apps/admin/src/app/(dashboard)/components/RetentionCard/RetentionCard.tsx
  • apps/admin/src/app/(dashboard)/components/AppSidebar/AppSidebar.tsx
**/*.{tsx,css}

📄 CodeRabbit inference engine (AGENTS.md)

Use React + TailwindCSS v4 + shadcn/ui for UI development

Files:

  • apps/admin/src/app/(dashboard)/components/SignupsTrendChart/SignupsTrendChart.tsx
  • apps/admin/src/app/(dashboard)/components/TimeRangePicker/TimeRangePicker.tsx
  • apps/admin/src/app/(dashboard)/layout.tsx
  • apps/admin/src/app/(dashboard)/components/RevenueTrendChart/RevenueTrendChart.tsx
  • apps/admin/src/app/(dashboard)/components/WeekPicker/WeekPicker.tsx
  • apps/admin/src/app/(dashboard)/page.tsx
  • apps/admin/src/app/layout.tsx
  • apps/admin/src/app/(dashboard)/components/FunnelChart/FunnelChart.tsx
  • apps/admin/src/app/(dashboard)/components/LeaderboardTable/LeaderboardTable.tsx
  • apps/admin/src/app/(dashboard)/components/MetricCard/MetricCard.tsx
  • apps/admin/src/app/(dashboard)/components/TrafficSourcesChart/TrafficSourcesChart.tsx
  • apps/admin/src/app/(dashboard)/components/DemoCountdown/DemoCountdown.tsx
  • apps/admin/src/app/(dashboard)/components/WAUTrendChart/WAUTrendChart.tsx
  • apps/admin/src/app/(dashboard)/components/RetentionCard/RetentionCard.tsx
  • apps/admin/src/app/(dashboard)/components/AppSidebar/AppSidebar.tsx
**/{components,features}/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

**/{components,features}/**/*.tsx: Nest components in parent's components/ folder if used only once, promote to highest shared parent's components/ if used 2+ times
Use one component per file - do not combine multiple components in a single file

Files:

  • apps/admin/src/app/(dashboard)/components/SignupsTrendChart/SignupsTrendChart.tsx
  • apps/admin/src/app/(dashboard)/components/TimeRangePicker/TimeRangePicker.tsx
  • apps/admin/src/app/(dashboard)/components/RevenueTrendChart/RevenueTrendChart.tsx
  • apps/admin/src/app/(dashboard)/components/WeekPicker/WeekPicker.tsx
  • apps/admin/src/app/(dashboard)/components/FunnelChart/FunnelChart.tsx
  • apps/admin/src/app/(dashboard)/components/LeaderboardTable/LeaderboardTable.tsx
  • apps/admin/src/app/(dashboard)/components/MetricCard/MetricCard.tsx
  • apps/admin/src/app/(dashboard)/components/TrafficSourcesChart/TrafficSourcesChart.tsx
  • apps/admin/src/app/(dashboard)/components/DemoCountdown/DemoCountdown.tsx
  • apps/admin/src/app/(dashboard)/components/WAUTrendChart/WAUTrendChart.tsx
  • apps/admin/src/app/(dashboard)/components/RetentionCard/RetentionCard.tsx
  • apps/admin/src/app/(dashboard)/components/AppSidebar/AppSidebar.tsx
🧠 Learnings (3)
📚 Learning: 2025-12-18T23:19:10.415Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-18T23:19:10.415Z
Learning: Applies to **/*.{tsx,css} : Use React + TailwindCSS v4 + shadcn/ui for UI development

Applied to files:

  • apps/admin/package.json
📚 Learning: 2025-12-18T23:19:10.415Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-18T23:19:10.415Z
Learning: Applies to **/{components,features}/**/[!.]*.tsx : Organize project structure with one folder per component: ComponentName/ComponentName.tsx with index.ts barrel export

Applied to files:

  • apps/admin/src/app/(dashboard)/components/WeekPicker/index.ts
📚 Learning: 2025-12-18T23:19:10.415Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-18T23:19:10.415Z
Learning: Applies to src/components/{ui,ai-elements,react-flow}/**/*.tsx : Use kebab-case single files for shadcn/ui components in src/components/ui/, src/components/ai-elements, and src/components/react-flow/ directories

Applied to files:

  • apps/admin/src/app/(dashboard)/components/AppSidebar/AppSidebar.tsx
🧬 Code graph analysis (8)
apps/admin/src/app/(dashboard)/components/TimeRangePicker/TimeRangePicker.tsx (2)
apps/admin/src/app/(dashboard)/components/TimeRangePicker/index.ts (2)
  • TimeRange (1-1)
  • TimeRangePicker (1-1)
packages/ui/src/components/ui/toggle-group.tsx (1)
  • ToggleGroup (83-83)
apps/admin/src/app/(dashboard)/layout.tsx (1)
packages/ui/src/components/ui/breadcrumb.tsx (4)
  • BreadcrumbLink (106-106)
  • BreadcrumbItem (105-105)
  • BreadcrumbSeparator (108-108)
  • BreadcrumbPage (107-107)
apps/admin/src/app/(dashboard)/components/WeekPicker/WeekPicker.tsx (2)
apps/admin/src/app/(dashboard)/components/WeekPicker/index.ts (1)
  • WeekPicker (1-1)
packages/ui/src/components/ui/button.tsx (1)
  • Button (60-60)
apps/admin/src/app/(dashboard)/components/MetricCard/MetricCard.tsx (2)
packages/ui/src/components/ui/card.tsx (5)
  • Card (85-85)
  • CardHeader (86-86)
  • CardTitle (88-88)
  • CardDescription (90-90)
  • CardContent (91-91)
packages/ui/src/components/ui/skeleton.tsx (1)
  • Skeleton (13-13)
apps/admin/src/app/(dashboard)/components/DemoCountdown/DemoCountdown.tsx (2)
apps/admin/src/app/(dashboard)/components/DemoCountdown/index.ts (1)
  • DemoCountdown (1-1)
packages/ui/src/components/ai-elements/image.tsx (1)
  • Image (9-24)
apps/admin/src/app/(dashboard)/components/WAUTrendChart/WAUTrendChart.tsx (4)
packages/ui/src/components/ui/chart.tsx (4)
  • ChartConfig (11-19)
  • ChartContainer (352-352)
  • ChartTooltip (353-353)
  • ChartTooltipContent (354-354)
apps/admin/src/app/(dashboard)/components/WAUTrendChart/index.ts (1)
  • WAUTrendChart (1-1)
packages/ui/src/components/ui/card.tsx (5)
  • Card (85-85)
  • CardHeader (86-86)
  • CardTitle (88-88)
  • CardDescription (90-90)
  • CardContent (91-91)
packages/ui/src/components/ui/skeleton.tsx (1)
  • Skeleton (13-13)
apps/admin/src/app/(dashboard)/components/RetentionCard/RetentionCard.tsx (3)
apps/admin/src/app/(dashboard)/components/RetentionCard/index.ts (1)
  • RetentionCard (1-1)
packages/ui/src/components/ui/card.tsx (5)
  • Card (85-85)
  • CardHeader (86-86)
  • CardTitle (88-88)
  • CardDescription (90-90)
  • CardContent (91-91)
packages/ui/src/components/ui/skeleton.tsx (1)
  • Skeleton (13-13)
packages/trpc/src/lib/posthog-client.ts (1)
packages/trpc/src/env.ts (1)
  • env (4-16)
⏰ 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). (7)
  • GitHub Check: Deploy Marketing
  • GitHub Check: Deploy Docs
  • GitHub Check: Deploy API
  • GitHub Check: Deploy Admin
  • GitHub Check: Deploy Web
  • GitHub Check: Build
  • GitHub Check: Lint
🔇 Additional comments (32)
apps/admin/src/app/(dashboard)/components/MetricCard/index.ts (1)

1-1: LGTM!

Clean barrel export following the component organization guidelines.

apps/admin/src/app/(dashboard)/components/MetricCard/MetricCard.tsx (1)

23-54: Well-structured component with proper state handling.

The component correctly handles the four states (loading, error, value present, no data) in the right priority order. The default formatter using toLocaleString() is a sensible choice, and the headerAction slot provides good flexibility.

apps/admin/src/app/(dashboard)/components/WAUTrendChart/WAUTrendChart.tsx (1)

1-110: LGTM! Well-structured chart component.

The component demonstrates solid React and TypeScript practices with proper error handling, loading states, and defensive coding. The empty state styling with dashed borders aligns with the PR's UX consistency goals.

packages/trpc/src/router/analytics/analytics.ts (2)

32-54: LGTM! Helper functions are well-implemented.

Both formatFunnelResults and formatWeekData handle edge cases properly with defensive null checks and correct percentage calculations.


57-96: LGTM! These endpoints are safely implemented.

The getFullJourneyFunnel, getRetention, getTrafficSources, and getRevenueTrend endpoints use structured queries or hardcoded values, avoiding SQL injection risks. The revenue endpoint appropriately returns stub data as documented.

Also applies to: 145-170, 272-318, 320-347

packages/trpc/src/lib/posthog-client.ts (2)

132-161: Verify PostHog credentials before API calls.

The API authentication (lines 145-147) depends on env.POSTHOG_API_KEY and env.POSTHOG_PROJECT_ID. As flagged in packages/trpc/src/env.ts, these variables currently allow empty strings. Once that validation is fixed, this implementation will be safe.

The caching strategy and error handling are well-implemented.

The critical validation issue must be addressed in packages/trpc/src/env.ts (lines 8-9) to ensure these credentials are non-empty.


163-233: LGTM! Query helper functions are well-structured.

The executeFunnelQuery, executeHogQLQuery, and executeRetentionQuery functions properly wrap queries with appropriate defaults and structure. The TypeScript typing ensures type safety throughout.

packages/trpc/src/router/analytics/index.ts (1)

1-1: LGTM! Standard barrel export.

apps/admin/src/app/(dashboard)/layout.tsx (1)

42-42: LGTM! Breadcrumb text updates align with rebranding.

The changes from "Admin Panel" to "Superset" and "Dashboard" to "Home" are purely presentational and consistent with the dashboard enhancements in this PR.

Also applies to: 46-46

apps/admin/src/app/(dashboard)/components/WAUTrendChart/index.ts (1)

1-1: LGTM! Standard barrel export following project conventions.

apps/admin/src/app/(dashboard)/components/DemoCountdown/index.ts (1)

1-1: LGTM! Clean barrel export following project conventions.

The barrel export pattern aligns with the project structure guidelines and enables clean component imports. Based on learnings, this follows the expected pattern: one folder per component with index.ts barrel export.

apps/admin/src/app/(dashboard)/components/FunnelChart/index.ts (1)

1-1: LGTM! Consistent barrel export pattern.

apps/admin/src/app/(dashboard)/components/WeekPicker/index.ts (1)

1-1: LGTM! Follows established component organization pattern.

apps/admin/src/app/(dashboard)/components/SignupsTrendChart/index.ts (1)

1-1: LGTM! Clean barrel export.

apps/admin/src/app/(dashboard)/components/LeaderboardTable/index.ts (1)

1-1: LGTM! Consistent with other dashboard components.

apps/admin/src/app/(dashboard)/components/RevenueTrendChart/index.ts (1)

1-1: LGTM! Standard barrel export.

apps/admin/src/app/(dashboard)/components/TimeRangePicker/index.ts (1)

1-1: LGTM! Proper use of type-only export.

Correctly uses the type keyword for the TimeRange type export, which is a TypeScript best practice that enables better tree-shaking and clearer intent for type-only exports.

apps/admin/src/app/(dashboard)/components/RetentionCard/index.ts (1)

1-1: LGTM! Clean barrel export completing the dashboard components suite.

apps/admin/src/app/(dashboard)/components/TrafficSourcesChart/index.ts (1)

1-1: LGTM!

Clean barrel export following the project's component organization pattern.

apps/admin/src/app/layout.tsx (1)

25-28: LGTM!

Metadata updates align with the dashboard branding changes across the PR.

apps/admin/src/app/(dashboard)/components/AppSidebar/AppSidebar.tsx (1)

62-65: LGTM on the active route detection logic.

The isActive helper correctly handles the root path with an exact match while using startsWith for nested routes, preventing false positives on "/".

apps/admin/src/app/(dashboard)/page.tsx (1)

65-142: LGTM on the dashboard composition.

Clean structure with consistent patterns across all chart components:

  • Each chart receives data, isLoading, error, and headerAction props uniformly.
  • Time range state is properly lifted and controlled at the page level.
  • Loading and error states are delegated appropriately to child components.
apps/admin/src/app/(dashboard)/components/TimeRangePicker/TimeRangePicker.tsx (1)

19-35: LGTM!

Clean implementation with proper handling of the empty-value case in onValueChange. The type assertion on line 24 is acceptable here since the ToggleGroupItem values are constrained to the TIME_RANGES array.

packages/trpc/src/root.ts (1)

4-4: LGTM!

Clean integration of the analytics router following the established pattern for router registration.

apps/admin/src/app/(dashboard)/components/SignupsTrendChart/SignupsTrendChart.tsx (1)

39-111: LGTM!

Well-structured chart component with:

  • Proper loading, error, and empty state handling
  • Consistent empty-state styling with dashed borders (matching PR objectives)
  • Reasonable tick interval calculation for readability
  • Clean separation of chart configuration
apps/admin/src/app/(dashboard)/components/RetentionCard/RetentionCard.tsx (2)

32-55: LGTM on RetentionCell implementation.

The color scale logic is clear and the inline styles are appropriate for runtime-computed colors. Good handling of null rates with a dash placeholder.


57-115: LGTM on the RetentionCard structure.

  • Consistent loading/error/empty state handling matching other dashboard components
  • Clean table structure with proper accessibility (semantic <table>, <thead>, <tbody>)
  • Empty state uses consistent dashed-border styling
apps/admin/src/app/(dashboard)/components/WeekPicker/WeekPicker.tsx (1)

41-63: LGTM! Well-implemented week navigation control.

The component correctly handles boundary conditions, prevents layout shift with fixed-width labels, and provides clear visual feedback for disabled states. The suppressHydrationWarning on line 32 would be appropriate if this component displayed real-time data.

apps/admin/src/app/(dashboard)/components/TrafficSourcesChart/TrafficSourcesChart.tsx (1)

54-130: LGTM! Excellent implementation of traffic sources visualization.

The component correctly:

  • Handles all state conditions (loading, error, empty, data) with appropriate UI
  • Uses Tailwind 500 palette colors for consistent visual design
  • Implements safe color cycling with modulo operator (line 120)
  • Formats numbers with locale awareness via toLocaleString()
  • Provides clear tooltips with visitor counts

The vertical bar chart layout with right-aligned labels provides good readability for source names.

apps/admin/src/app/(dashboard)/components/LeaderboardTable/LeaderboardTable.tsx (1)

40-129: Well-structured leaderboard with good UX patterns.

The component implements:

  • Detailed loading skeletons matching the final layout
  • Consistent error and empty state messaging
  • Clear ranking display with user avatars
  • Flexible header action support
  • Configurable count label

The table layout and styling provide good readability.

apps/admin/src/app/(dashboard)/components/DemoCountdown/DemoCountdown.tsx (1)

29-39: The YC logo asset exists at apps/admin/public/yc-logo.png and will resolve correctly.

packages/shared/src/constants.ts (1)

42-42: Verify backwards compatibility if this changes an existing cookie name.

The POSTHOG_COOKIE_NAME constant is properly centralized and consistently used across all apps (web, admin, marketing, docs) as the persistence_name parameter in PostHog SDK initialization. The value "superset" is valid and compliant with cookie naming standards.

However, if this constant was previously set to a different value (e.g., "ph_superset"), existing users will not be recognized under the new cookie name, potentially fragmenting analytics data. Verify:

  1. Was this constant previously set to a different value?
  2. Do you need to preserve user tracking continuity by reading from old cookie names if the new one doesn't exist?

Comment on lines +18 to +24
useEffect(() => {
const timer = setInterval(() => {
setDaysLeft(calculateDaysLeft());
}, 100);

return () => clearInterval(timer);
}, []);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Excessive update frequency wastes CPU and battery.

The 100ms interval (line 20) updates the countdown 10 times per second, which is unnecessary for a day counter. Even with 6 decimal places, updating every second (1000ms) would provide smooth animation while being 10× more efficient.

This impacts performance, especially on mobile devices and battery-powered laptops.

Recommended fix: Change to 1 second interval
 	useEffect(() => {
 		const timer = setInterval(() => {
 			setDaysLeft(calculateDaysLeft());
-		}, 100);
+		}, 1000); // Update once per second
 
 		return () => clearInterval(timer);
 	}, []);

Alternatively, consider reducing decimal precision to 2-3 places and updating less frequently:

-	const fraction = (daysLeft - wholeDays).toFixed(6).slice(1); // ".466739"
+	const fraction = (daysLeft - wholeDays).toFixed(2).slice(1); // ".47"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
const timer = setInterval(() => {
setDaysLeft(calculateDaysLeft());
}, 100);
return () => clearInterval(timer);
}, []);
useEffect(() => {
const timer = setInterval(() => {
setDaysLeft(calculateDaysLeft());
}, 1000); // Update once per second
return () => clearInterval(timer);
}, []);
🤖 Prompt for AI Agents
In apps/admin/src/app/(dashboard)/components/DemoCountdown/DemoCountdown.tsx
around lines 18-24, the setInterval is currently 100ms which updates the day
counter 10×/sec and wastes CPU/battery; change the interval to 1000ms (1 second)
so the countdown updates once per second, keep the existing cleanup, and
optionally reduce decimal precision in calculateDaysLeft to 2-3 places (or
format the displayed value) to further lower update frequency and rendering
work.

Comment on lines +99 to +108
<Avatar className="h-8 w-8">
<AvatarImage src={entry.avatarUrl ?? undefined} />
<AvatarFallback>
{entry.name
.split(" ")
.map((n) => n[0])
.join("")
.toUpperCase()
.slice(0, 2)}
</AvatarFallback>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avatar fallback handles edge cases inconsistently.

The initials generation logic has edge cases that could produce poor UX:

  1. Empty or whitespace-only names produce no initials
  2. Single-word names produce one character instead of two
  3. Names with multiple consecutive spaces could access undefined

While these won't cause crashes, they degrade the user experience.

Recommended fix: Defensive initials generation
 											<AvatarFallback>
-													{entry.name
-														.split(" ")
-														.map((n) => n[0])
-														.join("")
-														.toUpperCase()
-														.slice(0, 2)}
+													{entry.name
+														.trim()
+														.split(/\s+/)  // Split on any whitespace
+														.filter(n => n.length > 0)
+														.map((n) => n[0])
+														.join("")
+														.toUpperCase()
+														.slice(0, 2) || "?"}  // Fallback for empty names
 											</AvatarFallback>
🤖 Prompt for AI Agents
In
apps/admin/src/app/(dashboard)/components/LeaderboardTable/LeaderboardTable.tsx
around lines 99 to 108, the AvatarFallback initials logic is brittle: it can
produce empty strings for blank/whitespace names, return only one character for
single-word names, and may read undefined when encountering extra spaces;
replace that logic with a defensive initials generator that trims the name,
splits on one-or-more whitespace, filters out empty segments, uppercases parts,
then returns either the first two letters of the first segment for single-word
names or the first letters of the first two segments for multi-word names
(fallback to a placeholder like "?" or the first available character if nothing
valid) so initials are always 1–2 characters and never undefined.

Comment thread packages/trpc/src/env.ts
Comment on lines +8 to +9
POSTHOG_API_KEY: z.string(),
POSTHOG_PROJECT_ID: z.string(),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Enforce non-empty validation for PostHog credentials.

The PostHog environment variables lack .min(1) validation, allowing empty strings to pass. This is inconsistent with other required credentials (CLERK_SECRET_KEY, BLOB_READ_WRITE_TOKEN) and will cause runtime failures when the PostHog client attempts API authentication in packages/trpc/src/lib/posthog-client.ts (lines 145-147).

🔎 Proposed fix
 	server: {
 		CLERK_SECRET_KEY: z.string().min(1),
 		BLOB_READ_WRITE_TOKEN: z.string().min(1),
-		POSTHOG_API_KEY: z.string(),
-		POSTHOG_PROJECT_ID: z.string(),
+		POSTHOG_API_KEY: z.string().min(1),
+		POSTHOG_PROJECT_ID: z.string().min(1),
 	},
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
POSTHOG_API_KEY: z.string(),
POSTHOG_PROJECT_ID: z.string(),
POSTHOG_API_KEY: z.string().min(1),
POSTHOG_PROJECT_ID: z.string().min(1),
🤖 Prompt for AI Agents
In packages/trpc/src/env.ts around lines 8 to 9, the POSTHOG_API_KEY and
POSTHOG_PROJECT_ID schemas are missing .min(1) checks so empty strings pass
validation; update both z.string() entries to z.string().min(1) to enforce
non-empty values consistent with other secrets, rebuild types if needed, and run
tests to ensure the PostHog client will receive valid credentials.

Comment on lines +118 to +129
const { results } = await executeHogQLQuery<[[number]]>(`
SELECT count(DISTINCT person_id) as wau_users
FROM (
SELECT person_id, count(DISTINCT toDate(timestamp)) as active_days
FROM events
WHERE timestamp >= now() - INTERVAL ${weekStart} DAY
AND timestamp < now() - INTERVAL ${weekEnd} DAY
AND event = 'workspace_created'
GROUP BY person_id
HAVING active_days >= 3
)
`);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

SQL injection risk: use parameterized queries or validate integer values.

The HogQL query directly interpolates weekStart and weekEnd values (lines 123-124) into the SQL string. While these are computed from a validated numeric input, string interpolation in SQL queries is inherently risky and violates secure coding practices.

If the validation logic is modified or bypassed in the future, this becomes an exploitable SQL injection vector.

🔎 Recommended fix

Since HogQL may not support parameterized queries, explicitly validate and sanitize the integer values before interpolation:

 		.query(async ({ input }) => {
 			const days = input?.days ?? 30;
 			const numWeeks = Math.ceil(days / 7);
+			
+			// Ensure integer values to prevent SQL injection
+			if (!Number.isInteger(days) || days < 7 || days > 180) {
+				throw new Error('Invalid days parameter');
+			}
 
 			// Calculate WAU for each week
 			const weeklyData: { week: string; count: number }[] = [];
 
 			for (let i = numWeeks - 1; i >= 0; i--) {
 				const weekEnd = i * 7;
 				const weekStart = weekEnd + 7;
+				
+				// Verify computed values are safe integers
+				if (!Number.isSafeInteger(weekStart) || !Number.isSafeInteger(weekEnd)) {
+					throw new Error('Invalid week calculation');
+				}
 
 				// Only count workspace_created as meaningful product usage
 				const { results } = await executeHogQLQuery<[[number]]>(`

Consider abstracting HogQL queries into a query builder that handles proper value sanitization.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/trpc/src/router/analytics/analytics.ts around lines 118 to 129, the
HogQL query interpolates weekStart and weekEnd directly, creating an SQL
injection risk; ensure these values are strictly validated and sanitized before
interpolation: coerce to Number, verify Number.isInteger(value) and value is
within an acceptable range (e.g., 0..365), or throw on invalid input, and then
use the sanitized integer variables in the template; optionally extract this
validation into a small helper or a HogQL query builder to centralize
sanitization.

Comment on lines +186 to +197
const { results } = await executeHogQLQuery<[string, number][]>(`
SELECT
distinct_id,
count() as workspaces_created
FROM events
WHERE event = 'workspace_created'
AND timestamp >= now() - INTERVAL ${weekStart + 7} DAY
AND timestamp < now() - INTERVAL ${weekStart} DAY
GROUP BY distinct_id
ORDER BY workspaces_created DESC
LIMIT ${limit}
`);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

SQL injection risk: validate integer values before interpolation.

Similar to the issue in getWAUTrend, this HogQL query interpolates weekStart and limit directly into the SQL string (lines 192-193, 196). Apply the same integer validation pattern to prevent potential SQL injection.

🔎 Recommended fix
 		.query(async ({ input }) => {
 			const limit = input?.limit ?? 10;
 			const weekOffset = input?.weekOffset ?? 0;
 			const weekStart = weekOffset === 0 ? 0 : -weekOffset * 7;
+			
+			// Validate safe integers
+			if (!Number.isSafeInteger(weekStart) || !Number.isSafeInteger(limit)) {
+				throw new Error('Invalid parameters');
+			}
 
 			const { results } = await executeHogQLQuery<[string, number][]>(`

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/trpc/src/router/analytics/analytics.ts around lines 186 to 197, the
HogQL query interpolates weekStart and limit directly which is a SQL injection
risk; validate and sanitize both values as integers before building the query by
applying the same pattern used in getWAUTrend: parse them to numbers (e.g.,
Number()/parseInt), ensure they are finite integers (Math.floor) and within
acceptable ranges (non-negative for weekStart, positive and capped for limit),
throw an error or fallback to a safe default if validation fails, then
interpolate only the validated integer values (or better, use a
parameterized/templated query API if available) so the constructed SQL contains
only safe integers.

Comment on lines +242 to +251
const { results } = await executeHogQLQuery<[string, number][]>(`
SELECT
formatDateTime(toDate(timestamp), '%Y-%m-%d') as date,
count(DISTINCT person_id) as signups
FROM events
WHERE event = 'auth_completed'
AND timestamp >= now() - INTERVAL ${days} DAY
GROUP BY date
ORDER BY date ASC
`);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

SQL injection risk: validate integer before interpolation.

The days parameter is interpolated directly into the HogQL query (line 248). Apply integer validation as recommended in the previous endpoints.

🔎 Recommended fix
 		.query(async ({ input }) => {
 			const days = input?.days ?? 30;
+			
+			// Validate safe integer
+			if (!Number.isSafeInteger(days)) {
+				throw new Error('Invalid days parameter');
+			}
 
 			const { results } = await executeHogQLQuery<[string, number][]>(`

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/trpc/src/router/analytics/analytics.ts around lines 242 to 251, the
days variable is interpolated directly into the HogQL query creating an SQL
injection risk; validate and sanitize days before use by parsing it as an
integer (e.g., Number.parseInt or equivalent), ensure it is a finite
non-negative integer within an acceptable max bound, and throw or return a
validation error if it fails validation, then use the validated integer (not the
raw input) in the interpolated query.

Add POSTHOG_API_KEY and POSTHOG_PROJECT_ID to admin deployment
jobs in both preview and production workflows for server-side
analytics queries.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add POSTHOG_API_KEY and POSTHOG_PROJECT_ID to turbo.jsonc globalEnv
  so they're passed through to all tasks during build/dev
- Downgrade biome schema to 2.3.8 to match CLI version

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The tRPC analytics router runs in the API app, not admin.
Added POSTHOG_API_KEY and POSTHOG_PROJECT_ID to deploy-api
jobs in both preview and production workflows.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/deploy-preview.yml (1)

395-414: Remove unused PostHog server-side credentials from Admin deployment.

The Admin app's env.ts does not declare POSTHOG_API_KEY or POSTHOG_PROJECT_ID in either its server or client environment validation. Code search confirms these variables are not referenced anywhere in the Admin codebase. Only the client-side keys (NEXT_PUBLIC_POSTHOG_KEY and NEXT_PUBLIC_POSTHOG_HOST) are actually declared and used.

Remove POSTHOG_API_KEY and POSTHOG_PROJECT_ID from lines 395-396 (environment) and lines 413-414 (deploy arguments) in the Admin deployment section.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b31e7d4 and b7e7207.

📒 Files selected for processing (2)
  • .github/workflows/deploy-preview.yml
  • .github/workflows/deploy-production.yml
⏰ 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). (6)
  • GitHub Check: Deploy Admin
  • GitHub Check: Deploy API
  • GitHub Check: Deploy Marketing
  • GitHub Check: Deploy Docs
  • GitHub Check: Deploy Web
  • GitHub Check: Build
🔇 Additional comments (4)
.github/workflows/deploy-preview.yml (1)

137-159: Code implementation is correct, but verify GitHub secrets are configured in repository settings.

The PostHog server-side credentials are properly added to both the API and Admin deployments, following the same pattern as other secrets (added to build environment and Vercel deploy flags). Ensure that POSTHOG_API_KEY and POSTHOG_PROJECT_ID secrets are configured in the repository's GitHub Settings > Secrets and variables section before deploying.

.github/workflows/deploy-production.yml (3)

274-275: LGTM! Server-side PostHog credentials added to admin deployment.

The admin dashboard appropriately has both client-side tracking credentials (NEXT_PUBLIC_POSTHOG_KEY/NEXT_PUBLIC_POSTHOG_HOST) and server-side API credentials (POSTHOG_API_KEY/POSTHOG_PROJECT_ID) to support the new analytics features.


292-293: LGTM! Vercel runtime environment properly configured.

The PostHog credentials are correctly passed as runtime environment variables to match the build-time configuration, ensuring the admin analytics features work in production.


107-109: No action needed — server-side credentials are properly secured.

The POSTHOG_API_KEY and POSTHOG_PROJECT_ID are correctly defined as server-only environment variables in packages/trpc/src/env.ts (not in the client object), which prevents client-side exposure. The credentials are never exported from posthog-client.ts—only query functions that use them internally are exposed. All analytics endpoints are protected with adminProcedure, and only sanitized query results are returned to clients.

Comment on lines +87 to +88
POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }}
POSTHOG_PROJECT_ID: ${{ secrets.POSTHOG_PROJECT_ID }}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the deploy-production.yml file around lines 87-88
cat -n .github/workflows/deploy-production.yml | sed -n '80,120p'

Repository: superset-sh/superset

Length of output: 2415


🏁 Script executed:

# Search for POSTHOG_API_KEY usage in the codebase
rg -n "POSTHOG_API_KEY" --type ts --type js --type env -C 2

Repository: superset-sh/superset

Length of output: 91


🏁 Script executed:

# Search for POSTHOG_PROJECT_ID usage in the codebase
rg -n "POSTHOG_PROJECT_ID" --type ts --type js --type env -C 2

Repository: superset-sh/superset

Length of output: 91


🏁 Script executed:

# Check if there's env validation in trpc package or api directory
find . -type f \( -name "env.ts" -o -name "env.js" -o -name ".env*" \) | grep -E "(trpc|api|server)" | head -20

Repository: superset-sh/superset

Length of output: 112


🏁 Script executed:

# Look for PostHog setup in the API codebase
rg -n "posthog\|PostHog" --type ts --type js -C 2 | head -100

Repository: superset-sh/superset

Length of output: 46


🏁 Script executed:

cat -n ./apps/api/src/env.ts

Repository: superset-sh/superset

Length of output: 1430


🏁 Script executed:

cat -n ./packages/trpc/src/env.ts

Repository: superset-sh/superset

Length of output: 593


🏁 Script executed:

# Also search more broadly for posthog (case insensitive)
rg -i "posthog" --type ts --type js --type jsx --type tsx -C 3

Repository: superset-sh/superset

Length of output: 91


🏁 Script executed:

# Look for usage of these env variables in the tRPC package
rg -n "env\.|POSTHOG" packages/trpc --type ts -C 3

Repository: superset-sh/superset

Length of output: 3158


🏁 Script executed:

# Check deploy-admin section for similar setup
cat -n .github/workflows/deploy-production.yml | sed -n '265,300p'

Repository: superset-sh/superset

Length of output: 2478


🏁 Script executed:

# Search for how tRPC env is imported and used
rg -n "from.*trpc.*env|import.*env" apps/api --type ts -C 2

Repository: superset-sh/superset

Length of output: 2650


Ensure POSTHOG_API_KEY and POSTHOG_PROJECT_ID secrets are configured in GitHub repository settings.

These server-side credentials are required by the tRPC package for authenticated requests to PostHog's analytics API. Without these secrets configured, the deployment will fail during the build phase when the tRPC environment validation runs, or the PostHog integration will not function in production.

🤖 Prompt for AI Agents
.github/workflows/deploy-production.yml lines 87-88: the workflow references
POSTHOG_API_KEY and POSTHOG_PROJECT_ID secrets but they may not be configured in
the repository settings; add both secrets in the GitHub repository (Settings →
Secrets and variables → Actions) with the correct PostHog API key and project ID
values, then re-run the workflow to ensure the tRPC PostHog integration can
validate environment variables during build/deploy.

@saddlepaddle saddlepaddle merged commit 33403d3 into main Dec 23, 2025
12 checks passed
@Kitenite Kitenite deleted the posthog-admin-dashboard branch December 25, 2025 18:31
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.

1 participant