Skip to content

fix(export): include properties field and add property_keys filter to /export/events#389

Open
Shrotriya-lalit wants to merge 4 commits into
Openpanel-dev:mainfrom
Shrotriya-lalit:fix/export-events-properties-field
Open

fix(export): include properties field and add property_keys filter to /export/events#389
Shrotriya-lalit wants to merge 4 commits into
Openpanel-dev:mainfrom
Shrotriya-lalit:fix/export-events-properties-field

Conversation

@Shrotriya-lalit

@Shrotriya-lalit Shrotriya-lalit commented Jun 7, 2026

Copy link
Copy Markdown

Problem

GET /export/events never returned the properties field — it was missing from the SQL SELECT, so every exported event had an empty properties object.

Fixes #281

Changes

  • export.controller.ts: add properties: true to the select object so properties are always included in the export response
  • export.controller.ts: add optional property_keys query param — accepts a comma-separated list of keys to return only specific properties, reducing payload size for events with large property maps
  • event.service.ts: extend GetEventListOptions with propertyKeys?: string[] and use ClickHouse mapFilter at query time when keys are specified; add AS properties alias so the filtered column name is preserved
    correctly

Usage

# All properties (default)
GET /export/events?project_id=xxx

# Only specific keys — useful for large property payloads
GET /export/events?project_id=xxx&property_keys=source,url

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

* **New Features**
* Add property filtering for event exports: specify which event properties to include via the property_keys parameter to reduce payloads.
* **Improvements**
* Query parsing now accepts comma-separated property_keys and ignores empty/null entries, and forwards selected properties in export results.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Shrotriya-lalit and others added 3 commits June 7, 2026 09:39
The export controller's `getEventList` call never enabled `properties`
in its select object, so the column was silently omitted from SQL and
every exported event had an empty properties field.

Fixes Openpanel-dev#281

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When exporting events with large property sets, callers can now pass
`?property_keys=key1,key2` to receive only the specified keys in each
event's properties map instead of the full payload.

Uses ClickHouse's mapFilter function at query time so only the requested
keys are transferred — no post-processing overhead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
mapFilter(...) in a bare SELECT returns a column named after the full
expression. transformEvent reads event.properties so the field was silently
dropped. Adding 'AS properties' restores the expected column name.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@CLAassistant

CLAassistant commented Jun 7, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 779797a9-3812-4285-b5ff-49b7b9d02db0

📥 Commits

Reviewing files that changed from the base of the PR and between 91762e5 and 61c2df6.

📒 Files selected for processing (2)
  • apps/api/src/controllers/export.controller.ts
  • packages/db/src/services/event.service.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/db/src/services/event.service.ts
  • apps/api/src/controllers/export.controller.ts

📝 Walkthrough

Walkthrough

The PR adds selective event properties export support to the /export/events API. A new optional property_keys query parameter is normalized into a string array, passed to the service layer, and used to conditionally filter the properties returned from ClickHouse.

Changes

Event Properties Selective Export

Layer / File(s) Summary
API schema and request handling
apps/api/src/controllers/export.controller.ts
eventsScheme normalizes includes and property_keys query parameters (comma-separated strings → trimmed arrays; null → undefined). The events controller destructures property_keys from request.query and passes it to getEventList as propertyKeys, while explicitly including properties: true in the select projection.
Service options and database query
packages/db/src/services/event.service.ts
GetEventListOptions adds optional propertyKeys?: string[] field. getEventList destructures this option and conditionally applies a mapFilter to the ClickHouse properties selection: when propertyKeys is a non-empty array, only those keys are returned; otherwise the full properties map is selected.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A question asked: where do properties hide?
Through schema and service, now they cannot hide!
Query the keys, filter the load,
Event properties travel the road! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(export): include properties field and add property_keys filter to /export/events' clearly and concisely summarizes the main changes: adding the missing properties field and the new property_keys filter parameter to the /export/events endpoint.
Linked Issues check ✅ Passed The PR fully addresses the requirements from issue #281: it restores the properties field to /export/events responses and adds a property_keys mechanism to limit returned property keys, matching both the documented API behavior and the issue's expectations.
Out of Scope Changes check ✅ Passed All changes are directly related to the stated objectives: both files modified (export.controller.ts and event.service.ts) only contain changes necessary to implement properties inclusion and filtering.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
packages/db/src/services/event.service.ts (1)

553-559: 💤 Low value

Consider deduplicating propertyKeys before constructing the filter.

If the caller passes duplicate keys in property_keys, the IN clause will contain duplicates. While this doesn't break functionality, deduplicating would make the query slightly more efficient.

