Skip to content

FEAT: Application Analytics (ProgramApplicationEvent)#3763

Merged
steven-tey merged 99 commits intomainfrom
application-analytics
May 3, 2026
Merged

FEAT: Application Analytics (ProgramApplicationEvent)#3763
steven-tey merged 99 commits intomainfrom
application-analytics

Conversation

@steven-tey
Copy link
Copy Markdown
Collaborator

@steven-tey steven-tey commented Apr 16, 2026

Summary by CodeRabbit

  • New Features

    • Added application event tracking to monitor partner applicant journey (visits, form starts, submissions, approvals, and rejections).
    • Launched application analytics dashboard showing funnel conversion metrics, referral source breakdowns, geographic distribution, and partner-level performance.
    • Added username field to partner profiles for better profile personalization.
  • Refactor

    • Reorganized application data models and tracking infrastructure for improved modularity.

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Apr 16, 2026

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

Project Deployment Actions Updated (UTC)
dub Ready Ready Preview May 3, 2026 3:46am

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 16, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Introduces application event tracking and analytics for partner programs, spanning database models for recording application lifecycle stages (visited, started, submitted, approved, rejected), API endpoints for event ingestion and analytics retrieval, client-side tracking components and hooks, comprehensive analytics UI components with filtering and visualization, and updates to existing approval/rejection flows to emit events.

Changes

Cohort / File(s) Summary
Database Schema
packages/prisma/schema/application.prisma, packages/prisma/schema/partner.prisma, packages/prisma/schema/program.prisma
New ProgramApplication and ProgramApplicationEvent models with lifecycle timestamps (visited/started/submitted/approved/rejected), referral tracking, and metadata; Partner extended with username and referral relations; ProgramEnrollment linked to ProgramApplicationEvent.
Event Tracking Infrastructure
apps/web/lib/application-events/schema.ts, apps/web/lib/application-events/utils.ts, apps/web/lib/application-events/update-application-event.ts
Zod schemas for event validation and analytics queries, cookie name utility, and functions to persist event state (mark submitted, track approvals/rejections).
Event Ingestion API
apps/web/app/(ee)/api/track/application/route.ts
POST/OPTIONS handlers for tracking visit and start events; enforces per-IP rate limiting (10/10s), bot detection, cookie-based deduplication, and validates against schema; dispatches to appropriate event handlers.
Analytics API Routes
apps/web/app/(ee)/api/applications/events/route.ts, apps/web/app/(ee)/api/applications/analytics/route.ts
GET endpoints for retrieving event lists with pagination/sorting and aggregated analytics grouped by count, country, referral source, partner, partner group, or timeseries; both support optional filtering.
Client-side Tracking
apps/web/ui/application-analytics.tsx, apps/web/ui/partners/groups/design/.../program-application-form.tsx, apps/web/ui/partners/program-application-sheet.tsx
ApplicationAnalytics component for tracking page visits, useTrackApplyStart hook for tracking form interaction start; integrated into application forms.
Analytics UI Components
apps/web/app/app.dub.co/.../program/analytics/application-events/applications-funnel-chart.tsx, ...applications-breakdown-cards.tsx, ...applications-partners-table.tsx
Funnel chart showing visit→start→submit→enrolled conversion, breakdown cards for referral source and country analytics, partners table with filtering and lifecycle stage details.
Analytics Hooks and Utilities
apps/web/app/app.dub.co/.../application-events/use-application-events.ts, ...use-applications-analytics.ts, ...use-applications-analytics-query.ts, ...use-applications-analytics-filters.tsx
Hooks for fetching events and analytics data, parsing query parameters, and managing filter state (partner/group/country/referral source).
Analytics Page Integration
apps/web/app/app.dub.co/.../program/analytics/page-client.tsx
Added "Applications" tab alongside existing performance/commissions tabs; conditionally renders funnel chart, partners table, and breakdown cards based on active tab.
Partner Action Updates
apps/web/lib/actions/partners/bulk-approve-partners.ts, apps/web/lib/actions/partners/bulk-reject-partner-applications.ts, apps/web/lib/actions/partners/create-program-application.ts, apps/web/lib/actions/partners/complete-program-applications.ts, apps/web/lib/api/partners/applications/approve-partner.ts, apps/web/lib/api/partners/applications/reject-partner.ts
Updated approval/rejection/application creation flows to call trackApplicationEvents or markApplicationEventSubmitted for event persistence.
Cron and E2E Routes
apps/web/app/(ee)/api/cron/partners/auto-reject/route.ts, apps/web/app/(ee)/api/e2e/partners/pending-program-application/route.ts
Auto-reject cron now records rejected events; E2E route formatting cleaned up.
Profile and Application Pages
apps/web/app/(ee)/partners.dub.co/.../apply/page.tsx, apps/web/app/(ee)/partners.dub.co/.../page.tsx, apps/web/app/(ee)/partners.dub.co/.../profile/profile-details-form.tsx, apps/web/app/(ee)/partners.dub.co/.../marketplace/.../page.tsx
ApplicationAnalytics component added to application pages for tracking visits; profile form extended with username field (validation, uniqueness check).
Types and Constants
apps/web/lib/types.ts, apps/web/lib/api/create-id.ts
New TypeScript types exported for application events/analytics; createId now supports "pga_evt_" prefix.
Testing and Seeding
apps/web/tests/partners/applications/approve-reject-partner-application.test.ts, apps/web/scripts/dev/seed-application-events.ts
Test suite formatting refactored; new seeding script generates realistic application event timelines for development.
Miscellaneous
apps/web/app/app.dub.co/.../billing/plan-usage.tsx
Import statement reordering (no functional change).

