ga4-export-fixer 0.2.3-dev.0 → 0.2.3-dev.1

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.
@@ -0,0 +1,369 @@
1
+ {
2
+ "event_date": "Date of the event, cast to DATE from the original YYYYMMDD string",
3
+ "event_datetime": "Event datetime converted to the configured timezone from event_timestamp (or custom timestamp if configured)",
4
+ "event_timestamp": "Time in microseconds (UTC) when the event was logged by Google Analytics",
5
+ "event_custom_timestamp": "Event timestamp in microseconds derived from a custom event parameter (e.g. collected via Date.now()), falling back to event_timestamp when the custom parameter is null. Only present when customTimestampParam is configured",
6
+ "event_name": "Name of the event (e.g. page_view, purchase, scroll)",
7
+ "session_id": "Unique session identifier, constructed by concatenating user_pseudo_id with the ga_session_id event parameter",
8
+ "user_pseudo_id": "Pseudonymous user identifier (e.g. app instance ID or client ID) assigned when the user first opens the app or visits the site",
9
+ "user_id": "Last non-null user_id observed within the session, ordered by timestamp. Reflects the most recent authenticated identity for the session",
10
+ "merged_user_id": "Coalesced identifier: uses the session-level user_id if available, otherwise falls back to user_pseudo_id",
11
+ "page_location": "Full page URL extracted from the page_location event parameter",
12
+ "page": {
13
+ "description": "Parsed page details struct derived from the page_location event parameter",
14
+ "columns": {
15
+ "hostname": "Hostname extracted from the page URL (e.g. www.example.com)",
16
+ "path": "URL path component with scheme, hostname, query string, and fragment removed",
17
+ "query": "Raw query string including the leading '?' character, if present",
18
+ "query_params": {
19
+ "description": "Parsed query parameters as an array of key-value pairs",
20
+ "columns": {
21
+ "key": "Query parameter name",
22
+ "value": "Query parameter value"
23
+ }
24
+ }
25
+ }
26
+ },
27
+ "landing_page": {
28
+ "description": "Session landing page: the first page struct (where entrances > 0) within the session, ordered by timestamp",
29
+ "columns": {
30
+ "hostname": "Hostname of the landing page",
31
+ "path": "Path of the landing page",
32
+ "query": "Query string of the landing page",
33
+ "query_params": {
34
+ "description": "Parsed query parameters of the landing page URL",
35
+ "columns": {
36
+ "key": "Query parameter name",
37
+ "value": "Query parameter value"
38
+ }
39
+ }
40
+ }
41
+ },
42
+ "event_params": {
43
+ "description": "Event parameters array. Excluded parameters (from excludedEventParams and promoted parameters) are filtered out",
44
+ "columns": {
45
+ "key": "Name of the event parameter",
46
+ "value": {
47
+ "description": "A record containing the event parameter value",
48
+ "columns": {
49
+ "string_value": "String value of the event parameter",
50
+ "int_value": "Integer value of the event parameter",
51
+ "float_value": "Float value of the event parameter",
52
+ "double_value": "Double value of the event parameter"
53
+ }
54
+ }
55
+ }
56
+ },
57
+ "session_params": {
58
+ "description": "Session-level parameters aggregated from event_params. For each configured session parameter, the last non-null value within the session is kept. Empty array if no sessionParams are configured",
59
+ "columns": {
60
+ "key": "Name of the session parameter",
61
+ "value": {
62
+ "description": "A record containing the session parameter value",
63
+ "columns": {
64
+ "string_value": "String value of the session parameter",
65
+ "int_value": "Integer value of the session parameter",
66
+ "float_value": "Float value of the session parameter",
67
+ "double_value": "Double value of the session parameter"
68
+ }
69
+ }
70
+ }
71
+ },
72
+ "user_properties": {
73
+ "description": "User properties set via the Google Analytics SDK or gtag API",
74
+ "columns": {
75
+ "key": "Name of the user property",
76
+ "value": {
77
+ "description": "A record containing the user property value",
78
+ "columns": {
79
+ "string_value": "String value of the user property",
80
+ "int_value": "Integer value of the user property",
81
+ "double_value": "Double value of the user property",
82
+ "float_value": "Float value of the user property (currently unused by GA4)",
83
+ "set_timestamp_micros": "Time in microseconds at which the user property was last set"
84
+ }
85
+ }
86
+ }
87
+ },
88
+ "ecommerce": {
89
+ "description": "Ecommerce data with fixes applied: transaction_id is nullified when set to '(not set)', and purchase_revenue is corrected for NaN values and a legacy GA4 bug where it was missing on purchase events (falls back to the 'value' event parameter)",
90
+ "columns": {
91
+ "total_item_quantity": "Total number of items in this event (sum of items.quantity)",
92
+ "purchase_revenue_in_usd": "Purchase revenue in USD. Populated for purchase events only",
93
+ "purchase_revenue": "Purchase revenue in local currency, with NaN and missing-value fixes applied",
94
+ "refund_value_in_usd": "Refund amount in USD. Populated for refund events only",
95
+ "refund_value": "Refund amount in local currency. Populated for refund events only",
96
+ "shipping_value_in_usd": "Shipping cost in USD",
97
+ "shipping_value": "Shipping cost in local currency",
98
+ "tax_value_in_usd": "Tax value in USD",
99
+ "tax_value": "Tax value in local currency",
100
+ "transaction_id": "Transaction ID of the ecommerce transaction. Nullified when the original value is '(not set)'",
101
+ "unique_items": "Number of unique items in this event, based on item_id, item_name, and item_brand"
102
+ }
103
+ },
104
+ "items": {
105
+ "description": "Array of items associated with the event (e.g. products in a purchase or product list)",
106
+ "columns": {
107
+ "item_id": "ID of the item",
108
+ "item_name": "Name of the item",
109
+ "item_brand": "Brand of the item",
110
+ "item_variant": "Variant of the item",
111
+ "item_category": "Category of the item",
112
+ "item_category2": "Second-level category of the item",
113
+ "item_category3": "Third-level category of the item",
114
+ "item_category4": "Fourth-level category of the item",
115
+ "item_category5": "Fifth-level category of the item",
116
+ "price_in_usd": "Item price in USD",
117
+ "price": "Item price in local currency",
118
+ "quantity": "Quantity of the item (defaults to 1 if not specified)",
119
+ "item_revenue_in_usd": "Item revenue in USD (price_in_usd * quantity). Purchase events only",
120
+ "item_revenue": "Item revenue in local currency (price * quantity). Purchase events only",
121
+ "item_refund_in_usd": "Item refund in USD. Refund events only",
122
+ "item_refund": "Item refund in local currency. Refund events only",
123
+ "coupon": "Coupon code applied to the item",
124
+ "affiliation": "Product affiliation (e.g. supplying company or store location)",
125
+ "location_id": "Location associated with the item",
126
+ "item_list_id": "ID of the list in which the item was presented",
127
+ "item_list_name": "Name of the list in which the item was presented",
128
+ "item_list_index": "Position of the item in a list",
129
+ "promotion_id": "ID of the product promotion",
130
+ "promotion_name": "Name of the product promotion",
131
+ "creative_name": "Name of the creative used in a promotional spot",
132
+ "creative_slot": "Name of the creative slot"
133
+ }
134
+ },
135
+ "user_ltv": {
136
+ "description": "Lifetime value record for the user. Not populated in intraday tables",
137
+ "columns": {
138
+ "revenue": "Lifetime value revenue of the user",
139
+ "currency": "Lifetime value currency of the user"
140
+ }
141
+ },
142
+ "collected_traffic_source": {
143
+ "description": "Traffic source data collected with the event, including UTM parameters and click identifiers",
144
+ "columns": {
145
+ "manual_campaign_id": "Manual campaign ID (utm_id)",
146
+ "manual_campaign_name": "Manual campaign name (utm_campaign)",
147
+ "manual_source": "Manual source (utm_source), also includes parsed referral parameters",
148
+ "manual_medium": "Manual medium (utm_medium), also includes parsed referral parameters",
149
+ "manual_term": "Manual keyword/term (utm_term)",
150
+ "manual_content": "Manual content metadata (utm_content)",
151
+ "manual_creative_format": "Manual creative format (utm_creative_format)",
152
+ "manual_marketing_tactic": "Manual marketing tactic (utm_marketing_tactic)",
153
+ "manual_source_platform": "Manual source platform (utm_source_platform)",
154
+ "gclid": "Google click identifier",
155
+ "dclid": "DoubleClick click identifier (DV360 / CM360)",
156
+ "srsltid": "Google Merchant Center identifier"
157
+ }
158
+ },
159
+ "session_first_traffic_source": {
160
+ "description": "The collected_traffic_source from the first event in the session (ordered by timestamp, nulls included). Useful for session-level first-click attribution",
161
+ "columns": {
162
+ "manual_campaign_id": "Manual campaign ID (utm_id)",
163
+ "manual_campaign_name": "Manual campaign name (utm_campaign)",
164
+ "manual_source": "Manual source (utm_source), also includes parsed referral parameters",
165
+ "manual_medium": "Manual medium (utm_medium), also includes parsed referral parameters",
166
+ "manual_term": "Manual keyword/term (utm_term)",
167
+ "manual_content": "Manual content metadata (utm_content)",
168
+ "manual_creative_format": "Manual creative format (utm_creative_format)",
169
+ "manual_marketing_tactic": "Manual marketing tactic (utm_marketing_tactic)",
170
+ "manual_source_platform": "Manual source platform (utm_source_platform)",
171
+ "gclid": "Google click identifier",
172
+ "dclid": "DoubleClick click identifier (DV360 / CM360)",
173
+ "srsltid": "Google Merchant Center identifier"
174
+ }
175
+ },
176
+ "session_traffic_source_last_click": {
177
+ "description": "Session-level last-click attributed traffic source across Google Ads and manual channels. Aggregated as the first non-null value in the session (the field is session-scoped in the GA4 export and identical across events in the same session)",
178
+ "columns": {
179
+ "manual_campaign": {
180
+ "description": "Last-click manual campaign attribution",
181
+ "columns": {
182
+ "campaign_id": "Campaign ID of the last clicked manual campaign",
183
+ "campaign_name": "Name of the last clicked manual campaign",
184
+ "source": "Source of the last clicked manual campaign",
185
+ "medium": "Medium of the last clicked manual campaign",
186
+ "term": "Keyword/term of the last clicked manual campaign",
187
+ "content": "Content metadata of the last clicked manual campaign",
188
+ "source_platform": "Platform of the last clicked manual campaign",
189
+ "creative_format": "Creative format of the last clicked manual campaign",
190
+ "marketing_tactic": "Marketing tactic of the last clicked manual campaign"
191
+ }
192
+ },
193
+ "google_ads_campaign": {
194
+ "description": "Last-click Google Ads campaign attribution",
195
+ "columns": {
196
+ "customer_id": "Google Ads customer ID",
197
+ "account_name": "Google Ads account name",
198
+ "campaign_id": "Google Ads campaign ID",
199
+ "campaign_name": "Google Ads campaign name",
200
+ "ad_group_id": "Google Ads ad group ID",
201
+ "ad_group_name": "Google Ads ad group name"
202
+ }
203
+ },
204
+ "cross_channel_campaign": {
205
+ "description": "Last-click cross-channel campaign attribution",
206
+ "columns": {
207
+ "campaign_name": "Cross-channel campaign name",
208
+ "campaign_id": "Cross-channel campaign ID",
209
+ "source_platform": "Cross-channel source platform",
210
+ "source": "Cross-channel source",
211
+ "medium": "Cross-channel medium"
212
+ }
213
+ },
214
+ "sa360_campaign": {
215
+ "description": "Last-click SA360 campaign attribution",
216
+ "columns": {
217
+ "campaign_id": "SA360 campaign ID",
218
+ "campaign_name": "SA360 campaign name",
219
+ "source": "SA360 source",
220
+ "medium": "SA360 medium",
221
+ "ad_group_id": "SA360 ad group ID",
222
+ "ad_group_name": "SA360 ad group name",
223
+ "creative_format": "SA360 creative format",
224
+ "engine_account_name": "SA360 engine account name",
225
+ "engine_account_type": "SA360 engine account type",
226
+ "manager_account_name": "SA360 manager account name"
227
+ }
228
+ },
229
+ "dv360_campaign": {
230
+ "description": "Last-click DV360 campaign attribution",
231
+ "columns": {
232
+ "advertiser_id": "DV360 advertiser ID",
233
+ "advertiser_name": "DV360 advertiser name",
234
+ "campaign_id": "DV360 campaign ID",
235
+ "campaign_name": "DV360 campaign name",
236
+ "creative_id": "DV360 creative ID",
237
+ "creative_format": "DV360 creative format",
238
+ "creative_name": "DV360 creative name",
239
+ "marketing_tactic": "DV360 marketing tactic",
240
+ "exchange_id": "DV360 exchange ID",
241
+ "exchange_name": "DV360 exchange name",
242
+ "insertion_order_id": "DV360 insertion order ID",
243
+ "insertion_order_name": "DV360 insertion order name",
244
+ "line_item_id": "DV360 line item ID",
245
+ "line_item_name": "DV360 line item name",
246
+ "partner_id": "DV360 partner ID",
247
+ "partner_name": "DV360 partner name",
248
+ "source": "DV360 source",
249
+ "medium": "DV360 medium"
250
+ }
251
+ },
252
+ "cm360_campaign": {
253
+ "description": "Last-click CM360 campaign attribution",
254
+ "columns": {
255
+ "account_id": "CM360 account ID",
256
+ "account_name": "CM360 account name",
257
+ "advertiser_id": "CM360 advertiser ID",
258
+ "advertiser_name": "CM360 advertiser name",
259
+ "campaign_id": "CM360 campaign ID",
260
+ "campaign_name": "CM360 campaign name",
261
+ "creative_id": "CM360 creative ID",
262
+ "creative_format": "CM360 creative format",
263
+ "creative_name": "CM360 creative name",
264
+ "creative_type": "CM360 creative type",
265
+ "creative_type_id": "CM360 creative type ID",
266
+ "creative_version": "CM360 creative version",
267
+ "placement_id": "CM360 placement ID",
268
+ "placement_cost_structure": "CM360 placement cost structure",
269
+ "placement_name": "CM360 placement name",
270
+ "rendering_id": "CM360 rendering ID",
271
+ "site_id": "CM360 site ID",
272
+ "site_name": "CM360 site name",
273
+ "source": "CM360 source",
274
+ "medium": "CM360 medium"
275
+ }
276
+ }
277
+ }
278
+ },
279
+ "user_traffic_source": {
280
+ "description": "Traffic source that first acquired the user. Sourced from the GA4 export traffic_source field. Not populated in intraday tables",
281
+ "columns": {
282
+ "name": "Name of the marketing campaign that first acquired the user",
283
+ "medium": "Medium that first acquired the user (e.g. paid search, organic, referral)",
284
+ "source": "Source network that first acquired the user"
285
+ }
286
+ },
287
+ "event_previous_timestamp": "Time in microseconds (UTC) when the previous event was logged",
288
+ "event_value_in_usd": "Currency-converted value (in USD) of the event's 'value' parameter",
289
+ "event_bundle_sequence_id": "Sequential ID of the bundle in which the event was uploaded",
290
+ "event_server_timestamp_offset": "Timestamp offset between collection time and upload time in microseconds",
291
+ "privacy_info": {
292
+ "description": "Consent status information for the user when consent mode is enabled",
293
+ "columns": {
294
+ "ads_storage": "Whether ad targeting/storage is enabled (Yes, No, or Unset)",
295
+ "analytics_storage": "Whether Analytics storage is enabled (Yes, No, or Unset)",
296
+ "uses_transient_token": "Whether measurement uses transient tokens instead of cookies (Yes, No, or Unset)"
297
+ }
298
+ },
299
+ "user_first_touch_timestamp": "Time in microseconds at which the user first opened the app or visited the site",
300
+ "device": {
301
+ "description": "Information about the device from which the event originated",
302
+ "columns": {
303
+ "category": "Device category (mobile, tablet, desktop)",
304
+ "mobile_brand_name": "Device brand name",
305
+ "mobile_model_name": "Device model name",
306
+ "mobile_marketing_name": "Device marketing name",
307
+ "mobile_os_hardware_model": "Device model retrieved directly from the operating system",
308
+ "operating_system": "Operating system of the device",
309
+ "operating_system_version": "OS version",
310
+ "vendor_id": "IDFV (present only if IDFA is not collected)",
311
+ "advertising_id": "Advertising ID / IDFA",
312
+ "language": "OS language",
313
+ "time_zone_offset_seconds": "Offset from GMT in seconds",
314
+ "is_limited_ad_tracking": "Whether the device has Limit Ad Tracking enabled",
315
+ "web_info": {
316
+ "description": "Web-specific device information",
317
+ "columns": {
318
+ "browser": "Browser used to view content",
319
+ "browser_version": "Browser version",
320
+ "hostname": "Hostname associated with the logged event"
321
+ }
322
+ }
323
+ }
324
+ },
325
+ "geo": {
326
+ "description": "Geographic location where the event was initiated, based on IP address",
327
+ "columns": {
328
+ "continent": "Continent from which the event was reported",
329
+ "sub_continent": "Subcontinent from which the event was reported",
330
+ "country": "Country from which the event was reported",
331
+ "region": "Region from which the event was reported",
332
+ "metro": "Metro area from which the event was reported",
333
+ "city": "City from which the event was reported"
334
+ }
335
+ },
336
+ "app_info": {
337
+ "description": "Information about the app in which the event was initiated",
338
+ "columns": {
339
+ "id": "Package name or bundle ID of the app",
340
+ "firebase_app_id": "Firebase App ID associated with the app",
341
+ "install_source": "Store that installed the app",
342
+ "version": "App versionName (Android) or short bundle version (iOS)"
343
+ }
344
+ },
345
+ "stream_id": "Numeric ID of the data stream from which the event originated",
346
+ "platform": "Data stream platform (Web, iOS, or Android)",
347
+ "event_dimensions": {
348
+ "description": "Event dimension metadata",
349
+ "columns": {
350
+ "hostname": "Hostname where the event was recorded"
351
+ }
352
+ },
353
+ "is_active_user": "Whether the user was active at any point during the calendar day. Only populated in daily tables",
354
+ "batch_event_index": "Sequential order of the event within a batch, based on occurrence on the device",
355
+ "batch_page_id": "Sequential number assigned to each page, incrementing within an engagement",
356
+ "batch_ordering_id": "Monotonically increasing number incremented per network request from a given page",
357
+ "publisher": {
358
+ "description": "Publisher data from AdMob integration. Not populated in intraday or Fresh Daily tables",
359
+ "columns": {
360
+ "ad_revenue_in_usd": "Estimated ad revenue in USD. Populated for ad impression events only",
361
+ "ad_format": "Ad format (e.g. Interstitial, Banner, Rewarded, Native advanced)",
362
+ "ad_source_name": "Source network that served the ad (e.g. AdMob Network, Meta Audience Network)",
363
+ "ad_unit_id": "Ad unit identifier"
364
+ }
365
+ },
366
+ "row_inserted_timestamp": "Timestamp of when the row was inserted or last refreshed by the incremental pipeline",
367
+ "data_is_final": "Whether the data is considered final and not expected to change. Determined by export type (intraday/fresh = not final) or by a configurable day threshold since event_date",
368
+ "export_type": "GA4 export type the event originated from: 'daily', 'intraday', or 'fresh'"
369
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ga4-export-fixer",
3
- "version": "0.2.3-dev.0",
3
+ "version": "0.2.3-dev.1",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -12,7 +12,8 @@
12
12
  "tables",
13
13
  "inputValidation.js",
14
14
  "defaultConfig.js",
15
- "config.js"
15
+ "config.js",
16
+ "columns"
16
17
  ],