♻️ Proposed refactor to deduplicate keys
   if (select.properties) {
     if (propertyKeys && propertyKeys.length > 0) {
-      const keys = propertyKeys.map((k) => sqlstring.escape(k)).join(', ');
+      const keys = [...new Set(propertyKeys)].map((k) => sqlstring.escape(k)).join(', ');
       sb.select.properties = `mapFilter((k, v) -> k IN (${keys}), properties) AS properties`;
     } else {
       sb.select.properties = 'properties';
     }
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/db/src/services/event.service.ts` around lines 553 - 559,
Deduplicate the propertyKeys array before building the IN clause so duplicates
aren't injected into sb.select.properties; e.g., within the block that checks
propertyKeys and propertyKeys.length, derive a uniqueKeys set (e.g., via
Array.from(new Set(propertyKeys)) or similar), then map and escape those
uniqueKeys when creating keys and the mapFilter expression assigned to
sb.select.properties (keep using sqlstring.escape, mapFilter and the same
expression structure).
apps/api/src/controllers/export.controller.ts (1)

86-109: ⚡ Quick win

Consider extracting the duplicate preprocessing logic into a helper function.

The preprocessing logic for includes and property_keys is identical. This duplication makes the code harder to maintain if the normalization logic needs to change.

♻️ Proposed refactor to eliminate duplication
+const preprocessCommaSeparatedArray = (arg: unknown) => {
+  if (arg == null) return undefined;
+  if (Array.isArray(arg)) return arg;
+  if (typeof arg === 'string')
+    return arg.split(',').map((s) => s.trim()).filter(Boolean);
+  return arg;
+};
+
 export const eventsScheme = z.object({
   project_id: z.string().optional(),
   projectId: z.string().optional(),
   profileId: z.string().optional(),
   event: z.union([z.string(), z.array(z.string())]).optional(),
   start: z.coerce.string().optional(),
   end: z.coerce.string().optional(),
   page: z.coerce.number().optional().default(1),
   limit: z.coerce.number().optional().default(50),
   filters: z
     .preprocess((value) => {
       if (value == null || value === '') return undefined;
       if (Array.isArray(value)) return value;
       if (typeof value === 'string') {
         try {
           return JSON.parse(value);
         } catch {
           return undefined;
         }
       }
       return value;
     }, z.array(zChartEventFilter))
     .optional(),
-  includes: z
-    .preprocess(
-      (arg) => {
-        if (arg == null) return undefined;
-        if (Array.isArray(arg)) return arg;
-        if (typeof arg === 'string')
-          return arg.split(',').map((s) => s.trim()).filter(Boolean);
-        return arg;
-      },
-      z.array(z.string()),
-    )
-    .optional(),
-  property_keys: z
-    .preprocess(
-      (arg) => {
-        if (arg == null) return undefined;
-        if (Array.isArray(arg)) return arg;
-        if (typeof arg === 'string')
-          return arg.split(',').map((s) => s.trim()).filter(Boolean);
-        return arg;
-      },
-      z.array(z.string()),
-    )
-    .optional(),
+  includes: z.preprocess(preprocessCommaSeparatedArray, z.array(z.string())).optional(),
+  property_keys: z.preprocess(preprocessCommaSeparatedArray, z.array(z.string())).optional(),
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/controllers/export.controller.ts` around lines 86 - 109, Extract
the duplicated preprocessing function used for the includes and property_keys
z.preprocess calls into a single helper (e.g., preprocessToStringArray or
normalizeArrayInput) and replace both inline arrow functions with that helper;
the helper should accept the raw arg, return undefined for null/undefined,
return the arg if already an array, split a string by commas trimming/filtering
empty entries, and otherwise return the arg so it can be validated by
z.array(z.string()); define the helper near the schema (or export it if reused
elsewhere) and then call z.preprocess(preprocessToStringArray,
z.array(z.string())).optional() for both includes and property_keys.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@apps/api/src/controllers/export.controller.ts`:
- Around line 86-109: Extract the duplicated preprocessing function used for the
includes and property_keys z.preprocess calls into a single helper (e.g.,
preprocessToStringArray or normalizeArrayInput) and replace both inline arrow
functions with that helper; the helper should accept the raw arg, return
undefined for null/undefined, return the arg if already an array, split a string
by commas trimming/filtering empty entries, and otherwise return the arg so it
can be validated by z.array(z.string()); define the helper near the schema (or
export it if reused elsewhere) and then call
z.preprocess(preprocessToStringArray, z.array(z.string())).optional() for both
includes and property_keys.

In `@packages/db/src/services/event.service.ts`:
- Around line 553-559: Deduplicate the propertyKeys array before building the IN
clause so duplicates aren't injected into sb.select.properties; e.g., within the
block that checks propertyKeys and propertyKeys.length, derive a uniqueKeys set
(e.g., via Array.from(new Set(propertyKeys)) or similar), then map and escape
those uniqueKeys when creating keys and the mapFilter expression assigned to
sb.select.properties (keep using sqlstring.escape, mapFilter and the same
expression structure).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: adab4330-650e-4104-9d03-d45d046303df

📥 Commits

Reviewing files that changed from the base of the PR and between b94d256 and 91762e5.

📒 Files selected for processing (2)
  • apps/api/src/controllers/export.controller.ts
  • packages/db/src/services/event.service.ts

…ess helper

- Deduplicate propertyKeys before building ClickHouse IN clause to avoid
  redundant keys in the SQL query
- Extract repeated comma-separated array preprocessing logic into a shared
  helper to eliminate duplication between includes and property_keys fields

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.

/export/events API does not include event properties although documented

2 participants