ga4-export-fixer 0.5.2-dev.2 → 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.2",
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,8 +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
- _event_row_id: itemListAttribution ? `farm_fingerprint(concat(user_pseudo_id, cast(event_timestamp as string), event_name, to_json_string(items)))` : undefined,
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.
234
+ // Non-determinism is safe: colliding rows have identical items (to_json_string(items) is in the hash),
235
+ // so swapping row numbers between them produces the same final result.
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,
230
237
  // flag if the data is "final" and is not expected to change anymore
231
238
  data_is_final: helpers.isFinalData(mergedConfig.dataIsFinal.detectionMethod, mergedConfig.dataIsFinal.dayThreshold),
232
239
  export_type: helpers.getGa4ExportType('_table_suffix'),
@@ -269,12 +276,11 @@ ${excludedEventsSQL}`,
269
276
  itemListAttribution.lookbackTimeMs
270
277
  );
271
278
  const passthroughEvents = `event_name in ('view_item_list', 'select_item', 'view_promotion', 'select_promotion')`;
272
- const ecommerceFilter = helpers.ga4EcommerceEvents.filter(e => e !== 'refund').map(e => `'${e}'`).join(', ');
273
279
 
274
280
  return {
275
281
  name: 'item_list_data',
276
282
  columns: {
277
- '_event_row_id': '_event_row_id',
283
+ '_item_list_attribution_row_id': '_item_list_attribution_row_id',
278
284
  'items': `array_agg(
279
285
  (select as struct item.* replace(
280
286
  coalesce(if(${passthroughEvents}, item.item_list_name, _item_list_attr.item_list_name), '(not set)') as item_list_name,
@@ -283,19 +289,19 @@ ${excludedEventsSQL}`,
283
289
  ))
284
290
  )`,
285
291
  },
286
- 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}))`,
287
- 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'],
288
294
  };
289
295
  })() : null;
290
296
 
291
297
  const finalColumnOrder = getFinalColumnOrder(eventDataStep, sessionDataStep);
292
298
 
293
- // 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
294
300
  // COALESCE handles events without items (not in ecommerce filter) where the LEFT JOIN returns NULL
295
301
  const itemListOverrides = itemListDataStep ? {
296
302
  items: 'coalesce(item_list_data.items, event_data.items)',
297
303
  } : {};
298
- const itemListExcludedColumns = itemListDataStep ? ['_event_row_id'] : [];
304
+ const itemListExcludedColumns = itemListDataStep ? ['_item_list_attribution_row_id'] : [];
299
305
 
300
306
  // Join event_data and session_data, include additional logic
301
307
  const finalStep = {
@@ -331,7 +337,7 @@ ${excludedEventsSQL}`,
331
337
  leftJoin: [
332
338
  ...(itemListDataStep ? [{
333
339
  table: 'item_list_data',
334
- condition: 'using(_event_row_id)'
340
+ condition: 'using(_item_list_attribution_row_id)'
335
341
  }] : []),
336
342
  {
337
343
  table: 'session_data',