Allow targeting campaigns and bounties by partner tags - #4221
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThis PR adds partner-tag eligibility to bounties and campaigns, supports multiple workflow trigger conditions, introduces event-based workflow execution, centralizes response transformation and campaign scheduling, and updates related APIs, UI components, persistence models, and tests. ChangesEligibility and workflow modernization
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 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 |
…filtering through partner/cron paths.
There was a problem hiding this comment.
Actionable comments posted: 15
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/scripts/programs/backfill-reuse-commission.ts (1)
320-336: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
event: "commissionRecorded"doesn't match the metrics passed here.
EVENT_ATTRIBUTES.commissionRecordedresolves tototalCommissions/partnerGroup(apps/web/lib/api/workflows/execute-workflows.ts), yet this call suppliesleads,saleAmount, andconversionsand no commission total — so lead/sale workflows won't be selected and commission workflows get no value. GivencommissionType, the event should likely be derived (leadRecordedvssaleRecorded).🐛 Suggested fix
executeWorkflows({ - event: "commissionRecorded", + event: commissionType === "lead" ? "leadRecorded" : "saleRecorded",🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/scripts/programs/backfill-reuse-commission.ts` around lines 320 - 336, Update the workflow event in the commissionType branch to use the event matching the commission type: leadRecorded for leads and saleRecorded for sales. Keep the existing leads, saleAmount, and conversions metrics aligned with those event definitions, and do not use commissionRecorded for this path.
🧹 Nitpick comments (10)
apps/web/lib/api/workflows/send-campaign/execute.ts (2)
361-386: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDrop the stray
awaitinsidePromise.all.The
awaitin the ternary makes the branch resolve beforePromise.allis even constructed, which is misleading (and inconsistent with the non-commission branch returning a promise).♻️ Proposed cleanup
commissions - ? await prisma.commission.aggregate({ + ? prisma.commission.aggregate({🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/lib/api/workflows/send-campaign/execute.ts` around lines 361 - 386, Remove the stray await before prisma.commission.aggregate in the commissions ternary within the Promise.all call, returning the aggregate promise directly while preserving the existing Promise.resolve fallback for the non-commission branch.
439-453: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win
take: 1000silently drops enrollments beyond the cap.Programs with a large enrollment burst in a 12h window will never receive the campaign for the overflow, with no signal. Consider cursor pagination, or at minimum log when the result count hits the limit so it is observable.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/lib/api/workflows/send-campaign/execute.ts` around lines 439 - 453, Update the programEnrollment query in the campaign execution flow to avoid silently excluding enrollments beyond take: 1000, preferably by fetching all matching records through cursor pagination. If retaining the cap, detect when the result reaches the limit and emit an explicit diagnostic log indicating possible overflow.apps/web/lib/api/campaigns/transform-campaign.ts (1)
16-34: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDerive the payload type from
campaignEligibilityIncludesto avoid drift.The include shape is written twice (value + type literal); adding a field to one will silently diverge from the other.
♻️ Suggested refactor
export type TransformCampaignInput = Prisma.CampaignGetPayload<{ - include: { - groups: { - select: { - groupId: true; - }; - }; - partnerTags: { - select: { - partnerTagId: true; - }; - }; - workflow: { - select: { - triggerConditions: true; - }; - }; - }; + include: typeof campaignEligibilityIncludes & { + workflow: { + select: { + triggerConditions: true; + }; + }; + }; }>;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/lib/api/campaigns/transform-campaign.ts` around lines 16 - 34, Update TransformCampaignInput to derive its Prisma payload type from the existing campaignEligibilityIncludes symbol instead of duplicating the include literal, ensuring type and runtime include shapes remain synchronized.apps/web/tests/workflows/send-campaign-workflow.test.ts (1)
858-871: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winNegative assertion can pass vacuously.
The positive case polls up to 90s via
verifyCampaignSent, yet here emails are checked immediately after triggering. If delivery is async, this assertion passes regardless of whether an email is eventually sent. Add a short wait (or re-check after a delay) before asserting emptiness, and mirror the notification-email cleanup used by the AND-match test inonTestFinished.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/tests/workflows/send-campaign-workflow.test.ts` around lines 858 - 871, Update the negative campaign notification test around the workflow trigger and emailsSent assertion to wait briefly or re-check after a delay before asserting no emails were sent, preventing a vacuous immediate pass. Also add the same notification-email cleanup used by the AND-match test to this test’s onTestFinished handler.apps/web/lib/api/workflows/utils.ts (1)
49-52: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDerive the result object from
WORKFLOW_DATA_REQUIREMENTSto avoid drift.Hardcoding the two keys means adding a third requirement silently returns an incomplete record (the
Record<WorkflowDataRequirement, boolean>annotation would catch it, but only if the constant is also updated in lockstep).return Object.fromEntries( WORKFLOW_DATA_REQUIREMENTS.map((r) => [r, requirements.has(r)]), ) as Record<WorkflowDataRequirement, boolean>;(requires exporting
WORKFLOW_DATA_REQUIREMENTSfromattribute-definitions.ts)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/lib/api/workflows/utils.ts` around lines 49 - 52, Update the result construction in the workflow requirements helper to derive every key from WORKFLOW_DATA_REQUIREMENTS, mapping each requirement to whether requirements contains it, instead of hardcoding commissions and partnerLinkStats. Export WORKFLOW_DATA_REQUIREMENTS from attribute-definitions.ts and retain the Record<WorkflowDataRequirement, boolean> typing for the generated object.apps/web/lib/api/workflows/check-workflow-conditions.ts (1)
28-86: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winPer-condition validation is now duplicated with
validateWorkflowConditions.Lines 28–86 replicate the server-side loop in
apps/web/lib/api/workflows/validate-workflow-conditions.ts(lines 77–142) almost verbatim; the two will drift (the server also enforcessendCampaignduplicate-attribute/exclusivity rules that this checker doesn't). Consider extracting a singlecollectConditionErrors()helper and havingvalidateWorkflowConditionsmap the returned messages toDubApiError.Also, Line 72's
condition.value === undefinedis redundant —== nullalready coversundefined.♻️ Minor cleanup for the redundant guard
- if (condition.value == null || condition.value === undefined) { + if (condition.value == null) { errors.push(`Condition ${conditionIndex + 1}: Please enter a value.`); continue; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/lib/api/workflows/check-workflow-conditions.ts` around lines 28 - 86, Remove the redundant condition.value === undefined check in the per-condition validation loop, since condition.value == null already covers both null and undefined. To eliminate duplicated validation, extract the shared logic into a collectConditionErrors helper, including sendCampaign duplicate-attribute/exclusivity rules, then update validateWorkflowConditions to reuse it and map returned messages to DubApiError.apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/[bountyId]/bounty-info.tsx (1)
54-54: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winInvalid Tailwind class
font-regularused at both newBountyEligibilitySummarycall sites.font-regularis not a valid Tailwind utility in this codebase; weight 400 must usefont-normal.
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/[bountyId]/bounty-info.tsx#L54-L54: changeclassName="font-regular"toclassName="font-normal".apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/add-edit-bounty/confirm-create-bounty-modal.tsx#L136-L136: changeclassName="font-regular"toclassName="font-normal".Based on learnings: "Tailwind's normal font weight (400) must use
font-normal. Do not usefont-regular(it is not a valid Tailwind utility here)."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/app/app.dub.co/`(dashboard)/[slug]/(ee)/program/bounties/[bountyId]/bounty-info.tsx at line 54, Replace the invalid font-regular className with font-normal at the BountyEligibilitySummary call sites in apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/[bountyId]/bounty-info.tsx (lines 54-54) and apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/add-edit-bounty/confirm-create-bounty-modal.tsx (lines 136-136).Source: Learnings
apps/web/ui/partners/groups/partner-groups-select.tsx (1)
45-86: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoffNear-verbatim duplicate of
PartnerTagsSelect. The search/sort/latch state machine is identical apart from entity names; extracting a shared hook (e.g.useAudienceLimitItems) would keep the two selectors from drifting.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/ui/partners/groups/partner-groups-select.tsx` around lines 45 - 86, The duplicated search/sort/latch state machine around sortGroups and the related useEffect calls should be extracted into a shared hook, such as useAudienceLimitItems, and reused by both PartnerGroupsSelect and PartnerTagsSelect. Preserve the existing selected-item ordering, search behavior, readiness checks, and groups/items update latch while removing the duplicated implementation.apps/web/tests/bounties/index.test.ts (1)
222-254: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winTest doesn't actually exercise clearing tags. The bounty is created with
partnerTagIds: null, so the PATCHnulltransitions from empty → empty. Create with real tag IDs (e.g. an E2E partner tag fixture) so the clear path is verified.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/tests/bounties/index.test.ts` around lines 222 - 254, Update the test “PATCH /bounties/{bountyId} - clear partner tags” to create the bounty with one or more real E2E partner tag IDs instead of null, using the available partner-tag fixture. Keep the PATCH request setting partnerTagIds to null and retain the assertion that bounty.partnerTags is empty, so the test verifies clearing existing tags.apps/web/ui/partners/tags-multi-select.tsx (1)
154-241: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy liftThis list rendering duplicates the new
AudienceLimitSelectShell. Both were added in this PR with the same cmdk list, checkbox markup, and empty/loading branches; only the header (ToggleGroup vs Switch) differs. Consider driving this component through the shell with a pluggable header.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/ui/partners/tags-multi-select.tsx` around lines 154 - 241, Refactor the tag list rendering around AudienceLimitSelectShell instead of duplicating its cmdk list, checkbox, empty, and loading branches. Provide the tag-specific header through the shell’s pluggable header mechanism, preserving the existing search, selection, async loading, and no-match behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/web/app/`(ee)/api/campaigns/[campaignId]/route.ts:
- Around line 89-94: Update the triggerConditions handling in the campaign
update flow and its persistence spread to distinguish an omitted value from an
explicit null or empty array: guard validation with a defined-value check,
normalize null to the workflow’s empty-conditions representation, and persist
the normalized value so explicit null or [] clears existing conditions. Keep the
update behavior unchanged when triggerConditions is undefined.
In
`@apps/web/app/app.dub.co/`(dashboard)/[slug]/(ee)/program/bounties/add-edit-bounty/audience-eligibility-panel.tsx:
- Around line 16-21: Normalize the value passed from the audience eligibility
form’s `groupIds` field in the `PartnerGroupsSelect` render callback, matching
the existing `partnerTagIds` handling. Ensure an undefined value is converted to
the expected empty selection before reaching `PartnerGroupsSelect` and its
`AudienceLimitSelectShell`, while preserving the field’s change handler.
In
`@apps/web/app/app.dub.co/`(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/campaign-recipients-selector.tsx:
- Around line 25-35: Update the selectedGroups derivation and the corresponding
tag selection memo to resolve selected entities by ID from the full available
dataset, following the existing PartnerTagsSelect/PartnerGroupsSelect approach,
rather than filtering only the paginated default list. Ensure selections outside
the first page still contribute to rendered chips and plusCount; if full
entities cannot be fetched, preserve their count with a count-only chip.
In
`@apps/web/app/app.dub.co/`(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/duplicate-logic-warning.tsx:
- Around line 23-25: Update the parsedTriggerConditions initialization to
require triggerConditions to contain at least one item before calling
sendCampaignConditionsSchema.safeParse. Treat an empty array like an absent
value so duplicate campaign lookups are skipped rather than issuing an
unfiltered request.
In
`@apps/web/app/app.dub.co/`(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/transactional-campaign-logic.tsx:
- Around line 182-187: Update the label expression in the field-rendering logic
to safely handle attributes missing from SEND_CAMPAIGN_ATTRIBUTES, mirroring the
optional operator lookup behavior. Preserve the existing "activity" fallback for
unset or unknown values so rendering does not throw for legacy or renamed keys.
- Around line 66-67: Update canAddCondition in the transactional campaign
condition controls to depend only on availableAttributesToAdd.length > 0,
removing the fields.length > 0 requirement so users can add the first condition
while preserving behavior for populated lists.
In `@apps/web/lib/api/campaigns/schedule-campaigns.ts`:
- Around line 34-50: Update deleteCampaignSchedule so failures from both
qstash.messages.cancel and qstash.schedules.delete are caught and handled as
non-fatal warnings, matching the existing behavior in scheduleMarketingCampaign.
Keep the campaign deletion/cancellation flow successful when QStash reports a
stale or missing message or schedule ID.
In `@apps/web/lib/api/workflows/award-bounty/execute.ts`:
- Around line 39-42: Update the award-bounty workflow handler around
parseWorkflowConfig to validate that conditions contains exactly one entry;
reject the workflow during validation and defensively return before evaluating
action or awarding the bounty when the count differs. Keep using the single
condition only after this guard passes.
In `@apps/web/lib/api/workflows/send-campaign/execute.ts`:
- Around line 347-359: Replace the tag-targeted programEnrollment.findUnique
call with findFirst, keeping the partnerId_programId constraint,
campaignAudienceWhere filters, and programEnrollmentInclude unchanged so nested
relation filters such as programPartnerTags are supported.
In `@apps/web/lib/api/workflows/utils.ts`:
- Around line 20-28: Update isScheduledWorkflow to read triggerConditions
defensively without calling the throwing parseWorkflowConfig path; treat empty,
missing, or invalid workflow conditions as unscheduled and preserve the existing
partnerEnrolledDays detection for valid conditions.
In `@apps/web/lib/bounty/api/transform-bounty.ts`:
- Around line 16-25: Update the transformation around performanceCondition in
transformBounty so an explicitly supplied performanceCondition from the PATCH
result is preserved instead of being overwritten by the pre-update workflow
triggerConditions value. Use the returned bounty condition when present, and
retain the existing triggerConditions fallback only when no explicit condition
was supplied.
In `@apps/web/lib/bounty/api/trigger-draft-bounty-submissions.ts`:
- Line 3: Update the draft-submission job flow around getBountiesByGroups and
isEligiblePerformanceBounty to enforce programPartnerTags eligibility before
publishing jobs. Ensure each scheduled job is published only when the actual
partner enrollment matches the bounty’s partner tags, or propagate the matching
partner tag IDs into the scheduled-job creation data so create-draft-submissions
applies the intended filter.
In `@apps/web/tests/workflows/comparison-operators.test.ts`:
- Around line 84-104: Update WORKFLOW_OPERATORS.lte.validate to accept scalar
non-negative numbers, including zero, while rejecting non-numbers, NaN, and
negative values with the existing validation message. Remove the requirement for
a { min, max } object and keep the lte comparator contract consistent with these
tests.
In `@apps/web/ui/partners/audience-limit-select-shell.tsx`:
- Around line 105-113: The onSelect handler in the audience limit select shell
should revert selectedIds to null when deselecting the final item, matching
TagsMultiSelect’s “All tags” behavior; preserve array updates when items remain
selected or a new item is added, and verify the downstream bounty/campaign APIs
receive the intended representation.
In `@apps/web/ui/partners/groups/partner-groups-select.tsx`:
- Around line 37-43: The useAsync effects oscillate because they repeatedly set
state from a Boolean expression that becomes false after async mode is enabled.
Update apps/web/ui/partners/groups/partner-groups-select.tsx lines 37-43,
apps/web/ui/partners/partner-tags-select.tsx lines 34-44, and
apps/web/ui/partners/tags-multi-select.tsx lines 57-67 so each effect only calls
setUseAsync(true) when useAsync is false and the corresponding collection length
meets its maximum page-size threshold, preserving the one-way latch behavior.
---
Outside diff comments:
In `@apps/web/scripts/programs/backfill-reuse-commission.ts`:
- Around line 320-336: Update the workflow event in the commissionType branch to
use the event matching the commission type: leadRecorded for leads and
saleRecorded for sales. Keep the existing leads, saleAmount, and conversions
metrics aligned with those event definitions, and do not use commissionRecorded
for this path.
---
Nitpick comments:
In
`@apps/web/app/app.dub.co/`(dashboard)/[slug]/(ee)/program/bounties/[bountyId]/bounty-info.tsx:
- Line 54: Replace the invalid font-regular className with font-normal at the
BountyEligibilitySummary call sites in
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/[bountyId]/bounty-info.tsx
(lines 54-54) and
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/add-edit-bounty/confirm-create-bounty-modal.tsx
(lines 136-136).
In `@apps/web/lib/api/campaigns/transform-campaign.ts`:
- Around line 16-34: Update TransformCampaignInput to derive its Prisma payload
type from the existing campaignEligibilityIncludes symbol instead of duplicating
the include literal, ensuring type and runtime include shapes remain
synchronized.
In `@apps/web/lib/api/workflows/check-workflow-conditions.ts`:
- Around line 28-86: Remove the redundant condition.value === undefined check in
the per-condition validation loop, since condition.value == null already covers
both null and undefined. To eliminate duplicated validation, extract the shared
logic into a collectConditionErrors helper, including sendCampaign
duplicate-attribute/exclusivity rules, then update validateWorkflowConditions to
reuse it and map returned messages to DubApiError.
In `@apps/web/lib/api/workflows/send-campaign/execute.ts`:
- Around line 361-386: Remove the stray await before prisma.commission.aggregate
in the commissions ternary within the Promise.all call, returning the aggregate
promise directly while preserving the existing Promise.resolve fallback for the
non-commission branch.
- Around line 439-453: Update the programEnrollment query in the campaign
execution flow to avoid silently excluding enrollments beyond take: 1000,
preferably by fetching all matching records through cursor pagination. If
retaining the cap, detect when the result reaches the limit and emit an explicit
diagnostic log indicating possible overflow.
In `@apps/web/lib/api/workflows/utils.ts`:
- Around line 49-52: Update the result construction in the workflow requirements
helper to derive every key from WORKFLOW_DATA_REQUIREMENTS, mapping each
requirement to whether requirements contains it, instead of hardcoding
commissions and partnerLinkStats. Export WORKFLOW_DATA_REQUIREMENTS from
attribute-definitions.ts and retain the Record<WorkflowDataRequirement, boolean>
typing for the generated object.
In `@apps/web/tests/bounties/index.test.ts`:
- Around line 222-254: Update the test “PATCH /bounties/{bountyId} - clear
partner tags” to create the bounty with one or more real E2E partner tag IDs
instead of null, using the available partner-tag fixture. Keep the PATCH request
setting partnerTagIds to null and retain the assertion that bounty.partnerTags
is empty, so the test verifies clearing existing tags.
In `@apps/web/tests/workflows/send-campaign-workflow.test.ts`:
- Around line 858-871: Update the negative campaign notification test around the
workflow trigger and emailsSent assertion to wait briefly or re-check after a
delay before asserting no emails were sent, preventing a vacuous immediate pass.
Also add the same notification-email cleanup used by the AND-match test to this
test’s onTestFinished handler.
In `@apps/web/ui/partners/groups/partner-groups-select.tsx`:
- Around line 45-86: The duplicated search/sort/latch state machine around
sortGroups and the related useEffect calls should be extracted into a shared
hook, such as useAudienceLimitItems, and reused by both PartnerGroupsSelect and
PartnerTagsSelect. Preserve the existing selected-item ordering, search
behavior, readiness checks, and groups/items update latch while removing the
duplicated implementation.
In `@apps/web/ui/partners/tags-multi-select.tsx`:
- Around line 154-241: Refactor the tag list rendering around
AudienceLimitSelectShell instead of duplicating its cmdk list, checkbox, empty,
and loading branches. Provide the tag-specific header through the shell’s
pluggable header mechanism, preserving the existing search, selection, async
loading, and no-match behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 2182a490-8976-48a3-b776-ebc902ca8983
📒 Files selected for processing (99)
apps/web/app/(ee)/api/bounties/[bountyId]/route.tsapps/web/app/(ee)/api/bounties/route.tsapps/web/app/(ee)/api/campaigns/[campaignId]/duplicate/route.tsapps/web/app/(ee)/api/campaigns/[campaignId]/route.tsapps/web/app/(ee)/api/campaigns/route.tsapps/web/app/(ee)/api/cron/bounties/create-draft-submissions/route.tsapps/web/app/(ee)/api/cron/bounties/notify-partners/route.tsapps/web/app/(ee)/api/cron/campaigns/broadcast/route.tsapps/web/app/(ee)/api/e2e/workflows/route.tsapps/web/app/(ee)/api/embed/referrals/bounties/[bountyId]/social-content-stats/route.tsapps/web/app/(ee)/api/embed/referrals/bounties/[bountyId]/upload/route.tsapps/web/app/(ee)/api/partner-profile/programs/[programId]/bounties/[bountyId]/route.tsapps/web/app/(ee)/api/partner-profile/programs/[programId]/bounties/[bountyId]/social-content-stats/route.tsapps/web/app/(ee)/api/partner-profile/programs/[programId]/bounties/route.tsapps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.tsapps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.tsapps/web/app/(ee)/api/stripe/integration/webhook/utils/attribute-via-promotion-code-id.tsapps/web/app/(ee)/api/stripe/integration/webhook/utils/sync-customer.tsapps/web/app/(ee)/api/workflows/create-partner-commission/route.tsapps/web/app/(ee)/api/workflows/partner-approved/route.tsapps/web/app/(ee)/app.dub.co/embed/referrals/get-referrals-embed-data.tsapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/[bountyId]/bounty-info.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/add-edit-bounty/add-edit-bounty-sheet.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/add-edit-bounty/audience-eligibility-panel.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/add-edit-bounty/confirm-create-bounty-modal.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/add-edit-bounty/use-add-edit-bounty-form.tsapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/bounty-card.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/campaign-controls.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/campaign-editor.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/campaign-groups-selector.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/campaign-recipients-selector.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/duplicate-logic-warning.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/transactional-campaign-logic.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/use-campaign-confirmation-modals.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/utils.tsapps/web/lib/actions/partners/accept-program-invite.tsapps/web/lib/actions/partners/upload-bounty-submission-file.tsapps/web/lib/api/campaigns/get-campaign-or-throw.tsapps/web/lib/api/campaigns/schedule-campaigns.tsapps/web/lib/api/campaigns/transform-campaign.tsapps/web/lib/api/campaigns/validate-campaign.tsapps/web/lib/api/commissions/create-manual-commissions.tsapps/web/lib/api/conversions/track-lead.tsapps/web/lib/api/conversions/track-sale.tsapps/web/lib/api/fraud/release-hold-commissions.tsapps/web/lib/api/groups/upsert-group-move-rules.tsapps/web/lib/api/partner-tags/throw-if-invalid-partner-tag-ids.tsapps/web/lib/api/workflows/attribute-definitions.tsapps/web/lib/api/workflows/award-bounty/execute.tsapps/web/lib/api/workflows/check-workflow-conditions.tsapps/web/lib/api/workflows/evaluate-workflow-conditions.tsapps/web/lib/api/workflows/execute-workflows.tsapps/web/lib/api/workflows/move-group/execute.tsapps/web/lib/api/workflows/operator-definitions.tsapps/web/lib/api/workflows/parse-workflow-config.tsapps/web/lib/api/workflows/send-campaign/execute.tsapps/web/lib/api/workflows/send-campaign/schema.tsapps/web/lib/api/workflows/types.tsapps/web/lib/api/workflows/utils.tsapps/web/lib/api/workflows/validate-workflow-conditions.tsapps/web/lib/api/workflows/workflow-type-attributes.tsapps/web/lib/bounty/api/bounty-availability.tsapps/web/lib/bounty/api/create-bounty-submission.tsapps/web/lib/bounty/api/get-bounties-by-groups.tsapps/web/lib/bounty/api/get-bounties-for-partner.tsapps/web/lib/bounty/api/get-bounty-submission-upload-url.tsapps/web/lib/bounty/api/get-bounty-with-details.tsapps/web/lib/bounty/api/transform-bounty.tsapps/web/lib/bounty/api/trigger-draft-bounty-submissions.tsapps/web/lib/integrations/shopify/create-sale.tsapps/web/lib/integrations/shopify/process-order.tsapps/web/lib/swr/use-partners-count-by-groupids.tsapps/web/lib/webhook/sample-events/bounty-created.jsonapps/web/lib/webhook/sample-events/bounty-updated.jsonapps/web/lib/zod/schemas/bounties.tsapps/web/lib/zod/schemas/campaigns.tsapps/web/lib/zod/schemas/partner-profile.tsapps/web/lib/zod/schemas/workflows.tsapps/web/prisma/schema/bounty.prismaapps/web/prisma/schema/campaign.prismaapps/web/prisma/schema/tag.prismaapps/web/prisma/schema/workflow.prismaapps/web/scripts/dev/seed-partner-enrollment.tsapps/web/scripts/dev/seed.tsapps/web/scripts/programs/backfill-reuse-commission.tsapps/web/tests/bounties/index.test.tsapps/web/tests/campaigns/index.test.tsapps/web/tests/workflows/comparison-operators.test.tsapps/web/tests/workflows/find-groups-with-matching-rules.test.tsapps/web/tests/workflows/move-group-workflow.test.tsapps/web/tests/workflows/send-campaign-workflow.test.tsapps/web/ui/partners/audience-limit-select-shell.tsxapps/web/ui/partners/bounties/bounty-eligibility-summary.tsxapps/web/ui/partners/groups/partner-groups-select.tsxapps/web/ui/partners/partner-tags-select.tsxapps/web/ui/partners/tags-multi-select.tsxpackages/email/src/index.tspackages/utils/src/functions/index.tspackages/utils/src/functions/pluck.ts
💤 Files with no reviewable changes (5)
- apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/utils.ts
- apps/web/app/(ee)/api/e2e/workflows/route.ts
- apps/web/tests/workflows/move-group-workflow.test.ts
- apps/web/lib/api/workflows/parse-workflow-config.ts
- apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/campaign-groups-selector.tsx
| useEffect( | ||
| () => | ||
| setUseAsync( | ||
| Boolean(groups && !useAsync && groups.length >= GROUPS_MAX_PAGE_SIZE), | ||
| ), | ||
| [groups, useAsync], | ||
| ); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
useAsync latch oscillates in all three new selectors. Each effect computes setUseAsync(Boolean(items && !useAsync && items.length >= MAX)) while listing useAsync as a dependency, so enabling async search immediately re-runs the effect and disables it again, thrashing the SWR key.
apps/web/ui/partners/groups/partner-groups-select.tsx#L37-L43: change to a one-way latch that only callssetUseAsync(true)when!useAsync && groups.length >= GROUPS_MAX_PAGE_SIZE.apps/web/ui/partners/partner-tags-select.tsx#L34-L44: apply the same one-way latch usingPARTNER_TAGS_MAX_PAGE_SIZE.apps/web/ui/partners/tags-multi-select.tsx#L57-L67: apply the same one-way latch (or reuse the shared hook if extracted).
📍 Affects 3 files
apps/web/ui/partners/groups/partner-groups-select.tsx#L37-L43(this comment)apps/web/ui/partners/partner-tags-select.tsx#L34-L44apps/web/ui/partners/tags-multi-select.tsx#L57-L67
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/ui/partners/groups/partner-groups-select.tsx` around lines 37 - 43,
The useAsync effects oscillate because they repeatedly set state from a Boolean
expression that becomes false after async mode is enabled. Update
apps/web/ui/partners/groups/partner-groups-select.tsx lines 37-43,
apps/web/ui/partners/partner-tags-select.tsx lines 34-44, and
apps/web/ui/partners/tags-multi-select.tsx lines 57-67 so each effect only calls
setUseAsync(true) when useAsync is false and the corresponding collection length
meets its maximum page-size threshold, preserving the one-way latch behavior.
…ubinc/dub into limt-bounties-campaigns-by-tag
|
@coderabbitai full review please |
|
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (8)
apps/web/lib/api/workflows/check-workflow-conditions.ts (1)
55-75: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMinor consistency cleanups. Line 62's message lacks the
Condition N:prefix used by every other error (and shows the raw attribute key instead of a label), and line 72's=== undefinedis already covered by== null. AlsoconditionIndex(line 30) is just an alias fori.♻️ Suggested tidy-up
- const operatorLabel = operatorDefinition.label ?? condition.operator; - errors.push( - `Operator "${operatorLabel}" is not valid for the activity "${condition.attribute}".`, - ); + const operatorLabel = operatorDefinition.label ?? condition.operator; + errors.push( + `Condition ${i + 1}: Operator "${operatorLabel}" is not valid for the selected activity.`, + ); continue; } // Attributes with inputType "none" (e.g. partnerJoined) don't require a value. if (attributeDefinition.inputType === "none") { continue; } - if (condition.value == null || condition.value === undefined) { + if (condition.value == null) {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/lib/api/workflows/check-workflow-conditions.ts` around lines 55 - 75, Update the validation loop to prefix the invalid-operator error with the condition number and use the resolved attribute label instead of condition.attribute. Simplify the missing-value check to a single nullish comparison, and remove the redundant conditionIndex alias by using the loop index directly in the error message.apps/web/lib/api/workflows/send-campaign/execute.ts (1)
249-279: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
partnerLinkStatsrequirement is computed but never used. Link stats columns are always selected inprogramEnrollmentInclude, sogetWorkflowDataRequirements(...).partnerLinkStatsis dead information here. Either drop the destructure comment expectation or make the select conditional to keep the requirements contract meaningful.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/lib/api/workflows/send-campaign/execute.ts` around lines 249 - 279, Update the program enrollment include flow around programEnrollmentInclude and getWorkflowDataRequirements so partnerLinkStats controls whether the links stats fields are selected. Make the links selection conditional on the computed requirement while preserving the existing link ordering and required link identifiers, ensuring the requirements contract is meaningful.apps/web/ui/partners/bounties/bounty-eligibility-summary.tsx (1)
85-174: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate tooltip/label pattern between
GroupsLabelandTagsLabel.Both functions repeat the same "single item inline / multiple items in a scrollable tooltip" structure. Consider extracting a shared
EligibilityLabel({ items, renderIcon, renderRow })helper to reduce duplication.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/ui/partners/bounties/bounty-eligibility-summary.tsx` around lines 85 - 174, Extract the duplicated single-item versus multi-item rendering flow from GroupsLabel and TagsLabel into a shared EligibilityLabel helper, accepting items plus renderIcon and renderRow callbacks. Update both components to supply their group/tag-specific content while preserving the existing empty handling, inline label text, count suffix, and scrollable tooltip behavior.apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/transactional-campaign-logic.tsx (1)
105-116: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winWiden the form value type instead of casting
nullthroughnumber.null as unknown as numberappears inappend,setValue,ConditionRow.onUpdate, andValueInput.onChange— the empty state is legitimatelynull, so thetriggerConditions[].valuetype should modelnumber | nulland letcheckWorkflowConditionsreject nulls at save time. The casts currently hide that from the compiler at four sites.Also applies to: 151-162
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/app/app.dub.co/`(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/transactional-campaign-logic.tsx around lines 105 - 116, Update the triggerConditions[].value form type and related condition types to allow number | null, then remove the null-through-unknown casts in append, setValue, ConditionRow.onUpdate, and ValueInput.onChange. Preserve null as the legitimate empty state and keep checkWorkflowConditions responsible for rejecting null values during save validation.apps/web/lib/bounty/api/bounty-availability.ts (1)
42-51: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRedundant truthiness check on an always-defined array.
groupIdsis the result of.filter(...), sogroupIds &&(Line 62) is dead; same shape reads cleaner as a plain length check.♻️ Nit
- ...(groupIds && groupIds.length > 0 + ...(groupIds.length > 0🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/lib/bounty/api/bounty-availability.ts` around lines 42 - 51, In buildBountyEligibilityWhere, remove the redundant groupIds truthiness check and use a direct groupIds.length condition wherever the filtered array is tested, preserving the existing behavior for empty and non-empty group ID lists.apps/web/app/(ee)/api/partner-profile/programs/[programId]/bounties/[bountyId]/route.ts (1)
92-92: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRename
bountyWithoutGroupsnow thatpartnerTagsis also stripped.♻️ Nit
- const { groups, partnerTags, ...bountyWithoutGroups } = bounty; + const { groups, partnerTags, ...bountyWithoutEligibility } = bounty;(and update the spread below accordingly)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/app/`(ee)/api/partner-profile/programs/[programId]/bounties/[bountyId]/route.ts at line 92, Rename the destructured remainder variable in the bounty response handling from bountyWithoutGroups to a name reflecting that both groups and partnerTags were removed, and update the corresponding spread usage below to use the new name.apps/web/lib/bounty/api/trigger-draft-bounty-submissions.ts (1)
129-139: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winConsider chunking
partnerIdsper QStash message.
partnerIdscomes straight from the caller (e.g. bulk partner-tag updates inapps/web/lib/actions/partners/tags/update-program-partner-tags.ts), so a single body can grow with the selection size and hit QStash payload limits. Chunking into fixed batches (as the notify-partners cron does for emails) keeps message size bounded.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/lib/bounty/api/trigger-draft-bounty-submissions.ts` around lines 129 - 139, Update the QStash publishing flow in the Promise.allSettled mapping to split each bounty’s partnerIds into fixed-size batches before calling qstash.publishJSON. Publish one message per chunk with the same bountyId and each chunk as partnerIds, reusing the existing batching pattern or shared helper used by the notify-partners cron to keep payload sizes bounded.apps/web/app/(ee)/api/embed/referrals/bounties/[bountyId]/upload/route.ts (1)
22-30: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winHydrate
programPartnerTagsin the referrals embed auth layer instead of per route. Both embed routes issue the same extraprogramPartnerTag.findManybecause the enrollment supplied bywithReferralsEmbedTokenlacks the new tags relation; including it once removes the duplicated query and keeps eligibility inputs consistent for future routes.
apps/web/app/(ee)/api/embed/referrals/bounties/[bountyId]/upload/route.ts#L22-L30: drop the local lookup and passprogramEnrollmentstraight through once the middleware includes tags.apps/web/app/(ee)/api/embed/referrals/bounties/[bountyId]/social-content-stats/route.ts#L53-L61: same — remove the localpartnerTagsquery and the spread merge.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/app/`(ee)/api/embed/referrals/bounties/[bountyId]/upload/route.ts around lines 22 - 30, Update withReferralsEmbedToken to hydrate programPartnerTags on the enrollment, then remove the local tag lookup and merge in apps/web/app/(ee)/api/embed/referrals/bounties/[bountyId]/upload/route.ts#L22-L30 and apps/web/app/(ee)/api/embed/referrals/bounties/[bountyId]/social-content-stats/route.ts#L53-L61; both routes should pass programEnrollment directly through the existing authorization flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/web/lib/api/workflows/operator-definitions.ts`:
- Around line 39-56: Update the label in the lte operator definition from
“under” to an inclusive phrase such as “at most” or “no more than,” while
preserving the existing validate and evaluate behavior using the <= comparison.
In `@apps/web/lib/api/workflows/validate-workflow-conditions.ts`:
- Around line 31-38: Move the awardBounty condition-count validation before the
early return in the workflow validation flow, ensuring an empty conditions array
is rejected alongside any count other than one. Update the relevant validation
logic near executeAwardBountyWorkflow so exactly one condition is required
before accessing conditions[0].
In `@apps/web/lib/zod/schemas/campaigns.ts`:
- Around line 79-87: Update the triggerConditions preprocessing in
sendCampaignConditionsSchema to catch JSON.parse failures and return an invalid
value that Zod can convert into its normal structured validation error. Preserve
successful JSON parsing and subsequent sendCampaignConditionsSchema validation.
In `@apps/web/tests/bounties/index.test.ts`:
- Around line 201-205: Remove the duplicate partnerTags property declarations
from the response type literals in the POST /bounties tests, including the
declarations near the tests starting at “with partnerTagIds null returns empty
partnerTags” and the corresponding later case. Keep one partnerTags member per
type literal with its existing type.
- Around line 229-245: Update the tag-removal test around the bounty creation
and PATCH flow to seed the bounty with a valid partner tag instead of
partnerTagIds: null. Assert the created or fetched bounty includes that tag,
then PATCH partnerTagIds: null and assert the response contains no partner tags,
ensuring nested tag deletion is actually exercised.
In `@apps/web/ui/partners/bounties/bounty-eligibility-summary.tsx`:
- Around line 25-49: Update the useGroups and usePartnerTags calls in the bounty
eligibility summary to pass the selected IDs from bountyGroups and
bountyPartnerTags, matching the ID-scoped selector fetch behavior. Keep
eligibleGroups and eligibleTags derived from those scoped results, and preserve
the existing loading and empty-state handling.
In `@apps/web/ui/partners/tags-multi-select.tsx`:
- Around line 57-67: Update the effect that derives useAsync from partnerTags so
it only enables the flag when useAsync is currently false and the page reaches
PARTNER_TAGS_MAX_PAGE_SIZE, without subsequently assigning false after it
becomes true. Preserve the existing dependency handling and monotonic true-only
behavior.
---
Nitpick comments:
In `@apps/web/app/`(ee)/api/embed/referrals/bounties/[bountyId]/upload/route.ts:
- Around line 22-30: Update withReferralsEmbedToken to hydrate
programPartnerTags on the enrollment, then remove the local tag lookup and merge
in
apps/web/app/(ee)/api/embed/referrals/bounties/[bountyId]/upload/route.ts#L22-L30
and
apps/web/app/(ee)/api/embed/referrals/bounties/[bountyId]/social-content-stats/route.ts#L53-L61;
both routes should pass programEnrollment directly through the existing
authorization flow.
In
`@apps/web/app/`(ee)/api/partner-profile/programs/[programId]/bounties/[bountyId]/route.ts:
- Line 92: Rename the destructured remainder variable in the bounty response
handling from bountyWithoutGroups to a name reflecting that both groups and
partnerTags were removed, and update the corresponding spread usage below to use
the new name.
In
`@apps/web/app/app.dub.co/`(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/transactional-campaign-logic.tsx:
- Around line 105-116: Update the triggerConditions[].value form type and
related condition types to allow number | null, then remove the
null-through-unknown casts in append, setValue, ConditionRow.onUpdate, and
ValueInput.onChange. Preserve null as the legitimate empty state and keep
checkWorkflowConditions responsible for rejecting null values during save
validation.
In `@apps/web/lib/api/workflows/check-workflow-conditions.ts`:
- Around line 55-75: Update the validation loop to prefix the invalid-operator
error with the condition number and use the resolved attribute label instead of
condition.attribute. Simplify the missing-value check to a single nullish
comparison, and remove the redundant conditionIndex alias by using the loop
index directly in the error message.
In `@apps/web/lib/api/workflows/send-campaign/execute.ts`:
- Around line 249-279: Update the program enrollment include flow around
programEnrollmentInclude and getWorkflowDataRequirements so partnerLinkStats
controls whether the links stats fields are selected. Make the links selection
conditional on the computed requirement while preserving the existing link
ordering and required link identifiers, ensuring the requirements contract is
meaningful.
In `@apps/web/lib/bounty/api/bounty-availability.ts`:
- Around line 42-51: In buildBountyEligibilityWhere, remove the redundant
groupIds truthiness check and use a direct groupIds.length condition wherever
the filtered array is tested, preserving the existing behavior for empty and
non-empty group ID lists.
In `@apps/web/lib/bounty/api/trigger-draft-bounty-submissions.ts`:
- Around line 129-139: Update the QStash publishing flow in the
Promise.allSettled mapping to split each bounty’s partnerIds into fixed-size
batches before calling qstash.publishJSON. Publish one message per chunk with
the same bountyId and each chunk as partnerIds, reusing the existing batching
pattern or shared helper used by the notify-partners cron to keep payload sizes
bounded.
In `@apps/web/ui/partners/bounties/bounty-eligibility-summary.tsx`:
- Around line 85-174: Extract the duplicated single-item versus multi-item
rendering flow from GroupsLabel and TagsLabel into a shared EligibilityLabel
helper, accepting items plus renderIcon and renderRow callbacks. Update both
components to supply their group/tag-specific content while preserving the
existing empty handling, inline label text, count suffix, and scrollable tooltip
behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: e37997ac-ab77-4069-9713-ebf2248ea167
📒 Files selected for processing (100)
apps/web/app/(ee)/api/bounties/[bountyId]/route.tsapps/web/app/(ee)/api/bounties/route.tsapps/web/app/(ee)/api/campaigns/[campaignId]/duplicate/route.tsapps/web/app/(ee)/api/campaigns/[campaignId]/route.tsapps/web/app/(ee)/api/campaigns/route.tsapps/web/app/(ee)/api/cron/bounties/create-draft-submissions/route.tsapps/web/app/(ee)/api/cron/bounties/notify-partners/route.tsapps/web/app/(ee)/api/cron/campaigns/broadcast/route.tsapps/web/app/(ee)/api/e2e/workflows/route.tsapps/web/app/(ee)/api/embed/referrals/bounties/[bountyId]/social-content-stats/route.tsapps/web/app/(ee)/api/embed/referrals/bounties/[bountyId]/upload/route.tsapps/web/app/(ee)/api/partner-profile/programs/[programId]/bounties/[bountyId]/route.tsapps/web/app/(ee)/api/partner-profile/programs/[programId]/bounties/[bountyId]/social-content-stats/route.tsapps/web/app/(ee)/api/partner-profile/programs/[programId]/bounties/route.tsapps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.tsapps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.tsapps/web/app/(ee)/api/stripe/integration/webhook/utils/attribute-via-promotion-code-id.tsapps/web/app/(ee)/api/stripe/integration/webhook/utils/sync-customer.tsapps/web/app/(ee)/api/workflows/create-partner-commission/route.tsapps/web/app/(ee)/api/workflows/partner-approved/route.tsapps/web/app/(ee)/app.dub.co/embed/referrals/get-referrals-embed-data.tsapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/[bountyId]/bounty-info.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/add-edit-bounty/add-edit-bounty-sheet.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/add-edit-bounty/audience-eligibility-panel.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/add-edit-bounty/confirm-create-bounty-modal.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/add-edit-bounty/use-add-edit-bounty-form.tsapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/bounty-card.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/campaign-controls.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/campaign-editor.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/campaign-groups-selector.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/campaign-recipients-selector.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/duplicate-logic-warning.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/transactional-campaign-logic.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/use-campaign-confirmation-modals.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/utils.tsapps/web/lib/actions/partners/accept-program-invite.tsapps/web/lib/actions/partners/tags/update-program-partner-tags.tsapps/web/lib/actions/partners/upload-bounty-submission-file.tsapps/web/lib/api/campaigns/get-campaign-or-throw.tsapps/web/lib/api/campaigns/schedule-campaigns.tsapps/web/lib/api/campaigns/transform-campaign.tsapps/web/lib/api/campaigns/validate-campaign.tsapps/web/lib/api/commissions/create-manual-commissions.tsapps/web/lib/api/conversions/track-lead.tsapps/web/lib/api/conversions/track-sale.tsapps/web/lib/api/fraud/release-hold-commissions.tsapps/web/lib/api/groups/upsert-group-move-rules.tsapps/web/lib/api/partner-tags/throw-if-invalid-partner-tag-ids.tsapps/web/lib/api/workflows/attribute-definitions.tsapps/web/lib/api/workflows/award-bounty/execute.tsapps/web/lib/api/workflows/check-workflow-conditions.tsapps/web/lib/api/workflows/evaluate-workflow-conditions.tsapps/web/lib/api/workflows/execute-workflows.tsapps/web/lib/api/workflows/move-group/execute.tsapps/web/lib/api/workflows/operator-definitions.tsapps/web/lib/api/workflows/parse-workflow-config.tsapps/web/lib/api/workflows/send-campaign/execute.tsapps/web/lib/api/workflows/send-campaign/schema.tsapps/web/lib/api/workflows/types.tsapps/web/lib/api/workflows/utils.tsapps/web/lib/api/workflows/validate-workflow-conditions.tsapps/web/lib/api/workflows/workflow-type-attributes.tsapps/web/lib/bounty/api/bounty-availability.tsapps/web/lib/bounty/api/create-bounty-submission.tsapps/web/lib/bounty/api/get-bounties-by-groups.tsapps/web/lib/bounty/api/get-bounties-for-partner.tsapps/web/lib/bounty/api/get-bounty-submission-upload-url.tsapps/web/lib/bounty/api/get-bounty-with-details.tsapps/web/lib/bounty/api/transform-bounty.tsapps/web/lib/bounty/api/trigger-draft-bounty-submissions.tsapps/web/lib/integrations/shopify/create-sale.tsapps/web/lib/integrations/shopify/process-order.tsapps/web/lib/swr/use-partners-count-by-groupids.tsapps/web/lib/webhook/sample-events/bounty-created.jsonapps/web/lib/webhook/sample-events/bounty-updated.jsonapps/web/lib/zod/schemas/bounties.tsapps/web/lib/zod/schemas/campaigns.tsapps/web/lib/zod/schemas/partner-profile.tsapps/web/lib/zod/schemas/workflows.tsapps/web/prisma/schema/bounty.prismaapps/web/prisma/schema/campaign.prismaapps/web/prisma/schema/tag.prismaapps/web/prisma/schema/workflow.prismaapps/web/scripts/dev/seed-partner-enrollment.tsapps/web/scripts/dev/seed.tsapps/web/scripts/programs/backfill-reuse-commission.tsapps/web/tests/bounties/index.test.tsapps/web/tests/campaigns/index.test.tsapps/web/tests/workflows/comparison-operators.test.tsapps/web/tests/workflows/find-groups-with-matching-rules.test.tsapps/web/tests/workflows/move-group-workflow.test.tsapps/web/tests/workflows/send-campaign-workflow.test.tsapps/web/ui/partners/audience-limit-select-shell.tsxapps/web/ui/partners/bounties/bounty-eligibility-summary.tsxapps/web/ui/partners/groups/partner-groups-select.tsxapps/web/ui/partners/partner-tags-select.tsxapps/web/ui/partners/tags-multi-select.tsxpackages/email/src/index.tspackages/utils/src/functions/index.tspackages/utils/src/functions/pluck.ts
💤 Files with no reviewable changes (6)
- apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/utils.ts
- apps/web/lib/api/workflows/parse-workflow-config.ts
- apps/web/app/(ee)/api/e2e/workflows/route.ts
- apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/campaign-groups-selector.tsx
- apps/web/tests/workflows/move-group-workflow.test.ts
- apps/web/lib/bounty/api/get-bounties-by-groups.ts
| // Less than or equal to | ||
| lte: { | ||
| name: "lte", | ||
| label: "under", | ||
| validate(value: ConditionValue) { | ||
| if (typeof value !== "number" || isNaN(value) || value < 0) { | ||
| throw new Error("Please enter a value greater than or equal to 0."); | ||
| } | ||
| }, | ||
| evaluate(attributeValue: number | string, conditionValue: ConditionValue) { | ||
| if ( | ||
| typeof attributeValue !== "number" || | ||
| typeof conditionValue !== "number" | ||
| ) { | ||
| return false; | ||
| } | ||
|
|
||
| return attributeValue <= conditionValue; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Use an inclusive label for lte.
“Under” normally excludes the boundary, but this operator includes it (<=). Use “at most”/“no more than”, or make evaluation strict.
Proposed fix
- label: "under",
+ label: "at most",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Less than or equal to | |
| lte: { | |
| name: "lte", | |
| label: "under", | |
| validate(value: ConditionValue) { | |
| if (typeof value !== "number" || isNaN(value) || value < 0) { | |
| throw new Error("Please enter a value greater than or equal to 0."); | |
| } | |
| }, | |
| evaluate(attributeValue: number | string, conditionValue: ConditionValue) { | |
| if ( | |
| typeof attributeValue !== "number" || | |
| typeof conditionValue !== "number" | |
| ) { | |
| return false; | |
| } | |
| return attributeValue <= conditionValue; | |
| // Less than or equal to | |
| lte: { | |
| name: "lte", | |
| label: "at most", | |
| validate(value: ConditionValue) { | |
| if (typeof value !== "number" || isNaN(value) || value < 0) { | |
| throw new Error("Please enter a value greater than or equal to 0."); | |
| } | |
| }, | |
| evaluate(attributeValue: number | string, conditionValue: ConditionValue) { | |
| if ( | |
| typeof attributeValue !== "number" || | |
| typeof conditionValue !== "number" | |
| ) { | |
| return false; | |
| } | |
| return attributeValue <= conditionValue; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/lib/api/workflows/operator-definitions.ts` around lines 39 - 56,
Update the label in the lte operator definition from “under” to an inclusive
phrase such as “at most” or “no more than,” while preserving the existing
validate and evaluate behavior using the <= comparison.
| ...submissionBounty, | ||
| name: "Bounty for partner tag clear", | ||
| groupIds: [E2E_PARTNER_GROUP.id], | ||
| partnerTagIds: null, | ||
| }, | ||
| }); | ||
|
|
||
| expect(createStatus).toEqual(200); | ||
|
|
||
| const { status, data: bounty } = await http.patch<{ | ||
| id: string; | ||
| partnerTags: { id: string }[]; | ||
| }>({ | ||
| path: `/bounties/${created.id}`, | ||
| body: { | ||
| partnerTagIds: null, | ||
| }, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Seed a tag before testing tag removal.
This creates the bounty with partnerTagIds: null, then clears null; the assertion passes even if nested tag deletion is broken. Create it with a valid tag, assert it is returned, then PATCH null and assert it is removed.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/tests/bounties/index.test.ts` around lines 229 - 245, Update the
tag-removal test around the bounty creation and PATCH flow to seed the bounty
with a valid partner tag instead of partnerTagIds: null. Assert the created or
fetched bounty includes that tag, then PATCH partnerTagIds: null and assert the
response contains no partner tags, ensuring nested tag deletion is actually
exercised.
Summary by CodeRabbit
New Features
Bug Fixes
Improvements