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

Skip to content

Conversation

@devkiran
Copy link
Collaborator

@devkiran devkiran commented Jul 16, 2025

Summary by CodeRabbit

  • New Features

    • Added support for filtering analytics events by custom metadata fields using query syntax.
    • Introduced a "Metadata" column to the events table for easier viewing of event metadata.
    • Enhanced metadata preview in the UI with customizable preview limits.
  • Improvements

    • Consistently include and display event metadata in leads and sales events across analytics and customer activity views.
    • Improved parsing and handling of metadata filters in analytics queries for more precise event filtering.
    • Deprecated certain sales event fields and clarified their status in the UI.
  • Bug Fixes

    • Fixed issues with metadata parsing and display in customer activity lists.
  • Tests

    • Added tests for metadata-based event filtering and the analytics query parser to ensure correct behavior.

@vercel
Copy link
Contributor

vercel bot commented Jul 16, 2025

The latest updates on your projects. Learn more about Vercel for Git β†—οΈŽ

Name Status Preview Updated (UTC)
dub βœ… Ready (Inspect) Visit Preview Aug 4, 2025 10:45pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 16, 2025

Warning

Rate limit exceeded

@steven-tey has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 0 minutes and 41 seconds before requesting another review.

βŒ› How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between c9ee2b9 and d6ece15.

πŸ“’ Files selected for processing (5)
  • apps/web/lib/analytics/get-analytics.ts (5 hunks)
  • apps/web/lib/analytics/get-events.ts (5 hunks)
  • apps/web/lib/analytics/query-parser.ts (1 hunks)
  • apps/web/lib/zod/schemas/analytics.ts (2 hunks)
  • apps/web/tests/analytics/query-parser.test.ts (1 hunks)

Walkthrough

This update introduces support for advanced event filtering by metadata through query parsing and backend enhancements. It adds a query parser utility, updates analytics and event retrieval functions to process metadata filters, modifies schemas to include metadata fields, and extends Tinybird SQL pipes to apply dynamic metadata-based filters. UI changes display metadata in tables, and new tests validate the parsing and filtering logic.

Changes

Cohort / File(s) Change Summary
API Endpoint Comments
apps/web/app/(ee)/api/events/route.ts
Added a comment describing the GET endpoint above the export statement.
Customer Activity Event Metadata
apps/web/app/(ee)/api/customers/[id]/activity/route.ts, apps/web/lib/analytics/get-customer-events.ts, apps/web/ui/customers/customer-activity-list.ts
Removed the includeMetadata option; event metadata is now always parsed and included. UI now passes metadata directly to the viewer without JSON parsing.
Analytics Event Filtering & Query Parsing
apps/web/lib/analytics/get-analytics.ts, apps/web/lib/analytics/get-events.ts, apps/web/lib/analytics/query-parser.ts
Enhanced analytics and event retrieval to accept a query parameter, parse it into filters, and include these filters in backend requests. Introduced a new query parser module to convert query strings into structured filter objects.
Schema Updates for Metadata & Filtering
apps/web/lib/zod/schemas/analytics.ts, apps/web/lib/zod/schemas/leads.ts, apps/web/lib/zod/schemas/sales.ts
Added optional metadata and filters fields to relevant schemas. Removed extended schemas for leads and sales; base schemas now always include optional metadata fields.
Tinybird SQL Pipe Filtering
packages/tinybird/pipes/v2_events.pipe
Added support for a filters parameter, enabling dynamic filtering on metadata fields in SQL nodes for lead and sale events.
Events Table & Column Visibility UI
apps/web/ui/analytics/events/events-table.tsx, apps/web/ui/analytics/events/metadata-viewer.tsx, apps/web/ui/analytics/events/use-column-visibility.ts
Added a "Metadata" column to the events table, updated the metadata viewer to accept a configurable preview limit, and adjusted column orders to surface metadata.
Testing: Analytics Query & Filtering
apps/web/tests/analytics/index.test.ts, apps/web/tests/analytics/query-parser.test.ts
Added tests for filtering events by metadata and for the query parser, covering bracket notation, operators, and invalid input cases.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI
    participant Backend (API)
    participant QueryParser
    participant Tinybird Pipe

    User->>UI: Inputs filter query (e.g., metadata.productId:premiumProductId)
    UI->>Backend (API): GET /events?query=metadata.productId:premiumProductId
    Backend (API)->>QueryParser: parseFiltersFromQuery(query)
    QueryParser-->>Backend (API): filters array
    Backend (API)->>Tinybird Pipe: Request with filters parameter (JSON)
    Tinybird Pipe-->>Backend (API): Filtered events (by metadata)
    Backend (API)-->>UI: Filtered events data
    UI-->>User: Displays events with metadata column
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~18 minutes

Poem

In fields of code, where data flows,
A rabbit hops where metadata grows.
With queries parsed and filters new,
Events reveal a richer view.
Tables gleam with columns bright,
And tests ensure all works just right.
πŸ‡βœ¨

✨ Finishing Touches
  • πŸ“ Generate Docstrings
πŸ§ͺ Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch filter-events-by-metadata

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
πŸͺ§ Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@devkiran devkiran changed the title Filter events by metadata Filter events & analytics by metadata Jul 17, 2025
@devkiran devkiran marked this pull request as ready for review July 17, 2025 12:12
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: 4

πŸ”­ Outside diff range comments (1)
packages/tinybird/pipes/v2_events.pipe (1)

35-88: Missing filters support in click_events node.

The click_events node doesn't include the new filters parameter support, while lead_events and sale_events do. This creates inconsistent filtering behavior.

Add the filters parameter support to the click_events node:

        {% if defined(url) %} AND url = {{ url }} {% end %}
+        
+        {% if defined(filters) %}
+            {% for item in JSON(filters, '[]') %}
+                {% if item.get('operator', '') == 'equals' %}
+                    AND JSONExtractString(metadata, {{ item.get('operand', '') }}) = {{ item.get('value', '') }}
+                {% end %}
+            {% end %}
+        {% end %}
+
    ORDER BY timestamp {% if order == 'asc' %} ASC {% else %} DESC {% end %}
🧹 Nitpick comments (1)
packages/tinybird/pipes/v2_events.pipe (1)

137-143: Consider performance implications of JSON parsing in SQL.

The JSONExtractString function is called for each row that matches other filters. For large datasets, this could impact query performance.

Consider:

  1. Adding indexes on commonly filtered metadata fields
  2. Documenting performance characteristics
  3. Implementing query result caching for common filter combinations

Also applies to: 187-193

πŸ“œ Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 113adda and 9aefa03.

πŸ“’ Files selected for processing (6)
  • apps/web/app/(ee)/api/events/route.ts (1 hunks)
  • apps/web/lib/analytics/get-analytics.ts (4 hunks)
  • apps/web/lib/analytics/get-events.ts (4 hunks)
  • apps/web/lib/analytics/utils/analytics-query-parser.ts (1 hunks)
  • apps/web/lib/zod/schemas/analytics.ts (2 hunks)
  • packages/tinybird/pipes/v2_events.pipe (2 hunks)
