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 10, 2025

Summary by CodeRabbit

  • New Features

    • Introduced a Multi-Program Success Bonus (exponential, up to ~200+ points) and expanded partner scoring to ~0–265+ for finer ranking.
    • Updated final score composition to: Multi-Program Bonus + Similarity + Program Match.
  • UX / UI Improvements

    • Improved discovery sorting to better surface starred and high-scoring partners.
    • Replaced in-card star control with a dedicated Partner Star button component for consistent starring behavior.

@vercel
Copy link
Contributor

vercel bot commented Nov 10, 2025

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

Project Deployment Preview Updated (UTC)
dub Ready Ready Preview Nov 10, 2025 7:48am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 10, 2025

Walkthrough

Adds a Multi-Program Success Bonus (exponential, capped) and multiProgramSuccess metric into partner ranking, updates finalScore composition and SQL projections to include the bonus, and introduces a PartnerStarButton component replacing inline star toggles in UI components.

Changes

Cohort / File(s) Summary
Partner ranking logic
apps/web/lib/api/network/calculate-partner-ranking.ts
Adds multiProgramSuccess metric and multiProgramSuccessJoin; computes an exponential, capped Multi-Program Success Bonus (requires online presence & commissions across 2+ programs); updates finalScore to: multiProgramSuccessBonus + similarity + programMatch; adjusts SELECT projections, NULL/default handling, and discover sorting (starred: dp.starredAt, finalScore, profile presence; non-starred: prioritize finalScore).
Partner star button component
apps/web/ui/partners/partner-star-button.tsx
New exported PartnerStarButton component that toggles partner starredAt; supports an optional onToggleStarred override; performs optimistic cache updates across /api/network/partners*, calls updateDiscoveredPartner, applies server result on success, and revalidates on failure with toast errors.
UI usage updates
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/network/page-client.tsx, apps/web/ui/partners/partner-info-cards.tsx
Replace previous Button-based star toggle with PartnerStarButton import and usage; adjust imports accordingly and render PartnerStarButton when appropriate (network mode / partner present).

Sequence Diagram(s)

sequenceDiagram
    participant QB as Query Builder
    participant Joins as Metrics Joins
    participant BonusCalc as Multi-Program Bonus Calc
    participant ScoreAgg as Final Score Aggregator
    participant Discover as Discover Projection/Sort

    Note over QB,Joins: New joins include multiProgramSuccessJoin
    QB->>Joins: join similarity, programMatch, multiProgramSuccess
    Joins->>BonusCalc: provide commissions, onlinePresence, program counts
    BonusCalc-->>Joins: compute exponential, capped bonus
    Joins->>ScoreAgg: similarityScore, programMatchScore, multiProgramSuccessBonus
    ScoreAgg->>ScoreAgg: finalScore = multiProgramSuccessBonus + similarity + programMatch
    ScoreAgg->>Discover: SELECT projections include multiProgramSuccessBonus and finalScore

    rect rgba(63,81,181,0.08)
        Note over Discover: Discover sorting:
        Note over Discover: - starred: dp.starredAt → finalScore → profile presence
        Note over Discover: - non-starred: finalScore prioritized earlier
    end
Loading
sequenceDiagram
    participant UI as PartnerStarButton
    participant Cache as SWR Cache (/api/network/partners*)
    participant API as updateDiscoveredPartner
    participant Toast as Toast/Error

    UI->>Cache: optimistic mutate set starredAt (now or null)
    UI->>API: call updateDiscoveredPartner(workspaceId, partnerId, starred)
    API-->>UI: success (returns server starredAt)
    UI->>Cache: apply server starredAt to specific partner cache entry
    API-->>UI: failure
    UI->>Toast: show error
    UI->>Cache: mutatePrefix to revalidate/revert optimistic changes
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Review exponential bonus formula, cap and numeric stability.
  • Verify aggregation across programs, online-presence and commission filters (2+ vs 3+ logic).
  • Inspect SQL JOINs, NULL/default handling, and SELECT projection changes for correctness.
  • Validate optimistic cache mutation logic, error handling, and race conditions in PartnerStarButton.
  • Confirm discover sorting branches and any ordering/index implications.

Possibly related PRs

  • Partner UI tweaks #2873 — touches PartnerInfoCards and likely overlaps with PartnerStarButton integration and status-badge/props changes.

Poem

