+
Skip to content

Conversation

devkiran
Copy link
Collaborator

@devkiran devkiran commented Sep 11, 2025

Summary by CodeRabbit

  • New Features

    • Filter analytics by partner group (new group selector in analytics UI).
    • "View analytics" action added to Groups table for quick navigation.
  • Improvements

    • Analytics reports now include partner-group association for more accurate grouping.
    • Faster, parallel propagation of partner-link updates and telemetry for timelier data.
  • Chores

    • Background cron task updated to use the new partner-link propagation endpoint (no user action required).

Copy link
Contributor

vercel bot commented Sep 11, 2025

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

Project Deployment Preview Updated (UTC)
dub Ready Ready Preview Sep 11, 2025 0:20am

Copy link
Contributor

coderabbitai bot commented Sep 11, 2025

Walkthrough

Adds partner group propagation to link records: fetches programEnrollment.groupId, augments ExpandedLink with partnerGroupId, records partner_group_id to Tinybird, exposes groupId in analytics filters/pipes, renames a cron route to propagate updates, updates caching/recording calls, and adds a backfill migration.

Changes

Cohort / File(s) Summary
Cron route rename & invocation sites
apps/web/app/(ee)/api/cron/links/propagate-partner-link-updates/route.ts, apps/web/app/(ee)/api/groups/[groupIdOrSlug]/partners/route.ts, apps/web/app/(ee)/api/groups/[groupIdOrSlug]/route.ts, apps/web/lib/actions/partners/create-discount.ts, apps/web/lib/actions/partners/delete-discount.ts, apps/web/lib/actions/partners/update-discount.ts
Rename cron endpoint from /invalidate-for-discounts/propagate-partner-link-updates; update QStash publish targets and ensure parallelized cache expiry and Tinybird recording where applicable.
Link enrichment & recording (partnerGroupId)
apps/web/lib/tinybird/record-link.ts, apps/web/lib/api/links/utils/transform-link.ts, apps/web/lib/api/links/create-link.ts, apps/web/lib/api/links/update-link.ts, apps/web/lib/api/links/propagate-bulk-link-changes.ts, apps/web/lib/api/links/complete-ab-tests.ts, apps/web/app/api/links/[linkId]/transfer/route.ts, apps/web/lib/api/partners/create-and-enroll-partner.ts, apps/web/lib/partners/approve-partner-enrollment.ts, apps/web/app/(ee)/api/cron/merge-partner-accounts/route.ts, apps/web/app/api/tags/[id]/route.ts, apps/web/app/(ee)/api/cron/folders/delete/route.ts
Add partnerGroupId to link payloads and Tinybird calls; include programEnrollment.groupId via Prisma includes or compute partner→group mapping; set/clear partnerGroupId where required; update cache payloads to include partner/discount fields.
Tinybird schemas / datasources
packages/tinybird/datasources/dub_links_metadata.datasource, packages/tinybird/datasources/dub_links_metadata_latest.datasource, packages/tinybird/datasources/dub_regular_links_metadata_latest.datasource
Add public field partner_group_id to Tinybird datasource SCHEMAs (placed after folder_id).
Tinybird pipes: add groupId filter
packages/tinybird/pipes/v2_*.pipe (e.g., v2_browsers.pipe, v2_cities.pipe, v2_continents.pipe, v2_count.pipe, v2_countries.pipe, v2_devices.pipe, v2_events.pipe, v2_os.pipe, v2_referer_urls.pipe, v2_referers.pipe, v2_regions.pipe, v2_timeseries.pipe, v2_top_links.pipe, v2_top_partners.pipe, v2_top_tags.pipe, v2_top_urls.pipe, v2_triggers.pipe, v2_utms.pipe)
Add optional groupId parameter and map it to partner_group_id = {{ groupId }} in workspace_links; propagate defined(groupId) into PREWHERE gating across many nodes to enable group-level filtering when provided.
Analytics UI & schema
apps/web/lib/zod/schemas/analytics.ts, apps/web/lib/analytics/constants.ts, apps/web/ui/analytics/use-analytics-filters.tsx
Add groupId to analytics query schema and filter TB mapping; include 'groupId' in VALID_ANALYTICS_FILTERS; add Group filter UI and data load on program pages.
Partner/discount plumbing
apps/web/lib/planetscale/get-partner-discount.ts
Include ProgramEnrollment.groupId in SQL result and return partner.groupId.
Type/hook changes
apps/web/lib/swr/use-groups.ts, apps/web/lib/api/links/utils/transform-link.ts
Broaden useGroups generic to GroupExtendedProps; add optional `partnerGroupId?: string
Link flow refactors & caching adjustments
apps/web/lib/api/links/create-link.ts, apps/web/lib/api/links/update-link.ts, apps/web/lib/api/links/propagate-bulk-link-changes.ts, apps/web/lib/api/links/complete-ab-tests.ts
Move partner/discount lookups out of inline awaits into upfront retrievals; reuse values for Redis caching and Tinybird recording; update recordLink payloads to include partnerGroupId; adjust propagateBulkLinkChanges signature to accept skipRedisCache default.
Backfill migration script
apps/web/scripts/migrations/backfill-partner-group-tb.ts
New migration to iterate links, read programEnrollment.groupId, add partnerGroupId to payloads, and call recordLink to backfill Tinybird.
Minor / UI tweaks / small refactors
apps/web/app/(ee)/api/events/route.ts, apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/groups/groups-table.tsx, apps/web/app/api/links/[linkId]/transfer/route.ts
Change destructured binding letconst; add "View analytics" menu item linking to group analytics; ensure recordLink for transfer clears partnerGroupId (set null).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client
  participant App as Web App
  participant QStash
  participant Cron as /api/cron/links/propagate-partner-link-updates
  participant Redis
  participant TB as Tinybird

  Client->>App: Trigger partner/discount/group update
  App->>QStash: publishJSON({ groupId }) to propagate-partner-link-updates
  QStash->>Cron: POST (async)
  par expire cache and record
    Cron->>Redis: Expire related partner-link caches
    Cron->>TB: recordLink(links with partner_group_id)
  and
    TB-->>Cron: ingestion result
    Redis-->>Cron: ok
  end
Loading
sequenceDiagram
  autonumber
  participant API as Link API (create/update/merge/transfer/tag delete)
  participant DB as Prisma
  participant TB as Tinybird
  participant Redis

  API->>DB: fetch/update link(s) including programEnrollment{groupId} / includeTags
  DB-->>API: link(s) + programEnrollment.groupId
  API->>Redis: cache link with partner/discount fields
  API->>TB: recordLink({ ...link, partnerGroupId: programEnrollment?.groupId })
  TB-->>API: ingest response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60–90 minutes

Possibly related PRs

  • FEAT: Partner Groups #2735 — Strong overlap: implements partner-group propagation, Tinybird schema updates, and link-recording changes central to this diff.
  • Program Analytics page #2519 — Related analytics changes and pipeline edits touching Tinybird pipes and analytics UI.
  • Triggers refactor #2710 — Touches the same Tinybird pipes (e.g., triggers) and may conflict or overlap on node logic.

Suggested reviewers

  • steven-tey
  • TWilson023

Pre-merge checks (3 passed)

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title clearly and concisely describes the primary intent of the changeset—adding partnerGroupId (groupId) filtering to partner analytics—and aligns with the PR objectives and the detailed changes (Tinybird pipes, datasources, backend recording, and UI filters) in the raw summary; it is specific, single-sentence, and free of noise.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

Poem

A rabbit nudges data through,
“Group IDs now hop into view.”
Links leap, caches cleared with cheer,
Tinybird eats rows we steer.
Dashboards bloom — a carrot for you! 🥕📊

✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch groupId-filter

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@vercel vercel bot left a comment

Choose a reason for hiding this comment

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

Additional Comments:

apps/web/app/(ee)/api/cron/domains/delete/route.ts (lines 64-64):

The domain delete CRON job records links to Tinybird without fetching partner group data, causing missing partnerGroupId values for partner links.

View Details
📝 Patch Details
diff --git a/apps/web/app/(ee)/api/cron/domains/delete/route.ts b/apps/web/app/(ee)/api/cron/domains/delete/route.ts
index adfc1a89a..5edcb761c 100644
--- a/apps/web/app/(ee)/api/cron/domains/delete/route.ts
+++ b/apps/web/app/(ee)/api/cron/domains/delete/route.ts
@@ -43,6 +43,11 @@ export async function POST(req: Request) {
             tag: true,
           },
         },
+        programEnrollment: {
+          select: {
+            groupId: true,
+          },
+        },
       },
       take: 100,
       orderBy: {
@@ -61,7 +66,12 @@ export async function POST(req: Request) {
       linkCache.deleteMany(links),
 
       // Record link in Tinybird
-      recordLink(links),
+      recordLink(
+        links.map((link) => ({
+          ...link,
+          partnerGroupId: link.programEnrollment?.groupId || null,
+        }))
+      ),
 
       // Remove image from R2 storage if it exists
       links

Analysis

Domain Delete CRON Job Missing Partner Group Data

Issue Description

The domain delete CRON job at apps/web/app/(ee)/api/cron/domains/delete/route.ts was recording link deletions to Tinybird without including partner group information, causing incomplete analytics data for partner links.

Root Cause Analysis

The Problem

The Prisma query that fetches links for deletion (lines 35-50) only included tags relation but missing the programEnrollment relation:

const links = await prisma.link.findMany({
  where: { domain },
  include: {
    tags: {
      select: { tag: true },
    },
    // Missing programEnrollment relation
  },
});

Expected Data Structure

The recordLink function expects an ExpandedLink type that includes partnerGroupId?: string | null. This field is populated from the programEnrollment.groupId value, but the query wasn't fetching this relationship.

The Tinybird analytics schema includes partner_group_id as a tracked field, which gets populated by the transformLinkTB function:

export const transformLinkTB = (link: ExpandedLink) => {
  return {
    // ... other fields
    partner_group_id: link.partnerGroupId ?? "",
    // ... other fields
  };
};

Data Model Relationships

From the Prisma schema:

  • Link model has a compound relation to ProgramEnrollment: programEnrollment ProgramEnrollment? @relation(fields: [programId, partnerId], references: [programId, partnerId])
  • ProgramEnrollment model has groupId field that references PartnerGroup
  • PartnerGroup contains partner segmentation data crucial for analytics

Impact

Data Inconsistency: Partner links being deleted were recorded to Tinybird with empty partner_group_id values, making it impossible to:

  • Track partner performance by group
  • Generate accurate partner analytics reports
  • Analyze conversion rates by partner segments
  • Maintain data integrity in the analytics pipeline

Solution

The fix includes two changes to apps/web/app/(ee)/api/cron/domains/delete/route.ts:

  1. Enhanced Query: Added programEnrollment relation with groupId selection:
include: {
  tags: {
    select: { tag: true },
  },
  programEnrollment: {
    select: { groupId: true },
  },
},
  1. Data Mapping: Updated the recordLink call to properly map partnerGroupId:
recordLink(
  links.map((link) => ({
    ...link,
    partnerGroupId: link.programEnrollment?.groupId || null,
  }))
),

This ensures that when partner links are deleted, their group membership is correctly recorded in Tinybird analytics, maintaining data consistency across the partner program analytics pipeline.


apps/web/app/(ee)/api/cron/domains/transfer/route.ts (lines 83-89):

The domain transfer CRON job records links to Tinybird without fetching partner group data, causing missing partnerGroupId values for partner links.

View Details
📝 Patch Details
diff --git a/apps/web/app/(ee)/api/cron/domains/transfer/route.ts b/apps/web/app/(ee)/api/cron/domains/transfer/route.ts
index e9586783e..076f332ef 100644
--- a/apps/web/app/(ee)/api/cron/domains/transfer/route.ts
+++ b/apps/web/app/(ee)/api/cron/domains/transfer/route.ts
@@ -29,6 +29,13 @@ export async function POST(req: Request) {
     const links = await prisma.link.findMany({
       where: { domain, projectId: currentWorkspaceId },
       take: 100,
+      include: {
+        programEnrollment: {
+          select: {
+            groupId: true,
+          },
+        },
+      },
       orderBy: {
         createdAt: "desc",
       },
@@ -85,6 +92,7 @@ export async function POST(req: Request) {
             ...link,
             projectId: newWorkspaceId,
             folderId: null,
+            partnerGroupId: link.programEnrollment?.groupId,
           })),
         ),
       ]);

Analysis

Domain Transfer CRON Job Missing Partner Group Data

Bug Description

The domain transfer CRON job in apps/web/app/(ee)/api/cron/domains/transfer/route.ts was not fetching partner group data when recording link metadata to Tinybird. This resulted in null or missing partnerGroupId values for partner links in analytics data.

Technical Analysis

Root Cause

The initial Prisma query (lines 29-35) fetched link records without including the programEnrollment relationship:

const links = await prisma.link.findMany({
  where: { domain, projectId: currentWorkspaceId },
  take: 100,
  orderBy: {
    createdAt: "desc",
  },
});

When recordLink() was called (lines 83-89), the links being passed didn't have access to the partner group information:

recordLink(
  links.map((link) => ({
    ...link,
    projectId: newWorkspaceId,
    folderId: null,
    // Missing: partnerGroupId field
  })),
)

Evidence from Codebase

The correct pattern is demonstrated in apps/web/scripts/migrations/backfill-partner-group-tb.ts, which:

  1. Includes programEnrollment data in the Prisma query:
include: {
  programEnrollment: {
    select: {
      groupId: true,
    },
  },
}
  1. Maps the partner group ID when calling recordLink():
recordLink(
  links.map((link) => ({
    ...link,
    partnerGroupId: link.programEnrollment?.groupId,
  })),
)

Impact

Data Consistency: Partner links transferred between domains would have missing partnerGroupId values in Tinybird analytics, causing:

  • Incomplete partner attribution data
  • Inaccurate partner performance metrics
  • Potential issues with partner commission calculations

Analytics Integrity: The missing partner group associations would create gaps in analytics queries that depend on partner group segmentation.

Solution

The fix adds the missing programEnrollment include to the initial query and maps the partnerGroupId field when calling recordLink():

  1. Enhanced Query: Include programEnrollment relationship to access groupId
  2. Proper Mapping: Extract partnerGroupId from link.programEnrollment?.groupId when recording to Tinybird

This ensures partner links maintain their group associations during domain transfers, preserving analytics data integrity.

Files Modified

  • apps/web/app/(ee)/api/cron/domains/transfer/route.ts: Added programEnrollment include and partnerGroupId mapping

apps/web/app/(ee)/api/cron/domains/update/route.ts (lines 82-82):

The domain update CRON job records links to Tinybird without fetching partner group data, causing missing partnerGroupId values for partner links.

View Details
📝 Patch Details
diff --git a/apps/web/app/(ee)/api/cron/domains/update/route.ts b/apps/web/app/(ee)/api/cron/domains/update/route.ts
index 61707c974..1311f02a0 100644
--- a/apps/web/app/(ee)/api/cron/domains/update/route.ts
+++ b/apps/web/app/(ee)/api/cron/domains/update/route.ts
@@ -72,6 +72,11 @@ export async function POST(req: Request) {
             tag: true,
           },
         },
+        programEnrollment: {
+          select: {
+            groupId: true,
+          },
+        },
       },
     });
 
@@ -79,7 +84,12 @@ export async function POST(req: Request) {
       // update the `shortLink` field for each of the short links
       updateShortLinks(updatedLinks),
       // record new link values in Tinybird (dub_links_metadata)
-      recordLink(updatedLinks),
+      recordLink(
+        updatedLinks.map((link) => ({
+          ...link,
+          partnerGroupId: link.programEnrollment?.groupId,
+        }))
+      ),
       // expire the redis cache for the old links
       linkCache.expireMany(linksToUpdate),
     ]);

Analysis

Domain Update CRON Job Missing Partner Group Data Bug

Summary

The domain update CRON job (apps/web/app/(ee)/api/cron/domains/update/route.ts) was recording links to Tinybird without fetching partner group data, causing missing partnerGroupId values for partner links and creating data inconsistency in analytics.

Technical Analysis

The Problem

The domain update CRON job performs three key operations when updating links from an old domain to a new domain:

  1. Updates the domain in the database
  2. Updates short link URLs
  3. Records updated links to Tinybird for analytics

However, the query to fetch updated links (lines 62-80) only included basic link data and tags:

const updatedLinks = await prisma.link.findMany({
  where: { id: { in: linkIdsToUpdate } },
  include: {
    tags: { select: { tag: true } }
  }
});

This query was missing the programEnrollment relation needed to access partner group data.

Data Flow Impact

When recordLink(updatedLinks) was called (line 82), partner links were missing their partnerGroupId values because:

  1. The query didn't include programEnrollment.groupId
  2. Links were passed directly to recordLink() without mapping the partner group data
  3. The Tinybird schema transforms missing partnerGroupId to empty string ""

Pattern Analysis Evidence

Multiple other operations in the codebase correctly handle partner group data:

  • create-link.ts: Uses getPartnerAndDiscount() and passes partnerGroupId: partner?.groupId
  • update-link.ts: Uses getPartnerAndDiscount() and passes partnerGroupId: partner?.groupId
  • propagate-partner-link-updates/route.ts: Manually adds partnerGroupId: group.id
  • backfill-partner-group-tb.ts: Includes programEnrollment in query and maps partnerGroupId

The Fix

Updated the domain update CRON job to:

  1. Include partner enrollment data in query:

    include: {
      tags: { select: { tag: true } },
      programEnrollment: { select: { groupId: true } }
    }
  2. Map partner group data when recording to Tinybird:

    recordLink(
      updatedLinks.map((link) => ({
        ...link,
        partnerGroupId: link.programEnrollment?.groupId,
      }))
    )

Impact and Consequences

Before Fix

  • Partner links updated during domain migrations had missing partnerGroupId in Tinybird
  • Analytics queries grouping by partner groups would exclude these links
  • Partner attribution and commission tracking could be inaccurate
  • Data inconsistency between different link operations

After Fix

  • All partner links maintain proper group attribution during domain updates
  • Consistent partner analytics across all link operations
  • Accurate partner commission and attribution tracking
  • Data integrity maintained in Tinybird analytics

Related Components

  • Tinybird Schema: dub_links_metadata datasource with partner_group_id field
  • Link Analytics: Partner group attribution and reporting
  • Partner Commissions: Group-based reward calculations
  • Domain Migration: Bulk link updates preserving partner data

This fix ensures data consistency and prevents analytics gaps during domain migration operations.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (12)
packages/tinybird/pipes/v2_top_tags.pipe (2)

39-41: Activate PREWHERE when only groupId is present

Mirror other filters by adding defined(groupId) so queries prune via workspace_links_with_tags.

-                {% if defined(workspaceId) or defined(partnerId) or defined(programId) %}
+                {% if defined(workspaceId) or defined(partnerId) or defined(programId) or defined(groupId) %}
                     PREWHERE link_id in (SELECT link_id from workspace_links_with_tags)
                 {% end %}

Apply to all three locations above.

Also applies to: 99-101, 163-165


253-255: Broken reference: PREWHERE uses non-existent node

filtered_clicks references workspace_links, but this pipe defines workspace_links_with_tags. This will fail at compile/run time.

-            PREWHERE link_id in (SELECT link_id from workspace_links)
+            PREWHERE link_id in (SELECT link_id from workspace_links_with_tags)
packages/tinybird/pipes/v2_os.pipe (1)

51-54: Include groupId in PREWHERE gating

Without this, queries with only groupId won’t prune by workspace_links.

-        {% elif not defined(linkId) and (
-            defined(workspaceId) or defined(partnerId) or defined(programId)
-        ) %} PREWHERE link_id in (SELECT link_id from workspace_links)
+        {% elif not defined(linkId) and (
+            defined(workspaceId) or defined(partnerId) or defined(programId) or defined(groupId)
+        ) %} PREWHERE link_id in (SELECT link_id from workspace_links)

Apply at all shown locations.

Also applies to: 100-102, 143-145, 183-185

packages/tinybird/pipes/v2_regions.pipe (2)

48-50: Add groupId to PREWHERE gating

Enable pruning when only groupId is specified.

-          {% if not defined(linkId) and (defined(workspaceId) or defined(partnerId) or defined(programId)) %}
+          {% if not defined(linkId) and (defined(workspaceId) or defined(partnerId) or defined(programId) or defined(groupId)) %}
               PREWHERE link_id in (SELECT link_id from workspace_links)
           {% end %}

Apply to all four locations.

Also applies to: 93-95, 140-142, 182-184


40-45: Syntax error: trailing comma before FROM

There’s a trailing comma after clicks/leads, which breaks the SELECT list.

-    SELECT 
-        CONCAT(country, '-', region) as region,
-        country,
-        clicks,
+    SELECT 
+        CONCAT(country, '-', region) as region,
+        country,
+        clicks
-    SELECT 
-        CONCAT(country, '-', region) as region,
-        country,
-        leads,
+    SELECT 
+        CONCAT(country, '-', region) as region,
+        country,
+        leads

Also applies to: 85-90

packages/tinybird/pipes/v2_countries.pipe (1)

51-53: Enable PREWHERE when only groupId is provided

Add defined(groupId) to the gating to benefit from link pruning.

-        {% elif not defined(linkId) and (defined(workspaceId) or defined(partnerId) or defined(programId)) %}
+        {% elif not defined(linkId) and (defined(workspaceId) or defined(partnerId) or defined(programId) or defined(groupId)) %}
             PREWHERE link_id in (SELECT link_id from workspace_links)

Apply to all listed locations.

Also applies to: 97-99, 149-151, 198-200

packages/tinybird/pipes/v2_events.pipe (1)

56-58: Include groupId in gating for link-based filtering

Events should also respect groupId when it is the only scoping parameter.

-{% elif defined(workspaceId) or defined(partnerId) or defined(programId) %}
+{% elif defined(workspaceId) or defined(partnerId) or defined(programId) or defined(groupId) %}
     AND link_id IN (SELECT link_id FROM workspace_links)

Apply in all four places above.

Also applies to: 107-109, 184-186, 263-265

packages/tinybird/pipes/v2_utms.pipe (1)

59-62: Bug: groupId-only queries won’t use workspace_links PREWHERE.

All PREWHERE guards exclude groupId, so a request with only groupId defined won’t filter by workspace_links. Add defined(groupId) to the guard.

Apply:

-        {% elif not defined(linkId) and (
-            defined(workspaceId) or defined(partnerId) or defined(programId)
-        ) %} PREWHERE link_id in (SELECT link_id from workspace_links)
+        {% elif not defined(linkId) and (
+            defined(workspaceId) or defined(partnerId) or defined(programId) or defined(groupId)
+        ) %} PREWHERE link_id in (SELECT link_id from workspace_links)

Repeat the same change in utms_leads, utms_sales, and utms_sales_with_type blocks.

Also applies to: 125-127, 191-194, 259-262

packages/tinybird/pipes/v2_triggers.pipe (1)

51-54: Bug: groupId-only queries bypass PREWHERE.

Mirror the other filters by including defined(groupId).

-        {% elif not defined(linkId) and (
-            defined(workspaceId) or defined(partnerId) or defined(programId)
-        ) %} PREWHERE link_id in (SELECT link_id from workspace_links)
+        {% elif not defined(linkId) and (
+            defined(workspaceId) or defined(partnerId) or defined(programId) or defined(groupId)
+        ) %} PREWHERE link_id in (SELECT link_id from workspace_links)

Apply to trigger_clicks, trigger_leads, trigger_sales, and trigger_sales_with_type.

Also applies to: 100-102, 146-148, 186-189

packages/tinybird/pipes/v2_continents.pipe (1)

51-54: Bug: groupId-only queries bypass PREWHERE.

Include defined(groupId) so group-scoped queries hit workspace_links.

-        {% elif not defined(linkId) and (
-            defined(workspaceId) or defined(partnerId) or defined(programId)
-        ) %} PREWHERE link_id in (SELECT link_id from workspace_links)
+        {% elif not defined(linkId) and (
+            defined(workspaceId) or defined(partnerId) or defined(programId) or defined(groupId)
+        ) %} PREWHERE link_id in (SELECT link_id from workspace_links)

Apply to clicks, leads, sales, and sales_with_type nodes.

Also applies to: 109-111, 161-163, 210-212

packages/tinybird/pipes/v2_count.pipe (1)

51-54: Bug: groupId-only queries bypass PREWHERE.

Add defined(groupId) alongside other scoping params.

-        {% elif not defined(linkId) and (
-            defined(workspaceId) or defined(partnerId) or defined(programId)
-        ) %} PREWHERE link_id in (SELECT link_id from workspace_links)
+        {% elif not defined(linkId) and (
+            defined(workspaceId) or defined(partnerId) or defined(programId) or defined(groupId)
+        ) %} PREWHERE link_id in (SELECT link_id from workspace_links)

Also applies to: 98-100, 136-139, 192-195

apps/web/lib/planetscale/get-partner-discount.ts (1)

3-14: Type mismatch: LEFT JOIN’d Discount fields can be null.

QueryResult types for discountId/amount/type/maxDuration/couponId/couponTestId should be nullable to reflect LEFT JOIN and avoid unsafe assumptions.

 interface QueryResult {
   id: string;
   name: string;
   image: string | null;
   groupId: string | null;
-  discountId: string;
-  amount: number;
-  type: "percentage" | "flat";
-  maxDuration: number | null;
-  couponId: string | null;
-  couponTestId: string | null;
+  discountId: string | null;
+  amount: number | null;
+  type: "percentage" | "flat" | null;
+  maxDuration: number | null;
+  couponId: string | null;
+  couponTestId: string | null;
 }

Also safe as-is at runtime (guarded by result.discountId), but TS types should match the SQL.

♻️ Duplicate comments (1)
apps/web/lib/api/links/propagate-bulk-link-changes.ts (1)

18-38: Bug: Assumes single programId; breaks partnerGroupId mapping for mixed-program batches.

Needs composite (programId, partnerId) mapping to fetch correct enrollments.

-  if (partnerLinks.length > 0) {
-    const programId = partnerLinks[0].programId!;
-    const uniquePartnerIds = [
-      ...new Set(partnerLinks.map((link) => link.partnerId) as string[]),
-    ];
-
-    const enrollments = await prisma.programEnrollment.findMany({
-      where: {
-        partnerId: {
-          in: uniquePartnerIds,
-        },
-        programId,
-      },
-      select: {
-        partnerId: true,
-        groupId: true,
-      },
-    });
-
-    partnerGroupMap = new Map(
-      enrollments.map(({ partnerId, groupId }) => [partnerId, groupId]),
-    );
-  }
+  if (partnerLinks.length > 0) {
+    // Unique (programId, partnerId) pairs
+    const pairs = [
+      ...new Map(
+        partnerLinks.map((l) => [
+          `${l.programId}:${l.partnerId}`,
+          { programId: l.programId!, partnerId: l.partnerId! },
+        ]),
+      ).values(),
+    ];
+
+    const enrollments = await prisma.programEnrollment.findMany({
+      where: {
+        OR: pairs.map(({ programId, partnerId }) => ({ programId, partnerId })),
+      },
+      select: {
+        programId: true,
+        partnerId: true,
+        groupId: true,
+      },
+    });
+
+    partnerGroupMap = new Map(
+      enrollments.map(({ programId, partnerId, groupId }) => [
+        `${programId}:${partnerId}`,
+        groupId,
+      ]),
+    );
+  }
🧹 Nitpick comments (28)
packages/tinybird/pipes/v2_top_tags.pipe (1)

269-281: Normalize UTM encoding for consistency and correctness

Other pipes URL-encode UTM values. Do the same here to avoid mismatches on special characters.

-            AND url LIKE concat('%utm_source=', {{ String(utm_source) }}, '%')
+            AND url LIKE concat('%utm_source=', encodeURLFormComponent({{ String(utm_source) }}), '%')
-            AND url LIKE concat('%utm_medium=', {{ String(utm_medium) }}, '%')
+            AND url LIKE concat('%utm_medium=', encodeURLFormComponent({{ String(utm_medium) }}), '%')
-            AND url LIKE concat('%utm_campaign=', {{ String(utm_campaign) }}, '%')
+            AND url LIKE concat('%utm_campaign=', encodeURLFormComponent({{ String(utm_campaign) }}), '%')
-            AND url LIKE concat('%utm_term=', {{ String(utm_term) }}, '%')
+            AND url LIKE concat('%utm_term=', encodeURLFormComponent({{ String(utm_term) }}), '%')
-            AND url LIKE concat('%utm_content=', {{ String(utm_content) }}, '%')
+            AND url LIKE concat('%utm_content=', encodeURLFormComponent({{ String(utm_content) }}), '%')
apps/web/lib/actions/partners/update-discount.ts (1)

61-65: Route rename to propagate-partner-link-updates — OK; guard null groupId

URL change looks correct. Consider skipping the QStash call if partnerGroup?.id is falsy.

-            ? qstash.publishJSON({
+            ? (partnerGroup?.id
+                ? qstash.publishJSON({
                   url: `${APP_DOMAIN_WITH_NGROK}/api/cron/links/propagate-partner-link-updates`,
                   body: {
                     groupId: partnerGroup?.id,
                   },
-              })
+                })
+                : Promise.resolve())
             : Promise.resolve(),

Confirm the cron route treats groupId as required to avoid no-op fanouts.

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

272-281: Add “View analytics” menu item — nice UX win

Optional: preserve current analytics filters (date range, interval) by forwarding existing query params when pushing.

-                  router.push(
-                    `/${slug}/program/analytics?groupId=${row.original.id}`,
-                  )
+                  router.push(`/${slug}/program/analytics?groupId=${row.original.id}`)

If you want to retain filters, consider building from useRouterStuff().getQueryString() and merging groupId.

packages/tinybird/pipes/v2_referer_urls.pipe (1)

2-2: Fix description to reflect content

Consider “Top referer URLs” instead of “Top countries”.

packages/tinybird/pipes/v2_browsers.pipe (1)

2-2: Tweak description

Use “Top browsers”.

packages/tinybird/pipes/v2_devices.pipe (1)

2-2: Tweak description

Use “Top devices”.

packages/tinybird/pipes/v2_referers.pipe (1)

2-2: Tweak description

Use “Top referrers”.

packages/tinybird/pipes/v2_triggers.pipe (2)

1-2: Description mismatch (“Top countries”).

Rename to “Triggers” to reflect the endpoint.

-DESCRIPTION >
-	Top countries
+DESCRIPTION >
+	Triggers

169-170: Minor: standardize DateTime vs DateTime64.

Other pipes use DateTime64 for event timestamps; consider aligning for consistency/perf.

Also applies to: 227-228

packages/tinybird/pipes/v2_continents.pipe (1)

141-142: Minor: unify timestamp casting.

Mix of DateTime and DateTime64 across nodes; consider standardizing (prefer DateTime64).

Also applies to: 193-194, 242-243, 95-96

packages/tinybird/pipes/v2_count.pipe (2)

1-2: Description mismatch (“Top countries”).

Rename to “Counts” to reflect output.

-DESCRIPTION >
-	Top countries
+DESCRIPTION >
+	Counts

86-87: Minor: standardize DateTime vs DateTime64.

Consider using DateTime64 consistently across endpoints.

Also applies to: 121-122, 177-178, 233-234

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

55-60: Ensure recordLink tolerates undefined partnerGroupId.

recordLink should map undefined to null for partner_group_id; otherwise coerce explicitly.

-        partnerGroupId: link.programEnrollment?.groupId,
+        partnerGroupId: link.programEnrollment?.groupId ?? null,
apps/web/lib/planetscale/get-partner-discount.ts (1)

32-46: Optional: alias Discount fields to avoid future name collisions.

Aliasing (e.g., Discount.type AS discountType) can prevent clashes if more tables/fields are added.

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

83-86: Avoid passing programEnrollment to recordLink payload (potential excess property/type mismatch).

Prefer stripping programEnrollment before forwarding to Tinybird.

Apply:

-      recordLink({
-        ...response,
-        partnerGroupId: response.programEnrollment?.groupId,
-      }),
+      // Keep TB payload lean and TS-safe
+      (() => {
+        const { programEnrollment, ...rest } = response as any;
+        return recordLink({
+          ...rest,
+          partnerGroupId: programEnrollment?.groupId ?? null,
+        });
+      })(),
apps/web/lib/partners/approve-partner-enrollment.ts (1)

175-181: Always record the enrolled link with partnerGroupId (handles both updated and newly created link).

Unify the call so we also record when partnerLink is created via createPartnerLink.

Apply:

-        updatedLink
-          ? recordLink({
-              ...updatedLink,
-              partnerGroupId: group.id,
-            })
-          : Promise.resolve(null),
+        recordLink({
+          ...(updatedLink ?? partnerLink),
+          partnerGroupId: group.id,
+        }),
apps/web/app/(ee)/api/cron/merge-partner-accounts/route.ts (1)

174-190: Propagating partnerGroupId to TB after merge is correct; consider chunked ingestion.

Including programEnrollment.groupId and mapping partnerGroupId is spot on. If updatedLinks can be large, chunk the TB ingest to avoid 413s/timeouts.

Apply:

-        recordLink(
-          updatedLinks.map((link) => ({
-            ...link,
-            partnerGroupId: link.programEnrollment?.groupId,
-          })),
-        ),
+        (async () => {
+          const payload = updatedLinks.map((link) => ({
+            ...link,
+            partnerGroupId: link.programEnrollment?.groupId,
+          }));
+          const CHUNK = 500;
+          for (let i = 0; i < payload.length; i += CHUNK) {
+            await recordLink(payload.slice(i, i + CHUNK));
+          }
+        })(),

Also applies to: 193-200

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

101-105: Normalize undefined to null for consistency with TB schemas.

Avoid sending undefined (which may be omitted during serialization). Prefer explicit null.

Apply:

-            partnerGroupId: link.programEnrollment?.groupId,
+            partnerGroupId: link.programEnrollment?.groupId ?? null,
apps/web/lib/api/links/create-link.ts (2)

139-145: Don’t let partner lookup failure block all post-create tasks.

If getPartnerAndDiscount throws (DB/network), none of the subsequent tasks run. Catch and default to null so caching, Tinybird, image, qstash, usage updates, etc., still proceed.

Apply:

-    (async () => {
-      const { partner, discount } = await getPartnerAndDiscount({
-        programId: response.programId,
-        partnerId: response.partnerId,
-      });
+    (async () => {
+      let partner: Awaited<ReturnType<typeof getPartnerAndDiscount>>["partner"] | null = null;
+      let discount: Awaited<ReturnType<typeof getPartnerAndDiscount>>["discount"] | null = null;
+      try {
+        ({ partner, discount } = await getPartnerAndDiscount({
+          programId: response.programId,
+          partnerId: response.partnerId,
+        }));
+      } catch {
+        // swallow; proceed without partner/discount
+      }

154-157: Send explicit null for partnerGroupId.

Tinybird/transformers typically expect nullables; avoid undefined.

Apply:

-        recordLink({
-          ...response,
-          partnerGroupId: partner?.groupId,
-        }),
+        recordLink({
+          ...response,
+          partnerGroupId: partner?.groupId ?? null,
+        }),
apps/web/scripts/migrations/backfill-partner-group-tb.ts (1)

51-56: Normalize partnerGroupId to null.

Guarantee a concrete nullable for ingestion.

Apply:

-      links.map((link) => ({
-        ...link,
-        partnerGroupId: link.programEnrollment?.groupId,
-      })),
+      links.map((link) => ({
+        ...link,
+        partnerGroupId: link.programEnrollment?.groupId ?? null,
+      })),
apps/web/app/(ee)/api/cron/links/propagate-partner-link-updates/route.ts (2)

37-40: Return 204 No Content for missing group to avoid retries.

Explicit 204 makes intent clearer for job runners while keeping idempotency.

-  console.error(`Group ${groupId} not found.`);
-  return new Response("OK");
+  console.error(`Group ${groupId} not found.`);
+  return new Response(null, { status: 204 });

78-91: Surface failures from Promise.allSettled to avoid silent data loss.

Currently failures are swallowed; log and keep going per chunk.

-      await Promise.allSettled([
+      const results = await Promise.allSettled([
         // Expire the cache for the links
         linkCache.expireMany(
           linkChunk.map(({ domain, key }) => ({ domain, key })),
         ),

         // Record the updated links in Tinybird
         recordLink(
           linkChunk.map((link) => ({
             ...link,
             partnerGroupId: group.id,
           })),
         ),
       ]);
+      for (const r of results) {
+        if (r.status === "rejected") {
+          console.error("propagate-partner-link-updates error:", r.reason);
+        }
+      }
apps/web/lib/api/links/propagate-bulk-link-changes.ts (2)

47-53: Coalesce undefined to null for Tinybird schema consistency.

Map.get can return undefined. Coalesce to null to match nullable TB column.

-        partnerGroupId: link.partnerId
-          ? partnerGroupMap.get(link.partnerId)
-          : null,
+        partnerGroupId:
+          link.partnerId && link.programId
+            ? partnerGroupMap.get(`${link.programId}:${link.partnerId}`) ?? null
+            : null,

41-54: Optional: Avoid non-Promise values in Promise.all.

Minor readability: filter out falsy entry when skipRedisCache is true.

-return await Promise.all([
-  // update Redis cache
-  !skipRedisCache && linkCache.mset(links),
-  // update Tinybird
-  recordLink(/* ... */),
-]);
+const tasks: Promise<unknown>[] = [];
+if (!skipRedisCache) tasks.push(linkCache.mset(links));
+tasks.push(
+  recordLink(
+    links.map((link) => ({
+      ...link,
+      partnerGroupId:
+        link.partnerId && link.programId
+          ? partnerGroupMap.get(`${link.programId}:${link.partnerId}`) ?? null
+          : null,
+    })),
+  ),
+);
+return await Promise.all(tasks);
apps/web/ui/analytics/use-analytics-filters.tsx (1)

523-537: Use JSX for the group icon for consistency and clearer TSX semantics.

Calling the component as a function works but is atypical.

-                options: loadingGroups
-                  ? null
-                  : groups?.map((group) => ({
+                options: loadingGroups
+                  ? null
+                  : groups?.map((group) => ({
                     value: group.id,
                     label: group.name,
-                    icon: GroupColorCircle({ group }),
-                    right: nFormatter(group.partners, {
+                    icon: <GroupColorCircle group={group} />,
+                    right: nFormatter(
+                      // tolerate differing shapes across queries
+                      (typeof group.partners === "number"
+                        ? group.partners
+                        : (group as any).partnerCount ??
+                          (group as any)._count?.partners ??
+                          0) as number,
+                      {
                         full: true,
-                      }),
+                      },
+                    ),
                   })) ?? null,
apps/web/lib/api/links/update-link.ts (2)

177-217: Filter out non-promises in Promise.allSettled.

Several array entries can be boolean. Filtering improves clarity and avoids no-op settled values.

-      await Promise.allSettled([
+      await Promise.allSettled(
+        [
           // cache link in Redis
           linkCache.set({
             ...response,
             ...(partner && { partner }),
             ...(discount && { discount }),
           }),
@@
           testVariants &&
           testCompletedAt &&
           scheduleABTestCompletion(response),
-      ]);
+        ].filter(Boolean) as Promise<unknown>[]
+      );

203-207: Make R2 deletion robust to trailing slashes by parsing URL.

String replace can break if R2_URL has a trailing slash or differs by scheme/host casing. Extract the path instead.

-          storage.delete(oldLink.image.replace(`${R2_URL}/`, "")),
+          storage.delete(new URL(oldLink.image).pathname.slice(1)),
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e15843d and 1ac0b36.

📒 Files selected for processing (47)
  • apps/web/app/(ee)/api/cron/folders/delete/route.ts (2 hunks)
  • apps/web/app/(ee)/api/cron/links/propagate-partner-link-updates/route.ts (4 hunks)
  • apps/web/app/(ee)/api/cron/merge-partner-accounts/route.ts (1 hunks)
  • apps/web/app/(ee)/api/events/route.ts (1 hunks)
  • apps/web/app/(ee)/api/groups/[groupIdOrSlug]/partners/route.ts (1 hunks)
  • apps/web/app/(ee)/api/groups/[groupIdOrSlug]/route.ts (1 hunks)
  • apps/web/app/api/links/[linkId]/transfer/route.ts (1 hunks)
  • apps/web/app/api/tags/[id]/route.ts (2 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/groups/groups-table.tsx (2 hunks)
  • apps/web/lib/actions/partners/create-discount.ts (1 hunks)
  • apps/web/lib/actions/partners/delete-discount.ts (1 hunks)
  • apps/web/lib/actions/partners/update-discount.ts (1 hunks)
  • apps/web/lib/analytics/constants.ts (1 hunks)
  • apps/web/lib/api/links/complete-ab-tests.ts (2 hunks)
  • apps/web/lib/api/links/create-link.ts (1 hunks)
  • apps/web/lib/api/links/propagate-bulk-link-changes.ts (2 hunks)
  • apps/web/lib/api/links/update-link.ts (1 hunks)
  • apps/web/lib/api/links/utils/transform-link.ts (1 hunks)
  • apps/web/lib/api/partners/create-and-enroll-partner.ts (1 hunks)
  • apps/web/lib/partners/approve-partner-enrollment.ts (1 hunks)
  • apps/web/lib/planetscale/get-partner-discount.ts (3 hunks)
  • apps/web/lib/swr/use-groups.ts (1 hunks)
  • apps/web/lib/tinybird/record-link.ts (2 hunks)
  • apps/web/lib/zod/schemas/analytics.ts (2 hunks)
  • apps/web/scripts/migrations/backfill-partner-group-tb.ts (1 hunks)
  • apps/web/ui/analytics/use-analytics-filters.tsx (9 hunks)
  • packages/tinybird/datasources/dub_links_metadata.datasource (1 hunks)
  • packages/tinybird/datasources/dub_links_metadata_latest.datasource (1 hunks)
  • packages/tinybird/datasources/dub_regular_links_metadata_latest.datasource (1 hunks)
  • packages/tinybird/pipes/v2_browsers.pipe (1 hunks)
  • packages/tinybird/pipes/v2_cities.pipe (1 hunks)
  • packages/tinybird/pipes/v2_continents.pipe (1 hunks)
  • packages/tinybird/pipes/v2_count.pipe (1 hunks)
  • packages/tinybird/pipes/v2_countries.pipe (1 hunks)
  • packages/tinybird/pipes/v2_devices.pipe (1 hunks)
  • packages/tinybird/pipes/v2_events.pipe (1 hunks)
  • packages/tinybird/pipes/v2_os.pipe (1 hunks)
  • packages/tinybird/pipes/v2_referer_urls.pipe (1 hunks)
  • packages/tinybird/pipes/v2_referers.pipe (1 hunks)
  • packages/tinybird/pipes/v2_regions.pipe (1 hunks)
  • packages/tinybird/pipes/v2_timeseries.pipe (1 hunks)
  • packages/tinybird/pipes/v2_top_links.pipe (1 hunks)
  • packages/tinybird/pipes/v2_top_partners.pipe (1 hunks)
  • packages/tinybird/pipes/v2_top_tags.pipe (1 hunks)
  • packages/tinybird/pipes/v2_top_urls.pipe (1 hunks)
  • packages/tinybird/pipes/v2_triggers.pipe (1 hunks)
  • packages/tinybird/pipes/v2_utms.pipe (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-06T07:59:03.120Z
Learnt from: devkiran
PR: dubinc/dub#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
  • apps/web/app/(ee)/api/cron/folders/delete/route.ts
  • apps/web/scripts/migrations/backfill-partner-group-tb.ts
  • apps/web/app/(ee)/api/cron/links/propagate-partner-link-updates/route.ts
  • apps/web/lib/api/links/create-link.ts
  • apps/web/lib/api/links/update-link.ts
  • apps/web/lib/api/links/propagate-bulk-link-changes.ts
🧬 Code graph analysis (18)
apps/web/app/(ee)/api/cron/merge-partner-accounts/route.ts (1)
apps/web/lib/tinybird/record-link.ts (1)
  • recordLink (78-84)
apps/web/lib/swr/use-groups.ts (2)
apps/web/lib/zod/schemas/groups.ts (1)
  • getGroupsQuerySchema (71-94)
apps/web/lib/types.ts (1)
  • GroupExtendedProps (528-528)
apps/web/lib/api/links/complete-ab-tests.ts (1)
apps/web/lib/tinybird/record-link.ts (1)
  • recordLink (78-84)
apps/web/lib/partners/approve-partner-enrollment.ts (1)
apps/web/lib/tinybird/record-link.ts (1)
  • recordLink (78-84)
apps/web/lib/actions/partners/delete-discount.ts (1)
packages/utils/src/constants/main.ts (1)
  • APP_DOMAIN_WITH_NGROK (20-25)
apps/web/lib/actions/partners/update-discount.ts (1)
packages/utils/src/constants/main.ts (1)
  • APP_DOMAIN_WITH_NGROK (20-25)
apps/web/app/(ee)/api/groups/[groupIdOrSlug]/route.ts (1)
packages/utils/src/constants/main.ts (1)
  • APP_DOMAIN_WITH_NGROK (20-25)
apps/web/scripts/migrations/backfill-partner-group-tb.ts (1)
apps/web/lib/tinybird/record-link.ts (1)
  • recordLink (78-84)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/groups/groups-table.tsx (1)
packages/ui/src/menu-item.tsx (1)
  • MenuItem (43-86)
apps/web/app/(ee)/api/cron/links/propagate-partner-link-updates/route.ts (2)
apps/web/lib/api/links/cache.ts (1)
  • linkCache (113-113)
apps/web/lib/tinybird/record-link.ts (1)
  • recordLink (78-84)
apps/web/lib/api/partners/create-and-enroll-partner.ts (1)
apps/web/lib/tinybird/record-link.ts (1)
  • recordLink (78-84)
apps/web/lib/actions/partners/create-discount.ts (1)
packages/utils/src/constants/main.ts (1)
  • APP_DOMAIN_WITH_NGROK (20-25)
apps/web/app/api/links/[linkId]/transfer/route.ts (1)
apps/web/lib/tinybird/record-link.ts (1)
  • recordLink (78-84)
apps/web/app/(ee)/api/groups/[groupIdOrSlug]/partners/route.ts (1)
packages/utils/src/constants/main.ts (1)
  • APP_DOMAIN_WITH_NGROK (20-25)
apps/web/ui/analytics/use-analytics-filters.tsx (2)
apps/web/lib/swr/use-groups.ts (1)
  • useGroups (10-38)
apps/web/ui/partners/groups/group-color-circle.tsx (1)
  • GroupColorCircle (5-24)
apps/web/lib/api/links/create-link.ts (3)
apps/web/lib/planetscale/get-partner-discount.ts (1)
  • getPartnerAndDiscount (17-78)
apps/web/lib/tinybird/record-link.ts (1)
  • recordLink (78-84)
packages/utils/src/constants/main.ts (1)
  • APP_DOMAIN_WITH_NGROK (20-25)
apps/web/lib/api/links/update-link.ts (4)
apps/web/lib/planetscale/get-partner-discount.ts (1)
  • getPartnerAndDiscount (17-78)
apps/web/lib/api/links/cache.ts (1)
  • linkCache (113-113)
apps/web/lib/tinybird/record-link.ts (1)
  • recordLink (78-84)
packages/utils/src/constants/main.ts (1)
  • R2_URL (81-81)
apps/web/lib/api/links/propagate-bulk-link-changes.ts (2)
apps/web/lib/api/links/cache.ts (1)
  • linkCache (113-113)
apps/web/lib/tinybird/record-link.ts (1)
  • recordLink (78-84)
⏰ 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). (2)
  • GitHub Check: Vade Review
  • GitHub Check: build
🔇 Additional comments (42)
packages/tinybird/pipes/v2_top_urls.pipe (2)

21-21: Add groupId filter — looks good

The new partner_group_id predicate is correct and aligns with the rest of the filters.


43-45: Ensure groupId activates PREWHERE pruning

Include defined(groupId) in the PREWHERE gating so queries with only groupId trigger PREWHERE and avoid full table scans.

Apply:

-        {% if not defined(linkId) and (defined(workspaceId) or defined(partnerId) or defined(programId)) %}
+        {% if not defined(linkId) and (defined(workspaceId) or defined(partnerId) or defined(programId) or defined(groupId)) %}
             PREWHERE link_id in (SELECT link_id from workspace_links)
         {% end %}

Also apply the same change in packages/tinybird/pipes/v2_top_urls.pipe at the other occurrences (lines 81-83, 125-127, 166-169). rg search returned no matches; unable to verify occurrences — re-run the repo-wide check or confirm file paths.

packages/tinybird/pipes/v2_top_tags.pipe (1)

18-18: Add groupId filter — looks good

Predicate on partner_group_id is correct in workspace_links_with_tags.

packages/tinybird/pipes/v2_os.pipe (1)

21-21: Add groupId filter — looks good

Correct placement alongside partner/program filters.

packages/tinybird/pipes/v2_regions.pipe (1)

21-21: Add groupId filter — looks good

Consistent with other pipes.

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

23-31: Icon import add — OK

Importing LinesY is correct for the new menu item.

packages/tinybird/pipes/v2_countries.pipe (1)

21-21: Add groupId filter — looks good

Filter is consistent with other fields.

packages/tinybird/pipes/v2_events.pipe (1)

21-21: Add groupId filter — looks good

Correctly extends workspace_links.

packages/tinybird/pipes/v2_top_partners.pipe (1)

12-12: LGTM: groupId filter on partner_links

The conditional filter on partner_group_id is correctly scoped at the source join.

apps/web/lib/actions/partners/delete-discount.ts (1)

62-65: ```shell
#!/bin/bash
set -euo pipefail

echo "1) Search for occurrences of 'propagate-partner-link-updates' under apps/web"
rg -n --hidden -uu 'propagate-partner-link-updates' apps/web -S || true

echo
echo "2) Locate containing directories and print route.ts if present"
files=$(rg -n --hidden -uu 'propagate-partner-link-updates' apps/web -S --files-with-matches || true)
if [ -z "$files" ]; then
echo "No files found containing 'propagate-partner-link-updates'"
else
dirname -z $files 2>/dev/null | tr '\0' '\n' | sort -u | while read -r d; do
echo
echo "DIR: $d"
if [ -f "$d/route.ts" ]; then
echo "---- $d/route.ts (first 400 lines) ----"
sed -n '1,400p' "$d/route.ts" || true
else
echo "No route.ts in $d; listing contents:"
ls -la "$d" || true
fi
done
fi

echo
echo "3) Show handler/exports in any matching route.ts files (look for POST/GET/handler)"
rg -nC3 --hidden -uu '\b(export (const|async function)\s+(POST|GET|handler)|export default function|export default)' apps/web -S || true

echo
echo "4) Search for 'groupId' usages under apps/web and show context"
rg -nC3 '\bgroupId\b' apps/web -S || true

echo
echo "5) Show the delete-discount action around the snippet you referenced"
if [ -f "apps/web/lib/actions/partners/delete-discount.ts" ]; then
sed -n '1,240p' apps/web/lib/actions/partners/delete-discount.ts || true
else
echo "File not found: apps/web/lib/actions/partners/delete-discount.ts"
fi