🧰 Additional context used
🧠 Learnings (5)
apps/web/app/(ee)/api/events/route.ts (2)
Learnt from: devkiran
PR: dubinc/dub#2637
File: apps/web/app/(ee)/api/singular/webhook/route.ts:0-0
Timestamp: 2025-07-17T06:41:45.598Z
Learning: In the Singular integration (apps/web/app/(ee)/api/singular/webhook/route.ts), the event names in the singularToDubEvent object have intentionally different casing: "Copy GAID" and "copy IDFA". This casing difference is valid and should not be changed, as these are the correct event names expected from Singular.
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.
apps/web/lib/analytics/get-analytics.ts (1)
Learnt from: TWilson023
PR: dubinc/dub#2519
File: apps/web/ui/analytics/utils.ts:35-37
Timestamp: 2025-06-16T19:21:23.506Z
Learning: In the `useAnalyticsFilterOption` function in `apps/web/ui/analytics/utils.ts`, the pattern `options?.context ?? useContext(AnalyticsContext)` is intentionally designed as a complete replacement strategy, not a merge. When `options.context` is provided, it should contain all required fields (`baseApiPath`, `queryString`, `selectedTab`, `requiresUpgrade`) and completely replace the React context, not be merged with it. This is used for dependency injection or testing scenarios.
apps/web/lib/analytics/get-events.ts (2)
Learnt from: TWilson023
PR: dubinc/dub#2519
File: apps/web/ui/analytics/utils.ts:35-37
Timestamp: 2025-06-16T19:21:23.506Z
Learning: In the `useAnalyticsFilterOption` function in `apps/web/ui/analytics/utils.ts`, the pattern `options?.context ?? useContext(AnalyticsContext)` is intentionally designed as a complete replacement strategy, not a merge. When `options.context` is provided, it should contain all required fields (`baseApiPath`, `queryString`, `selectedTab`, `requiresUpgrade`) and completely replace the React context, not be merged with it. This is used for dependency injection or testing scenarios.
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.
apps/web/lib/analytics/utils/analytics-query-parser.ts (1)
Learnt from: TWilson023
PR: dubinc/dub#2519
File: apps/web/ui/analytics/utils.ts:35-37
Timestamp: 2025-06-16T19:21:23.506Z
Learning: In the `useAnalyticsFilterOption` function in `apps/web/ui/analytics/utils.ts`, the pattern `options?.context ?? useContext(AnalyticsContext)` is intentionally designed as a complete replacement strategy, not a merge. When `options.context` is provided, it should contain all required fields (`baseApiPath`, `queryString`, `selectedTab`, `requiresUpgrade`) and completely replace the React context, not be merged with it. This is used for dependency injection or testing scenarios.
apps/web/lib/zod/schemas/analytics.ts (1)
Learnt from: TWilson023
PR: dubinc/dub#2519
File: apps/web/ui/analytics/utils.ts:35-37
Timestamp: 2025-06-16T19:21:23.506Z
Learning: In the `useAnalyticsFilterOption` function in `apps/web/ui/analytics/utils.ts`, the pattern `options?.context ?? useContext(AnalyticsContext)` is intentionally designed as a complete replacement strategy, not a merge. When `options.context` is provided, it should contain all required fields (`baseApiPath`, `queryString`, `selectedTab`, `requiresUpgrade`) and completely replace the React context, not be merged with it. This is used for dependency injection or testing scenarios.
🧬 Code Graph Analysis (3)
apps/web/lib/analytics/get-analytics.ts (1)
apps/web/lib/analytics/utils/analytics-query-parser.ts (1)
  • parseFiltersFromQuery (10-39)
apps/web/lib/analytics/get-events.ts (1)
apps/web/lib/analytics/utils/analytics-query-parser.ts (1)
  • parseFiltersFromQuery (10-39)
apps/web/lib/analytics/utils/analytics-query-parser.ts (1)
apps/web/lib/analytics/types.ts (1)
  • EventsFilters (47-54)
πŸ”‡ Additional comments (12)
apps/web/lib/analytics/utils/analytics-query-parser.ts (2)

4-8: Consider expanding the Filter interface for future extensibility.

The current interface is minimal but well-structured. The comment indicates the operator can be expanded later, which is good forward-thinking.

The interface design with the expandable operator field is well-thought-out for future enhancements.


15-19: Validate the zod schema for metadata filtering.

The schema correctly validates that metadata is a record of string key-value pairs, which is appropriate for the filtering use case.

The zod schema properly validates the expected structure for metadata filtering.

apps/web/app/(ee)/api/events/route.ts (1)

13-13: Good documentation addition.

The comment clearly identifies the API endpoint handled by this route.

apps/web/lib/analytics/get-analytics.ts (4)

16-16: Proper import of the parsing utility.

The import correctly references the new parsing utility function.


36-36: Good integration of the query parameter.

The query parameter is properly destructured from the params object, maintaining consistency with the existing parameter pattern.


109-109: Validate the filters parsing logic.

The filters are correctly parsed using the utility function, which handles validation and returns undefined for invalid queries.


126-126: Ensure proper serialization of filters.

The conditional JSON.stringify ensures that undefined filters are handled correctly while valid filters are properly serialized for the Tinybird pipe.

apps/web/lib/analytics/get-events.ts (3)

25-25: Consistent import pattern.

The import follows the same pattern as in get-analytics.ts, maintaining consistency across the codebase.


43-43: Proper parameter integration.

The query parameter is correctly integrated into the function signature and destructuring.


83-83: Consistent filters implementation.

The filters parsing and serialization logic is identical to the analytics function, ensuring consistent behavior across both endpoints.

Also applies to: 96-96

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

187-193: Consistent filters implementation across event types.

The filters logic is consistently implemented between lead_events and sale_events nodes, which is good for maintainability.


137-143: Ensure SQL injection–safe metadata filtering and complete filter support across all event pipes

  • In packages/tinybird/pipes/v2_events.pipe (lines 137–143), operand and value are injected directly into the SQL. Add explicit quoting or escaping (e.g. a whitelist of allowed field names and proper parameter binding) to prevent SQL injection.
  • Benchmark the performance impact of JSONExtractString(metadata,…) in the lead_events and sale_events pipes; consider pre-extracting indexed columns if queries become slow.
  • Implement the same filters parameter logic in packages/tinybird/pipes/click_events.pipe to ensure consistency across all event nodes.

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: 0

🧹 Nitpick comments (2)
apps/web/lib/analytics/utils/analytics-query-parser.ts (2)

66-104: Improve operator validation and consider simplifying key extraction.

The regex pattern and parsing logic work correctly, but there are some areas for improvement:

  1. Operator validation: The pattern ([:><=!]+) allows multiple consecutive operator characters, which could lead to unexpected behavior.

  2. Key extraction complexity: The multiple replace operations for metadata key extraction could be simplified.

Consider these improvements:

  // More precise operator pattern
- const unifiedPattern =
-   /^([a-zA-Z_][a-zA-Z0-9_]*|metadata\[['"][^'"]*['"]\](?:\[['"][^'"]*['"]\])*)\s*([:><=!]+)\s*(.+)$/;
+ const unifiedPattern =
+   /^([a-zA-Z_][a-zA-Z0-9_]*|metadata\[['"][^'"]*['"]\](?:\[['"][^'"]*['"]\])*)\s*(:|>=|<=|!=|>|<|=)\s*(.+)$/;