🐰 I hopped through joins and sums today,
I found bonus blooms along the way,
Stars now toggle with a confident cheer,
Scores stretch taller — carrots near and dear! 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'Improve ranking algorithm' is vague and generic. While the PR does modify the ranking algorithm in calculate-partner-ranking.ts, the title does not convey the specific, substantive improvements: introducing a multi-program success bonus, expanding the score scale to 265+ points, and updating final score composition. Consider a more specific title like 'Add multi-program success bonus to partner ranking' or 'Enhance partner ranking with program diversity bonus' to clearly communicate the main change.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ 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 improve-algorithm

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

🧹 Nitpick comments (2)
apps/web/lib/api/network/calculate-partner-ranking.ts (2)

191-191: Document the rationale for excluding LARGE_PROGRAM_IDS.

Line 191 filters out LARGE_PROGRAM_IDS from the multi-program success calculation, but unlike the exclusion of ACME_PROGRAM_ID (which is documented elsewhere), there's no explanation for why large programs are excluded. This could be intentional to prevent bias from programs with many partners, but the reasoning should be documented.

Add a comment explaining the exclusion:

     WHERE pe_mult.programId != ${ACME_PROGRAM_ID}
+      -- Exclude large programs to prevent bias in multi-program success scoring
       AND pe_mult.programId NOT IN (${Prisma.join(LARGE_PROGRAM_IDS, ",")})

172-180: Consider adding a brief comment explaining the MAX aggregation pattern.

The use of MAX(p_filter_mult.website) to check for online presence is correct but may be unintuitive. This pattern is necessary due to the GROUP BY partnerId clause. A brief inline comment could help future maintainers understand this choice.

