-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Refactor Fraud management #3175
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…event types across multiple files, enhancing consistency and maintainability. Update Prisma schema to include relationships for fraud events and groups.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughMigrate fraud from event-level to group-level: introduce FraudEventGroup and per-event FraudEvent, implement hash-based dedupe/grouping, replace event resolution with group resolution, update Prisma schema, APIs, detectors, webhooks, actions, UI, migrations, hooks, and add modals/scripts. Changes
Sequence Diagram(s)sequenceDiagram
participant Stripe as Stripe (Webhook)
participant Server as App Server
participant DB as Prisma
participant Email as Email Service
Note over Stripe,Server: Duplicate payout detection & group creation
Stripe->>Server: account.updated (payout method)
Server->>DB: find partners by payoutMethodHash (incl. enrollments)
DB-->>Server: matching partners + program context
Server->>Server: build per-partner per-program events (hash, metadata)
Server->>DB: create/reuse FraudEventGroup(s) and create FraudEvent(s)
DB-->>Server: created/updated groups/events
alt no duplicates & first connect
Server->>Email: send "connected payout method" email
else duplicates detected
Server->>Email: send DuplicatePayoutMethod batch emails
end
Email-->>Server: ack
Server-->>Stripe: 200 OK
sequenceDiagram
participant UI as Browser (Admin)
participant Server as App Server
participant DB as Prisma
Note over UI,Server: Bulk resolve fraud groups
UI->>Server: POST /action bulkResolveFraudGroups (groupIds, reason)
Server->>DB: update FraudEventGroup where ids -> status="resolved", set resolvedAt, userId, reason
DB-->>Server: affected count
Server->>DB: create comments per unique partner (if reason supplied)
DB-->>Server: ack
Server-->>UI: success
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
… count API and introducing new endpoints for fraud event groups
…eprecated endpoints and enhancing error handling. Update related schemas and hooks for consistency.
…ming consistency. Introduce new FraudEventGroupTable and ResolvedFraudEventGroupTable components, replacing deprecated ones. Update imports and schemas for fraud group counts across multiple files.
…s with resolveFraudEventGroups across multiple files. Update related schemas, actions, and components to support group-based resolution of fraud events, enhancing consistency and maintainability.
… related schemas, components, and email templates for improved clarity and functionality.
There was a problem hiding this 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/app/(ee)/api/cron/merge-partner-accounts/route.ts (1)
274-295: Clarify the “no other partners share this payout method” check and verify handling of source-partner fraud groupsThe automatic resolution of
partnerDuplicatePayoutMethodfraud groups after a merge is a good fit for the partner-scoped rule and helps keep fraud groups in sync with the underlying data. To make the uniqueness condition more explicit and future‑proof, consider counting only other partners that share the samepayoutMethodHashand checking for zero:- if (targetAccount.payoutMethodHash) { - const duplicatePartners = await prisma.partner.count({ - where: { - payoutMethodHash: targetAccount.payoutMethodHash, - }, - }); - - if (duplicatePartners <= 1) { + if (targetAccount.payoutMethodHash) { + const otherPartnersWithSamePayoutMethod = await prisma.partner.count({ + where: { + payoutMethodHash: targetAccount.payoutMethodHash, + id: { not: targetPartnerId }, + }, + }); + + if (otherPartnersWithSamePayoutMethod === 0) { await resolveFraudGroups({ where: { partnerId: targetPartnerId, type: "partnerDuplicatePayoutMethod", }, resolutionReason: "Automatically resolved because partners with duplicate payout methods were merged. No other partners share this payout method.", }); } }Also, please double‑check that any
fraudEventGrouprows tied to the deletedsourcePartnerIdare either cascaded away or handled elsewhere so we don’t leave stale pending groups pointing at a non‑existent partner.Based on learnings, this is consistent with the partner-scoped
partnerDuplicatePayoutMethodrule behavior.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/web/app/(ee)/api/cron/merge-partner-accounts/route.ts(3 hunks)apps/web/lib/api/fraud/resolve-fraud-groups.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/web/lib/api/fraud/resolve-fraud-groups.ts
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: devkiran
Repo: dubinc/dub PR: 3175
File: apps/web/lib/actions/partners/bulk-reject-partner-applications.ts:14-21
Timestamp: 2025-12-03T09:19:48.164Z
Learning: In apps/web/lib/actions/partners/bulk-reject-partner-applications.ts, the bulkRejectPartnerApplicationsAction does not need explicit plan capability checks for fraud operations (when reportFraud is true) because the authorization is handled automatically by the underlying fraud operation functions (resolveFraudGroups, createFraudEvents) or through other automated mechanisms in the system.
📚 Learning: 2025-12-03T09:19:48.164Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3175
File: apps/web/lib/actions/partners/bulk-reject-partner-applications.ts:14-21
Timestamp: 2025-12-03T09:19:48.164Z
Learning: In apps/web/lib/actions/partners/bulk-reject-partner-applications.ts, the bulkRejectPartnerApplicationsAction does not need explicit plan capability checks for fraud operations (when reportFraud is true) because the authorization is handled automatically by the underlying fraud operation functions (resolveFraudGroups, createFraudEvents) or through other automated mechanisms in the system.
Applied to files:
apps/web/app/(ee)/api/cron/merge-partner-accounts/route.ts
📚 Learning: 2025-11-24T09:10:12.536Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3089
File: apps/web/lib/api/fraud/fraud-rules-registry.ts:17-25
Timestamp: 2025-11-24T09:10:12.536Z
Learning: In apps/web/lib/api/fraud/fraud-rules-registry.ts, the fraud rules `partnerCrossProgramBan` and `partnerDuplicatePayoutMethod` intentionally have stub implementations that return `{ triggered: false }` because they are partner-scoped rules handled separately during partner application/onboarding flows (e.g., in detect-record-fraud-application.ts), rather than being evaluated per conversion event like other rules in the registry. The stubs exist only to satisfy the `Record<FraudRuleType, ...>` type constraint.
Applied to files:
apps/web/app/(ee)/api/cron/merge-partner-accounts/route.ts
📚 Learning: 2025-11-24T08:55:31.332Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3089
File: apps/web/app/(ee)/api/fraud-rules/route.ts:71-87
Timestamp: 2025-11-24T08:55:31.332Z
Learning: In apps/web/app/(ee)/api/fraud-rules/route.ts, fraud rules cannot be created in a disabled state. When using prisma.fraudRule.upsert, the create branch intentionally omits the disabledAt field (defaulting to null, meaning enabled), while the update branch allows toggling enabled/disabled state via the disabledAt field. This is a business logic constraint.
Applied to files:
apps/web/app/(ee)/api/cron/merge-partner-accounts/route.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/app/(ee)/api/cron/merge-partner-accounts/route.ts
📚 Learning: 2025-08-14T05:17:51.825Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2735
File: apps/web/lib/actions/partners/delete-reward.ts:33-41
Timestamp: 2025-08-14T05:17:51.825Z
Learning: In the partner groups system, a rewardId can only belong to one group, establishing a one-to-one relationship between rewards and groups. This means using Prisma's `update` method (rather than `updateMany`) is appropriate when updating groups by rewardId.
Applied to files:
apps/web/app/(ee)/api/cron/merge-partner-accounts/route.ts
🧬 Code graph analysis (1)
apps/web/app/(ee)/api/cron/merge-partner-accounts/route.ts (2)
packages/prisma/index.ts (1)
prisma(3-9)apps/web/lib/api/fraud/resolve-fraud-groups.ts (1)
resolveFraudGroups(4-27)
⏰ 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 (2)
apps/web/app/(ee)/api/cron/merge-partner-accounts/route.ts (2)
2-2: Import ofresolveFraudGroupsis appropriate and consistent with the new fraud group modelThe added import is used in the post-merge cleanup and aligns with the group-based fraud resolution utilities introduced in this PR.
63-63: SelectingpayoutMethodHashis necessary for downstream fraud resolutionIncluding
payoutMethodHashin the partner selection is appropriate so the later uniqueness check can decide whether to auto-resolve duplicate-payout-method fraud groups; no issues here.
There was a problem hiding this 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/api/fraud/detect-record-fraud-application.ts (1)
49-79: Clarify duplicate payout-method semantics and tighten query / namingThe move to a program-scoped duplicate check via
programs: { some: { programId: program.id } }and emitting onepartnerDuplicatePayoutMethodrule perduplicatePartnerIdis a good fit for the new grouped-event flow, but there are two small points worth tightening:
Possible self-inclusion in
duplicatePartners
Depending on when this runs relative to enrollment creation, the current partner could satisfy theprograms: { some: { programId } }condition and end up induplicatePartners. That would cause a “duplicate payout method” signal even when only a single account exists for this payout method in the program. If the intent is “same payout method shared across different partners in this program”, consider explicitly excluding the current partner from the query:const duplicatePartners = await prisma.partner.findMany({ where: { payoutMethodHash: partner.payoutMethodHash, id: { not: partner.id }, programs: { some: { programId: program.id }, }, }, select: { id: true, }, });The rest of the logic (
duplicatePartnerIdsincludingpartner.id) can stay unchanged.Minor readability nit on variable shadowing
InduplicatePartners.map((partner) => partner.id), the parameter name shadows the outerpartner. Renaming the inner variable (e.g.porduplicatePartner) would reduce cognitive load without behavior change.Together these keep the semantics closer to “cross-account” duplicates and make the code slightly easier to reason about. Based on learnings, this matters since this file is the canonical implementation point for these partner-scoped fraud rules.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/lib/api/fraud/detect-record-fraud-application.ts(3 hunks)
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: devkiran
Repo: dubinc/dub PR: 3175
File: apps/web/lib/actions/partners/bulk-reject-partner-applications.ts:14-21
Timestamp: 2025-12-03T09:19:48.164Z
Learning: In apps/web/lib/actions/partners/bulk-reject-partner-applications.ts, the bulkRejectPartnerApplicationsAction does not need explicit plan capability checks for fraud operations (when reportFraud is true) because the authorization is handled automatically by the underlying fraud operation functions (resolveFraudGroups, createFraudEvents) or through other automated mechanisms in the system.
📚 Learning: 2025-11-24T09:10:12.536Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3089
File: apps/web/lib/api/fraud/fraud-rules-registry.ts:17-25
Timestamp: 2025-11-24T09:10:12.536Z
Learning: In apps/web/lib/api/fraud/fraud-rules-registry.ts, the fraud rules `partnerCrossProgramBan` and `partnerDuplicatePayoutMethod` intentionally have stub implementations that return `{ triggered: false }` because they are partner-scoped rules handled separately during partner application/onboarding flows (e.g., in detect-record-fraud-application.ts), rather than being evaluated per conversion event like other rules in the registry. The stubs exist only to satisfy the `Record<FraudRuleType, ...>` type constraint.
Applied to files:
apps/web/lib/api/fraud/detect-record-fraud-application.ts
📚 Learning: 2025-12-03T09:19:48.164Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3175
File: apps/web/lib/actions/partners/bulk-reject-partner-applications.ts:14-21
Timestamp: 2025-12-03T09:19:48.164Z
Learning: In apps/web/lib/actions/partners/bulk-reject-partner-applications.ts, the bulkRejectPartnerApplicationsAction does not need explicit plan capability checks for fraud operations (when reportFraud is true) because the authorization is handled automatically by the underlying fraud operation functions (resolveFraudGroups, createFraudEvents) or through other automated mechanisms in the system.
Applied to files:
apps/web/lib/api/fraud/detect-record-fraud-application.ts
📚 Learning: 2025-11-24T08:55:31.332Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3089
File: apps/web/app/(ee)/api/fraud-rules/route.ts:71-87
Timestamp: 2025-11-24T08:55:31.332Z
Learning: In apps/web/app/(ee)/api/fraud-rules/route.ts, fraud rules cannot be created in a disabled state. When using prisma.fraudRule.upsert, the create branch intentionally omits the disabledAt field (defaulting to null, meaning enabled), while the update branch allows toggling enabled/disabled state via the disabledAt field. This is a business logic constraint.
Applied to files:
apps/web/lib/api/fraud/detect-record-fraud-application.ts
📚 Learning: 2025-11-12T22:23:10.414Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 3098
File: apps/web/lib/actions/partners/message-program.ts:49-58
Timestamp: 2025-11-12T22:23:10.414Z
Learning: In apps/web/lib/actions/partners/message-program.ts, when checking if a partner can continue messaging after messaging is disabled, the code intentionally requires `senderPartnerId: null` (program-initiated messages) to prevent partners from continuing to send junk messages. Only conversations started by the program should continue after messaging is disabled, as a spam prevention mechanism.
Applied to files:
apps/web/lib/api/fraud/detect-record-fraud-application.ts
🧬 Code graph analysis (1)
apps/web/lib/api/fraud/detect-record-fraud-application.ts (2)
packages/prisma/client.ts (1)
FraudRuleType(17-17)apps/web/lib/api/fraud/create-fraud-events.ts (1)
createFraudEvents(12-154)
⏰ 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 (1)
apps/web/lib/api/fraud/detect-record-fraud-application.ts (1)
3-3: Type-safe rule typing and metadata propagation intocreateFraudEventslook solidWiring
triggeredRulesasPick<FraudEvent, "type" | "metadata">[], usingFraudRuleType.*enums, and then spreading...ruleintocreateFraudEventswith explicitprogramId/partnerIdkeeps this path aligned with the Prisma schema and the new grouped fraud-event pipeline. The cross-program ban rule carryingmetadata: nullis also consistent with a JSON/nullable metadata field and themetadata ?? undefinedhandling increateFraudEvents. Based on learnings, this keeps the partner-scoped implementations forpartnerCrossProgramBanandpartnerDuplicatePayoutMethodcorrectly localized here while the registry stubs remain type-satisfying only.Also applies to: 26-26, 42-44, 83-88
There was a problem hiding this 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
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/fraud/fraud-group-table.tsx (1)
264-270: Fix null partner handling before Map creation.The
partnersarray may contain null values (as evidenced by theNonNullablewrapper on line 89 and the null check on line 437), but the Map creation on line 269 accessesp.idwithout filtering nulls first. This will throw a runtime error if any selected row has a null partner.Apply this diff to filter null partners before deduplication:
const selectedRows = tableInstance.getSelectedRowModel().rows; - const partners = selectedRows.map((row) => row.original.partner); + const partners = selectedRows + .map((row) => row.original.partner) + .filter( + (p): p is NonNullable<FraudGroupProps["partner"]> => p !== null, + ); const selectedFraudGroups = selectedRows.map((row) => row.original); // Remove duplicates by partner ID const uniquePartners = Array.from( new Map(partners.map((p) => [p.id, p])).values(), );Note: Lines 294-300 already apply this filtering correctly in the "Ban partners" button's onClick handler.
🧹 Nitpick comments (1)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/fraud/fraud-group-table.tsx (1)
294-304: Consider extracting duplicate partner extraction logic.The partner extraction and deduplication logic is duplicated between lines 264-270 and 294-304. Once the null filtering is added to lines 264-270, consider storing the filtered
uniquePartnersearlier and reusing it here to reduce code duplication.Example refactor:
const partners = selectedRows .map((row) => row.original.partner) .filter((p): p is NonNullable<FraudGroupProps["partner"]> => p !== null); const uniquePartners = Array.from( new Map(partners.map((p) => [p.id, p])).values(), ); // ... later in Ban Partners button onClick: onClick={() => { setPendingBanPartners(uniquePartners); setShowBulkBanPartnersModal(true); }}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/fraud/fraud-group-table.tsx(10 hunks)
🧰 Additional context used
🧠 Learnings (11)
📓 Common learnings
Learnt from: devkiran
Repo: dubinc/dub PR: 3175
File: apps/web/lib/actions/partners/bulk-reject-partner-applications.ts:14-21
Timestamp: 2025-12-03T09:19:48.164Z
Learning: In apps/web/lib/actions/partners/bulk-reject-partner-applications.ts, the bulkRejectPartnerApplicationsAction does not need explicit plan capability checks for fraud operations (when reportFraud is true) because the authorization is handled automatically by the underlying fraud operation functions (resolveFraudGroups, createFraudEvents) or through other automated mechanisms in the system.
📚 Learning: 2025-12-03T09:19:48.164Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3175
File: apps/web/lib/actions/partners/bulk-reject-partner-applications.ts:14-21
Timestamp: 2025-12-03T09:19:48.164Z
Learning: In apps/web/lib/actions/partners/bulk-reject-partner-applications.ts, the bulkRejectPartnerApplicationsAction does not need explicit plan capability checks for fraud operations (when reportFraud is true) because the authorization is handled automatically by the underlying fraud operation functions (resolveFraudGroups, createFraudEvents) or through other automated mechanisms in the system.
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/fraud/fraud-group-table.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/fraud/fraud-group-table.tsx
📚 Learning: 2025-11-12T22:23:10.414Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 3098
File: apps/web/lib/actions/partners/message-program.ts:49-58
Timestamp: 2025-11-12T22:23:10.414Z
Learning: In apps/web/lib/actions/partners/message-program.ts, when checking if a partner can continue messaging after messaging is disabled, the code intentionally requires `senderPartnerId: null` (program-initiated messages) to prevent partners from continuing to send junk messages. Only conversations started by the program should continue after messaging is disabled, as a spam prevention mechanism.
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/fraud/fraud-group-table.tsx
📚 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/app.dub.co/(dashboard)/[slug]/(ee)/program/fraud/fraud-group-table.tsx
📚 Learning: 2025-11-24T09:10:12.536Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3089
File: apps/web/lib/api/fraud/fraud-rules-registry.ts:17-25
Timestamp: 2025-11-24T09:10:12.536Z
Learning: In apps/web/lib/api/fraud/fraud-rules-registry.ts, the fraud rules `partnerCrossProgramBan` and `partnerDuplicatePayoutMethod` intentionally have stub implementations that return `{ triggered: false }` because they are partner-scoped rules handled separately during partner application/onboarding flows (e.g., in detect-record-fraud-application.ts), rather than being evaluated per conversion event like other rules in the registry. The stubs exist only to satisfy the `Record<FraudRuleType, ...>` type constraint.
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/fraud/fraud-group-table.tsx
📚 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/app/app.dub.co/(dashboard)/[slug]/(ee)/program/fraud/fraud-group-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/fraud/fraud-group-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/app/app.dub.co/(dashboard)/[slug]/(ee)/program/fraud/fraud-group-table.tsx
📚 Learning: 2025-11-17T05:19:11.972Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3113
File: apps/web/app/(ee)/api/cron/payouts/charge-succeeded/send-paypal-payouts.ts:65-75
Timestamp: 2025-11-17T05:19:11.972Z
Learning: In the Dub codebase, `sendBatchEmail` (implemented in packages/email/src/send-via-resend.ts) handles filtering of emails with invalid `to` addresses internally. Call sites can safely use non-null assertions on email addresses because the email sending layer will filter out any entries with null/undefined `to` values before sending. This centralized validation pattern is intentional and removes the need for filtering at individual call sites.
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/fraud/fraud-group-table.tsx
📚 Learning: 2025-10-15T01:52:37.048Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/members/page-client.tsx:270-303
Timestamp: 2025-10-15T01:52:37.048Z
Learning: In React components with dropdowns or form controls that show modals for confirmation (e.g., role selection, delete confirmation), local state should be reverted to match the prop value when the modal is cancelled. This prevents the UI from showing an unconfirmed change. The solution is to either: (1) pass an onClose callback to the modal that resets the local state, or (2) observe modal visibility state and reset on close. Example context: RoleCell component in apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/members/page-client.tsx where role dropdown should revert to user.role when UpdateUserModal is cancelled.
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/fraud/fraud-group-table.tsx
⏰ 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 (8)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/fraud/fraud-group-table.tsx (8)
5-11: LGTM: Import updates align with the fraud group refactor.The renamed hooks (
useFraudGroups,useFraudGroupCount) and new modal imports (useBulkResolveFraudGroupsModal,useRejectPartnerApplicationModal) correctly reflect the shift from event-based to group-based fraud management.
37-86: LGTM: Core refactor properly migrates from event groups to groups.The component rename, hook updates (
useFraudGroupFilters,useFraudGroups,useFraudGroupCount), and state management shift fromgroupKeytogroupIdare consistent and correct. Theexclude: ["groupId"]parameter on line 60 appropriately prevents groupId filtering in the main table query while allowing detail-level fetches to use it (line 546).
88-116: LGTM: Modal wiring updated for group-based operations.The new
BulkResolveFraudGroupsModalfollows the same pattern as the ban modal, and both correctly mutate/api/fraud/groupson confirmation. Type updates toFraudGroupPropsare consistent throughout.
118-313: Table configuration correctly migrated to group-based model.The table column definitions, data bindings, and row actions are properly updated to use
FraudGroupPropswith renamed fields (eventCount,lastEventAt,id). The resource naming and pagination are correct.
315-329: LGTM: Navigation logic correctly uses id-based indexing.The previous/next navigation properly migrates from
groupKeytoidfor finding the current index and navigating between fraud groups.
331-411: LGTM: Render section properly integrated with group-based data.Modal rendering, details sheet wiring, and empty state checks correctly use the renamed data sources (
fraudGroups,fraudGroupCount,groupId) and props (fraudGroup). Navigation callbacks properly referencegroupIdfor previous/next actions.
414-488: LGTM: Row menu properly handles partner status and null cases.The
RowMenuButtoncorrectly integrates the newRejectPartnerApplicationModalfor pending partners and maintains the ban flow for active partners. The null check on line 437 appropriately guards against rows without partners or non-pending statuses.
530-558: LGTM: Detail fetch hook efficiently handles loaded vs. missing groups.The
useCurrentFraudGrouphook correctly reuses fraud groups from the main list when available and falls back to a targeted fetch (bygroupId) only when the selected group isn't loaded. The id-based lookups are consistent with the broader refactor.
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/fraud/fraud-group-table.tsx (1)
264-272: Fix null pointer crash in partner deduplication.Line 264 maps partners without filtering nulls, then line 268-270 attempts to access
.idon potentially null values. This will throw a runtime error if any selected fraud group haspartner: null.The correct pattern is already implemented in the "Ban partners" button (lines 295-300) and should be applied here.
Apply this diff to filter null partners before deduplication:
const selectedRows = tableInstance.getSelectedRowModel().rows; - const partners = selectedRows.map((row) => row.original.partner); const selectedFraudGroups = selectedRows.map((row) => row.original); + const partners = selectedRows + .map((row) => row.original.partner) + .filter( + (p): p is NonNullable<FraudGroupProps["partner"]> => p !== null, + ); + // Remove duplicates by partner ID const uniquePartners = Array.from( new Map(partners.map((p) => [p.id, p])).values(), );
♻️ Duplicate comments (10)
packages/utils/src/functions/pretty-print.ts (2)
1-15: Previous circular reference concern remains unaddressed.The earlier review flagged that
JSON.stringifythrowsTypeErroron circular references. This issue persists and should be resolved to prevent runtime crashes when debugging complex objects in fraud management workflows.
9-10: Previous stack trace exposure concern remains unaddressed.The earlier review noted that unconditionally including
stackin Error serialization may leak sensitive information in production logs. Given the fraud management context, this security concern should be addressed.apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx (1)
637-642: Align ban flow cache refresh behavior with bulk ban modal
BanPartnerModal’sonConfirmisasyncbut doesn’t awaitmutatePrefix("/api/partners"), whereasBulkBanPartnersModaldoes. This keeps the inconsistency previously flagged and can cause the single-ban flow to close the modal before the list refresh completes, unlike bulk ban.Consider restoring
await mutatePrefix("/api/partners")here (or making both flows consistently fire-and-forget if that’s the desired UX):const { BanPartnerModal, setShowBanPartnerModal } = useBanPartnerModal({ partner: row.original, onConfirm: async () => { - mutatePrefix("/api/partners"); + await mutatePrefix("/api/partners"); }, });apps/web/scripts/migrations/migrate-duplicate-payout-fraud-events.ts (2)
59-77: Per-partner FraudEventGroup metadata still derived from all eventsEach
FraudEventGroupfor a partner is currently built fromfraudEvents[0],fraudEvents[fraudEvents.length - 1], andfraudEvents.length, i.e. from the entire set of events for thegroupKey, not just that partner’s events. This can give every partner’s group the sameeventCount, timestamps, status, and resolution metadata, which is misleading if different partners have different numbers/timing/status of events.Consider deriving these fields from partner-specific events instead:
for (const partnerId of uniquePartnerIds) { const partnerEvents = fraudEvents.filter((e) => e.partnerId === partnerId); const firstFraudEvent = partnerEvents[0]; const lastFraudEvent = partnerEvents[partnerEvents.length - 1]; fraudEventGroups.push({ id: createId({ prefix: "frg_" }), programId: firstFraudEvent.programId, partnerId, type: "partnerDuplicatePayoutMethod", lastEventAt: lastFraudEvent.createdAt, eventCount: partnerEvents.length, userId: firstFraudEvent.userId, resolutionReason: firstFraudEvent.resolutionReason, resolvedAt: firstFraudEvent.resolvedAt, status: firstFraudEvent.status, createdAt: firstFraudEvent.createdAt, updatedAt: lastFraudEvent.createdAt, }); }
79-116: Group linking logic risks incorrect associations and lacks atomicityTwo remaining concerns in the linking section:
First-partner updateMany is too broad
The first branch:
await prisma.fraudEvent.updateMany({ where: { groupKey: fraudGroup.groupKey, fraudEventGroupId: null, type: "partnerDuplicatePayoutMethod", }, data: { fraudEventGroupId: fraudEventGroups.find( (group) => group.partnerId === partnerId, )?.id, }, });Updates all events for the
groupKeyto the first partner’s group, including events belonging to other partners. If the intent is that each partner’s group only “owns” its own events, you should filter bypartnerIdhere.No transaction around createMany + update/createMany
fraudEventGroup.createManyand the subsequentfraudEvent.updateMany/fraudEvent.createManycalls are not wrapped in a transaction. A failure after creating groups but before linking events (or mid-way through linking) can leave orphaned groups or partially-migrated data.Consider wrapping the per-
groupKeywork in a transaction:await prisma.$transaction(async (tx) => { // createMany on tx.fraudEventGroup // updateMany / createMany on tx.fraudEvent });apps/web/app/(ee)/api/fraud/groups/count/route.ts (1)
28-71: Normalize PrismagroupByresults before Zod parsing (still mismatched).As noted in the earlier review on this file,
prisma.fraudEventGroup.groupBy({ _count: true, ... })returns rows where_countis an object (e.g.{ _all: number, ... }), butfraudGroupCountSchemaexpects_countto be a plain number. Pushing{ _count: 0, type }intofraudGroupsalso mixes shapes.Please normalize the results to the schema shape before validation, e.g.:
- For
groupBy === "type": mapgroupedrows to{ type, _count: grouped.find(...)?._count._all ?? 0 }for everyFraudRuleType.- For
groupBy === "partnerId": map to{ partnerId, _count: _count._all }.This will make
z.array(fraudGroupCountSchema).parse(...)succeed consistently.apps/web/ui/modals/reject-partner-application-modal.tsx (1)
37-52: Potential duplicate toast notifications.The
onSuccesscallback shows a toast (line 41-43) and then invokesonConfirm(line 46). If consuming components also show toasts in theironConfirmcallbacks, users will see duplicate success messages.Consider documenting that
onConfirmshould not show toasts since the modal handles feedback, or remove the toast from the modal and let callers handle it consistently.apps/web/lib/api/fraud/create-fraud-events.ts (1)
137-153:lastEventAtis updated for groups with no new events; also O(G×N) counting.The update loop runs over all
finalGroupsand always setslastEventAt: new Date(), even when a group received zero new events (increment is 0). This incorrectly makes untouched groups look "recent" and performs unnecessary database writes. Additionally, thefilterper group creates O(groups × events) complexity.Fix both correctness and efficiency by aggregating counts by
fraudEventGroupIdand only updating affected groups:- await Promise.allSettled( - finalGroups.map((group) => - prisma.fraudEventGroup.update({ - where: { - id: group.id, - }, - data: { - lastEventAt: new Date(), - eventCount: { - increment: newEventsWithGroup.filter( - (e) => e.fraudEventGroupId === group.id, - ).length, - }, - }, - }), - ), - ); + const eventCountsByGroupId = newEventsWithGroup.reduce( + (map, event) => { + const groupId = event.fraudEventGroupId; + if (!groupId) return map; + map.set(groupId, (map.get(groupId) ?? 0) + 1); + return map; + }, + new Map<string, number>(), + ); + + await Promise.allSettled( + Array.from(eventCountsByGroupId.entries()).map(([groupId, count]) => + prisma.fraudEventGroup.update({ + where: { id: groupId }, + data: { + lastEventAt: new Date(), + eventCount: { increment: count }, + }, + }), + ), + );This ensures only groups that actually received new events get their
lastEventAtbumped.apps/web/lib/api/fraud/utils.ts (2)
86-115: Inconsistent error handling for missing identity fields.For
customerEmailMatch,referralSourceBanned, etc., missingcustomerIdthrows an error. However, forpartnerDuplicatePayoutMethod, missingduplicatePartnerIdsilently returnsundefinedin the record, which could produce inconsistent hashes.Consider adding validation:
case "partnerDuplicatePayoutMethod": + if (!eventMetadata?.duplicatePartnerId) { + throw new Error(`duplicatePartnerId is required for ${type} fraud rule.`); + } return { - duplicatePartnerId: eventMetadata?.duplicatePartnerId, + duplicatePartnerId: eventMetadata.duplicatePartnerId, };
117-131: Unsafe cast and mutation of metadata persists.This was flagged in a past review. The function casts
Prisma.JsonValuetoRecord<string, any>without validation and mutates the original object viadelete.Apply this fix to validate and avoid mutation:
export function sanitizeFraudEventMetadata( metadata: Prisma.JsonValue | undefined, ) { if (!metadata) { return undefined; } + // Prisma.JsonValue can be primitive, array, or object - only handle objects + if (typeof metadata !== "object" || Array.isArray(metadata)) { + return metadata; + } + - const sanitized = metadata as Record<string, any>; + const sanitized = { ...metadata } as Record<string, unknown>; delete sanitized.duplicatePartnerId; delete sanitized.payoutMethodHash; return Object.keys(sanitized).length > 0 ? sanitized : undefined; }
🧹 Nitpick comments (18)
packages/utils/src/functions/pretty-print.ts (1)
1-15: Consider optional improvements for production readiness.A few refinements would improve this utility:
- Explicit return type: Add
: stringto the function signature for clarity.- BigInt handling:
JSON.stringifythrows onBigIntvalues—consider adding a replacer case:if (typeof val === "bigint") return val.toString();- JSDoc documentation: Since this is a public utility, document parameters and behavior for consumers.
Example enhancement:
+/** + * Pretty-print a value to JSON with support for special types. + * @param value - The value to serialize + * @param indent - Number of spaces for indentation (default: 2) + * @returns JSON string representation + */ -export function prettyPrint(value: any, indent = 2) { +export function prettyPrint(value: any, indent = 2): string { return JSON.stringify( value, (_key, val) => { + if (typeof val === "bigint") return val.toString(); if (val instanceof Set) return { __type: "Set", values: [...val] }; // ... rest of replacerapps/web/lib/api/payouts/payout-eligibility-filter.ts (1)
13-20: LGTM! Field name updated correctly.The filter logic correctly migrates from
fraudEventstofraudEventGroupswhile preserving the filtering behavior (ensuring all fraud groups are resolved before payout eligibility).Minor: Consider updating the comment on Line 13 to say "fraud groups" instead of "fraud events" for consistency with the new terminology.
apps/web/lib/api/fraud/resolve-fraud-groups.ts (1)
4-27: LGTM! Clean implementation of group-based resolution.The function correctly:
- Filters for
status: "pending"to prevent double-resolution- Sets appropriate timestamps and resolution metadata
- Uses
updateManyfor efficient bulk updates- Returns count for operation feedback
Optional consideration: In high-concurrency scenarios, multiple simultaneous resolution calls with overlapping
whereclauses could create race conditions (though Prisma's transaction isolation should handle this). If this becomes an issue, consider adding optimistic locking or transaction-level isolation controls.apps/web/scripts/migrations/backfill-fraud-event-hashes.ts (1)
22-35: Improve error handling and progress logging.The batch processing logic has several areas for improvement:
- No error handling: If any single update fails, the entire batch fails and the script aborts. This could leave the migration incomplete.
- No progress logging: Users won't know how far the migration has progressed for large datasets.
- Promise.all fails fast: One failure aborts the entire batch.
Apply this diff to add error handling and progress logging:
+ let processedCount = 0; + let errorCount = 0; + for (const batch of chunks) { - await Promise.all( + const results = await Promise.allSettled( batch.map((event) => prisma.fraudEvent.update({ where: { id: event.id, }, data: { hash: createFraudEventHash(event), }, }), ), ); + + results.forEach((result, index) => { + if (result.status === 'fulfilled') { + processedCount++; + } else { + errorCount++; + console.error(`Failed to update event ${batch[index].id}:`, result.reason); + } + }); + + console.log(`Progress: ${processedCount}/${fraudEvents.length} processed, ${errorCount} errors`); } - console.log("Hash backfill completed."); + console.log(`Hash backfill completed. Successfully updated: ${processedCount}, Errors: ${errorCount}`);apps/web/app/(ee)/api/cron/merge-partner-accounts/route.ts (1)
274-294: Good automation of fraud resolution post-merge.The logic correctly auto-resolves
partnerDuplicatePayoutMethodfraud groups when merging eliminates the duplicate payout method condition. The<= 1check properly accounts for the target partner itself.Consider using
FraudRuleType.partnerDuplicatePayoutMethodinstead of the string literal"partnerDuplicatePayoutMethod"on line 288 for consistency with other files in this PR (e.g.,route.tsin the ban/process endpoint).+import { FraudRuleType } from "@dub/prisma/client"; import { resolveFraudGroups } from "@/lib/api/fraud/resolve-fraud-groups";await resolveFraudGroups({ where: { partnerId: targetPartnerId, - type: "partnerDuplicatePayoutMethod", + type: FraudRuleType.partnerDuplicatePayoutMethod, },apps/web/scripts/migrations/cleanup-fraud-events.ts (2)
36-43: Add type filter to deleteMany for defensive safety.While the
groupByquery filters bytype: "partnerDuplicatePayoutMethod", thedeleteManyonly filters bygroupKey. IfgroupKeysemantics change or if there's data inconsistency, this could inadvertently delete unrelated fraud events.Adding the type constraint makes the deletion more explicit and safer:
for (const batch of chunks) { - await prisma.fraudEvent.deleteMany({ + const result = await prisma.fraudEvent.deleteMany({ where: { groupKey: { in: batch.map(({ groupKey }) => groupKey), }, + type: "partnerDuplicatePayoutMethod", }, }); + + console.log(`Deleted ${result.count} fraud events.`); }
106-114: Same issue: Add type filter to deleteMany in cleanupSamePartnerGroups.Apply the same defensive fix here to ensure only
partnerDuplicatePayoutMethodevents are deleted:for (const batch of chunks) { - await prisma.fraudEvent.deleteMany({ + const result = await prisma.fraudEvent.deleteMany({ where: { groupKey: { in: batch.map(({ groupKey }) => groupKey), }, + type: "partnerDuplicatePayoutMethod", }, }); + + console.log(`Deleted ${result.count} fraud events.`); }apps/web/scripts/migrations/migrate-fraud-events.ts (1)
68-81: Consider addingfraudEventGroupId: nullfilter to updateMany for extra safety.The initial
findManycorrectly filters forfraudEventGroupId: null, ensuring idempotency. However, adding the same filter toupdateManyprovides an extra safeguard against edge cases where events might be updated by a concurrent process:batch.map(([groupKey, groupId]) => prisma.fraudEvent.updateMany({ where: { groupKey, + fraudEventGroupId: null, }, data: { fraudEventGroupId: groupId, }, }), ),apps/web/ui/partners/fraud-risks/fraud-events-tables/fraud-paid-traffic-detected-table.tsx (1)
47-127: Paid-traffic source rendering and tooltip behavior look robust.The migration to
useFraudEventswithfraudEventSchemas["paidTrafficDetected"], the defensive metadata checks, and the newDynamicTooltipWrapper+HighlightedUrlflow all look correct and resilient to missing/invalid data. Nicely balances UX detail with safe fallbacks.apps/web/lib/actions/fraud/bulk-resolve-fraud-groups.ts (1)
63-77: Consider atomicity for comment creation with resolution.The comment creation is not atomic with
resolveFraudGroups. Ifprisma.partnerComment.createManyfails after resolution succeeds, the state could be inconsistent (groups resolved but no comments).This is likely acceptable given the nature of comments as supplementary data, but worth noting.
apps/web/lib/api/fraud/detect-duplicate-payout-method-fraud.ts (1)
42-76: Avoid generating “self-duplicate” payout method fraud eventsIn the nested loops, you create events for every enrolled partner, including the current partner itself:
for (const enrolledPartner of enrolledPartners) { fraudEvents.push({ programId, partnerId: partner.id, type: FraudRuleType.partnerDuplicatePayoutMethod, metadata: { payoutMethodHash, duplicatePartnerId: enrolledPartner, }, }); }This produces events where
metadata.duplicatePartnerId === partner.id, which doesn’t represent a true “duplicate” and unnecessarily inflates event volume. If the intent is to flag only other accounts sharing the payout method, filter out the self‑case before pushing:for (const enrolledPartner of enrolledPartners) { if (enrolledPartner === partner.id) continue; fraudEvents.push({ programId, partnerId: partner.id, type: FraudRuleType.partnerDuplicatePayoutMethod, metadata: { payoutMethodHash, duplicatePartnerId: enrolledPartner, }, }); }This keeps semantics clearer while still allowing symmetric A↔B events if desired.
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/commissions/commission-table.tsx (1)
131-137: Consider memoizing fraud counts by partnerId to avoid repeated scans
fraudGroupCount?.find(({ partnerId }) => partnerId === row.original.partner.id)is executed for every row, scanning the entirefraudGroupCountarray each time. For large tables and many fraud groups this becomes O(rows × groups).You can turn this into O(rows) with a small memoized map:
const fraudByPartner = useMemo( () => new Map( (fraudGroupCount ?? []).map(({ partnerId, _count }) => [ partnerId, _count, ]), ), [fraudGroupCount], ); // ... const partnerHasPendingFraud = fraudByPartner.get( row.original.partner.id, );Not required for correctness, but it keeps rendering cost predictable as datasets grow.
Also applies to: 272-283, 318-319
apps/web/ui/modals/bulk-resolve-fraud-groups-modal.tsx (2)
85-96: Confirmation text could be more specific.The
confirmationTextalways says "confirm resolve events" (plural form when count > 1), but for a single event it would show "confirm resolve event" which is good. However, consider including the actual count in the confirmation text for added safety:- const confirmationText = `confirm resolve ${eventWord}`; + const confirmationText = `confirm resolve ${totalEventCount} ${eventWord}`;This prevents accidental bulk operations by making the user acknowledge the exact count.
205-219: PreferuseMemooveruseCallbackfor memoizing JSX.
useCallbackis semantically intended for memoizing function references. For JSX elements,useMemois more appropriate and consistent with the pattern used inuseResolveFraudGroupModalin the other modal file.- const BulkResolveFraudGroupsModalCallback = useCallback(() => { - return ( + const BulkResolveFraudGroupsModalCallback = useMemo( + () => ( <BulkResolveFraudGroupsModal showBulkResolveFraudGroupsModal={showBulkResolveFraudGroupsModal} setShowBulkResolveFraudGroupsModal={setShowBulkResolveFraudGroupsModal} fraudGroups={fraudGroups} onConfirm={onConfirm} /> - ); - }, [ + ), + [ showBulkResolveFraudGroupsModal, - setShowBulkResolveFraudGroupsModal, fraudGroups, onConfirm, - ]); + ], + );Also,
setShowBulkResolveFraudGroupsModalis stable and doesn't need to be in the dependency array.apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/fraud/resolve-fraud-group-modal.tsx (1)
57-62: Minor:groupIdin defaultValues is redundant.The
groupIdis set indefaultValueson line 60 but isn't used from form data—it's always explicitly passed fromfraudGroup.idinonSubmit(line 72). Consider removing it from defaultValues to avoid confusion.} = useForm<FormData>({ defaultValues: { resolutionReason: "", - groupId: fraudGroup.id, }, });packages/prisma/schema/fraud.prisma (1)
55-76: Document the migration timeline for deprecated fields.The deprecated fields block has TODO comments indicating these should be removed after migration. Consider adding a more specific timeline or tracking issue reference to ensure cleanup happens.
- // DEPRECATED FIELDS: TODO – remove after migration + // DEPRECATED FIELDS: TODO – remove after migration (tracked in issue #XXXX) + // Target removal: v2.x after backfill completesapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/fraud/fraud-group-table.tsx (2)
427-437: Consider conditional modal instantiation for efficiency.The
RejectPartnerApplicationModalis instantiated even whenpartnermight be null (line 431), though line 437 prevents rendering. While this works correctly, you could optimize by conditionally instantiating the modal only when needed:const conditionalModal = partner?.status === "pending" ? useRejectPartnerApplicationModal({ partner, onConfirm: async () => { ... } }) : null;However, the current implementation is safe and functional.
544-545: Minor: Rename variable for consistency with refactor.The variable
fetchedFraudEventGroupsstill contains "Event" from the old naming convention. For consistency with the group-based refactor, consider renaming tofetchedFraudGroups.- const { fraudGroups: fetchedFraudEventGroups, loading: isLoading } = + const { fraudGroups: fetchedFraudGroups, loading: isLoading } = useFraudGroups({ query: { groupId: groupId ?? undefined }, enabled: Boolean(shouldFetch), }); - if (!currentFraudGroup && fetchedFraudEventGroups?.[0]?.id === groupId) { - currentFraudGroup = fetchedFraudEventGroups[0]; + if (!currentFraudGroup && fetchedFraudGroups?.[0]?.id === groupId) { + currentFraudGroup = fetchedFraudGroups[0]; }
Summary by CodeRabbit
New Features
Improvements
✏️ Tip: You can customize this high-level summary in your review settings.