Sequence Diagram

sequenceDiagram
    participant Client as Client Browser
    participant AnalyticsComp as ApplicationAnalytics<br/>Component
    participant TrackAPI as POST<br/>/api/track/application
    participant Prisma as Prisma<br/>Database
    participant RateLimiter as Rate Limiter<br/>& Bot Detection

    Client->>AnalyticsComp: Route change (pathname)
    AnalyticsComp->>TrackAPI: POST { eventName: "visit", url, referrer }
    TrackAPI->>RateLimiter: Check IP rate limit (10/10s)
    alt Rate limit exceeded
        TrackAPI-->>AnalyticsComp: 429 Error
    else Rate limit OK
        RateLimiter->>TrackAPI: Allow
        TrackAPI->>Prisma: Insert ProgramApplicationEvent<br/>(visited_at=now)
        Prisma-->>TrackAPI: Event created
        TrackAPI->>TrackAPI: Set HTTP-only cookie<br/>(event ID, program-scoped)
        TrackAPI-->>AnalyticsComp: 202 OK
    end
Loading
sequenceDiagram
    participant Dashboard as Analytics<br/>Dashboard
    participant AnalyticsAPI as GET<br/>/api/applications/analytics
    participant Prisma as Prisma<br/>Query
    participant Dashboard2 as Render<br/>UI

    Dashboard->>AnalyticsAPI: Fetch with groupBy=timeseries<br/>(start, end, timezone)
    AnalyticsAPI->>Prisma: Query ProgramApplicationEvent<br/>aggregate counts by time bucket
    Prisma-->>AnalyticsAPI: Aggregated rows per bucket
    AnalyticsAPI->>AnalyticsAPI: Fill missing buckets<br/>with zero values
    AnalyticsAPI-->>Dashboard: JSON [ { bucket, count } ]
    Dashboard->>Dashboard2: Render TimeSeriesChart<br/>with data points

    Dashboard->>AnalyticsAPI: Fetch with groupBy=partner
    AnalyticsAPI->>Prisma: GROUP BY partnerId,<br/>count stages
    Prisma-->>AnalyticsAPI: Partner aggregate rows
    AnalyticsAPI->>AnalyticsAPI: Join partner metadata
    AnalyticsAPI-->>Dashboard: JSON [ { partner, stages } ]
    Dashboard->>Dashboard2: Render ApplicationsPartnersTable
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • pepeladeira
  • devkiran

🐰 Hop into analytics delight,
Events tracked from visit to site,
Partners funnel bright,
Dashboard shows the light,
Applications in flight!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.60% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately summarizes the main change: adding application analytics functionality centered on a new ProgramApplicationEvent model and related tracking infrastructure.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch application-analytics

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get your free trial and get 200 agent minutes per Slack user (a $50 value).


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.

…alytics components. Update schema and API integration for improved event handling.
…ctoring event submission logic. Update event handling to include partner ID and submitted timestamp, and improve URL slug identification for program tracking.
@steven-tey
Copy link
Copy Markdown
Collaborator Author

@coderabbitai final review, any breaking changes, security issues?

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 3, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

Let me pull the key files to do a thorough security and breaking-change analysis.


🏁 Script executed:

#!/bin/bash
# Read the new tracking route handler
fd "route.ts" --full-path | grep "track/application" | xargs cat

# Read the application-events schema
fd "schema.ts" --full-path | grep "application-events" | xargs cat

# Read the update-application-event utility
fd "update-application-event.ts" | xargs cat

# Read the utils file
fd "utils.ts" --full-path | grep "application-events" | xargs cat

Length of output: 14067


🏁 Script executed:

#!/bin/bash
# Check for any remaining references to old ProgramApplication location in program.prisma
# and references across the codebase that might still expect the old schema structure
rg "ProgramApplicationRejectionReason" --type=ts -n -C2

# Check for old prisma import patterns
rg "programApplication\." --type=ts -n -C2 | head -80

# Check for references to the removed fields in program.prisma
rg "rejectionReason|rejectionNote|reviewedAt" --type=ts -n -C2 | head -60

Length of output: 20450


🏁 Script executed:

#!/bin/bash
# Check COMMON_CORS_HEADERS definition
rg "COMMON_CORS_HEADERS" --type=ts -n -C3 | head -40

# Check cookie settings (SameSite, Secure, HttpOnly flags)
rg "APPLICATION_ID_COOKIE" --type=ts -n -C5

# Check rate limiting implementation
rg "getIP\|ratelimit\|Ratelimit" --type=ts -n -C3 | head -60

