Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@steven-tey
Copy link
Collaborator

@steven-tey steven-tey commented Nov 11, 2025

Summary by CodeRabbit

  • New Features

    • Added Net Revenue, EPC, Avg LTV, Click→Lead, Click→Conversion, Lead→Conversion, and ROAS columns to partners table with header tooltips, formatting, and sorting.
    • Table headers now show tooltips explaining metric definitions.
    • Background job now computes and updates these partner metrics automatically, with more efficient batching and clearer empty-batch responses.
  • Refactor

    • Ranking and related logic now use click‑to‑conversion metrics.
  • Chores

    • Data schema, indexes, and a program-category association added to persist and query the new metrics.

@vercel
Copy link
Contributor

vercel bot commented Nov 11, 2025

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

Project Deployment Preview Updated (UTC)
dub Ready Ready Preview Nov 13, 2025 8:58pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 11, 2025

Walkthrough

Updates partner metric pipeline and UI: new enrollment analytics fields and Prisma indexes, cron stream dedupes program:partner events then fetches/merges link & commission stats to compute derived metrics and batch-update ProgramEnrollment; ranking and table schemas/columns updated to surface the new metrics and header tooltips added.

Changes

Cohort / File(s) Summary
Stats aggregation & updater
apps/web/app/(ee)/api/cron/streams/update-partner-stats/route.ts
Added ProgramEnrollmentStats type and ProgramEnrollment import; dedupes program:partner keys from Redis events; parallel fetch of link & commission stats; merge results; compute derived metrics (netRevenue, earningsPerClick, averageLifetimeValue, clickToLeadRate, clickToConversionRate, leadToConversionRate, returnOnAdSpend, daysSinceLastConversion, consistencyScore); SUB_BATCH_SIZE sub-batching and conditional batched updates; early-return for empty batches.
Dashboard partners table
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx
Added columns and headerTooltips for netRevenue, earningsPerClick (EPC), averageLifetimeValue (Avg LTV), clickToLeadRate, clickToConversionRate, leadToConversionRate, returnOnAdSpend; per-column rendering/formatting, typed accessors, and updated sortable & default visible columns.
Ranking logic
apps/web/lib/api/network/calculate-partner-ranking.ts
Swapped metric usage from conversionRate to clickToConversionRate across filters, aggregations, CASE scoring, and final displayed metric selection and documentation.
Zod schemas / API validation
apps/web/lib/zod/schemas/partners.ts
Added sortBy options and nullable numeric fields to EnrolledPartnerSchema: netRevenue, earningsPerClick, averageLifetimeValue, clickToLeadRate, clickToConversionRate, leadToConversionRate, returnOnAdSpend; updated totalSaleAmount and netRevenue descriptions.
Prisma schema
packages/prisma/schema/program.prisma
Added analytic fields to ProgramEnrollment (netRevenue, earningsPerClick, averageLifetimeValue, clickToLeadRate, clickToConversionRate, leadToConversionRate, returnOnAdSpend); deprecated legacy leadConversionRate/conversionRate; added indexes for new metrics; added ProgramCategory model with unique constraint.
UI table helper
packages/ui/src/table/table.tsx
Introduced HeaderWithTooltip wrapper, conditional header tooltip rendering, and imports for Tooltip and ReactNode; header rendering now wraps content when headerTooltip meta is present.

Sequence Diagram(s)

sequenceDiagram
    participant Redis as Redis stream
    participant Cron as Cron handler
    participant Fetch as Link & Commission fetchers
    participant Agg as Merger / Metrics calculator
    participant DB as Prisma (ProgramEnrollment)
    participant UI as Dashboard

    Note over Redis,Cron: batch of partner events arrives
    Redis->>Cron: deliver events
    Cron->>Cron: dedupe programId:partnerId keys
    Cron->>Fetch: parallel fetch(linkStats) and fetch(commissionStats)
    Fetch-->>Cron: linkStats, commissionStats
    Cron->>Agg: merge stats per programId:partnerId
    Agg->>Agg: compute derived metrics (EPC, AvgLTV, rates, ROAS, consistency)
    Agg->>DB: update ProgramEnrollment (sub-batches, conditional fields)
    DB-->>Agg: update acknowledgements
    UI->>DB: query enrollments (sortable by new metrics)
    DB-->>UI: return enriched rows
    UI->>UI: render columns and header tooltips
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas needing extra attention:
    • Division-by-zero, nullish handling, and numeric type boundaries in EPC, Avg LTV, ROAS, and rate calculations.
    • Correctness of programId:partnerId deduplication and merging logic from Redis events.
    • Batched update payloads: ensure only defined fields are written and SUB_BATCH_SIZE behavior is correct.
    • Prisma schema migration impacts (new fields, indexes, deprecated fields) and any downstream query adjustments.
    • Consistent replacement of conversionRate → clickToConversionRate across queries and aggregations.

