ga4-export-fixer 0.5.2-dev.3 → 0.5.2-dev.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ga4-export-fixer",
3
- "version": "0.5.2-dev.3",
3
+ "version": "0.5.2-dev.4",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -161,6 +161,9 @@ const _generateEnhancedEventsSQL = (mergedConfig) => {
161
161
 
162
162
  // item list attribution config
163
163
  const itemListAttribution = mergedConfig.itemListAttribution;
164
+ const ecommerceEventsFilter = itemListAttribution
165
+ ? helpers.ga4EcommerceEvents.filter(e => e !== 'refund').map(e => `'${e}'`).join(', ')
166
+ : null;
164
167
 
165
168
  // auto-adjust bufferDays for time-based item list attribution lookback
166
169
  const effectiveBufferDays = (itemListAttribution && itemListAttribution.lookbackType === 'TIME')
@@ -225,11 +228,12 @@ const _generateEnhancedEventsSQL = (mergedConfig) => {
225
228
  // ecommerce
226
229
  ecommerce: helpers.fixEcommerceStruct('ecommerce'),
227
230
  items: 'items',
228
- // unique row id for item list attribution join.
229
- // row_number() over() breaks hash collisions for batched events with identical data.
231
+ // unique row id for item list attribution join. Only computed for ecommerce events.
232
+ // row_number() breaks hash collisions for batched events with identical data.
233
+ // partition by event_name avoids a single-partition bottleneck in the window function.
230
234
  // Non-determinism is safe: colliding rows have identical items (to_json_string(items) is in the hash),
231
235
  // so swapping row numbers between them produces the same final result.
232
- _event_row_id: itemListAttribution ? `farm_fingerprint(concat(user_pseudo_id, cast(event_timestamp as string), event_name, to_json_string(items), cast(row_number() over() as string)))` : undefined,
236
+ _item_list_attribution_row_id: itemListAttribution ? `if(event_name in (${ecommerceEventsFilter}), farm_fingerprint(concat(user_pseudo_id, cast(event_timestamp as string), event_name, to_json_string(items), cast(row_number() over(partition by event_name, user_pseudo_id) as string))), null)` : undefined,
233
237
  // flag if the data is "final" and is not expected to change anymore
234
238
  data_is_final: helpers.isFinalData(mergedConfig.dataIsFinal.detectionMethod, mergedConfig.dataIsFinal.dayThreshold),
235
239
  export_type: helpers.getGa4ExportType('_table_suffix'),
@@ -272,12 +276,11 @@ ${excludedEventsSQL}`,
272
276
  itemListAttribution.lookbackTimeMs
273
277
  );
274
278
  const passthroughEvents = `event_name in ('view_item_list', 'select_item', 'view_promotion', 'select_promotion')`;
275
- const ecommerceFilter = helpers.ga4EcommerceEvents.filter(e => e !== 'refund').map(e => `'${e}'`).join(', ');
276
279
 
277
280
  return {
278
281
  name: 'item_list_data',
279
282
  columns: {
280
- '_event_row_id': '_event_row_id',
283
+ '_item_list_attribution_row_id': '_item_list_attribution_row_id',
281
284
  'items': `array_agg(
282
285
  (select as struct item.* replace(
283
286
  coalesce(if(${passthroughEvents}, item.item_list_name, _item_list_attr.item_list_name), '(not set)') as item_list_name,
@@ -286,19 +289,19 @@ ${excludedEventsSQL}`,
286
289
  ))
287
290
  )`,
288
291
  },
289
- from: `(select _event_row_id, event_name, item, ${attrExpr} as _item_list_attr from event_data, unnest(items) as item where event_name in (${ecommerceFilter}))`,
290
- groupBy: ['_event_row_id'],
292
+ from: `(select _item_list_attribution_row_id, event_name, item, ${attrExpr} as _item_list_attr from event_data, unnest(items) as item where event_name in (${ecommerceEventsFilter}))`,
293
+ groupBy: ['_item_list_attribution_row_id'],
291
294
  };
292
295
  })() : null;
293
296
 
294
297
  const finalColumnOrder = getFinalColumnOrder(eventDataStep, sessionDataStep);
295
298
 
296
- // When item list attribution is enabled, override the items column and exclude _event_row_id
299
+ // When item list attribution is enabled, override the items column and exclude _item_list_attribution_row_id
297
300
  // COALESCE handles events without items (not in ecommerce filter) where the LEFT JOIN returns NULL
298
301
  const itemListOverrides = itemListDataStep ? {
299
302
  items: 'coalesce(item_list_data.items, event_data.items)',
300
303
  } : {};
301
- const itemListExcludedColumns = itemListDataStep ? ['_event_row_id'] : [];
304
+ const itemListExcludedColumns = itemListDataStep ? ['_item_list_attribution_row_id'] : [];
302
305
 
303
306
  // Join event_data and session_data, include additional logic
304
307
  const finalStep = {
@@ -334,7 +337,7 @@ ${excludedEventsSQL}`,
334
337
  leftJoin: [
335
338
  ...(itemListDataStep ? [{
336
339
  table: 'item_list_data',
337
- condition: 'using(_event_row_id)'
340
+ condition: 'using(_item_list_attribution_row_id)'
338
341
  }] : []),
339
342
  {
340
343
  table: 'session_data',