# Check how 'via' param is used / partner lookup from referral
rg "via\|referredByPartnerId\|referralSource" --type=ts -n -C3 | head -60

Length of output: 6506


@steven-tey: Here is the final security and breaking-change review for PR #3763.


🔐 Security Issues

1. Missing sameSite attribute on tracking cookie (Medium)

In trackVisitEvent, the cookie is set without an explicit sameSite attribute:

cookieStore.set(cookieName, programApplicationEvent.id, {
  httpOnly: true,
  maxAge: APPLICATION_ID_COOKIE_MAX_AGE,
  secure: process.env.NODE_ENV === "production",
  // ❌ missing sameSite
  // ❌ missing path
});

Browsers default to SameSite=Lax, but it should be explicit. Additionally, without a path, the cookie scope defaults to the current request path (e.g., /api/track/application), which could prevent it from being read by the server components that later check it. Recommended fix:

cookieStore.set(cookieName, programApplicationEvent.id, {
  httpOnly: true,
  maxAge: APPLICATION_ID_COOKIE_MAX_AGE,
  secure: process.env.NODE_ENV === "production",
  sameSite: "lax",
  path: "/",
});

2. Unauthenticated endpoint allows fake event injection (Medium)

POST /api/track/application is fully public — any caller can fabricate visit/start events for any program by submitting an arbitrary url containing a valid program slug. There is no validation that the url belongs to an allowed domain for the program. Consider adding an origin or Referer header check against the program's configured domain, or at minimum add per-program rate limiting in addition to per-IP.


3. Wildcard CORS on a cookie-setting endpoint (Low)

COMMON_CORS_HEADERS sets "Access-Control-Allow-Origin": "*". This is acceptable for pure analytics, but cookies set server-side are never sent back to cross-origin callers using credentialed requests when CORS is wildcard, so the functional impact is minimal. However, the wildcard does allow any web origin to fire the tracking endpoint. This is consistent with similar public tracking endpoints in the codebase.


4. Partner username enumeration via via parameter (Low)

The via query param lookup logs server-side whether a partner username was found or not:

console.log(`Partner not found for username ${searchParams.via}. Not setting referredByPartnerId.`);

This is server-side only (no client-visible distinction), so the practical risk is low, but it does mean the endpoint can silently probe the new partner.username uniqueness index.


5. Silent error swallowing (Low)

There are multiple bare catch {} blocks in the route handler (trackVisitEvent, trackStartEvent, markApplicationEventSubmitted, trackApplicationEvents). While intentional to avoid blocking flows, this makes it impossible to observe failures in production without Axiom/log tooling. Consider at least catch (e) { console.error(..., e) } consistently.


⚠️ Breaking Changes

1. APPLICATION_EVENT_STAGES missing "rejected" — type inconsistency

APPLICATION_EVENT_STAGES in schema.ts is:

["visited", "started", "submitted", "approved"] as const

But applicationEventsQuerySchema accepts "rejected" as a valid event value, and trackApplicationEvents takes event: "approved" | "rejected". The derived type ApplicationEventStages (used in STAGE_VALUE_KEY and UI components) doesn't include "rejected", creating a type gap. If any UI component ever needs to show rejected analytics, this will require a broader change.


2. New @@unique([programId, partnerId]) constraint on ProgramApplicationEvent — migration risk

@@unique([programId, partnerId])

If the database already has ProgramApplicationEvent rows with duplicate (programId, partnerId) pairs from prior data, the migration adding this constraint will fail. Ensure either a data-cleanup migration runs first or that this table is newly created in this PR.


3. ProgramApplicationRejectionReason / ProgramApplication moved between Prisma schema files

These were removed from program.prisma and added to application.prisma. TypeScript-level imports from @dub/prisma/client are unaffected, but Prisma migrations will reflect the model move. This is safe as long as the generated migration does not drop and recreate the table — worth verifying via prisma migrate dev --preview-feature output.


4. Missing groupId in sharedFilterSchema

The sharedFilterSchema used by both analytics endpoints does not include groupId:

const sharedFilterSchema = analyticsQuerySchema.pick({...}).extend({
  partnerId: ...,
  referralSource: ...,
  country: ...,
  // ❌ no groupId
});

But the analytics route handler in GET /api/applications/analytics does filter by groupId. This means groupId filtering is not validated at the schema level and its query param will be silently ignored if the handler reads it from searchParams outside the schema.


Summary Table

Issue Severity Type
Missing sameSite/path on cookie Medium Security
Unauthenticated fake event injection Medium Security
APPLICATION_EVENT_STAGES missing "rejected" Medium Breaking/Correctness
@@unique migration risk on existing data Medium Breaking
Wildcard CORS Low Security
via username enumeration Low Security
Silent catch {} blocks Low Observability
groupId absent from sharedFilterSchema Low Correctness
identityProgramSlug typo (should be identify...) Trivial Code quality

@steven-tey steven-tey merged commit 2d239f0 into main May 3, 2026
11 checks passed
@steven-tey steven-tey deleted the application-analytics branch May 3, 2026 03:52
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.

3 participants