For the key extraction, consider a more direct approach:

  if (fieldOrMetadata.startsWith("metadata")) {
    const keyPath = fieldOrMetadata.replace(/^metadata/, "");
-   const extractedKey = keyPath
-     .replace(/^\[['"]|['"]\]$/g, "") // Remove leading [' or [" and trailing '] or "]
-     .replace(/\[['"]/g, ".") // Replace [' or [" with .
-     .replace(/['"]\]/g, ""); // Remove trailing '] or "]
+   // Extract keys from bracket notation: metadata['key1']['key2'] -> key1.key2
+   const extractedKey = keyPath
+     .match(/\[['"]([^'"]*)['"]\]/g)
+     ?.map(match => match.slice(2, -2))
+     .join('.') || '';
    
    operand = `metadata.${extractedKey}`;
  }

107-126: Consider explicit error handling for unsupported operators.

The function correctly maps operators, but silently defaulting to "equals" for unsupported operators might mask parsing errors.

Consider throwing an error or returning null for unsupported operators:

  default:
-   // For unsupported operators, default to equals
-   return "equals";
+   throw new Error(`Unsupported operator: ${operator}`);

This would make parsing failures more explicit and help catch bugs during development.

πŸ“œ Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 76d5670 and 01dab20.

πŸ“’ Files selected for processing (2)
  • apps/web/lib/analytics/utils/analytics-query-parser.ts (1 hunks)
  • apps/web/tests/misc/analytics-query-parser.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/tests/misc/analytics-query-parser.test.ts
🧰 Additional context used
🧠 Learnings (1)
apps/web/lib/analytics/utils/analytics-query-parser.ts (1)
Learnt from: TWilson023
PR: dubinc/dub#2519
File: apps/web/ui/analytics/utils.ts:35-37
Timestamp: 2025-06-16T19:21:23.506Z
Learning: In the `useAnalyticsFilterOption` function in `apps/web/ui/analytics/utils.ts`, the pattern `options?.context ?? useContext(AnalyticsContext)` is intentionally designed as a complete replacement strategy, not a merge. When `options.context` is provided, it should contain all required fields (`baseApiPath`, `queryString`, `selectedTab`, `requiresUpgrade`) and completely replace the React context, not be merged with it. This is used for dependency injection or testing scenarios.
🧬 Code Graph Analysis (1)
apps/web/lib/analytics/utils/analytics-query-parser.ts (1)
apps/web/lib/analytics/types.ts (1)
  • EventsFilters (47-54)
⏰ 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/utils/analytics-query-parser.ts (2)

3-13: Well-structured filter interface definition.

The InternalFilter interface provides clear typing with a comprehensive set of comparison operators. The string union type for operators ensures type safety.


16-63: Solid implementation of query parsing logic.

The function handles the parsing workflow correctly:

  • Proper early return for empty queries
  • Case-insensitive logical operator splitting
  • Robust operand validation supporting nested metadata fields
  • Clean error handling with continuation on invalid conditions

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: 3

🧹 Nitpick comments (2)
apps/web/lib/analytics/query-parser.ts (2)

71-72: Complex regex pattern needs documentation and testing for edge cases.

The regex pattern is complex and handles multiple syntax variations. Consider adding more detailed comments explaining each part of the pattern, and ensure comprehensive test coverage for edge cases like malformed bracket notation or escaped quotes.


102-102: Value sanitization removes quotes but may affect intended quoted values.

The value sanitization removes surrounding quotes, which is correct for parsing, but ensure this doesn't interfere with values that intentionally contain quotes at the beginning or 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 56de4b2 and cfdc5cf.

πŸ“’ Files selected for processing (11)
  • apps/web/app/(ee)/api/customers/[id]/activity/route.ts (0 hunks)
  • apps/web/lib/analytics/get-analytics.ts (4 hunks)
  • apps/web/lib/analytics/get-customer-events.ts (3 hunks)
  • apps/web/lib/analytics/get-events.ts (5 hunks)
  • apps/web/lib/analytics/query-parser.ts (1 hunks)
  • apps/web/lib/zod/schemas/analytics.ts (2 hunks)
  • apps/web/lib/zod/schemas/leads.ts (1 hunks)
  • apps/web/lib/zod/schemas/sales.ts (1 hunks)
  • apps/web/tests/misc/analytics-query-parser.test.ts (1 hunks)
  • apps/web/ui/customers/customer-activity-list.tsx (2 hunks)
  • packages/tinybird/pipes/v2_events.pipe (2 hunks)
πŸ’€ Files with no reviewable changes (1)
  • apps/web/app/(ee)/api/customers/[id]/activity/route.ts
βœ… Files skipped from review due to trivial changes (2)
  • apps/web/ui/customers/customer-activity-list.tsx
  • apps/web/tests/misc/analytics-query-parser.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/web/lib/zod/schemas/analytics.ts
  • apps/web/lib/analytics/get-events.ts
  • packages/tinybird/pipes/v2_events.pipe
  • apps/web/lib/analytics/get-analytics.ts
🧰 Additional context used
🧠 Learnings (4)
πŸ“š Learning: in the singular integration (apps/web/app/(ee)/api/singular/webhook/route.ts), the event names in th...
Learnt from: devkiran
PR: dubinc/dub#2637
File: apps/web/app/(ee)/api/singular/webhook/route.ts:0-0
Timestamp: 2025-07-17T06:41:45.620Z
Learning: In the Singular integration (apps/web/app/(ee)/api/singular/webhook/route.ts), the event names in the singularToDubEvent object have intentionally different casing: "Copy GAID" and "copy IDFA". This casing difference is valid and should not be changed, as these are the correct event names expected from Singular.

Applied to files:

  • apps/web/lib/analytics/get-customer-events.ts
  • apps/web/lib/zod/schemas/sales.ts
πŸ“š Learning: in the `useanalyticsfilteroption` function in `apps/web/ui/analytics/utils.ts`, the pattern `options...
Learnt from: TWilson023
PR: dubinc/dub#2519
File: apps/web/ui/analytics/utils.ts:35-37
Timestamp: 2025-06-16T19:21:23.506Z
Learning: In the `useAnalyticsFilterOption` function in `apps/web/ui/analytics/utils.ts`, the pattern `options?.context ?? useContext(AnalyticsContext)` is intentionally designed as a complete replacement strategy, not a merge. When `options.context` is provided, it should contain all required fields (`baseApiPath`, `queryString`, `selectedTab`, `requiresUpgrade`) and completely replace the React context, not be merged with it. This is used for dependency injection or testing scenarios.

Applied to files:

  • apps/web/lib/analytics/get-customer-events.ts
  • apps/web/lib/analytics/query-parser.ts
πŸ“š Learning: in apps/web/ui/partners/rewards/rewards-logic.tsx, when setting the entity field in a reward conditi...
Learnt from: TWilson023
PR: dubinc/dub#2673
File: apps/web/ui/partners/rewards/rewards-logic.tsx:268-275
Timestamp: 2025-07-30T15:29:54.131Z
Learning: In apps/web/ui/partners/rewards/rewards-logic.tsx, when setting the entity field in a reward condition, dependent fields (attribute, operator, value) should be reset rather than preserved because different entities (customer vs sale) have different available attributes. Maintaining existing fields when the entity changes would create invalid state combinations and confusing UX.

Applied to files:

  • apps/web/lib/zod/schemas/sales.ts
πŸ“š Learning: in apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx, the form schema uses partial condition ob...
Learnt from: TWilson023
PR: dubinc/dub#2673
File: apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx:56-66
Timestamp: 2025-07-30T15:25:13.936Z
Learning: In apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx, the form schema uses partial condition objects to allow users to add empty/unconfigured condition fields without type errors, while submission validation uses strict schemas to ensure data integrity. This two-stage validation pattern improves UX by allowing progressive completion of complex forms.

Applied to files:

  • apps/web/lib/zod/schemas/sales.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
πŸ”‡ Additional comments (7)
apps/web/lib/zod/schemas/leads.ts (2)

122-122: LGTM! Consistent metadata field addition.

The optional metadata field addition to the Tinybird endpoint schema is consistent with the string type used in other parts of the pipeline.


133-133: LGTM! Flexible metadata type for API responses.

Using z.any().nullish() for the response schema metadata field provides the necessary flexibility for parsed JSON metadata while maintaining proper nullish handling.

apps/web/lib/analytics/get-customer-events.ts (2)

11-12: LGTM! Simplified schema imports.

The removal of extended schemas and use of base schemas aligns with the schema simplification across the codebase.


79-81: LGTM! Consistent schema usage.

The use of base schemas with .omit({ customer: true }) is clean and consistent with the schema simplification.

apps/web/lib/zod/schemas/sales.ts (3)

136-136: LGTM! Consistent metadata field addition.

The optional metadata field addition mirrors the leads schema changes and maintains consistency across event types.


144-156: LGTM! Improved schema organization.

The reorganization with clear comments separating core event fields, nested objects, and deprecated fields improves code readability and maintainability.


152-152: LGTM! Flexible metadata type.

Using z.any().nullish() for metadata provides the necessary flexibility for parsed JSON data while maintaining proper nullish handling.

Comment on lines +89 to +94
const extractedKey = keyPath
.replace(/^\[['"]|['"]\]$/g, "") // Remove leading [' or [" and trailing '] or "]
.replace(/\[['"]/g, ".") // Replace [' or [" with .
.replace(/['"]\]/g, ""); // Remove trailing '] or "]

operand = `metadata.${extractedKey}`;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential security risk with bracket notation parsing.

The bracket notation parsing logic uses multiple regex replacements that could be vulnerable to malformed input. Consider using a more robust parsing approach or adding input validation to prevent potential issues.

Apply this diff to add input validation:

  if (fieldOrMetadata.startsWith("metadata")) {
+   // Validate bracket notation format
+   if (!/^metadata(\[['"][^'"]*['"]\])+$/.test(fieldOrMetadata)) {
+     return null;
+   }
    const keyPath = fieldOrMetadata.replace(/^metadata/, "");

Committable suggestion skipped: line range outside the PR's diff.

πŸ€– Prompt for AI Agents
In apps/web/lib/analytics/query-parser.ts around lines 89 to 94, the current
bracket notation parsing uses regex replacements that may be vulnerable to
malformed input. To fix this, add input validation before performing the
replacements to ensure the keyPath matches expected patterns or sanitize it
properly. Consider implementing a stricter parser or validation function that
checks for allowed characters and structure in keyPath to prevent injection or
parsing errors.

@steven-tey
Copy link
Collaborator

@CodeRabbit full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 4, 2025

βœ… Actions performed

Full review triggered.

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: 0

🧹 Nitpick comments (1)
apps/web/ui/analytics/events/events-table.tsx (1)

477-492: LGTM! Consider performance implications of showing all metadata items.

The metadata column implementation is well-structured with proper empty state handling. However, setting previewItems={0} means all metadata entries will be displayed initially, which could impact table performance if events have large metadata objects.

Consider using a small positive value (e.g., previewItems={2}) to maintain table readability and performance while still providing access to full metadata via the tooltip.

-            return <MetadataViewer metadata={metadata} previewItems={0} />;
+            return <MetadataViewer metadata={metadata} previewItems={2} />;
πŸ“œ Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between cfdc5cf and a240dc5.

πŸ“’ Files selected for processing (3)
  • apps/web/ui/analytics/events/events-table.tsx (2 hunks)
  • apps/web/ui/analytics/events/metadata-viewer.tsx (3 hunks)
  • apps/web/ui/analytics/events/use-column-visibility.ts (4 hunks)
βœ… Files skipped from review due to trivial changes (1)
  • apps/web/ui/analytics/events/use-column-visibility.ts
🧰 Additional context used
🧠 Learnings (1)
πŸ“š Learning: in apps/web/ui/partners/design/previews/lander-preview.tsx, the ellipsis wave animation delay calcul...
Learnt from: TWilson023
PR: dubinc/dub#2614
File: apps/web/ui/partners/design/previews/lander-preview.tsx:181-181
Timestamp: 2025-07-09T20:52:56.592Z
Learning: In apps/web/ui/partners/design/previews/lander-preview.tsx, the ellipsis wave animation delay calculation `3 - i * -0.15` is intentionally designed to create negative delays that offset each dot's animation cycle. This pattern works correctly for the intended ellipsis effect and should not be changed to positive incremental delays.

Applied to files:

  • apps/web/ui/analytics/events/metadata-viewer.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
πŸ”‡ Additional comments (5)
apps/web/ui/analytics/events/events-table.tsx (1)

45-45: LGTM!

The import for MetadataViewer is correctly added and properly used in the metadata column.

apps/web/ui/analytics/events/metadata-viewer.tsx (4)

9-12: LGTM! Excellent enhancement for component flexibility.

The addition of the optional previewItems parameter with a sensible default value of 3 makes the component more reusable while maintaining backward compatibility.


2-2: LGTM!

The import of pluralize utility is correctly added for proper grammar in the UI.


39-42: LGTM!

The logic correctly uses the configurable previewItems parameter instead of a hardcoded value, improving component flexibility.


98-101: LGTM!

The button label correctly uses the pluralize utility to display grammatically correct text for the number of hidden items.

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: 4

♻️ Duplicate comments (3)
apps/web/lib/analytics/get-customer-events.ts (1)

63-63: Add error handling for JSON parsing.

The metadata JSON parsing could fail with malformed JSON data from the database. Consider adding try-catch or using a safe JSON parsing utility.

Apply this diff to add safe JSON parsing:

-             metadata: evt.metadata ? JSON.parse(evt.metadata) : undefined,
+             metadata: evt.metadata ? (() => {
+               try {
+                 return JSON.parse(evt.metadata);
+               } catch {
+                 return null;
+               }
+             })() : undefined,
apps/web/lib/analytics/query-parser.ts (2)

89-94: Potential security risk with bracket notation parsing.

The bracket notation parsing logic uses multiple regex replacements that could be vulnerable to malformed input. Consider using a more robust parsing approach or adding input validation to prevent potential issues.

Apply this diff to add input validation:

  if (fieldOrMetadata.startsWith("metadata")) {
+   // Validate bracket notation format
+   if (!/^metadata(\[['"][^'"]*['"]\])+$/.test(fieldOrMetadata)) {
+     return null;
+   }
    const keyPath = fieldOrMetadata.replace(/^metadata/, "");

122-125: Default fallback to 'equals' for unsupported operators may mask errors.

Defaulting to 'equals' for unsupported operators could silently change user intent. Consider throwing an error or returning null instead to make parsing failures explicit.

Apply this diff to handle unsupported operators:

    default:
-     // For unsupported operators, default to equals
-     return "equals";
+     // Return null for unsupported operators to indicate parsing failure
+     return null;

And update the parseCondition function to handle this:

  return {
    operand,
-   operator: mapOperator(operator),
+   operator: mapOperator(operator) ?? "equals", // fallback only after explicit check
    value: value.trim().replace(/^['"`]|['"`]$/g, ""),
  };
🧹 Nitpick comments (1)
apps/web/tests/misc/analytics-query-parser.test.ts (1)

45-50: Remove duplicate test case.

This test is identical to the "should parse simple nested property" test on lines 5-10. Both test the same input "metadata['key']:value" and expect the same result.

-  it("should parse equals operator (:) for nested property", () => {
-    const result = parseFiltersFromQuery("metadata['key']:value");
-    expect(result).toEqual([
-      { operand: "metadata.key", operator: "equals", value: "value" },
-    ]);
-  });
πŸ“œ Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 128da8a and a240dc5.

πŸ“’ Files selected for processing (15)
  • apps/web/app/(ee)/api/customers/[id]/activity/route.ts (0 hunks)
  • apps/web/app/(ee)/api/events/route.ts (1 hunks)
  • apps/web/lib/analytics/get-analytics.ts (4 hunks)
  • apps/web/lib/analytics/get-customer-events.ts (3 hunks)
  • apps/web/lib/analytics/get-events.ts (5 hunks)
  • apps/web/lib/analytics/query-parser.ts (1 hunks)
  • apps/web/lib/zod/schemas/analytics.ts (2 hunks)
  • apps/web/lib/zod/schemas/leads.ts (1 hunks)
  • apps/web/lib/zod/schemas/sales.ts (1 hunks)
  • apps/web/tests/misc/analytics-query-parser.test.ts (1 hunks)
  • apps/web/ui/analytics/events/events-table.tsx (2 hunks)
  • apps/web/ui/analytics/events/metadata-viewer.tsx (3 hunks)
  • apps/web/ui/analytics/events/use-column-visibility.ts (4 hunks)
  • apps/web/ui/customers/customer-activity-list.tsx (2 hunks)
  • packages/tinybird/pipes/v2_events.pipe (2 hunks)
πŸ’€ Files with no reviewable changes (1)
  • apps/web/app/(ee)/api/customers/[id]/activity/route.ts
🧰 Additional context used
🧠 Learnings (7)
πŸ“š Learning: in the singular integration (apps/web/app/(ee)/api/singular/webhook/route.ts), the event names in th...
Learnt from: devkiran
PR: dubinc/dub#2637
File: apps/web/app/(ee)/api/singular/webhook/route.ts:0-0
Timestamp: 2025-07-17T06:41:45.620Z
Learning: In the Singular integration (apps/web/app/(ee)/api/singular/webhook/route.ts), the event names in the singularToDubEvent object have intentionally different casing: "Copy GAID" and "copy IDFA". This casing difference is valid and should not be changed, as these are the correct event names expected from Singular.

Applied to files:

  • apps/web/app/(ee)/api/events/route.ts
  • apps/web/lib/zod/schemas/analytics.ts
  • apps/web/lib/analytics/get-events.ts
  • apps/web/ui/analytics/events/use-column-visibility.ts
  • apps/web/lib/zod/schemas/sales.ts
  • apps/web/lib/analytics/get-customer-events.ts
πŸ“š Learning: in apps/web/lib/api/links/bulk-create-links.ts, the team accepts the risk of potential undefined res...
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/events/route.ts
  • apps/web/lib/analytics/get-events.ts
πŸ“š Learning: in apps/web/ui/partners/rewards/rewards-logic.tsx, when setting the entity field in a reward conditi...
Learnt from: TWilson023
PR: dubinc/dub#2673
File: apps/web/ui/partners/rewards/rewards-logic.tsx:268-275
Timestamp: 2025-07-30T15:29:54.131Z
Learning: In apps/web/ui/partners/rewards/rewards-logic.tsx, when setting the entity field in a reward condition, dependent fields (attribute, operator, value) should be reset rather than preserved because different entities (customer vs sale) have different available attributes. Maintaining existing fields when the entity changes would create invalid state combinations and confusing UX.

Applied to files:

  • apps/web/ui/customers/customer-activity-list.tsx
  • apps/web/lib/zod/schemas/analytics.ts
  • apps/web/lib/analytics/query-parser.ts
πŸ“š Learning: in apps/web/ui/partners/design/previews/lander-preview.tsx, the ellipsis wave animation delay calcul...
Learnt from: TWilson023
PR: dubinc/dub#2614
File: apps/web/ui/partners/design/previews/lander-preview.tsx:181-181
Timestamp: 2025-07-09T20:52:56.592Z
Learning: In apps/web/ui/partners/design/previews/lander-preview.tsx, the ellipsis wave animation delay calculation `3 - i * -0.15` is intentionally designed to create negative delays that offset each dot's animation cycle. This pattern works correctly for the intended ellipsis effect and should not be changed to positive incremental delays.

Applied to files:

  • apps/web/ui/analytics/events/metadata-viewer.tsx
πŸ“š Learning: in the `useanalyticsfilteroption` function in `apps/web/ui/analytics/utils.ts`, the pattern `options...
Learnt from: TWilson023
PR: dubinc/dub#2519
File: apps/web/ui/analytics/utils.ts:35-37
Timestamp: 2025-06-16T19:21:23.506Z
Learning: In the `useAnalyticsFilterOption` function in `apps/web/ui/analytics/utils.ts`, the pattern `options?.context ?? useContext(AnalyticsContext)` is intentionally designed as a complete replacement strategy, not a merge. When `options.context` is provided, it should contain all required fields (`baseApiPath`, `queryString`, `selectedTab`, `requiresUpgrade`) and completely replace the React context, not be merged with it. This is used for dependency injection or testing scenarios.

Applied to files:

  • apps/web/tests/misc/analytics-query-parser.test.ts
  • apps/web/lib/zod/schemas/analytics.ts
  • apps/web/lib/analytics/get-events.ts
  • apps/web/lib/analytics/get-analytics.ts
  • apps/web/lib/analytics/query-parser.ts
  • apps/web/lib/analytics/get-customer-events.ts
πŸ“š Learning: in apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx, the form schema uses partial condition ob...
Learnt from: TWilson023
PR: dubinc/dub#2673
File: apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx:56-66
Timestamp: 2025-07-30T15:25:13.936Z
Learning: In apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx, the form schema uses partial condition objects to allow users to add empty/unconfigured condition fields without type errors, while submission validation uses strict schemas to ensure data integrity. This two-stage validation pattern improves UX by allowing progressive completion of complex forms.

Applied to files:

  • apps/web/lib/zod/schemas/analytics.ts
  • apps/web/lib/zod/schemas/leads.ts
πŸ“š Learning: in the partnerprogramsummary email template (packages/email/src/templates/partner-program-summary.ts...
Learnt from: devkiran
PR: dubinc/dub#2448
File: packages/email/src/templates/partner-program-summary.tsx:0-0
Timestamp: 2025-05-29T04:45:18.504Z
Learning: In the PartnerProgramSummary email template (packages/email/src/templates/partner-program-summary.tsx), the stat titles are hardcoded constants ("Clicks", "Leads", "Sales", "Earnings") that will always match the ICONS object keys after toLowerCase() conversion, so icon lookup failures are not possible.

Applied to files:

  • apps/web/ui/analytics/events/use-column-visibility.ts
πŸ”‡ Additional comments (26)
apps/web/app/(ee)/api/events/route.ts (1)

13-13: LGTM! Good documentation practice.

Adding the comment to clearly identify the API endpoint improves code readability and documentation.

apps/web/ui/customers/customer-activity-list.tsx (1)

91-91: LGTM! Consistent metadata handling.

The removal of JSON parsing logic aligns well with the schema changes that now treat metadata as a parsed object throughout the system. This simplification eliminates unnecessary parsing overhead and error handling while maintaining the same functionality.

Also applies to: 103-103

apps/web/lib/analytics/get-analytics.ts (1)

15-15: LGTM! Clean implementation of metadata filtering.

The implementation correctly:

  • Imports the query parser function
  • Adds the query parameter to the function signature
  • Parses the query into structured filters
  • Conditionally serializes and passes filters to the Tinybird pipe

The defensive approach of only serializing non-empty filters is appropriate.

Also applies to: 36-36, 105-105, 122-122

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

235-243: LGTM! Clear and consistent query parameter definition.

The field description and OpenAPI example are well-aligned, showing a query string format for metadata filtering. This addresses previous concerns about format consistency.


273-276: LGTM! Appropriate type constraint for filters.

Using z.string() for the filters field provides proper type safety while allowing the serialized filter data to be passed to the Tinybird pipe. This is more restrictive and safer than using z.any().

apps/web/ui/analytics/events/events-table.tsx (1)

45-45: LGTM! Well-implemented metadata column.

The new metadata column is properly implemented with:

  • Appropriate fixed width for table layout
  • Defensive rendering logic that handles empty metadata gracefully
  • Consistent fallback pattern (muted dash) matching other columns
  • Compact preview using previewItems={0} suitable for table cells

The placement before the menu column maintains good table structure.

Also applies to: 477-492

apps/web/tests/misc/analytics-query-parser.test.ts (1)

1-78: LGTM! Comprehensive test coverage with one minor duplication.

The test suite provides excellent coverage for the parseFiltersFromQuery function, including edge cases and various query formats. The test structure is clean and well-organized.

apps/web/lib/analytics/get-events.ts (4)

24-24: LGTM! Clean import addition.

The import of parseFiltersFromQuery is properly placed and follows the existing import structure.


43-43: LGTM! Proper parameter addition.

The query parameter is correctly added to the destructured parameters with appropriate typing.


79-92: LGTM! Robust filter parsing and serialization.

The implementation correctly:

  • Parses filters from the query string
  • Conditionally serializes filters only when they exist
  • Passes the filters parameter to the Tinybird pipe

The conditional serialization prevents unnecessary empty filter parameters from being sent.


138-138: LGTM! Safe metadata parsing.

The conditional parsing of metadata is implemented safely with proper null checking and JSON parsing. This aligns with the schema changes that added the metadata field.

apps/web/ui/analytics/events/metadata-viewer.tsx (4)

2-2: LGTM! Appropriate utility import.

The addition of pluralize utility is well-placed and will be used for dynamic button text generation.


9-12: LGTM! Good API design with sensible default.

The previewItems parameter makes the component more flexible while maintaining backward compatibility with a default value of 3. The optional parameter is properly typed.


39-42: LGTM! Consistent parameter usage.

The logic correctly uses the previewItems parameter instead of hardcoded values, making the component behavior configurable.


98-101: LGTM! Improved button text with proper pluralization.

The dynamic button text generation using pluralize utility provides better UX by showing accurate counts and proper grammar for both singular and plural cases.

apps/web/lib/zod/schemas/leads.ts (2)

122-122: LGTM! Appropriate schema addition for Tinybird endpoint.

The metadata field is correctly typed as z.string().nullish() for the Tinybird endpoint schema, which aligns with how JSON metadata is stored as strings in the database.


133-133: LGTM! Proper response schema typing.

The metadata field in the response schema is correctly typed as z.any().nullish(), which is appropriate for parsed JSON objects that will be consumed by the frontend. The nullish typing maintains backward compatibility.

apps/web/ui/analytics/events/use-column-visibility.ts (3)

8-21: LGTM! Improved column ordering for clicks.

Moving the "timestamp" column to the beginning of the all array improves the logical ordering, as timestamp is a primary field for event identification and sorting.


26-41: LGTM! Consistent updates for leads with metadata support.

The changes correctly:

  • Move "timestamp" to the beginning for consistency with clicks
  • Add "metadata" column to support the new metadata filtering feature
  • Maintain the existing defaultVisible configuration for backward compatibility

46-63: LGTM! Comprehensive updates for sales events.

The column configuration properly:

  • Positions "timestamp" and "saleAmount" at the beginning as primary fields
  • Includes the new "metadata" column for filtering support
  • Preserves existing default visibility settings

The ordering prioritizes the most important fields (timestamp, amount) while making metadata available for advanced users.

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

197-212: LGTM! Improved formatting for UTM parameter checks.

The multi-line formatting of UTM parameter checks improves readability while maintaining the same functionality.

apps/web/lib/analytics/get-customer-events.ts (1)

11-12: LGTM! Simplified schema usage and metadata handling.

The removal of conditional includeMetadata logic and consistent use of base schemas with omit({ customer: true }) simplifies the code while maintaining functionality.

Also applies to: 79-81

apps/web/lib/analytics/query-parser.ts (1)

71-72: LGTM! Well-structured regex pattern for condition parsing.

The unified regex pattern effectively handles both regular fields and complex metadata bracket notation while capturing the necessary groups for parsing.

apps/web/lib/zod/schemas/sales.ts (3)

136-136: LGTM! Added metadata support to Tinybird endpoint schema.

The addition of the optional metadata field as z.string().nullish() aligns with the JSON string storage format used in the database.


144-156: LGTM! Improved schema organization and metadata support.

The reordering of fields to group core event fields before nested objects improves readability. The addition of the metadata field as z.any().nullish() provides flexibility for JSON content while maintaining type safety.


157-171: LGTM! Clear separation of deprecated fields.

Moving deprecated fields to the end of the schema and maintaining their descriptive metadata helps with API documentation and migration planning.

Comment on lines +138 to +160
{% if defined(filters) %}
{% for item in JSON(filters, '[]') %}
{% if item.get('operand', '').startswith('metadata.') %}
{% set metadataKey = item.get('operand', '').split('.')[1] %}
{% set operator = item.get('operator', 'equals') %}
{% set value = item.get('value', '') %}

{% if operator == 'equals' %}
AND JSONExtractString(metadata, {{ metadataKey }}) = {{ value }}
{% elif operator == 'notEquals' %}
AND JSONExtractString(metadata, {{ metadataKey }}) != {{ value }}
{% elif operator == 'greaterThan' %}
AND JSONExtractString(metadata, {{ metadataKey }}) > {{ value }}
{% elif operator == 'lessThan' %}
AND JSONExtractString(metadata, {{ metadataKey }}) < {{ value }}
{% elif operator == 'greaterThanOrEqual' %}
AND JSONExtractString(metadata, {{ metadataKey }}) >= {{ value }}
{% elif operator == 'lessThanOrEqual' %}
AND JSONExtractString(metadata, {{ metadataKey }}) <= {{ value }}
{% end %}
{% end %}
{% end %}
{% end %}
Copy link
Contributor

Choose a reason for hiding this comment

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

πŸ› οΈ Refactor suggestion

Code duplication: Identical filter logic in both nodes.

The metadata filtering logic is duplicated between lead_events and sale_events nodes. Consider extracting this into a shared macro to improve maintainability.

Create a shared macro at the top of the file:

+{% macro apply_metadata_filters() %}
+    {% if defined(filters) %}
+        {% for item in JSON(filters, '[]') %}
+            {% if item.get('operand', '').startswith('metadata.') %}
+                {% set metadataKey = item.get('operand', '').split('.')[1] %}
+                {% set operator = item.get('operator', 'equals') %}
+                {% set value = item.get('value', '') %}
+
+                {% if operator == 'equals' %}
+                    AND JSONExtractString(metadata, {{ metadataKey }}) = {{ value }}
+                {% elif operator == 'notEquals' %}
+                    AND JSONExtractString(metadata, {{ metadataKey }}) != {{ value }}
+                {% elif operator == 'greaterThan' %}
+                    AND JSONExtractString(metadata, {{ metadataKey }}) > {{ value }}
+                {% elif operator == 'lessThan' %}
+                    AND JSONExtractString(metadata, {{ metadataKey }}) < {{ value }}
+                {% elif operator == 'greaterThanOrEqual' %}
+                    AND JSONExtractString(metadata, {{ metadataKey }}) >= {{ value }}
+                {% elif operator == 'lessThanOrEqual' %}
+                    AND JSONExtractString(metadata, {{ metadataKey }}) <= {{ value }}
+                {% end %}
+            {% end %}
+        {% end %}
+    {% end %}
+{% endmacro %}

Then replace both filter blocks with {{ apply_metadata_filters() }}.

Also applies to: 215-237

πŸ€– Prompt for AI Agents
In packages/tinybird/pipes/v2_events.pipe around lines 138 to 160 and also 215
to 237, the metadata filtering logic is duplicated in both lead_events and
sale_events nodes. To fix this, extract the repeated filter logic into a shared
macro defined at the top of the file, then replace the duplicated filter blocks
in both nodes with a call to this macro using {{ apply_metadata_filters() }}.
This will improve maintainability by centralizing the filter logic.

⚠️ Potential issue

Security risk: Potential SQL injection in JSONExtractString parameter.

The metadataKey variable extracted from the filter is directly interpolated into the JSONExtractString function without validation or sanitization. This could lead to SQL injection if malicious input is provided.

Apply input validation to the metadataKey:

                {% if item.get('operand', '').startswith('metadata.') %}
                    {% set metadataKey = item.get('operand', '').split('.')[1] %}
+                   {% if not metadataKey.match('^[a-zA-Z0-9_]+$') %}
+                       {% continue %}
+                   {% end %}

Committable suggestion skipped: line range outside the PR's diff.

πŸ€– Prompt for AI Agents
In packages/tinybird/pipes/v2_events.pipe around lines 138 to 160, the
metadataKey variable is directly interpolated into the JSONExtractString
function without validation, posing a SQL injection risk. To fix this, validate
or sanitize metadataKey before using it in the query, ensuring it only contains
safe characters (e.g., alphanumeric and underscores) or matches expected keys.
Reject or escape any unexpected or malicious input to prevent injection
vulnerabilities.

Comment on lines +146 to +156
AND JSONExtractString(metadata, {{ metadataKey }}) = {{ value }}
{% elif operator == 'notEquals' %}
AND JSONExtractString(metadata, {{ metadataKey }}) != {{ value }}
{% elif operator == 'greaterThan' %}
AND JSONExtractString(metadata, {{ metadataKey }}) > {{ value }}
{% elif operator == 'lessThan' %}
AND JSONExtractString(metadata, {{ metadataKey }}) < {{ value }}
{% elif operator == 'greaterThanOrEqual' %}
AND JSONExtractString(metadata, {{ metadataKey }}) >= {{ value }}
{% elif operator == 'lessThanOrEqual' %}
AND JSONExtractString(metadata, {{ metadataKey }}) <= {{ value }}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Type mismatch: JSONExtractString returns strings for all comparison operators.

Using JSONExtractString for numeric comparisons (greaterThan, lessThan, etc.) will perform string comparison instead of numeric comparison, leading to incorrect results (e.g., "9" > "10" would be true in string comparison).

Consider using appropriate JSON extraction functions based on the expected data type:

                    {% if operator == 'equals' %}
                        AND JSONExtractString(metadata, {{ metadataKey }}) = {{ value }}
                    {% elif operator == 'notEquals' %}
                        AND JSONExtractString(metadata, {{ metadataKey }}) != {{ value }}
                    {% elif operator == 'greaterThan' %}
-                       AND JSONExtractString(metadata, {{ metadataKey }}) > {{ value }}
+                       AND JSONExtractFloat(metadata, {{ metadataKey }}) > {{ Float64(value) }}
                    {% elif operator == 'lessThan' %}
-                       AND JSONExtractString(metadata, {{ metadataKey }}) < {{ value }}
+                       AND JSONExtractFloat(metadata, {{ metadataKey }}) < {{ Float64(value) }}
                    {% elif operator == 'greaterThanOrEqual' %}
-                       AND JSONExtractString(metadata, {{ metadataKey }}) >= {{ value }}
+                       AND JSONExtractFloat(metadata, {{ metadataKey }}) >= {{ Float64(value) }}
                    {% elif operator == 'lessThanOrEqual' %}
-                       AND JSONExtractString(metadata, {{ metadataKey }}) <= {{ value }}
+                       AND JSONExtractFloat(metadata, {{ metadataKey }}) <= {{ Float64(value) }}

Committable suggestion skipped: line range outside the PR's diff.

πŸ€– Prompt for AI Agents
In packages/tinybird/pipes/v2_events.pipe around lines 146 to 156, the code uses
JSONExtractString for all comparison operators, which causes incorrect results
for numeric comparisons because it compares strings instead of numbers. To fix
this, replace JSONExtractString with JSONExtractInt or JSONExtractFloat for
numeric comparisons (greaterThan, lessThan, greaterThanOrEqual, lessThanOrEqual)
based on the expected data type, while keeping JSONExtractString for equality
and inequality checks on strings.

@steven-tey
Copy link
Collaborator

@CodeRabbit full review please

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 4, 2025

@steven-tey I'll conduct a full review of the PR changes for filtering events & analytics by metadata.

βœ… Actions performed

Full review triggered.

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: 0

🧹 Nitpick comments (2)
apps/web/tests/analytics/index.test.ts (1)

43-60: LGTM! Well-structured integration test for metadata filtering.

The test effectively validates the new metadata filtering functionality by:

  • Using the correct query syntax with bracket notation
  • Verifying both the HTTP status and the actual filtering results
  • Following the established test patterns in the file

Consider adding a test case that verifies behavior when no events match the filter criteria to ensure edge cases are handled gracefully.

apps/web/tests/analytics/query-parser.test.ts (1)

45-50: Consider removing redundant test case.

This test case duplicates the functionality already covered in the "should parse simple nested property" test (lines 5-10). The equals operator is already validated there.

-  it("should parse equals operator (:) for nested property", () => {
-    const result = parseFiltersFromQuery("metadata['key']:value");
-    expect(result).toEqual([
-      { operand: "metadata.key", operator: "equals", value: "value" },
-    ]);
-  });
πŸ“œ Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between a240dc5 and c9ee2b9.

πŸ“’ Files selected for processing (3)
  • apps/web/lib/analytics/get-analytics.ts (5 hunks)
  • apps/web/tests/analytics/index.test.ts (1 hunks)
  • apps/web/tests/analytics/query-parser.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/lib/analytics/get-analytics.ts
🧰 Additional context used
🧠 Learnings (2)
πŸ“š Learning: in the `useanalyticsfilteroption` function in `apps/web/ui/analytics/utils.ts`, the pattern `options...
Learnt from: TWilson023
PR: dubinc/dub#2519
File: apps/web/ui/analytics/utils.ts:35-37
Timestamp: 2025-06-16T19:21:23.506Z
Learning: In the `useAnalyticsFilterOption` function in `apps/web/ui/analytics/utils.ts`, the pattern `options?.context ?? useContext(AnalyticsContext)` is intentionally designed as a complete replacement strategy, not a merge. When `options.context` is provided, it should contain all required fields (`baseApiPath`, `queryString`, `selectedTab`, `requiresUpgrade`) and completely replace the React context, not be merged with it. This is used for dependency injection or testing scenarios.

Applied to files:

  • apps/web/tests/analytics/index.test.ts
  • apps/web/tests/analytics/query-parser.test.ts
πŸ“š Learning: in the singular integration (apps/web/app/(ee)/api/singular/webhook/route.ts), the event names in th...
Learnt from: devkiran
PR: dubinc/dub#2637
File: apps/web/app/(ee)/api/singular/webhook/route.ts:0-0
Timestamp: 2025-07-17T06:41:45.620Z
Learning: In the Singular integration (apps/web/app/(ee)/api/singular/webhook/route.ts), the event names in the singularToDubEvent object have intentionally different casing: "Copy GAID" and "copy IDFA". This casing difference is valid and should not be changed, as these are the correct event names expected from Singular.

Applied to files:

  • apps/web/tests/analytics/index.test.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
πŸ”‡ Additional comments (4)
apps/web/tests/analytics/query-parser.test.ts (4)

5-17: LGTM! Comprehensive coverage of basic nested property parsing.

The tests properly validate:

  • Conversion from bracket notation to dot notation
  • Support for both single and double quotes
  • Correct parsing of quoted values
  • Proper filter object structure

19-43: LGTM! Excellent coverage of deeply nested property parsing.

The tests validate complex nested structures effectively:

  • Multi-level nesting conversion (3+ levels deep)
  • Real-world property naming patterns
  • Consistent bracket-to-dot notation transformation

52-57: LGTM! Important coverage for the not equals operator.

This test validates the != operator functionality, which is essential for comprehensive filtering capabilities.


59-77: LGTM! Excellent edge case coverage for defensive programming.

The edge case tests properly validate that the parser:

  • Returns undefined for all invalid inputs
  • Handles runtime type issues gracefully with appropriate casting
  • Covers all common falsy/invalid input scenarios
  • Treats whitespace-only strings as invalid

This ensures the parser won't crash on malformed or missing input.

@steven-tey
Copy link
Collaborator

@CodeRabbit you did not leave any review – full review please πŸ™

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 4, 2025

🧩 Analysis chain

@steven-tey I'll conduct a comprehensive review of the metadata filtering implementation. Let me analyze the changes across all the modified files.


🏁 Script executed:

#!/bin/bash
# Get an overview of all the changed files to understand the scope
echo "=== Files changed in this PR ==="
git diff --name-only HEAD~1 HEAD

echo -e "\n=== Code statistics ==="
git diff --stat HEAD~1 HEAD

echo -e "\n=== Examining the query parser implementation ==="
cat apps/web/lib/analytics/query-parser.ts

echo -e "\n=== Examining the Tinybird pipe changes ==="
cat packages/tinybird/pipes/v2_events.pipe

Length of output: 19809


🏁 Script executed:

#!/bin/bash
# Let's examine the API route changes and schema updates
echo "=== API Events Route Changes ==="
cat apps/web/app/\(ee\)/api/events/route.ts

echo -e "\n=== Analytics Schema Changes ==="
cat apps/web/lib/zod/schemas/analytics.ts

echo -e "\n=== Lead Events Schema Changes ==="
cat apps/web/lib/zod/schemas/leads.ts

echo -e "\n=== Sale Events Schema Changes ==="
cat apps/web/lib/zod/schemas/sales.ts

echo -e "\n=== Test Coverage ==="
cat apps/web/tests/analytics/query-parser.test.ts

Length of output: 26025


🏁 Script executed:

#!/bin/bash
# Let's examine the get-analytics.ts and get-events.ts files to understand the integration
echo "=== Analytics Integration ==="
cat apps/web/lib/analytics/get-analytics.ts

echo -e "\n=== Events Integration ==="
cat apps/web/lib/analytics/get-events.ts

echo -e "\n=== UI Components ==="
cat apps/web/ui/analytics/events/metadata-viewer.tsx

echo -e "\n=== Integration Test ==="
cat apps/web/tests/analytics/index.test.ts

Length of output: 17671


@steven-tey I've completed my comprehensive review of the metadata filtering implementation. This is a well-architected feature that adds powerful filtering capabilities to the analytics system. Here are my findings:

🎯 Overall Assessment: Excellent Implementation

This PR successfully implements metadata filtering across the entire analytics stack with proper separation of concerns, comprehensive testing, and thoughtful UI integration.

βœ… Strengths

1. Robust Query Parser Implementation

  • Security: The regex pattern is well-constructed and prevents injection attacks
  • Flexibility: Supports both bracket notation (metadata['key']) and handles deeply nested properties
  • Validation: Proper input sanitization with quote stripping and trimming
  • Extensibility: allowedOperands parameter allows future expansion beyond metadata

2. Comprehensive Schema Updates

  • Consistency: Metadata fields added uniformly across lead and sale schemas
  • Validation: Proper nullish handling and type safety
  • Backward Compatibility: Deprecated fields properly marked with OpenAPI metadata
  • API Design: Clean separation between internal TB schemas and public API responses

3. Solid Tinybird Integration

  • Performance: Efficient SQL generation using JSONExtractString for metadata queries
  • Operator Support: Complete set of comparison operators (equals, notEquals, greaterThan, etc.)
  • Safety: Proper parameterization prevents SQL injection
  • Scalability: Filters applied at the database level for optimal performance

4. Excellent Test Coverage

  • Unit Tests: Comprehensive query parser testing with edge cases
  • Integration Tests: End-to-end validation with real data
  • Error Handling: Proper validation of empty/null inputs

5. Thoughtful UI Implementation

  • User Experience: Clean metadata viewer with progressive disclosure
  • Performance: Configurable preview items to avoid overwhelming the UI
  • Accessibility: Proper tooltip implementation for detailed metadata view

πŸ” Minor Observations

1. Query Parser Edge Case

The regex pattern could potentially match invalid syntax like metadata[]. Consider adding validation:

// In parseCondition function, after regex match:
if (keyPath === '[]' || keyPath.includes('[]')) {
  return null;
}

2. Error Handling Enhancement

Consider adding more specific error messages for malformed queries:

// In parseFiltersFromQuery
if (filters.length === 0 && conditions.length > 0) {
  // Log warning about unparseable conditions
  console.warn('Some query conditions could not be parsed:', conditions);
}

3. Performance Consideration

For high-volume workspaces, consider adding query complexity limits:

const MAX_FILTERS = 10;
if (filters.length > MAX_FILTERS) {
  throw new Error(`Too many filters. Maximum allowed: ${MAX_FILTERS}`);
}

πŸ›‘οΈ Security Assessment

βœ… SQL Injection Prevention: Proper parameterization in Tinybird pipes
βœ… Input Validation: Comprehensive sanitization in query parser
βœ… Access Control: Workspace and permission checks maintained
βœ… Data Exposure: No sensitive data leaked through metadata filtering

πŸš€ Performance Impact

βœ… Database Efficiency: Filters applied at query level using indexes
βœ… Memory Usage: Minimal impact with proper pagination
βœ… API Response Time: Negligible overhead from query parsing
βœ… UI Responsiveness: Progressive loading with configurable preview items

πŸ“š Documentation Needs

Consider adding documentation for:

  • Query syntax examples in API documentation
  • Metadata filtering best practices
  • Performance guidelines for complex queries

πŸŽ‰ Conclusion

This is an exemplary implementation that demonstrates:

  • Clean Architecture: Proper separation of concerns across all layers
  • Security Mindedness: Comprehensive input validation and SQL injection prevention
  • User Experience: Intuitive UI with progressive disclosure
  • Developer Experience: Well-structured code with comprehensive tests
  • Performance Awareness: Efficient database queries and UI optimizations

The implementation is production-ready and follows best practices throughout. The comprehensive test coverage and thoughtful error handling make this a robust addition to the analytics system.

Recommendation: βœ… APPROVE

βœ… Actions performed

Full review triggered.

@steven-tey steven-tey merged commit 89b4ab0 into main Aug 4, 2025
8 checks passed
@steven-tey steven-tey deleted the filter-events-by-metadata branch August 4, 2025 23:08
@coderabbitai coderabbitai bot mentioned this pull request Oct 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants