-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Rename tbNew → tbOld #2954
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rename tbNew → tbOld #2954
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughRepoints many Tinybird ingest builders and async side-effects from the "new" client/key to the "old" client/key, removes the Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Client as Caller
participant App as recordClick/Backfill
participant TBPrimary as Tinybird Primary (tbNew)
participant TBOld as Tinybird OLD (tbOld)
Client->>App: submit click/backfill payload
App->>App: enrich payload (workspace_id, domain, key)
App->>TBPrimary: POST payload (primary ingest)
alt async side-effect
App->>TBOld: waitUntil(POST payload to OLD API with OLD key) %% async
end
App-->>Client: return primary response
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/lib/tinybird/record-sale.ts (1)
29-34: Rename export to reflect the tbOld usage.Similar to
recordSaleNewTB, this export namerecordSaleWithTimestampNewTBstill contains "New" but usestbOld. Rename it torecordSaleWithTimestampOldTBfor consistency.Apply this diff to rename the export consistently:
-export const recordSaleWithTimestampNewTB = tbOld.buildIngestEndpoint({ +export const recordSaleWithTimestampOldTB = tbOld.buildIngestEndpoint({ datasource: "dub_sale_events", event: saleEventSchemaTB.extend({ timestamp: z.string(), }), });Then update the usage on line 37:
export const recordSaleWithTimestamp = async (payload: any) => { - waitUntil(recordSaleWithTimestampNewTB(payload)); + waitUntil(recordSaleWithTimestampOldTB(payload)); return await recordSaleWithTimestampTB(payload); };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
apps/web/app/(ee)/api/cron/framer/backfill-leads-batch/route.ts(1 hunks)apps/web/lib/api/audit-logs/record-audit-log.ts(3 hunks)apps/web/lib/tinybird/client.ts(1 hunks)apps/web/lib/tinybird/log-conversion-events.ts(2 hunks)apps/web/lib/tinybird/log-import-error.ts(1 hunks)apps/web/lib/tinybird/record-click-zod.ts(2 hunks)apps/web/lib/tinybird/record-click.ts(1 hunks)apps/web/lib/tinybird/record-lead.ts(2 hunks)apps/web/lib/tinybird/record-link.ts(3 hunks)apps/web/lib/tinybird/record-sale.ts(2 hunks)apps/web/lib/tinybird/record-webhook-event.ts(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-02T22:46:22.739Z
Learnt from: steven-tey
PR: dubinc/dub#2924
File: apps/web/lib/api/conversions/track-lead.ts:7-7
Timestamp: 2025-10-02T22:46:22.739Z
Learning: In apps/web/lib/api/conversions/track-lead.ts, lead events are cached in Redis for 5 minutes (keys: `leadCache:${customer.id}` and `leadCache:${customer.id}:${stringifiedEventName}`) to provide immediate data availability while Tinybird ingestion happens asynchronously. This caching pattern allows for async-only recording without breaking "wait" mode semantics.
Applied to files:
apps/web/lib/tinybird/record-lead.ts
🧬 Code graph analysis (8)
apps/web/lib/api/audit-logs/record-audit-log.ts (1)
apps/web/lib/tinybird/client.ts (1)
tbOld(9-12)
apps/web/lib/tinybird/log-import-error.ts (2)
apps/web/lib/tinybird/client.ts (2)
tb(3-6)tbOld(9-12)apps/web/lib/zod/schemas/import-error-log.ts (1)
importErrorLogSchema(3-23)
apps/web/lib/tinybird/record-lead.ts (2)
apps/web/lib/tinybird/client.ts (2)
tb(3-6)tbOld(9-12)apps/web/lib/zod/schemas/leads.ts (1)
leadEventSchemaTB(98-107)
apps/web/lib/tinybird/record-click-zod.ts (1)
apps/web/lib/tinybird/client.ts (1)
tbOld(9-12)
apps/web/lib/tinybird/record-webhook-event.ts (2)
apps/web/lib/tinybird/client.ts (2)
tb(3-6)tbOld(9-12)apps/web/lib/zod/schemas/webhooks.ts (1)
webhookEventSchemaTB(37-50)
apps/web/lib/tinybird/record-sale.ts (2)
apps/web/lib/tinybird/client.ts (2)
tb(3-6)tbOld(9-12)apps/web/lib/zod/schemas/sales.ts (1)
saleEventSchemaTB(120-137)
apps/web/lib/tinybird/log-conversion-events.ts (1)
apps/web/lib/tinybird/client.ts (1)
tbOld(9-12)
apps/web/lib/tinybird/record-link.ts (1)
apps/web/lib/tinybird/client.ts (1)
tbOld(9-12)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (14)
apps/web/lib/api/audit-logs/record-audit-log.ts (1)
2-2: LGTM! Consistent migration to tbOld.The changes correctly update the import and rename the endpoint from
recordAuditLogTBNewtorecordAuditLogTBOld. The dual ingestion pattern is preserved withtbOldinwaitUntiland the primarytbingest remaining unchanged.Also applies to: 59-59, 83-87
apps/web/app/(ee)/api/cron/framer/backfill-leads-batch/route.ts (1)
304-314: LGTM! Consistent dual ingestion pattern.The second fetch now correctly uses
TINYBIRD_API_KEY_OLDfor authorization, maintaining the dual ingestion pattern during the Tinybird migration. Both requests send identical click data to ensure data consistency across both Tinybird instances.apps/web/lib/tinybird/client.ts (1)
9-10: LGTM! Core client renamed from tbNew to tbOld.The export name and token source have been updated consistently. This is a breaking change for any modules importing
tbNew, but all affected files in this PR have been updated accordingly.apps/web/lib/tinybird/record-webhook-event.ts (1)
3-3: LGTM! Consistent endpoint migration.The import, endpoint definition, and
waitUntilcall have been updated consistently to usetbOld. The publicrecordWebhookEventfunction maintains its interface while implementing the dual ingestion pattern.Also applies to: 11-14, 17-17
apps/web/lib/tinybird/record-link.ts (1)
6-6: LGTM! Complete migration for both payload paths.All changes consistently use
tbOld:
- Import updated (line 6)
- Endpoint definition renamed (lines 54-58)
- Both single and array payload paths in
recordLinkupdated (lines 87, 95)The dual ingestion pattern is correctly implemented for both code paths.
Also applies to: 54-58, 86-96
apps/web/lib/tinybird/record-lead.ts (1)
4-4: LGTM! Both lead recording endpoints migrated.The migration correctly covers both variants:
recordLead→recordLeadTBOld(lines 12-15, 18)recordLeadWithTimestamp→recordLeadWithTimestampTBOld(lines 29-34, 37)All
waitUntilcalls updated and public APIs preserved.Also applies to: 12-15, 18-18, 29-34, 37-37
apps/web/lib/tinybird/log-import-error.ts (1)
3-3: LGTM! Final migration file completed consistently.The import error logging follows the same migration pattern as all other Tinybird integrations:
- Import updated (line 3)
- Endpoint renamed to
logImportErrorTBOld(lines 11-14)waitUntilcall updated (line 17)This completes the consistent migration across all Tinybird modules in the PR.
Also applies to: 11-14, 17-17
apps/web/lib/tinybird/record-click-zod.ts (3)
3-3: LGTM! Import updated correctly.The import now includes
tbOldto support the migration from the "new" to "old" Tinybird client.
51-51: LGTM! waitUntil call updated correctly.The
waitUntilcall now usesrecordClickZodTBOldwhich is consistent with the export rename.
44-48: No remaining references to the old export. All imports have been updated to recordClickZodTBOld.apps/web/lib/tinybird/record-sale.ts (1)
4-4: LGTM! Import updated correctly.The import now uses
tbOldinstead oftbNewto align with the migration.apps/web/lib/tinybird/log-conversion-events.ts (3)
3-3: LGTM! Import updated correctly.The import now includes
tbOldto support the migration from the "new" to "old" Tinybird client.
26-26: LGTM! waitUntil call updated correctly.The
waitUntilcall now useslogConversionEventTBOldwhich is consistent with the export rename.
20-23: No remaining imports of the old export. The rename is confined to this file; no further updates required.
| method: "POST", | ||
| headers: { | ||
| Authorization: `Bearer ${process.env.TINYBIRD_API_KEY_NEW}`, | ||
| Authorization: `Bearer ${process.env.TINYBIRD_API_KEY_OLD}`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
Verify that TINYBIRD_API_KEY_OLD is set in the environment.
The authorization header now uses TINYBIRD_API_KEY_OLD. Ensure this environment variable is properly configured in all deployment environments (development, staging, production) to prevent ingestion failures.
Run the following script to check if the environment variable is referenced and documented:
🏁 Script executed:
#!/bin/bash
# Search for TINYBIRD_API_KEY_OLD usage and configuration
rg -n 'TINYBIRD_API_KEY_OLD' -g '!node_modules' -C2Length of output: 1284
🏁 Script executed:
#!/bin/bash
# Search for TINYBIRD_API_KEY_OLD in environment examples and documentation
rg -n 'TINYBIRD_API_KEY_OLD' -g '.env*' -g '*.md'Length of output: 0
Add TINYBIRD_API_KEY_OLD to environment documentation
TINYBIRD_API_KEY_OLD is referenced in
- apps/web/lib/tinybird/record-click.ts (line 216)
- apps/web/app/(ee)/api/cron/framer/backfill-leads-batch/route.ts (line 309)
- apps/web/lib/tinybird/client.ts (line 10)
but isn’t listed in .env.example or any README. Document this variable and ensure it’s configured in all deployment environments.
| export const recordSaleNewTB = tbOld.buildIngestEndpoint({ | ||
| datasource: "dub_sale_events", | ||
| event: saleEventSchemaTB, | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rename export to reflect the tbOld usage.
The export name recordSaleNewTB still contains "New" but now uses tbOld. This is inconsistent with other files in this PR where exports were renamed from *TBNew to *TBOld (e.g., recordClickZodTBNew → recordClickZodTBOld, logConversionEventTBNew → logConversionEventTBOld). The name should reflect that it uses the "old" client.
Apply this diff to rename the export consistently:
-export const recordSaleNewTB = tbOld.buildIngestEndpoint({
+export const recordSaleOldTB = tbOld.buildIngestEndpoint({
datasource: "dub_sale_events",
event: saleEventSchemaTB,
});Then update the usage on line 18:
export const recordSale = async (payload: any) => {
- waitUntil(recordSaleNewTB(payload));
+ waitUntil(recordSaleOldTB(payload));
return await recordSaleTB(payload);
};📝 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.
| export const recordSaleNewTB = tbOld.buildIngestEndpoint({ | |
| datasource: "dub_sale_events", | |
| event: saleEventSchemaTB, | |
| }); | |
| export const recordSaleOldTB = tbOld.buildIngestEndpoint({ | |
| datasource: "dub_sale_events", | |
| event: saleEventSchemaTB, | |
| }); | |
| export const recordSale = async (payload: any) => { | |
| waitUntil(recordSaleOldTB(payload)); | |
| return await recordSaleTB(payload); | |
| }; |
🤖 Prompt for AI Agents
In apps/web/lib/tinybird/record-sale.ts around lines 12 to 15, the exported name
recordSaleNewTB is inconsistent because this module uses tbOld; rename the
export to recordSaleTBOld and update any references (specifically the usage on
line 18) to the new name to match the TBOld naming convention used elsewhere in
this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
apps/web/scripts/framer/3-backfill-tb-events.ts (1)
33-36: createReadStream passed a string encoding — will throw.
fs.createReadStream doesn’t accept a bare "utf-8" string. Use an options object or omit encoding.Apply this diff:
- fs.createReadStream( - "framer_remaining_events_final_final_2024.csv", - "utf-8", - ), + fs.createReadStream( + "framer_remaining_events_final_final_2024.csv", + { encoding: "utf8" }, + ),apps/web/scripts/perplexity/backfill-leads.ts (2)
25-26: createReadStream encoding arg is invalid.
Passing "utf-8" as a string will error. Use an options object.Apply this diff:
- Papa.parse(fs.createReadStream("perplexity_leads.csv", "utf-8"), { + Papa.parse(fs.createReadStream("perplexity_leads.csv", { encoding: "utf8" }), {
148-156: Potential runtime crashes from nested UA fields.
ua.device/browser/engine/os can be undefined; property access will throw before the fallback applies.Apply this diff for safe access:
- device: capitalize(ua.device.type) || "Desktop", - device_vendor: ua.device.vendor || "Unknown", - device_model: ua.device.model || "Unknown", - browser: ua.browser.name || "Unknown", - browser_version: ua.browser.version || "Unknown", - engine: ua.engine.name || "Unknown", - engine_version: ua.engine.version || "Unknown", - os: ua.os.name || "Unknown", - os_version: ua.os.version || "Unknown", + device: ua.device?.type ? capitalize(ua.device.type) : "Desktop", + device_vendor: ua.device?.vendor || "Unknown", + device_model: ua.device?.model || "Unknown", + browser: ua.browser?.name || "Unknown", + browser_version: ua.browser?.version || "Unknown", + engine: ua.engine?.name || "Unknown", + engine_version: ua.engine?.version || "Unknown", + os: ua.os?.name || "Unknown", + os_version: ua.os?.version || "Unknown",apps/web/scripts/fillout/backfill-customer-leads.ts (1)
22-23: createReadStream encoding arg is invalid.
"utf-8" string form is not supported here.Apply this diff:
- Papa.parse(fs.createReadStream("fillout-customers-updated.csv", "utf-8"), { + Papa.parse(fs.createReadStream("fillout-customers-updated.csv", { encoding: "utf8" }), {
🧹 Nitpick comments (1)
apps/web/scripts/fillout/backfill-customer-leads.ts (1)
128-145: Avoid async void forEach; awaits are dropped.
Using forEach with async doesn’t await network calls; batches may still be in flight.Apply this diff to await batches sequentially (or convert to Promise.all):
- Object.entries(clicksToCreateTB).forEach(async ([year, clicks]) => { - const clicksBatch = clicks as typeof clicksToCreate; - console.log(`backfilling ${clicksBatch.length} clicks for ${year}`); - const clickRes = await fetch( + for (const [year, clicks] of Object.entries(clicksToCreateTB)) { + const clicksBatch = clicks as typeof clicksToCreate; + console.log(`backfilling ${clicksBatch.length} clicks for ${year}`); + const clickRes = await fetch( `${process.env.TINYBIRD_API_URL}/v0/events?name=dub_click_events&wait=true`, { method: "POST", headers: { Authorization: `Bearer ${process.env.TINYBIRD_API_KEY}`, "Content-Type": "application/x-ndjson", }, body: (clicksBatch as typeof clicksToCreate) .map((d) => JSON.stringify(d)) .join("\n"), }, - ).then((res) => res.json()); - console.log("backfilled clicks", JSON.stringify(clickRes, null, 2)); - }); + ).then((res) => res.json()); + console.log("backfilled clicks", JSON.stringify(clickRes, null, 2)); + }Apply the same pattern to the leads loop at Lines 197-202 if ordering/awaiting matters.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
apps/web/app/(ee)/api/cron/framer/backfill-leads-batch/route.ts(2 hunks)apps/web/lib/middleware/link.ts(7 hunks)apps/web/lib/tinybird/record-click-zod.ts(2 hunks)apps/web/lib/tinybird/record-click.ts(4 hunks)apps/web/lib/zod/schemas/clicks.ts(1 hunks)apps/web/scripts/fillout/backfill-customer-leads.ts(1 hunks)apps/web/scripts/framer/3-backfill-tb-events.ts(1 hunks)apps/web/scripts/perplexity/backfill-leads.ts(1 hunks)apps/web/scripts/seed-data.ts(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- apps/web/lib/middleware/link.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/web/lib/tinybird/record-click.ts
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/lib/tinybird/record-click-zod.ts (1)
apps/web/lib/tinybird/client.ts (1)
tbOld(9-12)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (9)
apps/web/scripts/framer/3-backfill-tb-events.ts (2)
155-168: Added workspace_id/domain/key in clickData — looks good.
Fields align with schema-driven validation via clickEventSchemaTB.parse; no issues spotted.
231-246: Confirm which TB key to use here.
PR moves flows to tbOld elsewhere; this script still uses TINYBIRD_API_KEY. Ensure that’s intentional or mirror the dual-post/OLD key pattern.apps/web/scripts/perplexity/backfill-leads.ts (2)
131-164: Added workspace_id/domain/key in clickData — looks good.
Consistent with other backfill scripts and downstream schema.
169-179: Verify TB key selection.
This uses TINYBIRD_API_KEY; confirm whether tbOld should be used or dual-posting is desired.apps/web/scripts/fillout/backfill-customer-leads.ts (2)
76-84: Added workspace_id/domain/key in clickData — looks good.
Matches schema and other scripts.
132-143: Verify TB key selection.
Confirm whether this should post with tbOld credentials or dual-post during migration.apps/web/scripts/seed-data.ts (1)
99-103: Seed click payload enrichment — looks good.
workspace_id/domain/key additions are consistent with the updated schema.apps/web/app/(ee)/api/cron/framer/backfill-leads-batch/route.ts (2)
211-219: Added workspace_id/domain/key in clickData — looks good.
Schema-validated via clickEventSchemaTB; consistent with ingestion changes.
312-315: OLD TB key header — verify env and intent.
Ensure TINYBIRD_API_KEY_OLD is set in all environments and that duplicate posting (new + old) is intended and cost‑acceptable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/lib/analytics/get-analytics.ts (1)
208-234: Filter out null values when partner is not found.The
top_partnersmapping returns null when a partner is not found (line 225), but unliketop_links(line 206), it doesn't filter out these null values before returning.Apply this diff to ensure consistency with
top_links:- return response.data.map((item) => { + return response.data.map((item) => { const partner = partners.find((p) => p.id === item.groupByField); if (!partner) return null; return analyticsResponse[groupBy].parse({ ...item, partnerId: item.groupByField, partner: { ...partner, payoutsEnabledAt: partner.payoutsEnabledAt?.toISOString() || null, }, }); - }); + }).filter((d) => d !== null);
🧹 Nitpick comments (2)
apps/web/lib/analytics/get-analytics.ts (2)
116-120: Consider simplifying the pipe selection logic.The nested ternary with
["top_partners"].includes(groupBy)works correctly but could be more readable.Apply this diff for improved clarity:
- pipe: ["count", "timeseries"].includes(groupBy) - ? `v3_${groupBy}` - : ["top_partners"].includes(groupBy) - ? "v3_group_by_link_metadata" - : "v3_group_by", + pipe: ["count", "timeseries"].includes(groupBy) + ? `v3_${groupBy}` + : groupBy === "top_partners" + ? "v3_group_by_link_metadata" + : "v3_group_by",
161-206: Consider logging when links are not found.The
top_linksmapping correctly usesgroupByFieldto match analytics data with link records. When a link is not found (line 181-183), it returns null and filters it out (line 206).Consider adding logging for missing links to aid debugging:
const link = links.find((l) => l.id === item.groupByField); if (!link) { + console.warn(`Link not found for groupByField: ${item.groupByField}`); return null; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
apps/web/lib/analytics/constants.ts(1 hunks)apps/web/lib/analytics/get-analytics.ts(7 hunks)apps/web/lib/zod/schemas/analytics.ts(2 hunks)apps/web/ui/analytics/analytics-provider.tsx(0 hunks)apps/web/ui/analytics/locations.tsx(1 hunks)apps/web/ui/analytics/use-analytics-query.tsx(1 hunks)apps/web/ui/analytics/utils.ts(0 hunks)
💤 Files with no reviewable changes (2)
- apps/web/ui/analytics/utils.ts
- apps/web/ui/analytics/analytics-provider.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
apps/web/lib/analytics/get-analytics.ts (3)
apps/web/lib/zod/schemas/analytics.ts (1)
analyticsFilterTB(256-311)apps/web/lib/zod/schemas/analytics-response.ts (1)
analyticsResponse(29-492)apps/web/lib/analytics/constants.ts (1)
SINGULAR_ANALYTICS_ENDPOINTS(139-159)
apps/web/ui/analytics/locations.tsx (1)
packages/utils/src/constants/regions.ts (1)
REGIONS(1-3485)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (9)
apps/web/ui/analytics/locations.tsx (1)
70-75: Good improvement for handling "-Unknown" regions.The new logic appropriately falls back to the country name when a region ends with "-Unknown", which provides a better user experience than displaying "Unknown".
However, verify that
COUNTRIES[d.country]reliably returns a value for all country codes in your analytics data. If a country code is missing from the lookup, the label will be undefined.Optionally, consider adding a fallback to the raw
d.countryvalue for safety:- (d.region.endsWith("-Unknown") - ? COUNTRIES[d.country] - : d.region.split("-")[1]) + (d.region.endsWith("-Unknown") + ? COUNTRIES[d.country] || d.country + : d.region.split("-")[1])apps/web/lib/zod/schemas/analytics.ts (2)
286-286: LGTM!Adding
groupByto the picked fields fromanalyticsQuerySchemacorrectly aligns with the v3 Tinybird pipe refactoring wheregroupByis now passed as a parameter (line 138 in get-analytics.ts).
259-259: No action required — workspaceId schema change is correct.The workspaceId field at line 259 is properly defined as
z.string().optional()without transformation. This is the correct behavior because:
workspace.idflows directly from the database through analytics routes to Tinybird pipes without requiring prefixingprefixWorkspaceId()is only applied in API response contexts (for external-facing objects), not in data pipeline layers- All Tinybird pipes successfully receive and use
workspaceIdas-is for filteringThe codebase is consistent: raw workspace IDs are sent to Tinybird, and transformations happen only when building API responses.
apps/web/lib/analytics/constants.ts (1)
155-158: LGTM!The new singular endpoint mappings (
top_links → "link",top_urls → "url",timeseries → "start") correctly support the unified data mapping in get-analytics.ts (line 243), whereSINGULAR_ANALYTICS_ENDPOINTS[groupBy]is used to transformgroupByFieldinto the appropriate response field.apps/web/lib/analytics/get-analytics.ts (4)
122-131: LGTM!The unified data shape with
groupByFieldelegantly standardizes the response structure across all analytics groupBy types, reducing complexity while maintaining support for hierarchical data (cities, regions) through optional fields.
138-138: LGTM!Adding
groupByto the pipe parameters correctly enables the v3 unified pipes to receive the grouping dimension, which is essential for the new pipeline architecture.
238-245: LGTM!The default return mapping elegantly handles all remaining groupBy types by leveraging
SINGULAR_ANALYTICS_ENDPOINTSto dynamically mapgroupByFieldto the appropriate response field (e.g.,countries → country,devices → device), ensuring type safety through schema parsing.
3-3: No runtime compatibility issues found—the prismaEdge to prisma switch is appropriate.The verification confirms that
getAnalyticsis called exclusively from Node.js contexts (API routes, cron jobs, and scripts). No edge runtime routes invokegetAnalyticsor any function that uses it. The cron route that indirectly usesgetAnalytics(viacompleteABTests) does not declare edge runtime and already uses the standardprismaclient. The import change is safe and correct.apps/web/ui/analytics/use-analytics-query.tsx (1)
61-61: Breaking API change successfully implemented and all callers updated.The removal of the
defaultRootparameter fromuseAnalyticsQueryis complete. Verification confirms:
- Function signature no longer includes
defaultRoot- Both callers (analytics-provider.tsx and page-client.tsx) have been updated and pass only the remaining parameters
- No remaining references to
defaultRootexist in the codebaserootis now correctly derived purely from URL search parameters viasearchParams.get("root")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/lib/analytics/get-events.ts(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/web/lib/analytics/get-analytics.ts (2)
162-177: Add workspace filtering to prevent potential data leakage.The query fetches links by ID without verifying they belong to the requesting workspace. While Tinybird should only return IDs for the correct workspace, adding an explicit
workspaceIdfilter provides defense-in-depth and prevents data leakage if there's a bug in the Tinybird query or misconfiguration.Apply this diff to add workspace filtering:
const links = await prisma.link.findMany({ where: { - id: { - in: response.data.map((item) => item.groupByField), + AND: [ + { + id: { + in: response.data.map((item) => item.groupByField), + }, + }, + { + projectId: workspaceId, + }, - }, + ], }, select: {Note: Verify that the Link model uses
projectIdor adjust to the correct workspace relation field.
209-222: Add workspace filtering to prevent potential data leakage.Similar to the
top_linksquery, this lacks workspace filtering. Add an explicit filter to ensure only partners belonging to the requesting workspace are returned.Apply this diff to add workspace filtering:
const partners = await prisma.partner.findMany({ where: { - id: { - in: response.data.map((item) => item.groupByField), + AND: [ + { + id: { + in: response.data.map((item) => item.groupByField), + }, + }, + { + workspaceId: workspaceId, + }, - }, + ], }, select: {Note: Verify that the Partner model uses
workspaceIdor adjust to the correct workspace relation field.
🧹 Nitpick comments (2)
apps/web/lib/analytics/get-analytics.ts (2)
152-160: Add safeguard for empty response data.The code assumes
response.data[0]exists, but if the Tinybird query returns an empty array, this will throw an error. Consider adding a check or defaulting to zero counts.Apply this diff to add a safeguard:
if (groupBy === "count") { - const { groupByField, ...rest } = response.data[0]; + const { groupByField, ...rest } = response.data[0] || { + groupByField: "", + clicks: 0, + leads: 0, + sales: 0, + saleAmount: 0, + }; // Return the count value for deprecated count endpoints if (isDeprecatedClicksEndpoint) { return rest[event];
191-204: Consider adding a comment explaining the deprecated 'link' field.Line 193 sets
link: link.idfor backward compatibility with the deprecated field (as indicated in the schema). A brief inline comment would help future maintainers understand why this field is duplicated.return analyticsResponse[groupBy].parse({ id: link.id, + // Deprecated: 'link' field maintained for backward compatibility link: link.id, domain: link.domain,
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/lib/analytics/get-analytics.ts(7 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/lib/analytics/get-analytics.ts (3)
apps/web/lib/zod/schemas/analytics.ts (1)
analyticsFilterTB(256-311)apps/web/lib/zod/schemas/analytics-response.ts (1)
analyticsResponse(29-492)apps/web/lib/analytics/constants.ts (1)
SINGULAR_ANALYTICS_ENDPOINTS(139-159)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (2)
apps/web/lib/analytics/get-analytics.ts (2)
238-246: The generic mapping logic is clean and maintainable.The unified approach using
groupByFieldandSINGULAR_ANALYTICS_ENDPOINTSsimplifies the code compared to having separate branches for each analytics type.Optionally, consider adding error handling for parse failures to provide better debugging information:
return response.data.map((item) => { try { return schema.parse({ ...item, [SINGULAR_ANALYTICS_ENDPOINTS[groupBy]]: item.groupByField, }); } catch (error) { console.error(`Failed to parse analytics item for groupBy=${groupBy}:`, item, error); throw error; } });
3-3: ****The concern about edge runtime implications is unfounded.
getAnalyticsis called exclusively in standard Node.js server contexts (API routes, cron jobs, and batch scripts)—never in edge runtime environments. TheprismaEdge→prismamigration is correct; the codebase already establishes this pattern:prismaEdgefor edge contexts,prismafor server-side operations. This change is appropriate and improves resource management for these server contexts.Likely an incorrect or invalid review comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apps/web/app/(ee)/api/cron/partner-program-summary/route.ts(3 hunks)apps/web/app/(ee)/api/embed/referrals/analytics/route.ts(1 hunks)apps/web/app/api/analytics/demo/route.ts(0 hunks)apps/web/lib/analytics/types.ts(0 hunks)
💤 Files with no reviewable changes (2)
- apps/web/app/api/analytics/demo/route.ts
- apps/web/lib/analytics/types.ts
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/app/(ee)/api/embed/referrals/analytics/route.ts (2)
apps/web/lib/embed/referrals/auth.ts (1)
withReferralsEmbedToken(33-135)apps/web/lib/analytics/get-analytics.ts (1)
getAnalytics(19-247)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (3)
apps/web/app/(ee)/api/cron/partner-program-summary/route.ts (2)
72-72: No action required. The code changes are correct.The original review comment's concern about workspaceId nullability is unfounded. The Program schema defines workspaceId as a required String field (not nullable), and the getAnalytics function correctly accepts workspaceId as a parameter. The addition of workspaceId to the Prisma select at line 72 and its usage in the getAnalytics calls at lines 104 and 114 are both correct and require no changes.
99-119: Changes verified as correct — workspaceId is optional.The
workspaceIdparameter inAnalyticsFiltersis optional (workspaceId?: string), so it's appropriate that not all callers include it. The changes in lines 101-118 properly addworkspaceId: program.workspaceIdto both analytics calls, enriching them with workspace context while other calls continue to work with alternative filtering parameters.apps/web/app/(ee)/api/embed/referrals/analytics/route.ts (1)
6-6: Handler signature updated to use destructured context.The change from
programEnrollmentto{ links, program }aligns with using the destructured context fromwithReferralsEmbedToken. This is cleaner and provides direct access to the needed properties.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (3)
apps/web/lib/zod/schemas/usage.ts (1)
5-6: Tighten input validation for folderId/domain.To avoid garbage/whitespace inputs and reduce downstream TB errors, trim and minimally validate these params.
Apply:
- folderId: z.string().optional(), - domain: z.string().optional(), + folderId: z.string().trim().min(1).optional(), + // allow basic hostnames; prevents spaces/schemes. Adjust if IDNs needed. + domain: z + .string() + .trim() + .toLowerCase() + .regex(/^[^\s/]+(\.[^\s/]+)+$/, "invalid domain") + .optional(),apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/plan-usage.tsx (1)
288-301: Improve tab semantics for accessibility.Consider role="tablist" on the container and role="tab" on each button (aria-selected already present). Optionally wire aria-controls.
Example:
- <div className="grid gap-4 p-6 pb-0 ..."> + <div role="tablist" className="grid gap-4 p-6 pb-0 ..."> ... - <button + <button role="tab" aria-selected={activeResource === id}Also applies to: 297-299
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/usage-chart.tsx (1)
223-236: Optional: align gradient color with series color.Bars use violet classes, gradient is blue. Consider switching to violet for visual consistency.
Example:
- <LinearGradient id="usage-bar-gradient"> - <stop stopColor="#2563eb" ... /> - <stop stopColor="#3b82f6" ... /> - </LinearGradient> + <LinearGradient id="usage-bar-gradient"> + <stop stopColor="#7c3aed" stopOpacity={1} offset="20%" /> + <stop stopColor="#8b5cf6" stopOpacity={0.9} offset="100%" /> + </LinearGradient>
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
apps/web/app/(ee)/api/embed/referrals/analytics/route.ts(1 hunks)apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-table.tsx(1 hunks)apps/web/app/api/workspaces/[idOrSlug]/billing/usage/route.ts(2 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/plan-usage.tsx(7 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/usage-chart.tsx(3 hunks)apps/web/lib/analytics/get-analytics.ts(7 hunks)apps/web/lib/swr/use-usage.ts(2 hunks)apps/web/lib/zod/schemas/usage.ts(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 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/embed/referrals/analytics/route.ts
📚 Learning: 2025-08-16T11:14:00.667Z
Learnt from: devkiran
PR: dubinc/dub#2754
File: apps/web/lib/partnerstack/schemas.ts:47-52
Timestamp: 2025-08-16T11:14:00.667Z
Learning: The PartnerStack API always includes the `group` field in partner responses, so the schema should use `.nullable()` rather than `.nullish()` since the field is never omitted/undefined.
Applied to files:
apps/web/lib/analytics/get-analytics.ts
🧬 Code graph analysis (6)
apps/web/app/(ee)/api/embed/referrals/analytics/route.ts (2)
apps/web/lib/embed/referrals/auth.ts (1)
withReferralsEmbedToken(33-135)apps/web/lib/analytics/get-analytics.ts (1)
getAnalytics(19-249)
apps/web/app/api/workspaces/[idOrSlug]/billing/usage/route.ts (2)
apps/web/lib/zod/schemas/usage.ts (1)
usageQuerySchema(3-10)apps/web/lib/tinybird/client.ts (1)
tb(3-6)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/usage-chart.tsx (4)
apps/web/lib/swr/use-usage.ts (1)
useUsage(8-58)apps/web/lib/swr/use-domains.ts (1)
useDomains(15-107)apps/web/lib/types.ts (1)
Folder(513-513)packages/ui/src/charts/bars.tsx (1)
Bars(10-108)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/plan-usage.tsx (1)
apps/web/lib/swr/use-usage.ts (1)
useUsage(8-58)
apps/web/lib/swr/use-usage.ts (1)
apps/web/lib/swr/use-workspace.ts (1)
useWorkspace(6-46)
apps/web/lib/analytics/get-analytics.ts (3)
apps/web/lib/zod/schemas/analytics.ts (1)
analyticsFilterTB(256-311)apps/web/lib/zod/schemas/analytics-response.ts (1)
analyticsResponse(29-492)apps/web/lib/analytics/constants.ts (1)
SINGULAR_ANALYTICS_ENDPOINTS(139-159)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (11)
apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-table.tsx (1)
188-207: LGTM! Clean DOM simplification.The refactoring correctly simplifies the filter UI structure by removing unnecessary wrapper divs. The
AnimatedSizeContainerwill properly animate height transitions whenFilter.Listappears/disappears based on theactiveFilters.lengthcondition.apps/web/app/(ee)/api/embed/referrals/analytics/route.ts (2)
6-9: Guard clause correctly addresses the previous review concern.The empty links check prevents the unintended Tinybird fallback behavior that was flagged in the previous review. The implementation is correct: since the middleware always provides
linksas an array from the database query (even if empty), checkinglinks.length === 0is sufficient. The previously suggested!linkscheck would be redundant given the middleware's guarantees.
15-16: Clean architectural refactor to link-based analytics.The changes correctly shift from program-enrollment based filtering to link-based analytics:
- Using
linkIdsderived from the partner's actual links provides more precise analytics scoping- The
dataAvailableFromcalculation usingprogram.startedAt ?? program.createdAtis more semantically correct for determining the analytics data windowThe implementation integrates properly with
getAnalytics, which handleslinkIdsvia SQL aggregation when the array is non-empty.apps/web/lib/analytics/get-analytics.ts (6)
152-160: LGTM!The count endpoint handling correctly destructures to remove
groupByFieldand returns the appropriate format for both deprecated (single value) and regular (object) count endpoints.
162-207: LGTM!The top_links processing correctly:
- Fetches links by IDs from
groupByField- Enriches analytics data with link metadata
- Decodes case-sensitive keys
- Filters out null entries when links aren't found
The implementation properly handles the unified data schema and constructs valid analytics responses.
209-237: LGTM! Null filtering is already in place.The top_partners processing correctly filters out null entries at line 237, preventing invalid responses when partners aren't found. The past review comment concern about missing null filtering has been addressed.
241-248: LGTM! Clean generic handling for remaining groupBy types.The implementation elegantly handles all other analytics endpoints by:
- Using the appropriate schema from
analyticsResponse[groupBy]- Mapping
groupByFieldto the singular field name viaSINGULAR_ANALYTICS_ENDPOINTS- Spreading metrics (clicks, leads, sales, saleAmount) from the unified pipe response
This approach is maintainable and consistent with the top_links/top_partners patterns.
3-3: Looking at the search results, I need to verify the Prisma version and which routes actually use edge runtime. Let me check the codebase configuration.Based on the verification, Prisma Client at version 5.9.1+ no longer throws incompatibility errors at instantiation, but at query time, with version 5.12.0+ recommended for edge runtime. However, the core issue remains: when deploying a Vercel Edge Function that uses Prisma ORM, you need to use edge-compatible drivers and their respective driver adapters.
The script output confirmed that
getAnalyticsis imported and used directly fromapps/web/app/api/analytics/route.ts, which hasexport const runtime = "edge"configured. This means the function is called from an edge runtime context.Verify that getAnalytics and dependent Prisma setup are configured for edge runtime compatibility.
The migration from
prismaEdgetoprismarequires ensuring:
- Driver adapter configuration: Prisma must be configured with an edge-compatible driver adapter (e.g.,
@prisma/adapter-neon,@prisma/adapter-pg) in the Prisma schema generator or client initialization.- Affected routes: All routes calling
getAnalyticsthat haveexport const runtime = "edge"(including/api/analytics,/api/analytics/dashboard,/api/analytics/export, and potentially others) will execute in edge runtime and require proper adapter setup.- Fallback verification: If driver adapters are not configured, these routes will fail at query time with "PrismaClient is not configured to run in Edge Runtime" errors.
Confirm the Prisma schema has
previewFeatures = ["driverAdapters"]enabled and the appropriate driver adapter is instantiated when Prisma Client is initialized, or revert this import change to useprismaEdge.
122-131: Verify that all v3 pipes in Tinybird match the unified schema format.The code now expects a single unified response schema from all v3 pipes (
v3_count,v3_timeseries,v3_group_by,v3_group_by_link_metadata) withgroupByField, numeric metrics, and optional geo fields. Since these are Tinybird service pipes (not defined locally), you must manually confirm in your Tinybird console that:
- All v3 pipes are deployed and operational
- Each pipe returns the exact schema:
groupByField(string),clicks,leads,sales,saleAmount(numbers with .default(0) handling), and optionalcountry/regionfields- The
groupByFieldvalues correctly map to the expected identifiers:
v3_count: link ID or "total"v3_timeseries: timestamp or periodv3_group_by: category value (cities, devices, etc.)v3_group_by_link_metadata: partner ID- Optional
countryandregionfields are present only whengroupByis "cities" or "regions"If any v3 pipe returns a different structure or field types, Zod parsing will fail at runtime.
apps/web/lib/swr/use-usage.ts (1)
8-22: Breaking API concern verified as resolved.All
useUsage()call sites in the codebase already use the correct zero-arg signature:
usage-chart.tsx:40destructures{ usage, loading, activeResource }plan-usage.tsx:86destructures{ usage: usageTimeseries }plan-usage.tsx:273destructures{ activeResource }No legacy calls with a
resourceargument parameter were found. All destructuring patterns match the new return object structure.apps/web/app/api/workspaces/[idOrSlug]/billing/usage/route.ts (1)
12-19: Verify Tinybird v3_usage pipe contract on TB side.The codebase implementation is structurally correct:
- Schema properly defines all parameters:
resource,folderId(optional),domain(optional),start,end,timezone- Route correctly extends schema with
workspaceId- Pipe call passes all parameters correctly, with
folderIdanddomainconditionally includedHowever, the actual Tinybird pipe definition for "v3_usage" is not in this repository (it's configured in Tinybird's system). You must confirm on the Tinybird side that:
- The v3_usage pipe accepts these exact parameter names
folderIdanddomaincan both be provided simultaneously without conflicts
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/plan-usage.tsx
Outdated
Show resolved
Hide resolved
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/usage-chart.tsx
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/plan-usage.tsx (1)
88-94: Fix Links tab showing wrong data when activeResource ≠ "links".
currentTabFilteredUsagesums whatever is inusageTimeseries, butusageTimeseriescontains data for the currently active resource (either "events" or "links" depending onactiveResource). When the Events tab is active,usageTimeseriescontains events data, yet line 108 unconditionally assignscurrentTabFilteredUsageto the Links tab, causing it to display the summed events count instead of links count.Guard the assignment by checking
activeResource:const { usage: usageTimeseries } = useUsage(); const usageTabs = useMemo(() => { - const currentTabFilteredUsage = usageTimeseries?.reduce((acc, curr) => { - acc += curr.value; - return acc; - }, 0); + const currentTabFilteredUsage = + activeResource === "links" && usageTimeseries + ? usageTimeseries.reduce((acc, curr) => acc + curr.value, 0) + : undefined; const tabs = [ { id: "events", icon: CursorRays, title: "Events tracked", usage: usage, limit: usageLimit, }, { id: "links", icon: Hyperlink, title: "Links created", - usage: currentTabFilteredUsage, + usage: currentTabFilteredUsage ?? linksUsage, limit: linksLimit, }, ];Also add
activeResourceto the dependencies array (line 121-129).Also applies to: 108-108
🧹 Nitpick comments (1)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/plan-usage.tsx (1)
88-88: Consider callinguseUsageonce to avoid duplication.The
useUsagehook is called twice within the same component (lines 88 and 277). While SWR deduplicates identical requests, calling the hook once and destructuring both values improves readability.Apply this diff:
- const { usage: usageTimeseries } = useUsage(); + const { usage: usageTimeseries, activeResource } = useUsage();Then remove the duplicate call at line 277:
const { searchParams, queryParams } = useRouterStuff(); const { slug, totalLinks } = useWorkspace(); - const { activeResource } = useUsage();Also applies to: 277-277
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/plan-usage.tsx(9 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/plan-usage.tsx (3)
apps/web/lib/swr/use-workspace-users.ts (1)
useWorkspaceUsers(13-30)apps/web/lib/swr/use-partners-count.ts (1)
usePartnersCount(9-53)apps/web/lib/swr/use-usage.ts (1)
useUsage(8-58)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (1)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/plan-usage.tsx (1)
230-236: LGTM!The new Partner Groups usage category is properly implemented with correct data wiring from
useGroupsCount, appropriate icon choice, and consistent styling.
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/plan-usage.tsx
Show resolved
Hide resolved
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/plan-usage.tsx (1)
188-191: Conflicting Tailwind classes: md:grid-cols-3 vs md:grid-cols-2.Both are applied; later md:grid-cols-2 overrides earlier md:grid-cols-3. Pick one intent.
- className={cn( - "grid grid-cols-1 gap-[1px] overflow-hidden rounded-b-lg bg-neutral-200 md:grid-cols-3", - "md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-4", - )} + className={cn( + "grid grid-cols-1 gap-[1px] overflow-hidden rounded-b-lg bg-neutral-200", + "md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4", + )}
♻️ Duplicate comments (7)
apps/web/lib/analytics/get-events.ts (1)
69-69: Critical issue previously identified remains unresolved.The migration to
v3_eventswas already flagged in a previous review as a critical blocker because the corresponding Tinybird pipe definition (packages/tinybird/endpoints/v3_events.pipe) does not exist in the repository. This change will cause runtime failures whengetEvents()is called.You must either:
- Commit the missing
v3_events.pipedefinition topackages/tinybird/endpoints/, or- Confirm the endpoint already exists in your deployed Tinybird workspace and document this in the PR
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/plan-usage.tsx (2)
88-109: Fix Links usage: only use filtered timeseries when tab is "links" and filters are active.Currently always summing usageTimeseries for the Links card causes wrong numbers when tab ≠ "links" or no filters.
- const { usage: usageTimeseries } = useUsage(); + const { usage: usageTimeseries, activeResource } = useUsage(); + const { searchParamsObj } = useRouterStuff(); @@ - const usageTabs = useMemo(() => { - const currentTabFilteredUsage = usageTimeseries?.reduce((acc, curr) => { - acc += curr.value; - return acc; - }, 0); + const usageTabs = useMemo(() => { + const hasLinkFilters = + Boolean(searchParamsObj.folderId) || Boolean(searchParamsObj.domain); + const currentTabFilteredUsage = + activeResource === "links" && hasLinkFilters && usageTimeseries + ? usageTimeseries.reduce((acc, curr) => acc + curr.value, 0) + : undefined; @@ { id: "links", icon: Hyperlink, title: "Links created", - usage: currentTabFilteredUsage, + usage: + activeResource === "links" && hasLinkFilters && currentTabFilteredUsage !== undefined + ? currentTabFilteredUsage + : linksUsage, limit: linksLimit, },
121-129: Correct dependency array: remove stale deps; add activeResource and searchParamsObj.Prevents stale computations when tab or filters change.
- }, [ - plan, - usage, - usageLimit, - linksUsage, - linksLimit, - totalLinks, - usageTimeseries, - ]); + }, [ + usage, + usageLimit, + linksUsage, + linksLimit, + totalLinks, + usageTimeseries, + activeResource, + searchParamsObj, + ]);apps/web/lib/swr/use-usage.ts (1)
1-3: Add "use client" — this hook imports next/navigation.Without it, Next.js may treat this module as a Server Component and error when importing useSearchParams.
+ "use client"; + import { fetcher, getFirstAndLastDay } from "@dub/utils"; import { useSearchParams } from "next/navigation"; import { useMemo } from "react";apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/usage-chart.tsx (1)
1-2: Add "use client" — this component uses client-only hooks.Required for useEffect/useMemo and router/hooks.
+ "use client"; + import useDomains from "@/lib/swr/use-domains"; import useFolders from "@/lib/swr/use-folders";apps/web/lib/tinybird/record-sale.ts (1)
12-15: Rename exports to match the TBOld naming convention.The export names
recordSaleNewTBandrecordSaleWithTimestampNewTBstill contain "New" but now usetbOld. This is inconsistent with other files in this PR where exports were renamed from*TBNewto*TBOld(e.g.,logImportErrorTBOldin log-import-error.ts,recordClickZodTBOldin record-click-zod.ts).Rename both exports to reflect the tbOld usage:
-export const recordSaleNewTB = tbOld.buildIngestEndpoint({ +export const recordSaleTBOld = tbOld.buildIngestEndpoint({ datasource: "dub_sale_events", event: saleEventSchemaTB, });-export const recordSaleWithTimestampNewTB = tbOld.buildIngestEndpoint({ +export const recordSaleWithTimestampTBOld = tbOld.buildIngestEndpoint({ datasource: "dub_sale_events", event: saleEventSchemaTB.extend({ timestamp: z.string(), }), });Then update the waitUntil calls on lines 18 and 37 accordingly.
Also applies to: 29-34
apps/web/lib/analytics/get-analytics.ts (1)
116-131: Critical: v3 Tinybird pipes still missing from repository.This concern was previously flagged and remains unresolved. The code references v3 pipes (
v3_count,v3_timeseries,v3_group_by,v3_group_by_link_metadata) that don't exist inpackages/tinybird/. Unless these pipes are deployed directly to your Tinybird workspace and accessible at runtime, this will fail in production.
🧹 Nitpick comments (10)
apps/web/lib/api/audit-logs/record-audit-log.ts (1)
82-87: Consider clarifying the TODO comment or naming convention.The function rename from
recordAuditLogTBNewtorecordAuditLogTBOldis consistent with the PR objectives. However, the naming "tbOld" combined with the TODO comment "Remove after Tinybird migration" might be confusing for future maintainers. Consider either:
- Updating the comment to clarify what "migration" means (e.g., "TODO: Remove after rolling back to the old Tinybird configuration")
- Or using more descriptive names like
tbPrimary/tbSecondaryortbV2/tbV1to better indicate the relationship between the two clientsapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/plan-usage.tsx (1)
277-302: Avoid re-calling useUsage in each UsageTabCard; pass activeResource as a prop.Reduces duplicate SWR subscribers and keeps a single source of truth.
- const { activeResource } = useUsage(); + // receive activeResource from parent via propsAlso update call site:
- {usageTabs.map((tab) => ( - <UsageTabCard key={tab.id} {...tab} /> - ))} + {usageTabs.map((tab) => ( + <UsageTabCard key={tab.id} {...tab} activeResource={activeResource} /> + ))}And component signature:
-function UsageTabCard({ +function UsageTabCard({ id, icon: Icon, title, usage: usageProp, limit: limitProp, unit, requiresUpgrade, + activeResource, }: { id: string; icon: Icon; title: string; usage?: number; limit?: number; unit?: string; requiresUpgrade?: boolean; + activeResource: string; }) {apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/usage-chart.tsx (1)
192-197: Minor: unify chart series color with gradient.Series uses text-violet-500 while bars use blue gradient. Pick one palette for consistency.
- colorClassName: "text-violet-500", + colorClassName: "text-blue-600", @@ - <LinearGradient id="usage-bar-gradient"> - <stop stopColor="#2563eb" stopOpacity={1} offset="20%" /> - <stop stopColor="#3b82f6" stopOpacity={0.9} offset="100%" /> - </LinearGradient> + <LinearGradient id="usage-bar-gradient"> + <stop stopColor="#7c3aed" stopOpacity={1} offset="20%" /> + <stop stopColor="#8b5cf6" stopOpacity={0.9} offset="100%" /> + </LinearGradient>Also applies to: 223-229
apps/web/lib/zod/schemas/usage.ts (1)
5-6: Tighten validation for folderId/domain (avoid empty-string gotchas).Make them non-empty to prevent silently dropping values downstream and reduce input noise. Consider domain normalization later.
Apply:
- folderId: z.string().optional(), - domain: z.string().optional(), + folderId: z.string().min(1, "folderId cannot be empty").optional(), + domain: z.string().min(1, "domain cannot be empty").optional(),Optional: also
trim()and lowercasedomainvia a transform if that matches how it’s used elsewhere.apps/web/ui/links/link-display.tsx (1)
40-47: isMegaWorkspace gating looks correct; consider UX hint and SSR stability.Parity with prior gating is preserved. Optionally show a brief hint/tooltip when features are hidden for mega workspaces, and confirm
isMegaWorkspaceis stable on first render to avoid hydration flicker.Also applies to: 82-119
apps/web/ui/links/links-toolbar.tsx (1)
51-51: Minor: dedupe gating via a local constant.Readability nit only.
- const { slug, plan, isMegaWorkspace } = useWorkspace(); + const { slug, plan, isMegaWorkspace } = useWorkspace(); + const showCounts = !isMegaWorkspace; ... - showTotalCount={!isMegaWorkspace} + showTotalCount={showCounts} ... - {!isMegaWorkspace && ( + {showCounts && (Also applies to: 227-230
apps/web/lib/tinybird/record-lead.ts (1)
17-20: Use zod-inferred payload types instead of any.Tighten types for compile-time safety and better DX.
-export const recordLead = async (payload: any) => { +export const recordLead = async ( + payload: z.infer<typeof leadEventSchemaTB>, +) => { waitUntil(recordLeadTBOld(payload)); return await recordLeadTB(payload); }; @@ -export const recordLeadWithTimestamp = async (payload: any) => { +export const recordLeadWithTimestamp = async ( + payload: z.infer<typeof leadEventSchemaTB> & { timestamp: string }, +) => { waitUntil(recordLeadWithTimestampTBOld(payload)); return await recordLeadWithTimestampTB(payload); };Also applies to: 36-39
apps/web/lib/tinybird/record-webhook-event.ts (1)
1-4: Type the payload using the webhook zod schema.Keeps ingestion calls aligned with schema.
-import { waitUntil } from "@vercel/functions"; +import { waitUntil } from "@vercel/functions"; +import { z } from "zod"; @@ -export const recordWebhookEvent = async (payload: any) => { +export const recordWebhookEvent = async ( + payload: z.infer<typeof webhookEventSchemaTB.omit({ timestamp: true })>, +) => { waitUntil(recordWebhookEventTBOld(payload)); return await recordWebhookEventTB(payload); };Also applies to: 16-19
apps/web/app/api/workspaces/[idOrSlug]/billing/usage/route.ts (1)
27-29: Safer param spreading (don’t rely on truthiness).If you ever receive empty strings, current code drops them. Prefer explicit undefined checks (or keep current once schema enforces non-empty).
- ...(folderId && { folderId }), - ...(domain && { domain }), + ...(folderId !== undefined ? { folderId } : {}), + ...(domain !== undefined ? { domain } : {}),apps/web/lib/tinybird/client.ts (1)
9-12: Support optionalTINYBIRD_API_URL_OLDoverride for flexibility during migration.Currently both
tbandtbOldshare the same base URL. If old/new clusters diverge, useTINYBIRD_API_URL_OLDwith fallback toTINYBIRD_API_URL:export const tbOld = new Tinybird({ token: process.env.TINYBIRD_API_KEY_OLD as string, - baseUrl: process.env.TINYBIRD_API_URL as string, + baseUrl: + (process.env.TINYBIRD_API_URL_OLD as string | undefined) ?? + (process.env.TINYBIRD_API_URL as string), });This provides flexibility if the old cluster moves to a different endpoint during migration.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (39)
apps/web/app/(ee)/api/cron/framer/backfill-leads-batch/route.ts(1 hunks)apps/web/app/(ee)/api/cron/partner-program-summary/route.ts(3 hunks)apps/web/app/(ee)/api/embed/referrals/analytics/route.ts(1 hunks)apps/web/app/(ee)/api/events/route.ts(0 hunks)apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-table.tsx(1 hunks)apps/web/app/api/analytics/demo/route.ts(0 hunks)apps/web/app/api/analytics/route.ts(0 hunks)apps/web/app/api/workspaces/[idOrSlug]/billing/usage/route.ts(2 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/plan-usage.tsx(9 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/usage-chart.tsx(3 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/links/page-client.tsx(2 hunks)apps/web/lib/analytics/constants.ts(1 hunks)apps/web/lib/analytics/get-analytics.ts(7 hunks)apps/web/lib/analytics/get-events.ts(1 hunks)apps/web/lib/analytics/types.ts(0 hunks)apps/web/lib/api/audit-logs/record-audit-log.ts(3 hunks)apps/web/lib/swr/use-is-mega-folder.ts(0 hunks)apps/web/lib/swr/use-links-count.ts(1 hunks)apps/web/lib/swr/use-links.ts(2 hunks)apps/web/lib/swr/use-usage.ts(2 hunks)apps/web/lib/swr/use-workspace.ts(1 hunks)apps/web/lib/tinybird/client.ts(1 hunks)apps/web/lib/tinybird/log-conversion-events.ts(2 hunks)apps/web/lib/tinybird/log-import-error.ts(1 hunks)apps/web/lib/tinybird/record-click-zod.ts(2 hunks)apps/web/lib/tinybird/record-click.ts(1 hunks)apps/web/lib/tinybird/record-lead.ts(2 hunks)apps/web/lib/tinybird/record-link.ts(3 hunks)apps/web/lib/tinybird/record-sale.ts(2 hunks)apps/web/lib/tinybird/record-webhook-event.ts(1 hunks)apps/web/lib/zod/schemas/analytics.ts(2 hunks)apps/web/lib/zod/schemas/usage.ts(1 hunks)apps/web/ui/analytics/analytics-provider.tsx(0 hunks)apps/web/ui/analytics/locations.tsx(1 hunks)apps/web/ui/analytics/use-analytics-query.tsx(1 hunks)apps/web/ui/analytics/utils.ts(0 hunks)apps/web/ui/links/link-display.tsx(4 hunks)apps/web/ui/links/links-container.tsx(5 hunks)apps/web/ui/links/links-toolbar.tsx(2 hunks)
💤 Files with no reviewable changes (7)
- apps/web/lib/swr/use-is-mega-folder.ts
- apps/web/lib/analytics/types.ts
- apps/web/app/api/analytics/demo/route.ts
- apps/web/ui/analytics/analytics-provider.tsx
- apps/web/app/api/analytics/route.ts
- apps/web/app/(ee)/api/events/route.ts
- apps/web/ui/analytics/utils.ts
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-10-15T01:05:43.266Z
Learnt from: steven-tey
PR: dubinc/dub#2958
File: apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx:432-457
Timestamp: 2025-10-15T01:05:43.266Z
Learning: In apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx, defer refactoring the custom MenuItem component (lines 432-457) to use the shared dub/ui MenuItem component to a future PR, as requested by steven-tey.
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/links/page-client.tsx
📚 Learning: 2025-10-02T22:46:22.739Z
Learnt from: steven-tey
PR: dubinc/dub#2924
File: apps/web/lib/api/conversions/track-lead.ts:7-7
Timestamp: 2025-10-02T22:46:22.739Z
Learning: In apps/web/lib/api/conversions/track-lead.ts, lead events are cached in Redis for 5 minutes (keys: `leadCache:${customer.id}` and `leadCache:${customer.id}:${stringifiedEventName}`) to provide immediate data availability while Tinybird ingestion happens asynchronously. This caching pattern allows for async-only recording without breaking "wait" mode semantics.
Applied to files:
apps/web/lib/tinybird/record-lead.ts
📚 Learning: 2025-08-25T21:03:24.285Z
Learnt from: TWilson023
PR: dubinc/dub#2736
File: apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/bounty-card.tsx:1-1
Timestamp: 2025-08-25T21:03:24.285Z
Learning: In Next.js App Router, Server Components that use hooks can work without "use client" directive if they are only imported by Client Components, as they get "promoted" to run on the client side within the Client Component boundary.
Applied to files:
apps/web/lib/swr/use-usage.ts
📚 Learning: 2025-09-18T16:33:17.719Z
Learnt from: TWilson023
PR: dubinc/dub#2858
File: apps/web/ui/partners/partner-application-tabs.tsx:1-1
Timestamp: 2025-09-18T16:33:17.719Z
Learning: When a React component in Next.js App Router uses non-serializable props (like setState functions), adding "use client" directive can cause serialization warnings. If the component is only imported by Client Components, it's better to omit the "use client" directive to avoid these warnings while still getting client-side execution through promotion.
Applied to files:
apps/web/lib/swr/use-usage.ts
📚 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/embed/referrals/analytics/route.ts
🔇 Additional comments (31)
apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-table.tsx (1)
188-207: Clean UI refactoring—looks good!The simplified structure is well-implemented:
- Filter.Select and Filter.List receive the correct props
- AnimatedSizeContainer properly wraps the conditionally rendered Filter.List for smooth height transitions
- Responsive className on Filter.Select follows best practices
Note: This UI change appears unrelated to the PR title "Rename tbNew → tbOld," which suggests Tinybird client renaming. Consider keeping UI refactors in separate PRs for clearer change tracking.
apps/web/lib/api/audit-logs/record-audit-log.ts (2)
2-2: LGTM! Import updated correctly.The addition of
tbOldimport is necessary for the dual-write pattern implemented below.
59-60: Verify the dual-write pattern is correctly oriented.The rename from
tbNewtotbOldis correctly applied. However, please verify that writing totbOld(non-blocking) before awaitingtb(blocking) aligns with your migration strategy. Typically, the newer system would be in the async path while the legacy system is awaited, but this pattern suggeststbis the source of truth andtbOldis for backward compatibility.apps/web/lib/analytics/constants.ts (1)
155-159: The core concern is unfounded—no code enumerates SINGULAR_ANALYTICS_ENDPOINTS keys.All usage across the codebase is direct property access (e.g.,
SINGULAR_ANALYTICS_ENDPOINTS[tab],SINGULAR_ANALYTICS_ENDPOINTS[groupBy]), never key enumeration. Addingtimeseriesonly affects callers that explicitly reference it; there is no risk of it leaking into UI dimension tabs/filters. The existing type safety and scoped property access already prevent unintended side effects.Likely an incorrect or invalid review comment.
apps/web/app/(ee)/api/cron/framer/backfill-leads-batch/route.ts (1)
312-312: LGTM - Consistent with Tinybird migration.The authorization header correctly uses
TINYBIRD_API_KEY_OLD, aligning with the broader migration from tbNew to tbOld across the codebase.apps/web/lib/swr/use-workspace.ts (1)
40-40: LGTM - Clean computed property addition.The
isMegaWorkspaceflag provides a centralized way to identify workspaces with more than 1 million links, replacing the previoususeIsMegaFolderpattern throughout the codebase.apps/web/lib/tinybird/record-click.ts (1)
241-241: LGTM - Authorization header correctly updated.The change to use
TINYBIRD_API_KEY_OLDis consistent with the Tinybird migration strategy across the codebase.apps/web/app/(ee)/api/cron/partner-program-summary/route.ts (2)
72-72: LGTM - workspaceId selection added.Adding
workspaceIdto the select fields is necessary to pass workspace context to the analytics calls below.
104-104: LGTM - workspaceId parameter added to analytics calls.Both
getAnalyticscalls now correctly include theworkspaceIdparameter, providing proper workspace context for the analytics queries.Also applies to: 114-114
apps/web/lib/swr/use-links.ts (2)
15-15: LGTM - Clean migration to isMegaWorkspace.Replacing the
useIsMegaFolderhook with the centralizedisMegaWorkspaceproperty fromuseWorkspacesimplifies the dependency tree.
36-41: LGTM - Terminology and logic updated consistently.The comment and condition correctly use
isMegaWorkspace, maintaining the same behavior of not showing archived links on mega workspaces.apps/web/app/app.dub.co/(dashboard)/[slug]/links/page-client.tsx (2)
166-224: LGTM - Filter block visibility updated correctly.The condition now uses
workspace.isMegaWorkspaceto hide the filter block on mega workspaces, consistent with the migration pattern.
235-238: LGTM - Search placeholder logic updated.The search placeholder text now correctly uses
workspace.isMegaWorkspaceto determine whether to show the simplified placeholder for mega workspaces.apps/web/lib/swr/use-links-count.ts (2)
19-19: LGTM - Destructuring updated for new property.Adding
isMegaWorkspacefromuseWorkspacereplaces the previoususeIsMegaFolderhook dependency.
24-24: LGTM - Data fetch correctly gated.The condition
!isMegaWorkspaceproperly prevents expensive link count queries for mega workspaces, maintaining the same performance optimization.apps/web/ui/links/links-container.tsx (2)
6-6: LGTM - Hook migration completed.Replacing
useIsMegaFolderwith theisMegaWorkspaceproperty fromuseWorkspacecentralizes the mega workspace detection logic.Also applies to: 88-88
155-157: LGTM - Pagination logic updated correctly.Using
isMegaWorkspaceto setlinksCounttoInfinitymaintains the same behavior of disabling pagination for mega workspaces.apps/web/app/api/workspaces/[idOrSlug]/billing/usage/route.ts (1)
12-19: v3_usage contract: confirm params and workspace scoping.Ensure Tinybird pipe v3_usage:
- Accepts resource, workspaceId, start, end, timezone, and optional folderId/domain.
- Enforces
workspaceIdscoping regardless of folderId/domain to prevent cross-workspace leakage.- Returns data matching
usageResponse.apps/web/lib/tinybird/log-import-error.ts (1)
3-3: LGTM! Consistent migration from tbNew to tbOld.The import, export naming, and waitUntil call have been updated consistently to use the tbOld client. The dual-endpoint pattern (maintaining both TB and TBOld) enables a safe migration.
Also applies to: 11-14, 17-17
apps/web/app/(ee)/api/embed/referrals/analytics/route.ts (1)
6-17: LGTM! Analytics parameters correctly updated.The refactoring from
programEnrollmentto{ links, program }with derivedlinkIdsanddataAvailableFromis correct. The empty links guard on line 7 (added per previous review) prevents analytics queries with empty linkIds arrays.apps/web/lib/tinybird/record-click-zod.ts (1)
3-3: LGTM! Consistent migration to tbOld.The import, export naming (
recordClickZodTBOld), and waitUntil call have been updated consistently to use the tbOld client, matching the pattern across other files in this PR.Also applies to: 47-51, 54-54
apps/web/lib/zod/schemas/analytics.ts (2)
286-286: LGTM! groupBy propagation enables proper analytics filtering.Adding
groupBy: trueto the picked fields allows the groupBy parameter to be properly propagated through the analytics filter schema, which aligns with the simplified analytics query flow.
259-259: Verify Tinybird v3 analytics datasource requirements for workspaceId formatting.The
prefixWorkspaceIdtransformation was intentionally removed from theanalyticsFilterTBschema in commit 3cafb89 (finalize v3_ endpoints). Investigation confirms workspaceId is now passed directly to Tinybird without prefixing in the analytics flow—however, other parts of the codebase (e.g., audit logs inrecord-audit-log.ts) continue to apply the prefix. Confirm that the Tinybird v3 analytics endpoints have changed requirements and no longer need the prefix, or if prefixing should be restored either in the schema or at the call site.apps/web/lib/tinybird/record-link.ts (1)
6-6: LGTM! Consistent migration across single and bulk payload paths.The import, export naming (
recordLinkTBOld), and both waitUntil calls (lines 91 and 99) have been updated consistently to use the tbOld client. The dual-endpoint pattern is maintained for both array and single payload cases.Also applies to: 58-62, 91-91, 99-99
apps/web/ui/analytics/use-analytics-query.tsx (1)
61-61: LGTM! Simplified root handling.The removal of
defaultRootlogic simplifies the analytics query flow. Therootparameter is now sourced directly from the query string without fallback resolution, which aligns with the broader analytics refactoring in this PR.apps/web/lib/tinybird/log-conversion-events.ts (1)
3-3: LGTM! Consistent migration to tbOld.The import, export naming (
logConversionEventTBOld), and waitUntil call have been updated consistently to use the tbOld client, matching the pattern across all Tinybird modules in this PR.Also applies to: 20-23, 26-26
apps/web/lib/analytics/get-analytics.ts (5)
179-207: LGTM! Null filtering correctly applied.The top_links mapping properly filters out null entries (line 207) and enriches the response with link details. The use of
groupByFieldfor matching and spreading analytics metrics is correct.
209-237: LGTM! Null filtering correctly applied.The top_partners mapping properly filters out null entries (line 237), which addresses the previous critical review comment. The use of
groupByFieldfor matching partners and enriching with partner details is correct.
3-3: Import change is correct; no runtime compatibility issue.The change from
prismaEdgetoprismais appropriate. All endpoints importingget-analytics(analytics/route.ts, export/route.ts, dashboard/route.ts, and various scripts) run on Node.js runtime with no edge runtime exports. The function is not used in edge runtime contexts; edge-only analytics code (og/analytics) usesprismaEdgedirectly and does not importget-analytics. No issues found.
241-248: SINGULAR_ANALYTICS_ENDPOINTS mapping is complete and correct.All 21 values in VALID_ANALYTICS_ENDPOINTS are accounted for: three are handled as special cases (count, top_links, top_partners), one is transformed (trigger → triggers), and the remaining 17 all have corresponding entries in SINGULAR_ANALYTICS_ENDPOINTS. The generic fallback at lines 241-248 safely handles all non-special-case groupBy values.
153-160: Verify response.data[0] exists before accessing without guards.The code at line 153 destructures
response.data[0]without checking if the array is non-empty. While count queries typically return a single row, this assumes the Tinybird v3_count pipe always returns data. If the pipe can return an empty array (due to filters, data gaps, or errors), accessing[0]will throw a runtime error. Either:
- Add a guard:
if (!response.data?.length) { /* handle empty */ }- Confirm the Tinybird contract guarantees non-empty responses for count queries
- Add error handling around the pipe call
Summary by CodeRabbit
Bug Fixes
New Features
Chores