Possibly related PRs

Poem

🐇 I tally clicks and coins with care,

EPC and ROAS hop through the air.
Net revenue hums beneath the moon,
Tooltips sparkle, columns tune.
Indexes drum — partners leap in tune. 🎉

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.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 title 'Add more calculated stats' accurately summarizes the main change - introducing six new calculated metrics (EPC, Avg LTV, click-to-lead rate, click-to-conversion rate, lead-to-conversion rate, ROAS) throughout the partner stats and ranking systems.
✨ 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 calculated-stats

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
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: 4

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5baf89d and 84680ad.

📒 Files selected for processing (6)
  • apps/web/app/(ee)/api/cron/streams/update-partner-stats/route.ts (7 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx (3 hunks)
  • apps/web/lib/api/network/calculate-partner-ranking.ts (5 hunks)
  • apps/web/lib/zod/schemas/partners.ts (2 hunks)
  • packages/prisma/schema/program.prisma (5 hunks)
  • packages/ui/src/table/table.tsx (3 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-09-24T15:47:40.293Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/about-you-form.tsx:152-157
Timestamp: 2025-09-24T15:47:40.293Z
Learning: The Button component from dub/ui automatically adds type="button" when an onClick prop is passed, preventing accidental form submissions without requiring explicit type specification. The implementation uses: type={onClick ? "button" : type}

Applied to files:

  • packages/ui/src/table/table.tsx
📚 Learning: 2025-09-17T17:44:03.965Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2857
File: apps/web/lib/actions/partners/update-program.ts:96-0
Timestamp: 2025-09-17T17:44:03.965Z
Learning: In apps/web/lib/actions/partners/update-program.ts, the team prefers to keep the messagingEnabledAt update logic simple by allowing client-provided timestamps rather than implementing server-controlled timestamp logic to avoid added complexity.

Applied to files:

  • apps/web/app/(ee)/api/cron/streams/update-partner-stats/route.ts
📚 Learning: 2025-05-29T04:45:18.504Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2448
File: packages/email/src/templates/partner-program-summary.tsx:0-0
Timestamp: 2025-05-29T04:45:18.504Z
Learning: In the PartnerProgramSummary email template (packages/email/src/templates/partner-program-summary.tsx), the stat titles are hardcoded constants ("Clicks", "Leads", "Sales", "Earnings") that will always match the ICONS object keys after toLowerCase() conversion, so icon lookup failures are not possible.

Applied to files:

  • apps/web/app/(ee)/api/cron/streams/update-partner-stats/route.ts
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx
🧬 Code graph analysis (2)
packages/ui/src/table/table.tsx (1)
packages/ui/src/tooltip.tsx (1)
  • Tooltip (64-115)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx (2)
apps/web/lib/types.ts (1)
  • EnrolledPartnerProps (450-450)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
🔇 Additional comments (6)
packages/ui/src/table/table.tsx (2)

474-488: LGTM! Clean tooltip integration.

The IIFE pattern cleanly extracts the header content and tooltip metadata, then delegates to the HeaderWithTooltip wrapper. This preserves sortable header behavior while adding optional tooltip support.


700-719: LGTM! Simple and effective tooltip wrapper.

The component provides a clear visual affordance with the dotted underline and cursor-help styling, making it obvious to users that additional information is available on hover.

apps/web/lib/api/network/calculate-partner-ranking.ts (1)

61-61: LGTM! Consistent migration to clickToConversionRate.

All references to the legacy conversionRate field have been properly updated to use clickToConversionRate throughout the ranking logic, aggregations, and display mappings. The changes are consistent with the schema updates across the PR.

Also applies to: 146-146, 170-172, 227-227

apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx (1)

300-357: LGTM! Well-structured metric columns with helpful tooltips.

The new metric columns (EPC, Avg LTV, and rate metrics) are properly implemented with:

  • Appropriate formatting (currency for EPC/LTV, percentages for rates)
  • Null/undefined handling with fallback to "-"
  • Clear tooltips explaining each calculation
  • Proper typing with EnrolledPartnerProps
  • Inclusion in sortableColumns array

The tooltip descriptions effectively communicate how each metric is derived.

Also applies to: 421-427

packages/prisma/schema/program.prisma (2)

150-156: LGTM! Indexes will optimize sorting queries.

The new composite indexes on [programId, status, <metric>] for all the new calculated metrics will efficiently support the sorting functionality added to the partners table. This follows the existing pattern for other sortable metrics.


205-212: LGTM! Clean category model with proper constraints.

The new ProgramCategory model is well-structured with:

  • Proper foreign key relationship with cascade delete
  • Unique constraint on [programId, category] to prevent duplicates
  • Simple, normalized design

Copy link
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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7b2816c and 60c7945.

📒 Files selected for processing (1)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-05-29T04:45:18.504Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2448
File: packages/email/src/templates/partner-program-summary.tsx:0-0
Timestamp: 2025-05-29T04:45:18.504Z
Learning: In the PartnerProgramSummary email template (packages/email/src/templates/partner-program-summary.tsx), the stat titles are hardcoded constants ("Clicks", "Leads", "Sales", "Earnings") that will always match the ICONS object keys after toLowerCase() conversion, so icon lookup failures are not possible.

Applied to files:

  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx
🧬 Code graph analysis (1)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx (2)
apps/web/lib/types.ts (1)
  • EnrolledPartnerProps (450-450)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
⏰ 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: build

Copy link
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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 60c7945 and 9a9a433.

📒 Files selected for processing (1)
  • apps/web/lib/api/network/calculate-partner-ranking.ts (5 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-05-29T04:45:18.504Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2448
File: packages/email/src/templates/partner-program-summary.tsx:0-0
Timestamp: 2025-05-29T04:45:18.504Z
Learning: In the PartnerProgramSummary email template (packages/email/src/templates/partner-program-summary.tsx), the stat titles are hardcoded constants ("Clicks", "Leads", "Sales", "Earnings") that will always match the ICONS object keys after toLowerCase() conversion, so icon lookup failures are not possible.

Applied to files:

  • apps/web/lib/api/network/calculate-partner-ranking.ts
⏰ 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: build
🔇 Additional comments (5)
apps/web/lib/api/network/calculate-partner-ranking.ts (5)

42-42: LGTM! Documentation accurately reflects the metric change.

The documentation correctly describes that clickToConversionRate is the average click-to-conversion rate across all programs.


61-61: LGTM! Filter logic correctly updated.

The condition properly filters out partners with suspiciously high (≥100%) click-to-conversion rates.


138-138: LGTM! Aggregation correctly computes average click-to-conversion rate.

The aggregation properly computes the average across all program enrollments with appropriate NULL handling.


162-164: LGTM! Similarity scoring correctly updated.

The conversion rate component of the similarity score properly uses clickToConversionRate with appropriate logarithmic scaling for rates between 0-10%.


61-61: Schema verification passed — no action required.

The clickToConversionRate field exists in the ProgramEnrollment model and is properly indexed. The code correctly uses COALESCE to handle the nullable field type.

Copy link
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: 2

♻️ Duplicate comments (1)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx (1)

332-391: Zero values display as "-" instead of formatted zeros.

All new ratio columns use truthiness checks, so legitimate zero values (e.g., 0% conversion rate or 0.00x ROAS) render as "-" instead of "0.00%"/"0.00x". Partners who generate traffic but no conversions will see blanks rather than accurate zero stats, which is misleading for performance analysis.

Apply this diff to treat only null/undefined as "no data":

         accessorFn: (d: EnrolledPartnerProps) =>
-          d.averageLifetimeValue
+          d.averageLifetimeValue != null
             ? currencyFormatter(d.averageLifetimeValue)
             : "-",
         accessorFn: (d: EnrolledPartnerProps) =>
-          d.clickToLeadRate
-            ? `${parseFloat((d.clickToLeadRate * 100).toFixed(2))}%`
+          d.clickToLeadRate != null
+            ? `${(d.clickToLeadRate * 100).toFixed(2)}%`
             : "-",
         accessorFn: (d: EnrolledPartnerProps) =>
-          d.clickToConversionRate
-            ? `${parseFloat((d.clickToConversionRate * 100).toFixed(2))}%`
+          d.clickToConversionRate != null
+            ? `${(d.clickToConversionRate * 100).toFixed(2)}%`
             : "-",
         accessorFn: (d: EnrolledPartnerProps) =>
-          d.leadToConversionRate
-            ? `${parseFloat((d.leadToConversionRate * 100).toFixed(2))}%`
+          d.leadToConversionRate != null
+            ? `${(d.leadToConversionRate * 100).toFixed(2)}%`
             : "-",
         accessorFn: (d: EnrolledPartnerProps) =>
-          d.returnOnAdSpend
-            ? `${parseFloat((d.returnOnAdSpend * 100).toFixed(2))}%`
+          d.returnOnAdSpend != null
+            ? `${d.returnOnAdSpend.toFixed(2)}x`
             : "-",

Note: The diff above also removes the unnecessary parseFloat wrapper around toFixed results and fixes the ROAS format (see next comment).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9a9a433 and 59198db.

📒 Files selected for processing (2)
  • apps/web/app/(ee)/api/cron/streams/update-partner-stats/route.ts (5 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx (3 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-05-29T04:45:18.504Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2448
File: packages/email/src/templates/partner-program-summary.tsx:0-0
Timestamp: 2025-05-29T04:45:18.504Z
Learning: In the PartnerProgramSummary email template (packages/email/src/templates/partner-program-summary.tsx), the stat titles are hardcoded constants ("Clicks", "Leads", "Sales", "Earnings") that will always match the ICONS object keys after toLowerCase() conversion, so icon lookup failures are not possible.

Applied to files:

  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx
📚 Learning: 2025-07-30T15:29:54.131Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2673
File: apps/web/ui/partners/rewards/rewards-logic.tsx:268-275
Timestamp: 2025-07-30T15:29:54.131Z
Learning: In apps/web/ui/partners/rewards/rewards-logic.tsx, when setting the entity field in a reward condition, dependent fields (attribute, operator, value) should be reset rather than preserved because different entities (customer vs sale) have different available attributes. Maintaining existing fields when the entity changes would create invalid state combinations and confusing UX.

Applied to files:

  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx
📚 Learning: 2025-09-17T17:44:03.965Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2857
File: apps/web/lib/actions/partners/update-program.ts:96-0
Timestamp: 2025-09-17T17:44:03.965Z
Learning: In apps/web/lib/actions/partners/update-program.ts, the team prefers to keep the messagingEnabledAt update logic simple by allowing client-provided timestamps rather than implementing server-controlled timestamp logic to avoid added complexity.

Applied to files:

  • apps/web/app/(ee)/api/cron/streams/update-partner-stats/route.ts
🧬 Code graph analysis (2)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx (2)
apps/web/lib/types.ts (1)
  • EnrolledPartnerProps (450-450)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/(ee)/api/cron/streams/update-partner-stats/route.ts (1)
packages/prisma/index.ts (1)
  • prisma (3-9)
⏰ 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: build
🔇 Additional comments (4)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx (4)

260-310: LGTM! Helpful tooltips for existing metrics.

The headerTooltip additions provide clear explanations for each metric, improving the user experience without altering functionality.


313-321: LGTM! Net Revenue column is well-defined.

The column definition includes a clear tooltip explaining the calculation formula, and the accessor function correctly formats the currency value.


322-331: LGTM! EPC column definition is clear.

The Earnings Per Click column includes an appropriate tooltip with the calculation formula and correctly formats the value as currency.


436-450: LGTM! New metrics properly added to sortable columns.

All new calculated metrics are correctly included in the sortableColumns array, enabling users to sort by these performance indicators.

Copy link
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: 0

🧹 Nitpick comments (1)
apps/web/lib/zod/schemas/partners.ts (1)

27-65: Consider adding new calculated metrics to export columns.

The netRevenue field is included in the export columns (line 58), but the other new calculated metrics (earningsPerClick, averageLifetimeValue, clickToLeadRate, clickToConversionRate, leadToConversionRate, returnOnAdSpend) are not.

If these metrics are valuable for partner performance analysis, consider adding them to the export columns array to allow users to export this data.

Example additions:

  { id: "earningsPerClick", label: "EPC", default: false, numeric: true },
  { id: "averageLifetimeValue", label: "Avg LTV", default: false, numeric: true },
  { id: "clickToLeadRate", label: "Click-to-Lead Rate", default: false, numeric: true },
  { id: "clickToConversionRate", label: "Click-to-Conversion Rate", default: false, numeric: true },
  { id: "leadToConversionRate", label: "Lead-to-Conversion Rate", default: false, numeric: true },
  { id: "returnOnAdSpend", label: "ROAS", default: false, numeric: true },
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 29e88bb and 73a9550.

📒 Files selected for processing (1)
  • apps/web/lib/zod/schemas/partners.ts (3 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-08-16T11:14:00.667Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2754
File: apps/web/lib/partnerstack/schemas.ts:47-52
Timestamp: 2025-08-16T11:14:00.667Z
Learning: The PartnerStack API always includes the `group` field in partner responses, so the schema should use `.nullable()` rather than `.nullish()` since the field is never omitted/undefined.

Applied to files:

  • apps/web/lib/zod/schemas/partners.ts
📚 Learning: 2025-05-29T04:45:18.504Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2448
File: packages/email/src/templates/partner-program-summary.tsx:0-0
Timestamp: 2025-05-29T04:45:18.504Z
Learning: In the PartnerProgramSummary email template (packages/email/src/templates/partner-program-summary.tsx), the stat titles are hardcoded constants ("Clicks", "Leads", "Sales", "Earnings") that will always match the ICONS object keys after toLowerCase() conversion, so icon lookup failures are not possible.

Applied to files:

  • apps/web/lib/zod/schemas/partners.ts
📚 Learning: 2025-07-30T15:29:54.131Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2673
File: apps/web/ui/partners/rewards/rewards-logic.tsx:268-275
Timestamp: 2025-07-30T15:29:54.131Z
Learning: In apps/web/ui/partners/rewards/rewards-logic.tsx, when setting the entity field in a reward condition, dependent fields (attribute, operator, value) should be reset rather than preserved because different entities (customer vs sale) have different available attributes. Maintaining existing fields when the entity changes would create invalid state combinations and confusing UX.

Applied to files:

  • apps/web/lib/zod/schemas/partners.ts
🔇 Additional comments (3)
apps/web/lib/zod/schemas/partners.ts (3)

126-132: LGTM! New sort options align with calculated metrics.

The seven new sorting options for calculated metrics (netRevenue, earningsPerClick, averageLifetimeValue, clickToLeadRate, clickToConversionRate, leadToConversionRate, returnOnAdSpend) are well-integrated and follow the existing pattern.


387-387: Excellent clarity improvements to field descriptions.

The updated descriptions for totalSaleAmount and netRevenue are clearer and more informative. Including the calculation formula for netRevenue enhances API documentation quality.

Also applies to: 397-399


400-433: Well-designed calculated metrics with consistent schema patterns.

All six new calculated metrics follow a consistent pattern:

  • Use .nullish() to properly handle cases where calculations are impossible (e.g., division by zero)
  • Include clear descriptions with formulas in a standardized format
  • Use appropriate numeric types

The previous concern about earningsPerClick using .default(0) has been addressed—it now correctly uses .nullish() like the other calculated metrics.

Copy link
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: 0

♻️ Duplicate comments (1)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx (1)

322-391: Truthiness checks still treating zero as no-data.

This issue was flagged in a previous review comment and remains unresolved. All six new calculated columns use truthiness checks (d.field ? ... : "-"), so legitimate zero values render as "-" instead of formatted zeros. Partners with clicks but zero conversions will see blanks for rates/ROAS rather than "0.00%"/"0.00x", which misrepresents their performance.

The fix from the previous review should be applied across all new columns:

-            d.earningsPerClick ? currencyFormatter(d.earningsPerClick) : "-",
+            d.earningsPerClick != null
+              ? currencyFormatter(d.earningsPerClick)
+              : "-",

-            d.averageLifetimeValue
-              ? currencyFormatter(d.averageLifetimeValue)
+            d.averageLifetimeValue != null
+              ? currencyFormatter(d.averageLifetimeValue)
               : "-",

-            d.clickToLeadRate
-              ? `${parseFloat((d.clickToLeadRate * 100).toFixed(2))}%`
+            d.clickToLeadRate != null
+              ? `${parseFloat((d.clickToLeadRate * 100).toFixed(2))}%`
               : "-",

-            d.clickToConversionRate
-              ? `${parseFloat((d.clickToConversionRate * 100).toFixed(2))}%`
+            d.clickToConversionRate != null
+              ? `${parseFloat((d.clickToConversionRate * 100).toFixed(2))}%`
               : "-",

-            d.leadToConversionRate
-              ? `${parseFloat((d.leadToConversionRate * 100).toFixed(2))}%`
+            d.leadToConversionRate != null
+              ? `${parseFloat((d.leadToConversionRate * 100).toFixed(2))}%`
               : "-",

-            d.returnOnAdSpend
-              ? `${parseFloat(d.returnOnAdSpend.toFixed(2))}x`
+            d.returnOnAdSpend != null
+              ? `${parseFloat(d.returnOnAdSpend.toFixed(2))}x`
               : "-",
🧹 Nitpick comments (1)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx (1)

353-353: Consider consistent decimal precision across metrics.

Wrapping toFixed(2) in parseFloat() strips trailing zeros ("3.00" → "3"), which differs from currency formatting that preserves cents. This creates mixed precision displays:

  • EPC: "$3.00" (always 2 decimals)
  • ROAS: "3x" (trailing zeros stripped)
  • Rates: "0%" vs "1.5%" (variable decimals)

If consistent precision is preferred, remove parseFloat() to preserve two decimals:

-            `${parseFloat((d.clickToLeadRate * 100).toFixed(2))}%`
+            `${(d.clickToLeadRate * 100).toFixed(2)}%`

-            `${parseFloat((d.clickToConversionRate * 100).toFixed(2))}%`
+            `${(d.clickToConversionRate * 100).toFixed(2)}%`

-            `${parseFloat((d.leadToConversionRate * 100).toFixed(2))}%`
+            `${(d.leadToConversionRate * 100).toFixed(2)}%`

-            `${parseFloat(d.returnOnAdSpend.toFixed(2))}x`
+            `${d.returnOnAdSpend.toFixed(2)}x`

This would ensure "0.00%", "3.00x", etc., matching the currency formatter's behavior. If variable precision is intentional for cleaner display, this can be ignored.

Also applies to: 365-365, 377-377, 389-389

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 73a9550 and 166ad55.

📒 Files selected for processing (1)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx (3 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-05-29T04:45:18.504Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2448
File: packages/email/src/templates/partner-program-summary.tsx:0-0
Timestamp: 2025-05-29T04:45:18.504Z
Learning: In the PartnerProgramSummary email template (packages/email/src/templates/partner-program-summary.tsx), the stat titles are hardcoded constants ("Clicks", "Leads", "Sales", "Earnings") that will always match the ICONS object keys after toLowerCase() conversion, so icon lookup failures are not possible.

Applied to files:

  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx
📚 Learning: 2025-07-30T15:29:54.131Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2673
File: apps/web/ui/partners/rewards/rewards-logic.tsx:268-275
Timestamp: 2025-07-30T15:29:54.131Z
Learning: In apps/web/ui/partners/rewards/rewards-logic.tsx, when setting the entity field in a reward condition, dependent fields (attribute, operator, value) should be reset rather than preserved because different entities (customer vs sale) have different available attributes. Maintaining existing fields when the entity changes would create invalid state combinations and confusing UX.

Applied to files:

  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx
🧬 Code graph analysis (1)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx (2)
apps/web/lib/types.ts (1)
  • EnrolledPartnerProps (450-450)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
⏰ 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: build

@steven-tey steven-tey merged commit 9432008 into main Nov 13, 2025
7 of 8 checks passed
@steven-tey steven-tey deleted the calculated-stats branch November 13, 2025 21:13
@coderabbitai coderabbitai bot mentioned this pull request Dec 16, 2025
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