17
18
  "scripts": {
18
19
  "test": "node tests/ga4EventsEnhanced.test.js && node tests/mergeSQLConfigurations.test.js",
@@ -4,6 +4,7 @@ const inputValidation = require('../inputValidation.js');
4
4
  const constants = require('../constants.js');
5
5
  const preOperations = require('../preOperations.js');
6
6
  const { ga4EventsEnhancedConfig } = require('../defaultConfig.js'); // config defaults
7
+ const documentation = require('../documentation.js');
7
8
 
8
9
  // default configuration for the GA4 Events Enhanced table
9
10
  const defaultConfig = {
@@ -358,25 +359,7 @@ ${JSON.stringify(
358
359
  },
359
360
  onSchemaChange: 'EXTEND',
360
361
  tags: ['ga4_export_fixer'],
361
- // todo: include columns object
362
- columns: {
363
- event_date: 'Date of the event',
364
- event_params: {
365
- description: 'Event parameters array',
366
- columns: {
367
- key: 'Name of the event parameter',
368
- value: {
369
- description: 'A struct with the value of the event parameter',
370
- columns: {
371
- string_value: 'String value of the event parameter',
372
- int_value: 'Integer value of the event parameter',
373
- float_value: 'Float value of the event parameter',
374
- double_value: 'Double value of the event parameter',
375
- }
376
- }
377
- }
378
- }
379
- }
362
+ columns: documentation.getColumnDescriptions(mergedConfig)
380
363
  };
381
364
 
382
365
  // set the default values for table name and dataset, if not provided in the config