ga4-export-fixer 0.9.0-dev.15 → 0.9.0-dev.17
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/documentation.js
CHANGED
|
@@ -173,8 +173,8 @@ const getColumnDescriptions = (config, columnMetadata) => {
|
|
|
173
173
|
// Item-level enrichments are not yet supported and throw at SQL gen time — skip here.
|
|
174
174
|
if (config && Array.isArray(config.enrichments) && config.enrichments.length > 0) {
|
|
175
175
|
config.enrichments.forEach(e => {
|
|
176
|
-
const level = e.level ?? '
|
|
177
|
-
if (level !== '
|
|
176
|
+
const level = e.level ?? 'row';
|
|
177
|
+
if (level !== 'row') return;
|
|
178
178
|
const joinKeys = Array.isArray(e.joinKey) ? e.joinKey : [e.joinKey];
|
|
179
179
|
const joinKeyText = joinKeys.join(', ');
|
|
180
180
|
const sourceText = renderEnrichmentSource(e.source);
|
package/package.json
CHANGED
|
@@ -69,8 +69,9 @@ const ga4EventsEnhancedConfig = {
|
|
|
69
69
|
// each entry is a queryBuilder step (raw {name, query} or structured {name, select, from, ...})
|
|
70
70
|
customSteps: [],
|
|
71
71
|
// declarative external-data enrichments joined into the pipeline
|
|
72
|
-
// each entry: { name,
|
|
73
|
-
//
|
|
72
|
+
// each entry: { name, source, joinKey, columns, level?, dedupe? }
|
|
73
|
+
// `level` is optional — defaults to 'row' (one row of the enclosing table per join match).
|
|
74
|
+
// 'item' targets the items[] array (GA4-specific, ecommerce events only).
|
|
74
75
|
enrichments: [],
|
|
75
76
|
};
|
|
76
77
|
|
|
@@ -164,10 +164,10 @@ const _generateEnhancedEventsSQL = (mergedConfig) => {
|
|
|
164
164
|
const itemListAttribution = mergedConfig.itemListAttribution;
|
|
165
165
|
|
|
166
166
|
// Build enrichment-source CTEs and gather per-level join/column data. The utility routes
|
|
167
|
-
//
|
|
167
|
+
// row-level and item-level entries through separate output channels. Done up here so the
|
|
168
168
|
// items-scaffold activation state is known before building event_data (which needs
|
|
169
169
|
// _item_row_id when the scaffold is active for any reason).
|
|
170
|
-
const { steps: enrichmentSteps,
|
|
170
|
+
const { steps: enrichmentSteps, row: rowEnrichments, item: itemEnrichments }
|
|
171
171
|
= utils.buildEnrichments(mergedConfig.enrichments);
|
|
172
172
|
const itemEnrichmentsActive = itemEnrichments.joins.length > 0;
|
|
173
173
|
const itemsScaffoldActive = !!itemListAttribution || itemEnrichmentsActive;
|
|
@@ -279,7 +279,7 @@ ${excludedEventsSQL}`,
|
|
|
279
279
|
// items_unnested and need no extension.
|
|
280
280
|
const itemJoinKeysFromEventData = new Set();
|
|
281
281
|
for (const [i, e] of (mergedConfig.enrichments ?? []).entries()) {
|
|
282
|
-
const level = e.level ?? '
|
|
282
|
+
const level = e.level ?? 'row';
|
|
283
283
|
if (level !== 'item') continue;
|
|
284
284
|
const joinKeys = Array.isArray(e.joinKey) ? e.joinKey : [e.joinKey];
|
|
285
285
|
for (const c of joinKeys) {
|
|
@@ -424,13 +424,13 @@ ${excludedEventsSQL}`,
|
|
|
424
424
|
} : {};
|
|
425
425
|
const itemListExcludedColumns = itemListSteps ? ['_item_row_id'] : [];
|
|
426
426
|
|
|
427
|
-
// Wrap overlapping
|
|
427
|
+
// Wrap overlapping row-level enrichment columns in coalesce(enrich_<name>.<col>, <original>)
|
|
428
428
|
// so a missed JOIN falls back to the existing value. Purely additive columns (no overlap)
|
|
429
429
|
// pass through unchanged. Source-of-original precedence matches the final SELECT's spread
|
|
430
430
|
// order: itemListOverrides first (overrides finalColumnOrder for `items`), then
|
|
431
431
|
// session_data (wins over event_data in getFinalColumnOrder when both have the column).
|
|
432
|
-
const
|
|
433
|
-
for (const [col, enrichExpr] of Object.entries(
|
|
432
|
+
const wrappedRowEnrichmentColumns = {};
|
|
433
|
+
for (const [col, enrichExpr] of Object.entries(rowEnrichments.columns)) {
|
|
434
434
|
let originalExpr;
|
|
435
435
|
if (col in itemListOverrides) {
|
|
436
436
|
originalExpr = itemListOverrides[col];
|
|
@@ -439,7 +439,7 @@ ${excludedEventsSQL}`,
|
|
|
439
439
|
} else if (col in eventDataStep.select.columns && eventDataStep.select.columns[col] !== undefined) {
|
|
440
440
|
originalExpr = `event_data.${col}`;
|
|
441
441
|
}
|
|
442
|
-
|
|
442
|
+
wrappedRowEnrichmentColumns[col] = originalExpr
|
|
443
443
|
? `coalesce(${enrichExpr}, ${originalExpr})`
|
|
444
444
|
: enrichExpr;
|
|
445
445
|
}
|
|
@@ -449,7 +449,7 @@ ${excludedEventsSQL}`,
|
|
|
449
449
|
const alreadyMapped = [
|
|
450
450
|
...Object.keys(finalColumnOrder),
|
|
451
451
|
...Object.keys(itemListOverrides),
|
|
452
|
-
...
|
|
452
|
+
...rowEnrichments.columnNames,
|
|
453
453
|
'entrances',
|
|
454
454
|
mergedConfig.sessionParams.length > 0 ? 'session_params_prep' : undefined,
|
|
455
455
|
'data_is_final',
|
|
@@ -466,8 +466,8 @@ ${excludedEventsSQL}`,
|
|
|
466
466
|
// get the most important columns in the correct order
|
|
467
467
|
...finalColumnOrder,
|
|
468
468
|
...itemListOverrides,
|
|
469
|
-
//
|
|
470
|
-
...
|
|
469
|
+
// row-level enrichment columns: coalesce with the original when overlapping; otherwise add.
|
|
470
|
+
...wrappedRowEnrichmentColumns,
|
|
471
471
|
// explicit pass-throughs for the rest of event_data and session_data
|
|
472
472
|
...utils.buildQualifiedPassThroughs(eventDataStep, alreadyMapped),
|
|
473
473
|
...utils.buildQualifiedPassThroughs(sessionDataStep, alreadyMapped),
|
|
@@ -489,8 +489,8 @@ ${excludedEventsSQL}`,
|
|
|
489
489
|
table: 'session_data',
|
|
490
490
|
on: 'using(session_id)'
|
|
491
491
|
},
|
|
492
|
-
// The left joins for the
|
|
493
|
-
...
|
|
492
|
+
// The left joins for the row-level enrichment ctes
|
|
493
|
+
...rowEnrichments.joins,
|
|
494
494
|
],
|
|
495
495
|
where: helpers.incrementalDateFilter(mergedConfig)
|
|
496
496
|
};
|
|
@@ -234,7 +234,7 @@ const validateEnhancedEventsConfig = (config, options = {}) => {
|
|
|
234
234
|
if (!Array.isArray(config.enrichments)) {
|
|
235
235
|
throw new Error(`config.enrichments must be an array. Received: ${JSON.stringify(config.enrichments)}`);
|
|
236
236
|
}
|
|
237
|
-
const validLevels = ['
|
|
237
|
+
const validLevels = ['row', 'item'];
|
|
238
238
|
const seenNames = new Set();
|
|
239
239
|
for (let i = 0; i < config.enrichments.length; i++) {
|
|
240
240
|
const entry = config.enrichments[i];
|
package/utils.js
CHANGED
|
@@ -515,25 +515,27 @@ const buildPassThroughs = (explicitColumns, sourceColumns) => {
|
|
|
515
515
|
|
|
516
516
|
/**
|
|
517
517
|
* Builds the per-enrichment CTE definitions, JOIN clauses, and column-name mappings for the
|
|
518
|
-
* declarative `enrichments` feature. Routes
|
|
518
|
+
* declarative `enrichments` feature. Routes row-level and item-level entries through
|
|
519
519
|
* separate output channels so the caller can attach them to different downstream CTEs.
|
|
520
520
|
*
|
|
521
521
|
* Pure config-to-data mapping. No knowledge of downstream CTEs or specific table modules —
|
|
522
522
|
* intended to be called by any table module that exposes an `enrichments` config field.
|
|
523
523
|
*
|
|
524
524
|
* Encapsulates one generation-time throw:
|
|
525
|
-
* - Same-level enrichment-vs-enrichment column collisions (two
|
|
525
|
+
* - Same-level enrichment-vs-enrichment column collisions (two row-level enrichments or
|
|
526
526
|
* two item-level enrichments targeting the same column). Cross-level same-name is allowed —
|
|
527
|
-
* the two columns target structurally distinct slots (`enhanced_events.<col>` vs
|
|
527
|
+
* the two columns target structurally distinct slots (e.g. `enhanced_events.<col>` vs
|
|
528
528
|
* `items[].<col>`).
|
|
529
529
|
*
|
|
530
530
|
* @param {Array<Object>} enrichments - Validated enrichment entries. Each entry has fields:
|
|
531
|
-
* { name,
|
|
531
|
+
* { name, source, joinKey, columns, level?, dedupe? }. `level` is 'row' (default) or 'item'.
|
|
532
|
+
* 'row' means one row of the enclosing table per join match; 'item' targets a nested array
|
|
533
|
+
* (currently only the GA4 items[] array).
|
|
532
534
|
* @returns {Object} A struct with four fields:
|
|
533
535
|
* - `steps` — array of queryBuilder source-CTE step definitions (one `enrich_<name>` per
|
|
534
536
|
* entry, regardless of level — all source CTEs go to the top of the pipeline).
|
|
535
|
-
* - `
|
|
536
|
-
* `joins` to the
|
|
537
|
+
* - `row` — { joins, columns, columnNames } for row-level enrichments. Caller attaches
|
|
538
|
+
* `joins` to the row-grained downstream CTE (e.g. `enhanced_events`) and spreads `columns`
|
|
537
539
|
* into that CTE's `select.columns`.
|
|
538
540
|
* - `item` — { joins, columns, columnNames } for item-level enrichments. Caller attaches
|
|
539
541
|
* `joins` to the item-grained downstream CTE (e.g. `items_rebuilt`) and folds `columns`
|
|
@@ -545,20 +547,20 @@ const buildPassThroughs = (explicitColumns, sourceColumns) => {
|
|
|
545
547
|
* enrichment names and the conflicting column in the error message).
|
|
546
548
|
*
|
|
547
549
|
* @example
|
|
548
|
-
* const { steps,
|
|
549
|
-
* //
|
|
550
|
+
* const { steps, row, item } = buildEnrichments(config.enrichments);
|
|
551
|
+
* // row.joins → attach to enhanced_events; row.columns → spread into enhanced_events
|
|
550
552
|
* // item.joins → attach to items_rebuilt; item.columns → fold into items struct
|
|
551
553
|
*/
|
|
552
554
|
const buildEnrichments = (enrichments) => {
|
|
553
555
|
const steps = [];
|
|
554
556
|
const channels = {
|
|
555
|
-
|
|
557
|
+
row: { joins: [], columns: {}, columnNames: new Set() },
|
|
556
558
|
item: { joins: [], columns: {}, columnNames: new Set() },
|
|
557
559
|
};
|
|
558
560
|
const columnOwner = {};
|
|
559
561
|
|
|
560
562
|
for (const [i, e] of (enrichments ?? []).entries()) {
|
|
561
|
-
const level = e.level ?? '
|
|
563
|
+
const level = e.level ?? 'row';
|
|
562
564
|
const channel = channels[level];
|
|
563
565
|
const joinKeys = Array.isArray(e.joinKey) ? e.joinKey : [e.joinKey];
|
|
564
566
|
const cteName = `enrich_${e.name}`;
|
|
@@ -598,7 +600,7 @@ const buildEnrichments = (enrichments) => {
|
|
|
598
600
|
}
|
|
599
601
|
}
|
|
600
602
|
|
|
601
|
-
return { steps,
|
|
603
|
+
return { steps, row: channels.row, item: channels.item, columnOwner };
|
|
602
604
|
};
|
|
603
605
|
|
|
604
606
|
|