ga4-export-fixer 0.8.0-dev.1 → 0.8.0

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.8.0-dev.1",
3
+ "version": "0.8.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -17,7 +17,7 @@
17
17
  "createTable.js"
18
18
  ],
19
19
  "scripts": {
20
- "test": "node tests/ga4EventsEnhanced.test.js && node tests/assertions.test.js && node tests/mergeSQLConfigurations.test.js && node tests/preOperations.test.js && node tests/documentation.test.js && node tests/inputValidation.test.js && node tests/createTable.test.js && node tests/queryBuilder.test.js",
20
+ "test": "node tests/ga4EventsEnhanced.test.js && node tests/assertions.test.js && node tests/mergeSQLConfigurations.test.js && node tests/preOperations.test.js && node tests/documentation.test.js && node tests/inputValidation.test.js && node tests/createTable.test.js && node tests/queryBuilder.test.js && node tests/customSteps.test.js",
21
21
  "test:summary": "node tests/testRunner.js",
22
22
  "test:docs": "node tests/documentation.test.js",
23
23
  "test:preops": "node tests/preOperations.test.js",
@@ -27,6 +27,7 @@
27
27
  "test:assertions": "node tests/assertions.test.js",
28
28
  "test:createTable": "node tests/createTable.test.js",
29
29
  "test:queryBuilder": "node tests/queryBuilder.test.js",
30
+ "test:customSteps": "node tests/customSteps.test.js",
30
31
  "test:integration": "node tests/integration/integration.test.js",
31
32
  "release:dev": "./scripts/release-dev.sh",
32
33
  "readme": "node scripts/updateReadme.js",
@@ -1,70 +1,73 @@
1
- const { baseConfig } = require('../../defaultConfig.js');
2
-
3
- /*
4
- The default configuration for the GA4 Events Enhanced table.
5
- */
6
- const ga4EventsEnhancedConfig = {
7
- ...baseConfig,
8
- sourceTable: undefined,
9
- sourceTableType: 'GA4_EXPORT', // used with pre operations to detect if ga4 export specific pre operations are needed
10
- // optional but recommended
11
- schemaLock: undefined,
12
- // only used with js tables
13
- dataformTableConfig: {
14
- type: 'incremental',
15
- bigquery: {
16
- partitionBy: 'event_date',
17
- clusterBy: ['event_name', 'session_id', 'page_location', 'data_is_final'],
18
- labels: {
19
- 'ga4_export_fixer': 'true'
20
- }
21
- },
22
- onSchemaChange: 'EXTEND',
23
- tags: ['ga4_export_fixer'],
24
- },
25
- // optional
26
- includedExportTypes: {
27
- daily: true,
28
- fresh: false,
29
- intraday: true,
30
- },
31
- timezone: 'Etc/UTC',
32
- customTimestampParam: undefined,
33
- dataIsFinal: {
34
- detectionMethod: 'DAY_THRESHOLD', // 'EXPORT_TYPE' or 'DAY_THRESHOLD'
35
- dayThreshold: 3 // only used if detectionMethod is 'DAY_THRESHOLD'
36
- // according to GA4 documentation, the data up to 72 hours old is subject to possible changes
37
- // in reality, there have been cases where the data has changed even after 72 hours (4 day window would have covered these)
38
- },
39
- // optional item list attribution - disabled by default (compute-heavy, only useful for ecommerce sites)
40
- itemListAttribution: undefined,
41
- // number of additional days to take in for taking into account sessions that overlap days
42
- bufferDays: 1,
43
- // these parameters are excluded by default because they've been made available in other columns
44
- defaultExcludedEventParams: [
45
- 'page_location',
46
- 'ga_session_id',
47
- //'custom_event_timestamp', // removed if customTimestampParam is used
48
- ],
49
- excludedEventParams: [],
50
- eventParamsToColumns: [
51
- //{name: 'page_location', type: 'string', columnName: 'page_location2'},
52
- ],
53
- sessionParams: [],
54
- defaultExcludedEvents: [],
55
- // session_start and first_visit are excluded via the excludedEvents array
56
- // this allows the user to include them if needed
57
- excludedEvents: [
58
- 'session_start',
59
- 'first_visit'
60
- ],
61
- defaultExcludedColumns: [
62
- 'event_dimensions', // legacy column, not needed
63
- 'traffic_source', // renamed to user_traffic_source
64
- 'session_id'
65
- ],
66
- // exclude these columns when extracting raw data from the export tables
67
- excludedColumns: [],
68
- };
69
-
70
- module.exports = { ga4EventsEnhancedConfig };
1
+ const { baseConfig } = require('../../defaultConfig.js');
2
+
3
+ /*
4
+ The default configuration for the GA4 Events Enhanced table.
5
+ */
6
+ const ga4EventsEnhancedConfig = {
7
+ ...baseConfig,
8
+ sourceTable: undefined,
9
+ sourceTableType: 'GA4_EXPORT', // used with pre operations to detect if ga4 export specific pre operations are needed
10
+ // optional but recommended
11
+ schemaLock: undefined,
12
+ // only used with js tables
13
+ dataformTableConfig: {
14
+ type: 'incremental',
15
+ bigquery: {
16
+ partitionBy: 'event_date',
17
+ clusterBy: ['event_name', 'session_id', 'page_location', 'data_is_final'],
18
+ labels: {
19
+ 'ga4_export_fixer': 'true'
20
+ }
21
+ },
22
+ onSchemaChange: 'EXTEND',
23
+ tags: ['ga4_export_fixer'],
24
+ },
25
+ // optional
26
+ includedExportTypes: {
27
+ daily: true,
28
+ fresh: false,
29
+ intraday: true,
30
+ },
31
+ timezone: 'Etc/UTC',
32
+ customTimestampParam: undefined,
33
+ dataIsFinal: {
34
+ detectionMethod: 'DAY_THRESHOLD', // 'EXPORT_TYPE' or 'DAY_THRESHOLD'
35
+ dayThreshold: 3 // only used if detectionMethod is 'DAY_THRESHOLD'
36
+ // according to GA4 documentation, the data up to 72 hours old is subject to possible changes
37
+ // in reality, there have been cases where the data has changed even after 72 hours (4 day window would have covered these)
38
+ },
39
+ // optional item list attribution - disabled by default (compute-heavy, only useful for ecommerce sites)
40
+ itemListAttribution: undefined,
41
+ // number of additional days to take in for taking into account sessions that overlap days
42
+ bufferDays: 1,
43
+ // these parameters are excluded by default because they've been made available in other columns
44
+ defaultExcludedEventParams: [
45
+ 'page_location',
46
+ 'ga_session_id',
47
+ //'custom_event_timestamp', // removed if customTimestampParam is used
48
+ ],
49
+ excludedEventParams: [],
50
+ eventParamsToColumns: [
51
+ //{name: 'page_location', type: 'string', columnName: 'page_location2'},
52
+ ],
53
+ sessionParams: [],
54
+ defaultExcludedEvents: [],
55
+ // session_start and first_visit are excluded via the excludedEvents array
56
+ // this allows the user to include them if needed
57
+ excludedEvents: [
58
+ 'session_start',
59
+ 'first_visit'
60
+ ],
61
+ defaultExcludedColumns: [
62
+ 'event_dimensions', // legacy column, not needed
63
+ 'traffic_source', // renamed to user_traffic_source
64
+ 'session_id'
65
+ ],
66
+ // exclude these columns when extracting raw data from the export tables
67
+ excludedColumns: [],
68
+ // user-defined CTEs appended to the pipeline after enhanced_events
69
+ // each entry is a queryBuilder step (raw {name, query} or structured {name, select, from, ...})
70
+ customSteps: [],
71
+ };
72
+
73
+ module.exports = { ga4EventsEnhancedConfig };
@@ -324,8 +324,9 @@ ${excludedEventsSQL}`,
324
324
  const itemListExcludedColumns = itemListSteps ? ['_item_list_attribution_row_id'] : [];
325
325
 
326
326
  // Join event_data and session_data, include additional logic
327
- const finalStep = {
328
- name: 'final',
327
+ // Named 'enhanced_events' so user-supplied customSteps can reference it as a stable handle.
328
+ const enhancedEventsStep = {
329
+ name: 'enhanced_events',
329
330
  select: {
330
331
  columns: {
331
332
  // get the most important columns in the correct order
@@ -371,13 +372,31 @@ ${excludedEventsSQL}`,
371
372
  where: helpers.incrementalDateFilter(mergedConfig)
372
373
  };