echo
echo "6) Search for references to the old endpoint 'invalidate-for-discounts'"
rg -n --hidden -uu 'invalidate-for-discounts' -S || true


</blockquote></details>
<details>
<summary>apps/web/lib/api/links/utils/transform-link.ts (2)</summary><blockquote>

`17-18`: **LGTM: ExpandedLink now carries partnerGroupId**

Type extension is non-breaking; transformLink preserves it via spread. Ensure downstream Tinybird mapping uses this field.

---

`17-18`: **Confirmed: partnerGroupId → partner_group_id mapping exists**

apps/web/lib/tinybird/record-link.ts defines partner_group_id (z.string().nullish()) and populates it with link.partnerGroupId ?? "" (see lines ~29 and ~72).

</blockquote></details>
<details>
<summary>packages/tinybird/pipes/v2_utms.pipe (1)</summary><blockquote>

`15-16`: **Good: added partner_group_id filter.**

Conditional group filter matches existing style and placement.

</blockquote></details>
<details>
<summary>packages/tinybird/pipes/v2_triggers.pipe (1)</summary><blockquote>

`21-22`: **Good: added partner_group_id filter.**

</blockquote></details>
<details>
<summary>packages/tinybird/pipes/v2_continents.pipe (1)</summary><blockquote>

`21-22`: **Good: added partner_group_id filter.**

</blockquote></details>
<details>
<summary>packages/tinybird/pipes/v2_count.pipe (1)</summary><blockquote>

`21-22`: **Good: added partner_group_id filter.**

</blockquote></details>
<details>
<summary>apps/web/app/(ee)/api/events/route.ts (1)</summary><blockquote>

`20-31`: **Good: switch to const destructuring.**

Improves immutability; no behavior change.

</blockquote></details>
<details>
<summary>apps/web/app/(ee)/api/cron/folders/delete/route.ts (1)</summary><blockquote>

`34-41`: **Include programEnrollment for partnerGroupId — good; verify no include clash.**

If includeTags already sets programEnrollment, the spread could conflict. Confirm includeTags content or merge thoughtfully.

Would you check includeTags to ensure it doesn’t already define programEnrollment?

</blockquote></details>
<details>
<summary>apps/web/lib/planetscale/get-partner-discount.ts (1)</summary><blockquote>

`60-77`: **Good: partner now includes groupId.**

</blockquote></details>
<details>
<summary>apps/web/lib/analytics/constants.ts (1)</summary><blockquote>

`175-176`: **Good: groupId added to VALID_ANALYTICS_FILTERS.**

Matches TB changes and schema updates in this PR.

</blockquote></details>
<details>
<summary>apps/web/lib/api/partners/create-and-enroll-partner.ts (1)</summary><blockquote>