Example:

       CASE 
         WHEN (
+          -- Using MAX because of GROUP BY; returns field value if exists, NULL otherwise
           MAX(p_filter_mult.website) IS NOT NULL OR
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b38eaf1 and b30014b.

📒 Files selected for processing (1)
  • apps/web/lib/api/network/calculate-partner-ranking.ts (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/lib/api/network/calculate-partner-ranking.ts (1)
apps/web/lib/constants/program.ts (1)
  • LARGE_PROGRAM_IDS (2-5)
⏰ 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 (6)
apps/web/lib/api/network/calculate-partner-ranking.ts (6)

24-44: Excellent documentation of the new scoring system!

The detailed breakdown with concrete examples (2 programs = 50, 3 = 87, etc.) makes the exponential scaling immediately clear. The 0-265+ point range is well-explained and matches the implementation.


109-110: LGTM: Sorting logic correctly prioritizes the enhanced finalScore.

The updated ORDER BY clauses appropriately surface high-performing partners while maintaining logical tiebreakers (starredAt, profile presence, ID).


145-148: LGTM: Filter definition follows established pattern.

The new multi-program filter correctly reuses the helper function and maintains consistency with the optimization strategy.


182-182: Verify the use of totalCommissions vs totalConversions is intentional.

The multi-program bonus uses totalCommissions > 0 as the success metric (lines 182, 194), while the all-program metrics join uses totalConversions > 0 (line 160). This difference may be intentional—commissions represent actual revenue and could be a stronger signal of quality than conversions alone. Please confirm this distinction is deliberate.

Also applies to: 194-194


272-279: LGTM: Final score calculation is correct and well-documented.

The calculation properly integrates the new multi-program bonus with proper NULL handling. The order (Bonus + Similarity + Match) reflects the intended priority, and the maximum of 265 points is achievable (200 + 50 + 15).


299-300: LGTM: Join integration is correct.

The multiProgramSuccessJoin is properly integrated into the query structure, maintaining consistency with other metric joins.

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

🧹 Nitpick comments (3)
apps/web/ui/partners/partner-star-button.tsx (1)

40-55: Consider narrowing the cache key matching pattern.

The current pattern key.startsWith("/api/network/partners") is broad and will match all partner-related cache entries, including potentially unrelated endpoints like /api/network/partners/stats or /api/network/partners/[id]/details. This could lead to unintended cache updates.

If there are specific query parameter patterns you expect, consider a more precise matcher:

     mutate(
       (key) =>
-        typeof key === "string" && key.startsWith("/api/network/partners"),
+        typeof key === "string" && 
+        key.startsWith("/api/network/partners") && 
+        key.includes("workspaceId="),
       (data: any) => {

Alternatively, if all /api/network/partners* endpoints return arrays of partners, the current approach is acceptable.

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

173-177: Consider JOIN instead of IN subquery for better performance.

The subquery pattern (lines 173-177) filters discoverable partners using WHERE partnerId IN (SELECT ...). While this works, a direct INNER JOIN on the Partner table might perform better and allow MySQL to optimize the query plan more effectively, similar to the pattern used in similarProgramMetricsJoin (line 224).

     FROM ProgramEnrollment pe_mult
-    -- OPTIMIZATION: Only process enrollments for discoverable partners (using subquery to avoid JOIN)
-    WHERE pe_mult.partnerId IN (
-      SELECT p_filter_mult.id
-      FROM Partner p_filter_mult
-      WHERE ${discoverablePartnersMultiProgramFilter}
-    )
+    -- OPTIMIZATION: Only process enrollments for discoverable partners
+    INNER JOIN Partner p_filter_mult ON p_filter_mult.id = pe_mult.partnerId 
+      AND ${discoverablePartnersMultiProgramFilter}
+    WHERE TRUE
       AND pe_mult.programId != ${ACME_PROGRAM_ID}

This aligns with your existing optimization pattern and may benefit from better index usage.


264-271: Profile check in SELECT clause prevents index optimization.

The hasProfileCheck condition (line 266) is evaluated in the SELECT clause, which means the multi-program bonus calculation happens for every row before sorting. This prevents MySQL from using the bonus calculation to optimize the query plan or early-exit on sorting.

For better performance, consider computing the bonus within the multiProgramSuccessJoin subquery where hasProfileCheck can be evaluated earlier:

   const multiProgramSuccessJoin = Prisma.sql`LEFT JOIN (
     SELECT 
       pe_mult.partnerId,
-      -- Count of successful programs (based on commissions)
-      COUNT(DISTINCT CASE WHEN pe_mult.totalCommissions > 0 THEN pe_mult.programId END) as successfulProgramCount
+      COUNT(DISTINCT CASE WHEN pe_mult.totalCommissions > 0 THEN pe_mult.programId END) as successfulProgramCount,
+      -- Pre-compute bonus based on profile check in subquery
+      CASE 
+        WHEN ${hasProfileCheck} AND COUNT(...) >= 2 THEN
+          LEAST(200, POWER(COUNT(...) - 1, 1.5) * 20 + 30)
+        ELSE 0
+      END as multiProgramBonus
     FROM ProgramEnrollment pe_mult
+    INNER JOIN Partner p_mult_profile ON p_mult_profile.id = pe_mult.partnerId

However, this adds another JOIN and might not improve performance. Profile this change before applying.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b30014b and 764fa4f.

📒 Files selected for processing (4)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/network/page-client.tsx (3 hunks)
  • apps/web/lib/api/network/calculate-partner-ranking.ts (6 hunks)
  • apps/web/ui/partners/partner-info-cards.tsx (2 hunks)
  • apps/web/ui/partners/partner-star-button.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-09-24T16:10:37.349Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/ui/partners/partner-about.tsx:11-11
Timestamp: 2025-09-24T16:10:37.349Z
Learning: In the Dub codebase, the team prefers to import Icon as a runtime value from "dub/ui" and uses Icon as both a type and variable name in component props, even when this creates shadowing. This is their established pattern and should not be suggested for refactoring.

Applied to files:

  • apps/web/ui/partners/partner-info-cards.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/network/page-client.tsx
📚 Learning: 2025-10-15T01:05:43.266Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx:432-457
Timestamp: 2025-10-15T01:05:43.266Z
Learning: In apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx, defer refactoring the custom MenuItem component (lines 432-457) to use the shared dub/ui MenuItem component to a future PR, as requested by steven-tey.

Applied to files:

  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/network/page-client.tsx
📚 Learning: 2025-09-19T18:46:43.787Z
Learnt from: CR
Repo: dubinc/dub PR: 0
File: packages/hubspot-app/CLAUDE.md:0-0
Timestamp: 2025-09-19T18:46:43.787Z
Learning: Applies to packages/hubspot-app/app/cards/**/*.{js,jsx,ts,tsx} : Only use components exported by hubspot/ui-extensions within card components

Applied to files:

  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/network/page-client.tsx
📚 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/network/page-client.tsx
🧬 Code graph analysis (4)
apps/web/ui/partners/partner-star-button.tsx (3)
apps/web/lib/types.ts (1)
  • NetworkPartnerProps (455-455)
apps/web/lib/swr/use-workspace.ts (1)
  • useWorkspace (6-46)
apps/web/lib/actions/partners/update-discovered-partner.ts (1)
  • updateDiscoveredPartnerAction (10-46)
apps/web/ui/partners/partner-info-cards.tsx (1)
apps/web/ui/partners/partner-star-button.tsx (1)
  • PartnerStarButton (19-106)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/network/page-client.tsx (1)
apps/web/ui/partners/partner-star-button.tsx (1)
  • PartnerStarButton (19-106)
apps/web/lib/api/network/calculate-partner-ranking.ts (1)
apps/web/lib/constants/program.ts (1)
  • LARGE_PROGRAM_IDS (2-5)
🔇 Additional comments (5)
apps/web/ui/partners/partner-info-cards.tsx (1)

182-184: LGTM! Clean integration of the star button.

The conditional rendering and positioning of PartnerStarButton is consistent with the existing PartnerStatusBadgeWithTooltip pattern for enrolled partners. The size-9 className appropriately sizes the button for the card layout.

apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/network/page-client.tsx (1)

440-445: LGTM! Effective refactoring to use the shared component.

The migration from an inline Button to PartnerStarButton maintains all existing behavior through the onToggleStarred callback. The custom mutation logic (lines 256-293) with optimistic updates and populateCache is preserved, which is appropriate for this page's specific SWR data management needs.

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

109-110: Verify order-by behavior with starred partners.

The updated order clause for starred discover status now prioritizes starredAt DESC before finalScore DESC (line 109). This means more recently starred partners appear first, regardless of their score. Is this the intended behavior?

If the goal is to show highest-scored starred partners first (ignoring when they were starred), the order should be:

-        ? Prisma.sql`dp.starredAt DESC, finalScore DESC, ${hasProfileCheck} DESC, p.id ASC`
+        ? Prisma.sql`finalScore DESC, ${hasProfileCheck} DESC, p.id ASC`

Otherwise, if recency of starring is important for user workflow (e.g., "I just starred this to review later"), the current order is correct.


166-183: Verify multi-program join performance in staging with production-like data.

The code structure appears sound with deliberate optimizations (subquery approach to avoid expensive joins, proper filtering), but actual performance impact cannot be determined without database-level verification. The sandbox environment lacks database access for EXPLAIN plan analysis.

Recommend testing the full ranking query in a staging environment with production-like data volumes to confirm performance characteristics, particularly for scenarios with high volumes of ProgramEnrollment records and diverse partner counts.

apps/web/ui/partners/partner-star-button.tsx (1)

58-63: Add null check before API call.

The non-null assertion on workspaceId! (line 60) could cause a runtime error if useWorkspace returns undefined. While the Button is only rendered when a partner exists, the workspace might still be loading.

Apply this diff to add a guard:

     try {
+      if (!workspaceId) {
+        toast.error("Workspace not found");
+        mutatePrefix("/api/network/partners");
+        return;
+      }
+
       const result = await updateDiscoveredPartner({
-        workspaceId: workspaceId!,
+        workspaceId,
         partnerId: partner.id,
         starred,
       });
⛔ Skipped due to learnings
Learnt from: TWilson023
Repo: dubinc/dub PR: 2538
File: apps/web/ui/partners/overview/blocks/commissions-block.tsx:16-27
Timestamp: 2025-06-18T20:26:25.177Z
Learning: In the Dub codebase, components that use workspace data (workspaceId, defaultProgramId) are wrapped in `WorkspaceAuth` which ensures these values are always available, making non-null assertions safe. This is acknowledged as a common pattern in their codebase, though not ideal.
Learnt from: TWilson023
Repo: dubinc/dub PR: 2736
File: apps/web/lib/swr/use-bounty.ts:11-16
Timestamp: 2025-08-26T15:05:55.081Z
Learning: In the Dub codebase, workspace authentication and route structures prevent endless loading states when workspaceId or similar route parameters are missing, so gating SWR loading states on parameter availability is often unnecessary.

Comment on lines +85 to +88
} catch (error) {
// Revert optimistic update on error
mutatePrefix("/api/network/partners");
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Silent error handling may hide issues.

The catch block (lines 85-88) only reverts the optimistic update without notifying the user. This could leave users unaware that their action failed, especially for network errors or unexpected failures not caught by the !result?.data check.

Apply this diff to add user feedback:

     } catch (error) {
+      toast.error("Failed to star partner");
       // Revert optimistic update on error
       mutatePrefix("/api/network/partners");
     }

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

🤖 Prompt for AI Agents
In apps/web/ui/partners/partner-star-button.tsx around lines 85 to 88 the catch
block only reverts the optimistic update and swallows the error; update the
catch to (1) call mutatePrefix("/api/network/partners") to revert the optimistic
change, (2) extract a usable message from the caught error (error.message or a
fallback), and (3) surface that message to the user via the app's notification
mechanism (e.g., toast or setError state) so users see the failure and any
useful detail for retrying.

@steven-tey steven-tey merged commit e63867c into main Nov 10, 2025
8 checks passed
@steven-tey steven-tey deleted the improve-algorithm branch November 10, 2025 16:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants