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

Skip to content

Conversation

@devkiran
Copy link
Collaborator

@devkiran devkiran commented Nov 7, 2025

Summary by CodeRabbit

  • New Features

    • Group-level "top groups" analytics and a new "View analytics" menu item for quick group insights.
  • Improvements

    • Analytics filters now support selecting by group.
    • Link and partner workflows now include group/enrollment context, improving reporting accuracy and lifecycle handling (including deletions).
    • Bulk, cleanup, and migration processes updated to ensure consistent group-aware data processing.

…d discount data retrieval, and include program enrollment in link transformations. Update related functions to accommodate new data structure.
@vercel
Copy link
Contributor

vercel bot commented Nov 7, 2025

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

Project Deployment Preview Updated (UTC)
dub Ready Ready Preview Nov 8, 2025 11:39pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 7, 2025

Walkthrough

Adds programEnrollment.groupId propagation across link workflows: new includeProgramEnrollment, expanded Prisma includes, enrollment-aware partner lookup, Tinybird payloads with partner_group_id, analytics "top_groups" support, UI filter and menu, plus backfill/migration scripts to record group associations.

Changes

Cohort / File(s) Summary
Program enrollment include utility
apps/web/lib/api/links/include-program-enrollment.ts
New includeProgramEnrollment export selecting programEnrollment.groupId for Prisma Link includes.
Prisma include standardization (cron / routes / scripts)
apps/web/app/(ee)/api/cron/cleanup/e2e-tests/route.ts, apps/web/app/(ee)/api/cron/domains/delete/route.ts, apps/web/app/(ee)/api/cron/domains/update/route.ts, apps/web/app/(ee)/api/cron/folders/delete/route.ts, apps/web/app/(ee)/api/cron/merge-partner-accounts/route.ts, apps/web/app/(ee)/api/stripe/webhook/customer-subscription-deleted.ts, apps/web/app/api/links/bulk/route.ts, apps/web/app/api/tags/[id]/route.ts, apps/web/scripts/*, apps/web/scripts/partners/merge-partner-profile.ts, apps/web/scripts/bulk-delete-links.ts, apps/web/scripts/bulk-update-links.ts
Replace ad-hoc tag selects with { ...includeTags, ...includeProgramEnrollment } spreads to eagerly load tags and programEnrollment.groupId for link queries.
Partner/group API route refactors
apps/web/app/(ee)/api/groups/[groupIdOrSlug]/partners/route.ts, apps/web/app/(ee)/api/groups/[groupIdOrSlug]/route.ts
Fetch partner links with tags + enrollment, record links via recordLink, and refactor post-delete/side-effect flows into async batch waitUntil blocks (qstash, discount code tasks, audit, cache/Tinybird ops).
Admin & webhook changes
apps/web/app/(ee)/api/admin/delete-partner-account/route.ts, apps/web/app/api/webhooks/route.ts
Include programEnrollment in link queries; in webhooks route, await the Promise.allSettled inside waitUntil to ensure async side effects complete.
Link create/update/delete pipelines
apps/web/lib/api/links/create-link.ts, apps/web/lib/api/links/update-link.ts, apps/web/lib/api/links/delete-link.ts, apps/web/lib/api/links/bulk-update-links.ts, apps/web/lib/api/links/bulk-create-links.ts, apps/web/lib/api/links/complete-ab-tests.ts
Switch partner retrieval to getPartnerEnrollmentInfo, fetch enrollment/group before side effects, propagate programEnrollment.groupId into Tinybird/Redis payloads, and add includeProgramEnrollment to relevant queries.
Transform & Tinybird mapping
apps/web/lib/api/links/utils/transform-link.ts, apps/web/lib/tinybird/record-link.ts
ExpandedLink accepts optional programEnrollment.groupId; Tinybird payload adds partner_group_id populated from link.programEnrollment?.groupId.
Partner enrollment lookup & middleware
apps/web/lib/planetscale/get-partner-enrollment-info.ts, apps/web/lib/middleware/link.ts
Rename getPartnerAndDiscountgetPartnerEnrollmentInfo, extend return shape with group/groupId, and update middleware call sites.
Partner action handlers
apps/web/lib/actions/partners/ban-partner.ts, apps/web/lib/actions/partners/bulk-ban-partners.ts, apps/web/lib/actions/partners/unban-partner.ts, apps/web/lib/actions/partners/revoke-program-invite.ts, apps/web/lib/actions/partners/update-partner-enrollment.ts
Include programEnrollment/tags in queries, set/clear disabledAt where applicable, expire caches, and record deletions/updates via recordLink (use deleted: true for removals).
Analytics: backend & schemas
apps/web/lib/analytics/constants.ts, apps/web/lib/analytics/get-analytics.ts, apps/web/lib/zod/schemas/analytics.ts, apps/web/lib/zod/schemas/analytics-response.ts
Add top_groups endpoint, groupId filter, Tinybird pipe support, and top_groups response schema (group object + metrics).
Analytics UI
apps/web/ui/analytics/use-analytics-filters.tsx
Add Group filter (uses GroupColorCircle) and fetch top_groups for program page analytics when groupId is requested.
Program groups UI
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/groups/groups-table.tsx
Add "View analytics" row action navigating to program analytics with groupId query.
Backfill / migration scripts
apps/web/scripts/migrations/backfill-link-partner-group-ids.ts, apps/web/scripts/migrations/backfill-partner-links.ts, apps/web/scripts/migrations/backfill-banned-partner-links.ts, apps/web/scripts/perplexity/backfill-tenantids.ts, apps/web/scripts/move-links-to-folder.ts
New/updated scripts to scan links in batches, include programEnrollment, and call recordLink (often with deleted: true) to backfill partner group associations and banned link records.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant API as Link Create/Update API
    participant Enrollment as Enrollment Service
    participant DB as Database
    participant Tinybird as Tinybird

    Client->>API: Create/Update link
    API->>Enrollment: getPartnerEnrollmentInfo(programId, partnerId)
    Enrollment->>DB: select programEnrollment.groupId, partner, discount
    DB-->>Enrollment: { groupId, partner, discount }
    Enrollment-->>API: { group, partner, discount }
    API->>DB: create/update link (includeProgramEnrollment)
    DB-->>API: link record (includes programEnrollment.groupId)
    API->>Tinybird: recordLink(link, { partner_group_id: groupId })
    Tinybird-->>API: ack
    API-->>Client: success
Loading
sequenceDiagram
    participant Admin
    participant GroupAPI as Group DELETE API
    participant DB as Database
    participant QStash as QStash
    participant Cache as LinkCache
    participant Tinybird as Tinybird

    Admin->>GroupAPI: DELETE /groups/:id
    GroupAPI->>DB: fetch partnerLinks (includeTags + includeProgramEnrollment)
    DB-->>GroupAPI: partnerLinks[]
    rect rgba(220,235,255,0.6)
    GroupAPI->>QStash: publish remap-default-links (await in batch)
    GroupAPI->>QStash: publish remap-discount-codes
    GroupAPI->>DB: delete group & related rows
    GroupAPI->>Cache: expireMany(partnerLinks)
    GroupAPI->>Tinybird: recordLink(partnerLinks, { deleted: true })
    end
    Tinybird-->>GroupAPI: recorded
    GroupAPI-->>Admin: success
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Wide surface: 40+ files across DB queries, business logic, analytics, UI, and scripts.
  • Mixed edits: many repetitive include changes plus several non-trivial refactors (create/update flows, enrollment lookup rename, Tinybird payload changes).
  • Files meriting closer attention:
    • apps/web/lib/api/links/create-link.ts
    • apps/web/lib/api/links/update-link.ts
    • apps/web/app/(ee)/api/groups/[groupIdOrSlug]/route.ts
    • apps/web/lib/planetscale/get-partner-enrollment-info.ts
    • apps/web/scripts/migrations/backfill-link-partner-group-ids.ts
    • apps/web/lib/analytics/get-analytics.ts

Possibly related PRs

Suggested reviewers

  • devkiran
  • TWilson023

Poem

🐇 A rabbit nibbles groupId seeds at night,
Hops through links to line them up just right,
Tinybird hums partner_group_id bright,
Top_groups bloom in analytics light,
Hooray — the data hops into sight! 🎉

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.43% 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 'Partner group filters' directly addresses the main change: adding groupId support and filtering capabilities for partner programs throughout the codebase.
✨ 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 partner-group-filters

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a30f537 and a8e7c9f.

📒 Files selected for processing (1)
  • apps/web/lib/tinybird/record-link.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/lib/tinybird/record-link.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

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.

…ting various scripts to include program enrollment alongside existing tags.
@steven-tey steven-tey marked this pull request as ready for review November 8, 2025 06:21
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: 8

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a11cedf and 58dc5b5.

📒 Files selected for processing (38)
  • apps/web/app/(ee)/api/admin/delete-partner-account/route.ts (3 hunks)
  • apps/web/app/(ee)/api/cron/cleanup/e2e-tests/route.ts (2 hunks)
  • apps/web/app/(ee)/api/cron/domains/delete/route.ts (2 hunks)
  • apps/web/app/(ee)/api/cron/domains/update/route.ts (2 hunks)
  • apps/web/app/(ee)/api/cron/folders/delete/route.ts (2 hunks)
  • apps/web/app/(ee)/api/cron/merge-partner-accounts/route.ts (2 hunks)
  • apps/web/app/(ee)/api/groups/[groupIdOrSlug]/partners/route.ts (2 hunks)
  • apps/web/app/(ee)/api/groups/[groupIdOrSlug]/route.ts (2 hunks)
  • apps/web/app/(ee)/api/stripe/webhook/customer-subscription-deleted.ts (2 hunks)
  • apps/web/app/api/links/bulk/route.ts (2 hunks)
  • apps/web/app/api/tags/[id]/route.ts (2 hunks)
  • apps/web/app/api/webhooks/route.ts (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/groups/groups-table.tsx (2 hunks)
  • apps/web/lib/actions/partners/revoke-program-invite.ts (3 hunks)
  • apps/web/lib/actions/partners/update-partner-enrollment.ts (3 hunks)
  • apps/web/lib/analytics/constants.ts (4 hunks)
  • apps/web/lib/analytics/get-analytics.ts (2 hunks)
  • apps/web/lib/api/links/bulk-create-links.ts (3 hunks)
  • apps/web/lib/api/links/bulk-update-links.ts (2 hunks)
  • apps/web/lib/api/links/complete-ab-tests.ts (2 hunks)
  • apps/web/lib/api/links/create-link.ts (3 hunks)
  • apps/web/lib/api/links/delete-link.ts (2 hunks)
  • apps/web/lib/api/links/include-program-enrollment.ts (1 hunks)
  • apps/web/lib/api/links/update-link.ts (2 hunks)
  • apps/web/lib/api/links/utils/transform-link.ts (3 hunks)
  • apps/web/lib/middleware/link.ts (2 hunks)
  • apps/web/lib/planetscale/getPartnerEnrollmentInfo.ts (5 hunks)
  • apps/web/lib/tinybird/record-link.ts (2 hunks)
  • apps/web/lib/zod/schemas/analytics-response.ts (1 hunks)
  • apps/web/lib/zod/schemas/analytics.ts (2 hunks)
  • apps/web/scripts/bulk-delete-links.ts (2 hunks)
  • apps/web/scripts/bulk-update-links.ts (2 hunks)
  • apps/web/scripts/migrations/backfill-link-partner-group-ids.ts (1 hunks)
  • apps/web/scripts/migrations/backfill-partner-links.ts (2 hunks)
  • apps/web/scripts/move-links-to-folder.ts (2 hunks)
  • apps/web/scripts/partners/merge-partner-profile.ts (2 hunks)
  • apps/web/scripts/perplexity/backfill-tenantids.ts (1 hunks)
  • apps/web/ui/analytics/use-analytics-filters.tsx (4 hunks)
🧰 Additional context used
🧠 Learnings (10)
📓 Common learnings
Learnt from: devkiran
Repo: dubinc/dub PR: 2448
File: packages/email/src/templates/partner-program-summary.tsx:254-254
Timestamp: 2025-05-29T04:49:42.842Z
Learning: In the Dub codebase, it's acceptable to keep `partners.dub.co` hardcoded rather than making it configurable for different environments.
📚 Learning: 2025-06-06T07:59:03.120Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2177
File: apps/web/lib/api/links/bulk-create-links.ts:66-84
Timestamp: 2025-06-06T07:59:03.120Z
Learning: In apps/web/lib/api/links/bulk-create-links.ts, the team accepts the risk of potential undefined results from links.find() operations when building invalidLinks arrays, because existing links are fetched from the database based on the input links, so matches are expected to always exist.

Applied to files:

  • apps/web/app/(ee)/api/cron/cleanup/e2e-tests/route.ts
  • apps/web/lib/api/links/complete-ab-tests.ts
  • apps/web/scripts/bulk-update-links.ts
  • apps/web/scripts/perplexity/backfill-tenantids.ts
  • apps/web/app/(ee)/api/cron/merge-partner-accounts/route.ts
  • apps/web/app/(ee)/api/cron/folders/delete/route.ts
  • apps/web/lib/api/links/delete-link.ts
  • apps/web/app/api/links/bulk/route.ts
  • apps/web/lib/api/links/update-link.ts
  • apps/web/scripts/migrations/backfill-link-partner-group-ids.ts
  • apps/web/lib/api/links/utils/transform-link.ts
  • apps/web/app/(ee)/api/groups/[groupIdOrSlug]/partners/route.ts
  • apps/web/lib/actions/partners/revoke-program-invite.ts
  • apps/web/scripts/move-links-to-folder.ts
  • apps/web/app/(ee)/api/cron/domains/update/route.ts
  • apps/web/lib/api/links/create-link.ts
  • apps/web/scripts/bulk-delete-links.ts
  • apps/web/app/(ee)/api/groups/[groupIdOrSlug]/route.ts
  • apps/web/app/(ee)/api/admin/delete-partner-account/route.ts
  • apps/web/lib/api/links/bulk-update-links.ts
  • apps/web/lib/api/links/bulk-create-links.ts
📚 Learning: 2025-10-28T19:17:44.390Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2985
File: apps/web/app/(ee)/api/network/programs/[programSlug]/route.ts:32-37
Timestamp: 2025-10-28T19:17:44.390Z
Learning: In Prisma queries, the `include` clause is only used for relationships (one-to-one, one-to-many, many-to-many). Regular scalar fields, JSON fields, and other non-relational columns are automatically included in the query result and do not need to be specified in the `include` object.

Applied to files:

  • apps/web/app/(ee)/api/cron/cleanup/e2e-tests/route.ts
  • apps/web/lib/api/links/complete-ab-tests.ts
  • apps/web/scripts/bulk-update-links.ts
  • apps/web/scripts/migrations/backfill-partner-links.ts
  • apps/web/app/(ee)/api/cron/folders/delete/route.ts
  • apps/web/app/api/links/bulk/route.ts
  • apps/web/app/(ee)/api/stripe/webhook/customer-subscription-deleted.ts
  • apps/web/lib/actions/partners/update-partner-enrollment.ts
  • apps/web/app/api/tags/[id]/route.ts
  • apps/web/scripts/partners/merge-partner-profile.ts
  • apps/web/lib/actions/partners/revoke-program-invite.ts
  • apps/web/app/(ee)/api/cron/domains/update/route.ts
  • apps/web/scripts/bulk-delete-links.ts
  • apps/web/lib/api/links/bulk-update-links.ts
  • apps/web/app/(ee)/api/cron/domains/delete/route.ts
  • apps/web/lib/api/links/bulk-create-links.ts
  • apps/web/lib/api/links/include-program-enrollment.ts
📚 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/merge-partner-accounts/route.ts
  • apps/web/lib/actions/partners/update-partner-enrollment.ts
  • apps/web/lib/actions/partners/revoke-program-invite.ts
  • apps/web/app/(ee)/api/admin/delete-partner-account/route.ts
📚 Learning: 2025-06-16T19:21:23.506Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2519
File: apps/web/ui/analytics/utils.ts:35-37
Timestamp: 2025-06-16T19:21:23.506Z
Learning: In the `useAnalyticsFilterOption` function in `apps/web/ui/analytics/utils.ts`, the pattern `options?.context ?? useContext(AnalyticsContext)` is intentionally designed as a complete replacement strategy, not a merge. When `options.context` is provided, it should contain all required fields (`baseApiPath`, `queryString`, `selectedTab`, `requiresUpgrade`) and completely replace the React context, not be merged with it. This is used for dependency injection or testing scenarios.

Applied to files:

  • apps/web/ui/analytics/use-analytics-filters.tsx
  • apps/web/lib/analytics/constants.ts
  • apps/web/lib/analytics/get-analytics.ts
📚 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/tinybird/record-link.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/analytics/constants.ts
📚 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/groups/groups-table.tsx
📚 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/app/app.dub.co/(dashboard)/[slug]/(ee)/program/groups/groups-table.tsx
📚 Learning: 2025-08-14T05:57:35.546Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2735
File: apps/web/lib/actions/partners/update-discount.ts:60-66
Timestamp: 2025-08-14T05:57:35.546Z
Learning: In the partner groups system, discounts should always belong to a group. The partnerGroup relation should never be null when updating discounts, so optional chaining on partnerGroup?.id may be unnecessary defensive programming.

Applied to files:

  • apps/web/lib/planetscale/getPartnerEnrollmentInfo.ts
🧬 Code graph analysis (32)
apps/web/app/(ee)/api/cron/cleanup/e2e-tests/route.ts (1)
apps/web/lib/api/links/include-program-enrollment.ts (1)
  • includeProgramEnrollment (3-9)
apps/web/lib/api/links/complete-ab-tests.ts (1)
apps/web/lib/api/links/include-program-enrollment.ts (1)
  • includeProgramEnrollment (3-9)
apps/web/scripts/bulk-update-links.ts (1)
apps/web/lib/api/links/include-program-enrollment.ts (1)
  • includeProgramEnrollment (3-9)
apps/web/scripts/perplexity/backfill-tenantids.ts (1)
apps/web/lib/tinybird/record-link.ts (1)
  • recordLink (81-98)
apps/web/app/(ee)/api/cron/merge-partner-accounts/route.ts (1)
apps/web/lib/api/links/include-program-enrollment.ts (1)
  • includeProgramEnrollment (3-9)
apps/web/scripts/migrations/backfill-partner-links.ts (1)
apps/web/lib/api/links/include-program-enrollment.ts (1)
  • includeProgramEnrollment (3-9)
apps/web/app/(ee)/api/cron/folders/delete/route.ts (1)
apps/web/lib/api/links/include-program-enrollment.ts (1)
  • includeProgramEnrollment (3-9)
apps/web/lib/api/links/delete-link.ts (1)
apps/web/lib/api/links/include-program-enrollment.ts (1)
  • includeProgramEnrollment (3-9)
apps/web/app/api/links/bulk/route.ts (1)
apps/web/lib/api/links/include-program-enrollment.ts (1)
  • includeProgramEnrollment (3-9)
apps/web/app/(ee)/api/stripe/webhook/customer-subscription-deleted.ts (1)
apps/web/lib/api/links/include-program-enrollment.ts (1)
  • includeProgramEnrollment (3-9)
apps/web/lib/api/links/update-link.ts (3)
apps/web/lib/planetscale/getPartnerEnrollmentInfo.ts (1)
  • getPartnerEnrollmentInfo (17-82)
apps/web/lib/tinybird/record-link.ts (1)
  • recordLink (81-98)
apps/web/lib/storage.ts (2)
  • isNotHostedImage (257-259)
  • storage (251-251)
apps/web/ui/analytics/use-analytics-filters.tsx (1)
apps/web/ui/analytics/utils.ts (1)
  • useAnalyticsFilterOption (22-75)
apps/web/lib/actions/partners/update-partner-enrollment.ts (1)
apps/web/lib/api/links/include-program-enrollment.ts (1)
  • includeProgramEnrollment (3-9)
apps/web/app/api/tags/[id]/route.ts (1)
apps/web/lib/api/links/include-program-enrollment.ts (1)
  • includeProgramEnrollment (3-9)
apps/web/scripts/migrations/backfill-link-partner-group-ids.ts (3)
packages/prisma/index.ts (1)
  • prisma (3-9)
apps/web/lib/api/links/include-program-enrollment.ts (1)
  • includeProgramEnrollment (3-9)
apps/web/lib/tinybird/record-link.ts (1)
  • recordLink (81-98)
apps/web/lib/middleware/link.ts (1)
apps/web/lib/planetscale/getPartnerEnrollmentInfo.ts (1)
  • getPartnerEnrollmentInfo (17-82)
apps/web/scripts/partners/merge-partner-profile.ts (1)
apps/web/lib/api/links/include-program-enrollment.ts (1)
  • includeProgramEnrollment (3-9)
apps/web/lib/api/links/utils/transform-link.ts (1)
apps/web/lib/types.ts (1)
  • ProgramEnrollmentProps (483-483)
apps/web/app/(ee)/api/groups/[groupIdOrSlug]/partners/route.ts (4)
packages/prisma/index.ts (1)
  • prisma (3-9)
apps/web/lib/api/links/include-program-enrollment.ts (1)
  • includeProgramEnrollment (3-9)
apps/web/lib/api/bounties/trigger-draft-bounty-submissions.ts (1)
  • triggerDraftBountySubmissionCreation (8-93)
apps/web/lib/tinybird/record-link.ts (1)
  • recordLink (81-98)
apps/web/lib/actions/partners/revoke-program-invite.ts (2)
apps/web/lib/api/links/utils/transform-link.ts (1)
  • ExpandedLink (12-22)
apps/web/lib/tinybird/record-link.ts (1)
  • recordLink (81-98)
apps/web/scripts/move-links-to-folder.ts (1)
apps/web/lib/api/links/include-program-enrollment.ts (1)
  • includeProgramEnrollment (3-9)
apps/web/app/(ee)/api/cron/domains/update/route.ts (1)
apps/web/lib/api/links/include-program-enrollment.ts (1)
  • includeProgramEnrollment (3-9)
apps/web/lib/api/links/create-link.ts (4)
apps/web/lib/planetscale/getPartnerEnrollmentInfo.ts (1)
  • getPartnerEnrollmentInfo (17-82)
apps/web/lib/tinybird/record-link.ts (1)
  • recordLink (81-98)
apps/web/lib/storage.ts (2)
  • isNotHostedImage (257-259)
  • storage (251-251)
packages/prisma/index.ts (1)
  • prisma (3-9)
apps/web/scripts/bulk-delete-links.ts (1)
apps/web/lib/api/links/include-program-enrollment.ts (1)
  • includeProgramEnrollment (3-9)
apps/web/lib/analytics/get-analytics.ts (1)
packages/prisma/index.ts (1)
  • prisma (3-9)
apps/web/app/(ee)/api/groups/[groupIdOrSlug]/route.ts (5)
packages/prisma/index.ts (1)
  • prisma (3-9)
apps/web/lib/api/links/include-program-enrollment.ts (1)
  • includeProgramEnrollment (3-9)
apps/web/lib/api/discounts/queue-discount-code-deletion.ts (1)
  • queueDiscountCodeDeletion (13-37)
apps/web/lib/api/audit-logs/record-audit-log.ts (1)
  • recordAuditLog (47-74)
apps/web/lib/tinybird/record-link.ts (1)
  • recordLink (81-98)
apps/web/app/(ee)/api/admin/delete-partner-account/route.ts (1)
apps/web/lib/tinybird/record-link.ts (1)
  • recordLink (81-98)
apps/web/lib/api/links/bulk-update-links.ts (1)
apps/web/lib/api/links/include-program-enrollment.ts (1)
  • includeProgramEnrollment (3-9)
apps/web/app/(ee)/api/cron/domains/delete/route.ts (1)
apps/web/lib/api/links/include-program-enrollment.ts (1)
  • includeProgramEnrollment (3-9)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/groups/groups-table.tsx (1)
packages/ui/src/menu-item.tsx (1)
  • MenuItem (43-86)
apps/web/lib/api/links/bulk-create-links.ts (1)
apps/web/lib/api/links/include-program-enrollment.ts (1)
  • includeProgramEnrollment (3-9)
apps/web/lib/api/links/include-program-enrollment.ts (1)
packages/prisma/client.ts (1)
  • Prisma (29-29)
⏰ 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 (47)
apps/web/app/api/webhooks/route.ts (1)

109-109: Good fix, but unrelated to PR objective.

Adding await ensures the async IIFE properly waits for all operations (webhook toggling, email, cache updates) to complete before the function lifecycle ends. Without it, waitUntil receives a promise that resolves immediately, potentially terminating the function before background operations finish.

However, this change appears unrelated to the PR's stated objective of "Partner group filters." Consider moving this fix to a separate PR for clarity.

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

24-32: LGTM!

The LinesY icon import is correctly added and used in the new "View analytics" menu item.

apps/web/lib/zod/schemas/analytics.ts (1)

217-220: Schema keeps filters in sync.

Adding groupId here keeps the validation surface aligned with the new Tinybird filters. Looks good.

apps/web/ui/analytics/use-analytics-filters.tsx (1)

409-425: Group filter wiring looks solid.

Thanks for slotting the new Group filter into the program dashboard with the existing getFilterOptionTotal pattern and GroupColorCircle visuals—nice, consistent integration.

apps/web/lib/analytics/constants.ts (1)

84-138: Constants updated coherently.

VALID_ANALYTICS_ENDPOINTS, SINGULAR_ANALYTICS_ENDPOINTS, and VALID_ANALYTICS_FILTERS now agree on top_groups/groupId, so downstream consumers stay consistent. 👍

apps/web/lib/zod/schemas/analytics-response.ts (1)

534-553: Response schema matches the new dimension.

The top_groups object mirrors the other aggregated dimensions and exposes the full group metadata the UI expects. Nicely done.

apps/web/scripts/move-links-to-folder.ts (1)

47-50: LGTM! Program enrollment data included for tracking.

The addition of includeProgramEnrollment is consistent with the PR's objective to track program enrollment group data. The fetched data is used by recordLink at line 55 for analytics tracking.

apps/web/app/api/tags/[id]/route.ts (1)

76-77: LGTM! Consistent data enrichment for link tracking.

The addition of includeProgramEnrollment alongside includeTags ensures complete link metadata is available when recording deletions to Tinybird (line 94).

apps/web/lib/api/links/utils/transform-link.ts (2)

21-21: LGTM! Type extended to accept program enrollment data.

The ExpandedLink type now accepts programEnrollment data, which is used internally for analytics tracking but intentionally excluded from API responses (line 45).


45-45: LGTM! Program enrollment data excluded from API response.

The programEnrollment field is destructured and removed from the API response, keeping it internal-only for analytics purposes. This is appropriate for a draft PR preparing infrastructure for future group filtering features.

apps/web/lib/api/links/delete-link.ts (1)

19-19: LGTM! Consistent inclusion of program enrollment data.

The addition ensures program enrollment data is captured for Tinybird tracking when deleting links (line 35), while remaining hidden from the API response via transformLink (line 51).

apps/web/app/(ee)/api/cron/cleanup/e2e-tests/route.ts (1)

38-39: LGTM! Complete link data for E2E cleanup.

Including both includeTags and includeProgramEnrollment ensures complete link metadata is available for proper cleanup and tracking when removing E2E test data.

apps/web/lib/actions/partners/update-partner-enrollment.ts (1)

61-61: LGTM! Enriched link data for enrollment updates.

Including includeProgramEnrollment alongside includeTags ensures complete link metadata is captured when recording enrollment updates to Tinybird (line 69).

apps/web/scripts/bulk-delete-links.ts (1)

14-17: LGTM! Complete metadata for bulk deletion tracking.

The includes ensure full link metadata (tags and program enrollment) is captured for Tinybird tracking before bulk deletion (line 23).

apps/web/app/(ee)/api/stripe/webhook/customer-subscription-deleted.ts (1)

42-43: LGTM! Complete link data for subscription cancellation handling.

Including includeTags and includeProgramEnrollment ensures root domain links have complete metadata for cache invalidation (line 169) and Tinybird recording (line 172) during subscription cancellations.

apps/web/lib/tinybird/record-link.ts (2)

29-32: LGTM!

The partner_group_id field follows the established pattern for optional fields, using .nullish() with empty string transformation to align with Tinybird's schema expectations.


75-75: LGTM!

The optional chaining with fallback correctly handles cases where programEnrollment might not be loaded, maintaining consistency with the schema's empty string default.

apps/web/scripts/bulk-update-links.ts (1)

1-1: LGTM!

The addition of includeProgramEnrollment enriches the link data with group information, enabling accurate Tinybird recording. The spread pattern correctly merges both include objects.

Also applies to: 37-40

apps/web/lib/api/links/bulk-update-links.ts (1)

9-10: LGTM!

Refactoring to use shared includeTags and includeProgramEnrollment utilities improves maintainability and ensures consistent data loading across the codebase. The spread pattern correctly merges both include objects.

Also applies to: 104-105

apps/web/scripts/perplexity/backfill-tenantids.ts (1)

87-92: LGTM!

The manual enrichment correctly adds programEnrollment.groupId to each link before recording. This approach is appropriate since the programEnrollment is already fetched at the parent level (line 68), avoiding redundant nested includes.

apps/web/scripts/migrations/backfill-partner-links.ts (1)

1-1: LGTM!

The addition of includeProgramEnrollment ensures the migration script enriches links with group information before recording to Tinybird, maintaining consistency with the broader enrollment tracking changes.

Also applies to: 19-22

apps/web/app/(ee)/api/cron/merge-partner-accounts/route.ts (1)

3-3: LGTM!

The addition of includeProgramEnrollment ensures that merged partner links carry group information during Tinybird recording, maintaining data consistency throughout the merge operation.

Also applies to: 169-172

apps/web/app/(ee)/api/cron/domains/update/route.ts (1)

4-5: LGTM!

Refactoring to use shared utilities improves consistency and enriches domain-updated links with group information for accurate Tinybird recording.

Also applies to: 72-73

apps/web/lib/actions/partners/revoke-program-invite.ts (2)

3-3: LGTM! Excellent inline documentation.

The refactor to use includeTags maintains consistency, and the inline comment clearly explains why includeProgramEnrollment isn't needed here—preventing redundant nested includes since the programEnrollment is already fetched at the parent level.

Also applies to: 5-5, 35-41


77-82: LGTM!

The manual enrichment correctly constructs ExpandedLink objects with programEnrollment.groupId from the parent enrollment, ensuring Tinybird receives complete group information for deleted links.

Also applies to: 90-90

apps/web/app/api/links/bulk/route.ts (2)

10-11: LGTM: Imports added for program enrollment tracking.

The imports align with the PR's objective to add program enrollment group tracking across link operations.


512-513: LGTM: Enhanced link data retrieval for deletion.

The spread pattern correctly includes both tags and program enrollment data, ensuring deleted links have sufficient context for downstream operations like analytics and audit trails.

apps/web/app/(ee)/api/cron/folders/delete/route.ts (2)

3-3: LGTM: Import added for program enrollment tracking.


35-38: LGTM: Enriched link data for folder deletion.

The merged include ensures that when links are disassociated from folders and recorded to Tinybird (line 51), they include program enrollment context for accurate analytics.

apps/web/lib/api/links/complete-ab-tests.ts (2)

9-9: LGTM: Import added for program enrollment tracking.


70-70: LGTM: A/B test completion now includes enrollment data.

Including program enrollment ensures that when the winning URL is selected and the link is updated, downstream operations (recordLink at line 80 and webhooks at line 82-87) have access to group context for analytics and notifications.

apps/web/app/(ee)/api/cron/domains/delete/route.ts (2)

4-5: LGTM: Imports added for enriched link data.


43-44: LGTM: Domain deletion now tracks program enrollment.

The enriched link data ensures that batch deletions maintain accurate analytics records with program enrollment context when links are recorded to Tinybird (line 63).

apps/web/lib/api/links/include-program-enrollment.ts (1)

1-9: LGTM: Clean utility for program enrollment inclusion.

The helper provides a consistent, type-safe pattern for including program enrollment data across link queries. Selecting only groupId minimizes the performance impact while providing necessary context for group-level analytics and filtering.

apps/web/app/(ee)/api/admin/delete-partner-account/route.ts (2)

21-21: LGTM: Extended program selection to include groupId.


36-36: LGTM: Deleted links now recorded with program enrollment context.

The code correctly enriches each link with its programEnrollment.groupId before recording deletions to Tinybird. This ensures group-level analytics remain accurate even for deleted links. Note that groupId may be null for partners not assigned to a group, which is acceptable here.

Also applies to: 46-52

apps/web/lib/middleware/link.ts (2)

34-34: LGTM: Refactored to use enriched enrollment function.

The import change aligns with the PR's refactoring of enrollment retrieval via getPartnerEnrollmentInfo.


124-127: LGTM: Function call updated with backward compatibility.

The refactored getPartnerEnrollmentInfo maintains the same { partner, discount } destructuring pattern, ensuring backward compatibility while enabling future use of the additional group data returned by the new function.

apps/web/app/(ee)/api/groups/[groupIdOrSlug]/partners/route.ts (2)

4-5: LGTM: Imports added for enriched partner link handling.

Also applies to: 10-10


63-106: LGTM: Enhanced workflow with enriched partner link data.

The async IIFE pattern correctly coordinates the workflow:

  1. Fetches partner links with tags and program enrollment data
  2. Queues remap operations for default links and discount codes
  3. Triggers draft bounty submission creation
  4. Records partner links to Tinybird with enrollment context

The Promise.allSettled ensures all operations complete independently, and the enriched link data enables accurate group-level analytics. The added complexity is justified by the need to await link data before proceeding with dependent operations.

apps/web/scripts/partners/merge-partner-profile.ts (1)

68-71: Thanks for stitching program enrollment into the merge script
Including ...includeProgramEnrollment keeps the Tinybird replay in sync with group metadata. Looks good.

apps/web/lib/api/links/bulk-create-links.ts (1)

89-92: Enrollment include fits the bulk flow
Pulling programEnrollment alongside the bulk-created links keeps later transforms and analytics consistent. No concerns here.

apps/web/app/(ee)/api/groups/[groupIdOrSlug]/route.ts (1)

343-395: Good call on recording partner links before cleanup
The added fetch with includeProgramEnrollment ensures Tinybird receives the updated group signal when a group is deleted. This aligns with the broader enrollment tracking work.

apps/web/lib/planetscale/getPartnerEnrollmentInfo.ts (4)

13-13: LGTM: QueryResult interface extended correctly.

The nullable groupId field is appropriately typed for the LEFT JOIN query.


16-17: LGTM: Function name and comment updated appropriately.

The rename to getPartnerEnrollmentInfo better reflects the expanded scope of returning partner, group, and discount information.


42-43: LGTM: SELECT clause extended correctly.

Both couponTestId and groupId are properly retrieved and align with the QueryResult interface.


24-30: Early return logic is correct.

The group: null returns are appropriate for missing inputs (lines 24-30) and no-result scenarios (lines 54-60), and will be consistent once the type inconsistency at lines 68-70 is resolved.

Also applies to: 54-60

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 (5)
apps/web/lib/api/links/update-link.ts (1)

180-184: Cache still missing groupId for partner group filters.

The cache operation spreads partner and discount but omits group/programEnrollment, while Tinybird recording (lines 187-190) correctly includes it. This inconsistency means cache-hit responses won't reflect the partner's group, causing group filters to return stale data until the cache misses.

Apply this diff to align with the Tinybird flow:

         linkCache.set({
           ...response,
           ...(partner && { partner }),
           ...(discount && { discount }),
+          ...(group && { programEnrollment: { groupId: group.id } }),
         }),
apps/web/lib/planetscale/get-partner-enrollment-info.ts (1)

68-70: Type inconsistency remains: group should be null when groupId is null.

The past review comment on these lines is still applicable. When result.groupId is null, this returns group: { id: null }, but lines 27 and 57 return group: null. This inconsistency forces consumers to handle both !group and !group?.id checks.

Apply this diff to fix:

-    group: {
-      id: result.groupId,
-    },
+    group: result.groupId ? { id: result.groupId } : null,
apps/web/lib/api/links/create-link.ts (1)

151-155: Redis cache still missing group data - duplicate of previous review.

The past review comment on lines 151-161 remains valid. The Redis cache payload includes partner and discount but omits group/programEnrollment, while the Tinybird payload (lines 158-161) correctly includes programEnrollment.groupId. This inconsistency means cached links won't have group data for analytics filters.

Apply the suggested diff from the previous review:

         linkCache.set({
           ...response,
           ...(partner && { partner }),
           ...(discount && { discount }),
+          ...(group && { programEnrollment: { groupId: group.id } }),
         }),
apps/web/scripts/migrations/backfill-link-partner-group-ids.ts (2)

15-60: Add batch-level error handling before running this migration in prod.

A single throw from prisma.link.findMany or recordLink will crash the script with no cursor checkpoint, forcing a full restart and risking duplicate Tinybird writes. Wrap each batch in a try/catch, log the cursor you reached, and surface the failure so you can resume safely. See the diff below.

-  while (true) {
-    // Find the program links
-    const links = await prisma.link.findMany({
-      where: {
-        programId: {
-          not: null,
-        },
-        partnerId: {
-          not: null,
-        },
-        createdAt: {
-          lte: PR_MERGE_TIMESTAMP,
-        },
-      },
-      include: {
-        ...includeTags,
-        ...includeProgramEnrollment,
-      },
-      orderBy: {
-        id: "asc",
-      },
-      take: LINKS_PER_BATCH,
-      ...(cursor
-        ? {
-            cursor: { id: cursor },
-            skip: 1,
-          }
-        : {}),
-    });
-
-    console.log(`Found ${links.length} links to process.`);
-
-    if (links.length === 0) {
-      break;
-    }
-
-    cursor = links[links.length - 1].id;
-
-    const { successful_rows, quarantined_rows } = await recordLink(links);
-
-    if (successful_rows !== links.length) {
-      console.log(`Failed to record ${links.length - successful_rows} links.`);
-    }
-
-    if (quarantined_rows !== 0) {
-      console.log(`Quarantined ${quarantined_rows} links.`);
-    }
-  }
+  while (true) {
+    try {
+      const links = await prisma.link.findMany({
+        where: {
+          programId: {
+            not: null,
+          },
+          partnerId: {
+            not: null,
+          },
+          createdAt: {
+            lte: PR_MERGE_TIMESTAMP,
+          },
+        },
+        include: {
+          ...includeTags,
+          ...includeProgramEnrollment,
+        },
+        orderBy: {
+          id: "asc",
+        },
+        take: LINKS_PER_BATCH,
+        ...(cursor
+          ? {
+              cursor: { id: cursor },
+              skip: 1,
+            }
+          : {}),
+      });
+
+      console.log(
+        `Found ${links.length} links to process (cursor: ${cursor ?? "start"}).`,
+      );
+
+      if (links.length === 0) {
+        break;
+      }
+
+      cursor = links[links.length - 1].id;
+
+      const { successful_rows, quarantined_rows } = await recordLink(links);
+
+      if (successful_rows !== links.length) {
+        console.error(
+          `Failed to record ${links.length - successful_rows} links (cursor: ${cursor}).`,
+        );
+      }
+
+      if (quarantined_rows !== 0) {
+        console.warn(
+          `Quarantined ${quarantined_rows} links (cursor: ${cursor}).`,
+        );
+      }
+    } catch (error) {
+      console.error(
+        `Error processing batch at cursor ${cursor ?? "start"}:`,
+        error,
+      );
+      console.log(`Resume from cursor: ${cursor}`);
+      throw error;
+    }
+  }

63-63: Catch failures from main() and close the Prisma pool.

Right now a rejection from main() becomes an unhandled promise rejection and Prisma keeps the process alive. Please attach .catch/.finally so failures surface and the client disconnects cleanly.

-main();
+main()
+  .catch((error) => {
+    console.error("Migration failed:", error);
+    process.exit(1);
+  })
+  .finally(async () => {
+    await prisma.$disconnect();
+  });
🧹 Nitpick comments (3)
apps/web/scripts/migrations/backfill-banned-partner-links.ts (2)

58-58: Add error handling for the migration script.

The script executes main() without a try/catch wrapper, which means unhandled errors will crash the process without proper logging or cleanup.

Apply this diff to add error handling:

-main();
+main()
+  .then(() => {
+    console.log('Migration completed successfully');
+    process.exit(0);
+  })
+  .catch((error) => {
+    console.error('Migration failed:', error);
+    process.exit(1);
+  });

9-56: Consider adding start log and total counter for better observability.

The script logs batch progress but lacks an initial start message and running total, which would help track overall progress during long migrations.

Add logging at the start and track cumulative progress:

 async function main() {
+  console.log('Starting backfill of banned partner links...');
+  let totalProcessed = 0;
   let cursor: string | undefined = undefined;

   while (true) {
     // Find links for partners that were banned
     const links = await prisma.link.findMany({
       // ... query config
     });

-    console.log(`Found ${links.length} links to process.`);
+    totalProcessed += links.length;
+    console.log(`Found ${links.length} links in batch. Total processed: ${totalProcessed}`);

     if (links.length === 0) {
+      console.log(`Backfill complete. Total links processed: ${totalProcessed}`);
       break;
     }

     // ... rest of processing
   }
 }
apps/web/lib/api/links/create-link.ts (1)

149-214: Consider logging failures in critical operations.

Promise.allSettled provides resilience by preventing cascading failures, but critical operations like caching and Tinybird recording may fail silently. Consider adding error logging to track which operations fail.

Example approach:

-      await Promise.allSettled([
+      const results = await Promise.allSettled([
         // ... all operations
       ]);
+
+      // Log any critical failures
+      if (results[0].status === 'rejected') {
+        console.error('Failed to cache link:', results[0].reason);
+      }
+      if (results[1].status === 'rejected') {
+        console.error('Failed to record link in Tinybird:', results[1].reason);
+      }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 58dc5b5 and f79b708.

📒 Files selected for processing (9)
  • apps/web/lib/actions/partners/ban-partner.ts (3 hunks)
  • apps/web/lib/actions/partners/bulk-ban-partners.ts (3 hunks)
  • apps/web/lib/actions/partners/unban-partner.ts (3 hunks)
  • apps/web/lib/api/links/create-link.ts (3 hunks)
  • apps/web/lib/api/links/update-link.ts (2 hunks)
  • apps/web/lib/middleware/link.ts (2 hunks)
  • apps/web/lib/planetscale/get-partner-enrollment-info.ts (5 hunks)
  • apps/web/scripts/migrations/backfill-banned-partner-links.ts (1 hunks)
  • apps/web/scripts/migrations/backfill-link-partner-group-ids.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/lib/middleware/link.ts
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: devkiran
Repo: dubinc/dub PR: 2448
File: packages/email/src/templates/partner-program-summary.tsx:254-254
Timestamp: 2025-05-29T04:49:42.842Z
Learning: In the Dub codebase, it's acceptable to keep `partners.dub.co` hardcoded rather than making it configurable for different environments.
📚 Learning: 2025-06-06T07:59:03.120Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2177
File: apps/web/lib/api/links/bulk-create-links.ts:66-84
Timestamp: 2025-06-06T07:59:03.120Z
Learning: In apps/web/lib/api/links/bulk-create-links.ts, the team accepts the risk of potential undefined results from links.find() operations when building invalidLinks arrays, because existing links are fetched from the database based on the input links, so matches are expected to always exist.

Applied to files:

  • apps/web/lib/actions/partners/bulk-ban-partners.ts
  • apps/web/scripts/migrations/backfill-link-partner-group-ids.ts
  • apps/web/lib/api/links/update-link.ts
  • apps/web/scripts/migrations/backfill-banned-partner-links.ts
  • apps/web/lib/actions/partners/ban-partner.ts
  • apps/web/lib/api/links/create-link.ts
  • apps/web/lib/actions/partners/unban-partner.ts
📚 Learning: 2025-10-06T15:48:14.205Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2935
File: apps/web/lib/actions/partners/invite-partner-from-network.ts:21-28
Timestamp: 2025-10-06T15:48:14.205Z
Learning: For the network invites limit check in apps/web/lib/actions/partners/invite-partner-from-network.ts, the team accepts that concurrent invites may bypass the limit due to race conditions. Perfect atomicity is not required for this feature.

Applied to files:

  • apps/web/lib/actions/partners/bulk-ban-partners.ts
  • apps/web/lib/actions/partners/ban-partner.ts
📚 Learning: 2025-08-14T05:57:35.546Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2735
File: apps/web/lib/actions/partners/update-discount.ts:60-66
Timestamp: 2025-08-14T05:57:35.546Z
Learning: In the partner groups system, discounts should always belong to a group. The partnerGroup relation should never be null when updating discounts, so optional chaining on partnerGroup?.id may be unnecessary defensive programming.

Applied to files:

  • apps/web/lib/api/links/update-link.ts
  • apps/web/lib/planetscale/get-partner-enrollment-info.ts
  • apps/web/lib/api/links/create-link.ts
📚 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/lib/actions/partners/ban-partner.ts
  • apps/web/lib/api/links/create-link.ts
  • apps/web/lib/actions/partners/unban-partner.ts
📚 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/planetscale/get-partner-enrollment-info.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/planetscale/get-partner-enrollment-info.ts
🧬 Code graph analysis (7)
apps/web/lib/actions/partners/bulk-ban-partners.ts (2)
apps/web/lib/tinybird/record-link.ts (1)
  • recordLink (81-98)
apps/web/lib/api/discounts/queue-discount-code-deletion.ts (1)
  • queueDiscountCodeDeletion (13-37)
apps/web/scripts/migrations/backfill-link-partner-group-ids.ts (3)
packages/prisma/index.ts (1)
  • prisma (3-9)
apps/web/lib/api/links/include-program-enrollment.ts (1)
  • includeProgramEnrollment (3-9)
apps/web/lib/tinybird/record-link.ts (1)
  • recordLink (81-98)
apps/web/lib/api/links/update-link.ts (3)
apps/web/lib/planetscale/get-partner-enrollment-info.ts (1)
  • getPartnerEnrollmentInfo (17-82)
apps/web/lib/tinybird/record-link.ts (1)
  • recordLink (81-98)
apps/web/lib/storage.ts (2)
  • isNotHostedImage (257-259)
  • storage (251-251)
apps/web/scripts/migrations/backfill-banned-partner-links.ts (3)
packages/prisma/index.ts (1)
  • prisma (3-9)
apps/web/lib/api/links/include-program-enrollment.ts (1)
  • includeProgramEnrollment (3-9)
apps/web/lib/tinybird/record-link.ts (1)
  • recordLink (81-98)
apps/web/lib/actions/partners/ban-partner.ts (1)
apps/web/lib/tinybird/record-link.ts (1)
  • recordLink (81-98)
apps/web/lib/api/links/create-link.ts (4)
apps/web/lib/planetscale/get-partner-enrollment-info.ts (1)
  • getPartnerEnrollmentInfo (17-82)
apps/web/lib/tinybird/record-link.ts (1)
  • recordLink (81-98)
apps/web/lib/storage.ts (2)
  • isNotHostedImage (257-259)
  • storage (251-251)
packages/prisma/index.ts (1)
  • prisma (3-9)
apps/web/lib/actions/partners/unban-partner.ts (2)
apps/web/lib/api/links/include-program-enrollment.ts (1)
  • includeProgramEnrollment (3-9)
apps/web/lib/tinybird/record-link.ts (1)
  • recordLink (81-98)
⏰ 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 (10)
apps/web/lib/api/links/update-link.ts (3)

1-1: Good optimization with clear documentation.

Importing getPartnerEnrollmentInfo and using a raw SQL query instead of Prisma's includeProgramEnrollment is more efficient. The comment at line 166 clearly explains this decision.

Also applies to: 166-166


172-176: Well-structured async IIFE pattern.

Wrapping the side effects in an async IIFE to fetch enrollment info before dispatching parallel operations is the right approach, since both cache and Tinybird recording need this data.


192-225: Remaining side effects are correctly structured.

The conditional operations for cache invalidation, image management, webhook propagation, and AB test scheduling all have appropriate guards and are correctly parallelized.

apps/web/lib/api/links/create-link.ts (4)

2-2: LGTM: Import updated to use new enrollment info function.

The import correctly reflects the function rename from getPartnerAndDiscount to getPartnerEnrollmentInfo, which now provides group data in addition to partner and discount information.


134-134: LGTM: Clear explanation of data fetching strategy.

The comment appropriately explains why includeProgramEnrollment is omitted from the initial query, since enrollment data is retrieved via getPartnerEnrollmentInfo in the subsequent waitUntil block.


143-147: LGTM: Enrollment info fetched before side effects.

The refactor correctly wraps side effects in an async IIFE, allowing getPartnerEnrollmentInfo to be awaited before downstream operations. This ensures enrollment data (partner, discount, group) is available for both caching and analytics recording.


158-161: LGTM: Tinybird payload correctly includes group data.

The recordLink call properly includes programEnrollment.groupId when group exists, enabling group-level analytics filtering as intended by this PR.

apps/web/lib/actions/partners/ban-partner.ts (2)

9-9: LGTM!

The import is correctly added and used later in the file to record link deletions in Tinybird.


54-54: LGTM!

Setting both disabledAt and expiresAt is appropriate for ban operations. The fields serve complementary purposes: disabledAt indicates administrative disabling, while expiresAt ensures time-based expiration mechanisms also recognize the link as inactive.

apps/web/lib/actions/partners/unban-partner.ts (1)

117-131: Great to sync Tinybird after unban.

Hooking recordLink(links) into the post-unban side-effects keeps partner-group metadata in Tinybird aligned with the re-enabled links, preventing stale analytics.

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

♻️ Duplicate comments (3)
apps/web/scripts/migrations/backfill-link-partner-group-ids.ts (2)

10-74: Add error handling and progress tracking for resilience.

The migration still lacks error handling and progress tracking, as previously flagged. Any failure will crash the script and lose all progress.


76-76: Handle promise rejection from main().

The main() function is still invoked without error handling, as previously flagged.

apps/web/lib/actions/partners/ban-partner.ts (1)

37-39: Add includeProgramEnrollment to load groupId for Tinybird recording.

The links include is missing includeProgramEnrollment, which is essential for this PR's groupId propagation feature. When recordLink is called at line 132, it invokes transformLinkTB which accesses link.programEnrollment?.groupId to populate the partner_group_id field in Tinybird. Without loading this relation, programEnrollment will be undefined and partner_group_id will be recorded as an empty string, breaking the group filtering analytics.

Apply this diff to include the program enrollment data:

 links: {
-  include: includeTags,
+  include: { ...includeTags, ...includeProgramEnrollment },
 },

Note: This assumes includeProgramEnrollment is exported from the appropriate module. If not already imported, add:

import { includeProgramEnrollment } from "@/lib/api/links/include-program-enrollment";
🧹 Nitpick comments (1)
apps/web/scripts/migrations/backfill-link-partner-group-ids.ts (1)

74-74: Add Prisma cleanup on script completion.

The script should explicitly disconnect the Prisma client to prevent connection leaks.

Add cleanup after the main function completes (when implementing the error handling suggested above):

main()
  .catch((error) => {
    console.error("Migration failed:", error);
    process.exit(1);
  })
  .finally(async () => {
    await prisma.$disconnect();
  });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f79b708 and a30f537.

📒 Files selected for processing (3)
  • apps/web/lib/actions/partners/ban-partner.ts (4 hunks)
  • apps/web/scripts/migrations/backfill-banned-partner-links.ts (1 hunks)
  • apps/web/scripts/migrations/backfill-link-partner-group-ids.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/scripts/migrations/backfill-banned-partner-links.ts
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: devkiran
Repo: dubinc/dub PR: 2448
File: packages/email/src/templates/partner-program-summary.tsx:254-254
Timestamp: 2025-05-29T04:49:42.842Z
Learning: In the Dub codebase, it's acceptable to keep `partners.dub.co` hardcoded rather than making it configurable for different environments.
📚 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/lib/actions/partners/ban-partner.ts
📚 Learning: 2025-06-06T07:59:03.120Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2177
File: apps/web/lib/api/links/bulk-create-links.ts:66-84
Timestamp: 2025-06-06T07:59:03.120Z
Learning: In apps/web/lib/api/links/bulk-create-links.ts, the team accepts the risk of potential undefined results from links.find() operations when building invalidLinks arrays, because existing links are fetched from the database based on the input links, so matches are expected to always exist.

Applied to files:

  • apps/web/lib/actions/partners/ban-partner.ts
  • apps/web/scripts/migrations/backfill-link-partner-group-ids.ts
📚 Learning: 2025-10-06T15:48:14.205Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2935
File: apps/web/lib/actions/partners/invite-partner-from-network.ts:21-28
Timestamp: 2025-10-06T15:48:14.205Z
Learning: For the network invites limit check in apps/web/lib/actions/partners/invite-partner-from-network.ts, the team accepts that concurrent invites may bypass the limit due to race conditions. Perfect atomicity is not required for this feature.

Applied to files:

  • apps/web/lib/actions/partners/ban-partner.ts
📚 Learning: 2025-10-28T19:17:44.390Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2985
File: apps/web/app/(ee)/api/network/programs/[programSlug]/route.ts:32-37
Timestamp: 2025-10-28T19:17:44.390Z
Learning: In Prisma queries, the `include` clause is only used for relationships (one-to-one, one-to-many, many-to-many). Regular scalar fields, JSON fields, and other non-relational columns are automatically included in the query result and do not need to be specified in the `include` object.

Applied to files:

  • apps/web/lib/actions/partners/ban-partner.ts
🧬 Code graph analysis (2)
apps/web/lib/actions/partners/ban-partner.ts (1)
apps/web/lib/tinybird/record-link.ts (1)
  • recordLink (81-98)
apps/web/scripts/migrations/backfill-link-partner-group-ids.ts (3)
packages/prisma/index.ts (1)
  • prisma (3-9)
apps/web/lib/api/links/include-program-enrollment.ts (1)
  • includeProgramEnrollment (3-9)
apps/web/lib/tinybird/record-link.ts (1)
  • recordLink (81-98)
⏰ 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 (3)
apps/web/lib/actions/partners/ban-partner.ts (3)

6-6: LGTM: Imports added for new functionality.

The imports for includeTags and recordLink are correctly added to support the link enrichment and Tinybird recording features introduced in this PR.

Also applies to: 10-10


57-57: LGTM: Explicitly marking links as disabled on ban.

Setting disabledAt in addition to expiresAt provides clearer intent when a partner is banned and improves consistency for queries that filter on disabled links.


128-132: LGTM: Cache expiry and Tinybird recording improve data consistency.

Good additions:

  • linkCache.expireMany(links) ensures banned partner links are removed from cache
  • recordLink(links, { deleted: true }) maintains analytics consistency by recording link deletions

Both operations are properly parallelized with Promise.allSettled. However, the effectiveness of the Tinybird recording for group analytics depends on fixing the missing includeProgramEnrollment in the query (see comment on lines 37-39).

@steven-tey steven-tey merged commit 6d60f1b into main Nov 8, 2025
7 of 8 checks passed
@steven-tey steven-tey deleted the partner-group-filters branch November 8, 2025 23:44
dbryson added a commit to beyond-the-checkout/btc that referenced this pull request Nov 26, 2025
Cherry-picked upstream commits:
- 45a227d: Remove accessLevel from top_folders analytics schema
- 6d60f1b: Partner group filters (dubinc#3071) - adds top_groups groupBy
- 6ef200f: Hide group cards dynamically, transformLink import fix

Changes include:
- Add groupId filter and top_groups analytics endpoint
- Add includeProgramEnrollment for link queries with group context
- Update partner UI to hide group info for banned/deactivated partners
- Fix transformLink import path in get-events.ts

Conflicts resolved:
- Kept existing storage.upload() positional API (upstream uses object syntax)
- Preserved sortRewardsByEventOrder in partner-info-cards.tsx
@coderabbitai coderabbitai bot mentioned this pull request Dec 7, 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