`178-181`: **Passing partnerGroupId into Tinybird payload looks correct.**

group.id is already validated via getGroupOrThrow; adding partnerGroupId here should unblock group-level analytics. No further action.

</blockquote></details>
<details>
<summary>packages/tinybird/datasources/dub_regular_links_metadata_latest.datasource (1)</summary><blockquote>

`16-17`: **Schema extension to include partner_group_id looks good.**

Ensure ingestion always sends a string (empty string when unknown) to avoid nullable drift in TB.

</blockquote></details>
<details>
<summary>apps/web/lib/api/links/complete-ab-tests.ts (1)</summary><blockquote>

`70-75`: **Including programEnrollment.groupId in the update response is appropriate.**

Keeps the source-of-truth for partnerGroupId close to the write.

</blockquote></details>
<details>
<summary>packages/tinybird/pipes/v2_cities.pipe (1)</summary><blockquote>

`21-21`: **groupId filter added — LGTM.**

Matches the pattern in other pipes and will scope workspace_links correctly.

</blockquote></details>
<details>
<summary>packages/tinybird/datasources/dub_links_metadata.datasource (1)</summary><blockquote>

`15-17`: **Added partner_group_id to ingestion schema — LGTM.**

json path and column ordering mirror latest/regular sources. Confirm the transformer sets a default "" when absent.

</blockquote></details>
<details>
<summary>packages/tinybird/datasources/dub_links_metadata_latest.datasource (1)</summary><blockquote>

`16-17`: **Latest datasource includes partner_group_id — good alignment.**

No engine/partition changes needed. Confirm downstream pipes reference this column (they do via workspace_links).

</blockquote></details>
<details>
<summary>apps/web/app/(ee)/api/groups/[groupIdOrSlug]/partners/route.ts (1)</summary><blockquote>

`52-52`: **Switched QStash target to propagate-partner-link-updates — good routing.**

Please verify the new cron route is deployed across envs and is idempotent for repeated partnerIds.

</blockquote></details>
<details>
<summary>apps/web/app/api/links/[linkId]/transfer/route.ts (1)</summary><blockquote>

`96-99`: **Clearing partnerGroupId on transfer: good; verify partner/program invariants on cross-workspace moves.**

Setting partnerGroupId: null when transferring avoids stale group analytics in TB. Please double-check whether programId/partnerId should also be cleared or remapped when projectId changes, to prevent mismatched analytics and FK drift across workspaces.


Would you like a quick script to scan for links whose projectId no longer matches the owning program’s workspace?

</blockquote></details>
<details>
<summary>apps/web/app/(ee)/api/groups/[groupIdOrSlug]/route.ts (1)</summary><blockquote>