373
374
 
374
- const steps = [
375
+ const packageSteps = [
375
376
  eventDataStep,
376
377
  ...(itemListSteps ?? []),
377
378
  sessionDataStep,
378
- finalStep,
379
+ enhancedEventsStep,
379
380
  ];
380
381
 
382
+ // Layer 2 validation: customSteps name must not collide with package step names.
383
+ // Reserved set is derived from packageSteps at runtime (single source of truth) — what
384
+ // is reserved depends on config (e.g. item_list_* exist only when itemListAttribution is on).
385
+ const customSteps = mergedConfig.customSteps ?? [];
386
+ if (customSteps.length > 0) {
387
+ const reservedNames = new Set(packageSteps.map(s => s.name));
388
+ for (const [i, step] of customSteps.entries()) {
389
+ if (reservedNames.has(step.name)) {
390
+ throw new Error(
391
+ `config.customSteps[${i}].name '${step.name}' collides with a reserved package CTE name. ` +
392
+ `Reserved names (active for this config): ${[...reservedNames].join(', ')}. Choose a different name.`
393
+ );
394
+ }
395
+ }
396
+ }
397
+
398
+ const steps = [...packageSteps, ...customSteps];
399
+
381
400
  return utils.queryBuilder(steps);
382
401
  };
383
402