`215-237`: **Endpoint switch and settlement handling look good; confirm downstream job behavior.**

Good move to /api/cron/links/propagate-partner-link-updates with Promise.allSettled. Please confirm that the cron route propagates the defaultGroup reassignment to Tinybird (including partner_group_id) for affected links.

</blockquote></details>
<details>
<summary>apps/web/lib/swr/use-groups.ts (1)</summary><blockquote>

`4-4`: **Type widening to GroupExtendedProps: looks fine; validate callsites.**

Compile-time change only; ensure consumers expecting GroupProps still type-check.




Also applies to: 10-10

</blockquote></details>
<details>
<summary>apps/web/lib/actions/partners/create-discount.ts (1)</summary><blockquote>

`69-75`: **Propagation endpoint update: LGTM.**

Publishing to propagate-partner-link-updates with groupId keeps links in sync post-discount creation; audit log async handling is solid.



Also applies to: 77-91

</blockquote></details>
<details>
<summary>apps/web/lib/zod/schemas/analytics.ts (1)</summary><blockquote>

`117-120`: **groupId filter added: LGTM; confirm pipes support and doc interplay with partnerId.**

Schema changes look consistent. Please ensure TB pipes filter by partner_group_id when groupId is present and clarify in docs whether partnerId and groupId can be combined and how precedence works.




Also applies to: 319-319

</blockquote></details>
<details>
<summary>apps/web/lib/tinybird/record-link.ts (1)</summary><blockquote>

`29-32`: **partner_group_id added to schema and transform: LGTM; verify DS/pipes alignment.**

Nullish-to-empty-string normalization matches existing fields. Confirm datasources and pipes now include partner_group_id and the backfill is complete before enabling groupId analytics by default.




Also applies to: 72-72

</blockquote></details>
<details>
<summary>apps/web/app/api/tags/[id]/route.ts (1)</summary><blockquote>

`79-83`: **Prisma include looks good and minimal.**

Selecting only `programEnrollment.groupId` keeps the payload lean while enabling the downstream enrichment.

</blockquote></details>
<details>
<summary>apps/web/lib/api/links/create-link.ts (1)</summary><blockquote>

`146-151`: **LGTM on caching enrichment.**

Augmenting cache with `partner`/`discount` is fine and keeps reads fast.

</blockquote></details>
<details>
<summary>apps/web/app/(ee)/api/cron/links/propagate-partner-link-updates/route.ts (1)</summary><blockquote>

`52-58`: **LGTM on fetching links with tags.**

Including `...includeTags` aligns with Tinybird payload needs. No issues.

</blockquote></details>
<details>
<summary>apps/web/ui/analytics/use-analytics-filters.tsx (4)</summary><blockquote>

`137-143`: **Good: Groups fetched only on program pages.**

Scoped data fetching minimizes overhead elsewhere.

---

`201-226`: **LGTM: Adds groupId to active filters with proper special-casing.**

Destructuring `groupId` out of `searchParamsObj` prevents double-inclusion in the dynamic pass.

---

`877-898`: **Dependencies include groups/loadingGroups, preventing stale options.**

Memo invalidation looks correct.

---

`528-535`: **No change required — group.partners exists**

GroupSchemaExtended (apps/web/lib/zod/schemas/groups.ts) defines partners and partnersCount, so using group.partners is valid; the example "partnerCount" is incorrect.

</blockquote></details>
<details>
<summary>apps/web/lib/api/links/update-link.ts (2)</summary><blockquote>

`171-176`: **Good consolidation of partner/discount fetch.**

Up-front retrieval avoids duplicate queries later and keeps the post-update flow lean.

---

`185-189`: **Tinybird payload enrichment looks right.**

Passing `partnerGroupId` derived from the fetched partner aligns with the new analytics filter.

</blockquote></details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (5)
packages/tinybird/pipes/v2_devices.pipe (1)

21-21: Devices pipeline: groupId support — LGTM

Addresses the earlier gating gap; PREWHERE now includes groupId across all nodes.

Also applies to: 52-54, 100-102, 143-145, 184-185

packages/tinybird/pipes/v2_regions.pipe (1)

21-21: Cast groupId to String for consistency with ID columns.

Mirror the casting used elsewhere (e.g., linkId/customerId) to avoid quoting issues.

-        {% if defined(groupId) %} AND partner_group_id = {{ groupId }} {% end %}
+        {% if defined(groupId) %} AND partner_group_id = {{ String(groupId) }} {% end %}
packages/tinybird/pipes/v2_utms.pipe (1)

15-15: Cast groupId to String to match column type and escape correctly.

-        {% if defined(groupId) %} AND partner_group_id = {{ groupId }} {% end %}
+        {% if defined(groupId) %} AND partner_group_id = {{ String(groupId) }} {% end %}
packages/tinybird/pipes/v2_timeseries.pipe (1)

134-137: PREWHERE gate now considers groupId — resolved

This addresses the earlier gating gap for clicks when only groupId is provided.

packages/tinybird/pipes/v2_referers.pipe (1)

51-54: Clicks PREWHERE gate includes groupId — LGTM

This resolves the previously flagged issue for referers_clicks.

🧹 Nitpick comments (7)
packages/tinybird/pipes/v2_top_tags.pipe (1)

39-41: Include groupId in PREWHERE gates for performance parity

Without adding groupId, queries scoped only by groupId won’t prefilter events and may scan far more rows than needed.

Apply in each block:

-{% if defined(workspaceId) or defined(partnerId) or defined(programId) %}
+{% if defined(workspaceId) or defined(partnerId) or defined(programId) or defined(groupId) %}
     PREWHERE link_id in (SELECT link_id from workspace_links_with_tags)
 {% end %}

Also applies to: 99-101, 163-165

packages/tinybird/pipes/v2_count.pipe (2)

51-54: Adding groupId to PREWHERE gating: LGTM; optional: combine with customerId when both are provided.

Current elif skips workspace_links gating when customerId is also set. You can conjunct both predicates to narrow scans further.

Suggested refactor:

-        {% if defined(customerId) %}
-            PREWHERE click_id IN (
+        {% if defined(customerId) %}
+            PREWHERE click_id IN (
                 SELECT DISTINCT click_id
                 FROM dub_lead_events_mv
                 WHERE customer_id = {{ String(customerId) }}
-            )
+            ){% if not defined(linkId) and (defined(workspaceId) or defined(partnerId) or defined(programId) or defined(groupId)) %} 
+            AND link_id in (SELECT link_id from workspace_links){% endif %}
-        {% elif not defined(linkId) and (
+        {% elif not defined(linkId) and (
             defined(workspaceId) or defined(partnerId) or defined(programId) or defined(groupId)
         ) %} PREWHERE link_id in (SELECT link_id from workspace_links)

21-21: Cast groupId to String (partner_group_id is String).

  • partner_group_id is declared as String in:
    packages/tinybird/datasources/dub_links_metadata.datasource
    packages/tinybird/datasources/dub_links_metadata_latest.datasource
    packages/tinybird/datasources/dub_regular_links_metadata_latest.datasource
  • Apply diff:
-        {% if defined(groupId) %} AND partner_group_id = {{ groupId }} {% end %}
+        {% if defined(groupId) %} AND partner_group_id = {{ String(groupId) }} {% end %}
  • No ORDER BY / PRIMARY KEY matches found in those datasource files; consider adding an ORDER BY/PRIMARY KEY that includes partner_group_id to improve filtering performance.
packages/tinybird/pipes/v2_utms.pipe (1)

60-62: Group PREWHERE gating in utms_clicks: LGTM; optional: also conjunct when customerId is set.

Same optional combination as count_clicks to reduce scanned rows when both scopes apply.

packages/tinybird/pipes/v2_timeseries.pipe (2)

128-137: Clicks with customerId bypasses group scoping — confirm intent

When customerId is defined, the query uses click_id IN (leads...) and skips link_id prefiltering, so groupId/workspaceId/partnerId/programId are ignored. If you want customerId + groupId to intersect, add the workspace_links constraint inside the subquery.

Proposed patch:

-        {% if defined(customerId) %}
-            PREWHERE click_id IN (
-                SELECT DISTINCT click_id
-                FROM dub_lead_events_mv
-                WHERE customer_id = {{ String(customerId) }}
-            )
+        {% if defined(customerId) %}
+            PREWHERE click_id IN (
+                SELECT DISTINCT click_id
+                FROM dub_lead_events_mv
+                WHERE customer_id = {{ String(customerId) }}
+                {% if defined(groupId) or defined(workspaceId) or defined(partnerId) or defined(programId) %}
+                    AND link_id in (SELECT link_id from workspace_links)
+                {% end %}
+            )

254-256: Nit: missing description

Node description shows “undefined”. Optional: add a brief description for sales node for clarity in TB UI.

packages/tinybird/pipes/v2_referers.pipe (1)

45-54: Clicks with customerId bypasses group scoping — mirror fix here if desired

Same behavior as timeseries: customerId path ignores groupId/workspaceId/partnerId/programId. If intersection is desired, replicate the subquery filter.

Proposed patch:

-        {% if defined(customerId) %}
-            PREWHERE click_id IN (
-                SELECT DISTINCT click_id
-                FROM dub_lead_events_mv
-                WHERE customer_id = {{ String(customerId) }}
-            )
+        {% if defined(customerId) %}
+            PREWHERE click_id IN (
+                SELECT DISTINCT click_id
+                FROM dub_lead_events_mv
+                WHERE customer_id = {{ String(customerId) }}
+                {% if defined(groupId) or defined(workspaceId) or defined(partnerId) or defined(programId) %}
+                    AND link_id in (SELECT link_id from workspace_links)
+                {% end %}
+            )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bd94fec and 1f7644f.

📒 Files selected for processing (17)
  • packages/tinybird/pipes/v2_browsers.pipe (5 hunks)
  • packages/tinybird/pipes/v2_cities.pipe (5 hunks)
  • packages/tinybird/pipes/v2_continents.pipe (5 hunks)
  • packages/tinybird/pipes/v2_count.pipe (5 hunks)
  • packages/tinybird/pipes/v2_countries.pipe (5 hunks)
  • packages/tinybird/pipes/v2_devices.pipe (5 hunks)
  • packages/tinybird/pipes/v2_events.pipe (5 hunks)
  • packages/tinybird/pipes/v2_os.pipe (5 hunks)
  • packages/tinybird/pipes/v2_referer_urls.pipe (5 hunks)
  • packages/tinybird/pipes/v2_referers.pipe (5 hunks)
  • packages/tinybird/pipes/v2_regions.pipe (5 hunks)
  • packages/tinybird/pipes/v2_timeseries.pipe (5 hunks)
  • packages/tinybird/pipes/v2_top_links.pipe (5 hunks)
  • packages/tinybird/pipes/v2_top_tags.pipe (2 hunks)
  • packages/tinybird/pipes/v2_top_urls.pipe (5 hunks)
  • packages/tinybird/pipes/v2_triggers.pipe (5 hunks)
  • packages/tinybird/pipes/v2_utms.pipe (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/tinybird/pipes/v2_referer_urls.pipe
  • packages/tinybird/pipes/v2_os.pipe
  • packages/tinybird/pipes/v2_browsers.pipe
  • packages/tinybird/pipes/v2_cities.pipe
⏰ 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). (2)
  • GitHub Check: Vade Review
  • GitHub Check: build
🔇 Additional comments (27)
packages/tinybird/pipes/v2_top_tags.pipe (1)

18-18: Group filter on workspace_links_with_tags — LGTM

Conditionally filtering by partner_group_id when groupId is defined matches the pattern used elsewhere.

packages/tinybird/pipes/v2_events.pipe (1)

21-21: GroupId propagation and PREWHERE gating — LGTM

Consistent with other v2 pipes; groupId now flows through workspace_links and event predicates.

Also applies to: 56-58, 107-109, 184-186, 263-265

packages/tinybird/pipes/v2_continents.pipe (1)

21-21: Add groupId filtering and gating — LGTM

Workspace filter and PREWHERE gates now honor groupId across clicks/leads/sales paths.

Also applies to: 52-54, 109-111, 161-163, 210-212

packages/tinybird/pipes/v2_top_links.pipe (2)

21-21: GroupId support across nodes — LGTM

Optional partner_group_id filter and PREWHERE gates updated consistently.

Also applies to: 52-54, 101-103, 145-147, 187-188


21-21: Run corrected repo-wide check for missing groupId guards

Previous verification run failed with "/bin/bash: line 6: !: command not found" and a PCRE2 compile error. Re-run the corrected script below and paste the output.

#!/usr/bin/env bash
set -euo pipefail

echo "Pipes with workspace_links but missing partner_group_id = {{ groupId }}:"
rg -l "NODE workspace_links" packages/tinybird/pipes -g "**/*.pipe" | while IFS= read -r f; do
  if rg -q 'partner_group_id\s*=\s*\{\{\s*groupId\s*\}\}' "$f"; then
    continue
  fi
  echo "  - $f"
done

echo
echo "PREWHERE gates missing groupId in OR-chain:"
mapfile -t files < <(rg -l 'PREWHERE link_id in \(SELECT link_id from workspace_links' packages/tinybird/pipes -g "**/*.pipe" || true)
for f in "${files[@]:-}"; do
  contexts=$(rg -n -C5 'PREWHERE link_id in \(SELECT link_id from workspace_links' "$f" || true)
  [ -z "$contexts" ] && continue
  if echo "$contexts" | rg -q 'defined\(workspaceId\)' && echo "$contexts" | rg -q 'defined\(partnerId\)' && echo "$contexts" | rg -q 'defined\(programId\)'; then
    if echo "$contexts" | rg -q 'defined\(groupId\)'; then
      :
    else
      echo "  - $f"
    fi
  fi
done
packages/tinybird/pipes/v2_countries.pipe (1)

21-21: GroupId filters and PREWHERE gates — LGTM

Matches the established pattern; behavior unchanged when groupId is absent.

Also applies to: 51-53, 97-99, 149-151, 198-200

packages/tinybird/pipes/v2_top_urls.pipe (1)

21-21: GroupId-aware filtering for top URLs — LGTM

Workspace constraint and all PREWHERE gates now consider groupId.

Also applies to: 43-45, 81-83, 125-127, 167-169

packages/tinybird/pipes/v2_triggers.pipe (1)

21-21: Trigger analytics: groupId support — LGTM

Consistent gating and workspace filter additions.

Also applies to: 52-54, 100-102, 146-148, 187-189

packages/tinybird/pipes/v2_count.pipe (3)

98-100: Group PREWHERE gating for leads: LGTM.


137-139: Group PREWHERE gating for sales: LGTM.


193-195: Group PREWHERE gating for sales_with_type: LGTM.

packages/tinybird/pipes/v2_regions.pipe (4)

48-50: Group PREWHERE gating in regions_clicks: LGTM.


93-95: Group PREWHERE gating in regions_leads: LGTM.


140-142: Group PREWHERE gating in regions_sales: LGTM.


182-184: Group PREWHERE gating in regions_sales_with_type: LGTM.

packages/tinybird/pipes/v2_utms.pipe (3)

125-127: Group PREWHERE gating in utms_leads: LGTM.


192-194: Group PREWHERE gating in utms_sales: LGTM.


260-262: Group PREWHERE gating in utms_sales_with_type: LGTM.

packages/tinybird/pipes/v2_timeseries.pipe (5)

94-94: Add groupId filter in workspace_links — LGTM

Consistent with existing partnerId/programId filters; unblocks group-scoped analytics.


208-210: Leads PREWHERE gate includes groupId — LGTM

Parity with clicks/sales maintained.


274-276: Sales PREWHERE gate includes groupId — LGTM

Keeps filtering behavior consistent across metrics.


326-329: Sales-with-type PREWHERE gate includes groupId — LGTM

Inner CTE respects group scoping as expected.


80-110: Repo-wide sanity check for PREWHERE gates — no missing groupId found

Scanned packages/tinybird/pipes: no PREWHERE gates found that combine not defined(linkId) with workspaceId/partnerId/programId while lacking defined(groupId).

packages/tinybird/pipes/v2_referers.pipe (4)

21-21: Add groupId filter in workspace_links — LGTM

Matches the pattern used elsewhere; enables partner_group_id scoping.


101-103: Leads PREWHERE gate includes groupId — LGTM


145-147: Sales PREWHERE gate includes groupId — LGTM


186-189: Sales-with-type PREWHERE gate includes groupId — LGTM

@devkiran devkiran requested a review from steven-tey September 11, 2025 12:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载