@flisk/analyze-tracking 0.8.5 → 0.8.7

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/README.md CHANGED
@@ -101,17 +101,19 @@ See [schema.json](schema.json) for a JSON Schema of the output.
101
101
 
102
102
  | Library | JavaScript/TypeScript | Python | Ruby | Go |
103
103
  |---------|:---------------------:|:------:|:----:|:--:|
104
- | Google Analytics | ✅ | ❌ | ❌ | ❌ |
105
- | Segment | ✅ | | | |
106
- | Mixpanel | ✅ | ✅ | ✅ | ✅ |
107
- | Amplitude | ✅ | ✅ | | ✅ |
108
- | Rudderstack | ✅ | ✅ | ✳️ | ✳️ |
109
- | mParticle | ✅ | | | |
110
- | PostHog | ✅ | | | |
111
- | Pendo | ✅ | | | |
112
- | Heap | ✅ | ❌ | ❌ | ❌ |
113
- | Snowplow | ✅ | | | |
114
- | Custom Function | ✅ | ✅ | ✅ | ✅ |
104
+ | Google Analytics | ✅ | ❌ | ❌ | ❌ |
105
+ | Google Tag Manager | ✅ | | | |
106
+ | Segment | ✅ | ✅ | ✅ | ✅ |
107
+ | Mixpanel | ✅ | ✅ | | ✅ |
108
+ | Amplitude | ✅ | ✅ | | |
109
+ | Rudderstack | ✅ | | ✳️ | ✳️ |
110
+ | mParticle | ✅ | | | |
111
+ | PostHog | ✅ | | | |
112
+ | Pendo | ✅ | ❌ | ❌ | ❌ |
113
+ | Heap | ✅ | | | |
114
+ | Snowplow | ✅ | ✅ | ✅ | ✅ |
115
+ | Datadog RUM | ✅ | ❌ | ❌ | ❌ |
116
+ | Custom Function | ✅ | ✅ | ✅ | ✅ |
115
117
 
116
118
  ✳️ Rudderstack's SDKs often use the same format as Segment, so Rudderstack events may be detected as Segment events.
117
119
 
@@ -129,6 +131,24 @@ See [schema.json](schema.json) for a JSON Schema of the output.
129
131
  ```
130
132
  </details>
131
133
 
134
+ <details>
135
+ <summary>Google Tag Manager</summary>
136
+
137
+ **JavaScript/TypeScript**
138
+ ```js
139
+ dataLayer.push({
140
+ event: '<event_name>',
141
+ '<property_name>': '<property_value>'
142
+ });
143
+
144
+ // Or via window
145
+ window.dataLayer.push({
146
+ event: '<event_name>',
147
+ '<property_name>': '<property_value>'
148
+ });
149
+ ```
150
+ </details>
151
+
132
152
  <details>
133
153
  <summary>Segment</summary>
134
154
 
@@ -375,6 +395,27 @@ See [schema.json](schema.json) for a JSON Schema of the output.
375
395
 
376
396
  </details>
377
397
 
398
+ <details>
399
+ <summary>Datadog RUM</summary>
400
+
401
+ **JavaScript/TypeScript**
402
+ ```js
403
+ datadogRum.addAction('<event_name>', {
404
+ '<property_name>': '<property_value>'
405
+ });
406
+
407
+ // Or via window
408
+ window.DD_RUM.addAction('<event_name>', {
409
+ '<property_name>': '<property_value>'
410
+ });
411
+
412
+ // Or via global DD_RUM
413
+ DD_RUM.addAction('<event_name>', {
414
+ '<property_name>': '<property_value>'
415
+ });
416
+ ```
417
+ </details>
418
+
378
419
  <details>
379
420
  <summary>Snowplow (Structured Events)</summary>
380
421
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flisk/analyze-tracking",
3
- "version": "0.8.5",
3
+ "version": "0.8.7",
4
4
  "description": "Analyzes tracking code in a project and generates data schemas",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/schema.json CHANGED
@@ -67,6 +67,8 @@
67
67
  "pendo",
68
68
  "heap",
69
69
  "snowplow",
70
+ "datadog",
71
+ "gtm",
70
72
  "custom",
71
73
  "unknown"
72
74
  ],
@@ -75,6 +75,18 @@ const ANALYTICS_PROVIDERS = {
75
75
  name: 'googleanalytics',
76
76
  functionName: 'gtag',
77
77
  type: 'function'
78
+ },
79
+ DATADOG_RUM: {
80
+ name: 'datadog',
81
+ objectNames: ['datadogRum', 'DD_RUM'],
82
+ methodName: 'addAction',
83
+ type: 'member'
84
+ },
85
+ GOOGLE_TAG_MANAGER: {
86
+ name: 'gtm',
87
+ objectNames: ['dataLayer'],
88
+ methodName: 'push',
89
+ type: 'member'
78
90
  }
79
91
  };
80
92
 
@@ -131,8 +131,17 @@ function detectMemberBasedProvider(node) {
131
131
  return 'unknown';
132
132
  }
133
133
 
134
- const objectName = node.callee.object.name;
135
134
  const methodName = node.callee.property.name;
135
+ let objectName = node.callee.object.name;
136
+
137
+ // Handle nested member expressions like window.DD_RUM.addAction
138
+ if (!objectName && node.callee.object.type === NODE_TYPES.MEMBER_EXPRESSION) {
139
+ // For window.DD_RUM.addAction, we want to check if it matches DD_RUM.addAction pattern
140
+ const nestedObjectName = node.callee.object.property.name;
141
+ if (nestedObjectName) {
142
+ objectName = nestedObjectName;
143
+ }
144
+ }
136
145
 
137
146
  if (!objectName || !methodName) {
138
147
  return 'unknown';
@@ -20,6 +20,7 @@ const EXTRACTION_STRATEGIES = {
20
20
  googleanalytics: extractGoogleAnalyticsEvent,
21
21
  snowplow: extractSnowplowEvent,
22
22
  mparticle: extractMparticleEvent,
23
+ gtm: extractGTMEvent,
23
24
  custom: extractCustomEvent,
24
25
  default: extractDefaultEvent
25
26
  };
@@ -105,6 +106,43 @@ function extractMparticleEvent(node, constantMap) {
105
106
  return { eventName, propertiesNode };
106
107
  }
107
108
 
109
+ /**
110
+ * Extracts Google Tag Manager event data
111
+ * @param {Object} node - CallExpression node
112
+ * @param {Object} constantMap - Collected constant map
113
+ * @returns {EventData}
114
+ */
115
+ function extractGTMEvent(node, constantMap) {
116
+ if (!node.arguments || node.arguments.length === 0) {
117
+ return { eventName: null, propertiesNode: null };
118
+ }
119
+
120
+ // dataLayer.push({ event: 'event_name', property1: 'value1', property2: 'value2' })
121
+ const firstArg = node.arguments[0];
122
+
123
+ if (firstArg.type !== NODE_TYPES.OBJECT_EXPRESSION) {
124
+ return { eventName: null, propertiesNode: null };
125
+ }
126
+
127
+ // Find the 'event' property
128
+ const eventProperty = findPropertyByKey(firstArg, 'event');
129
+ if (!eventProperty) {
130
+ return { eventName: null, propertiesNode: null };
131
+ }
132
+
133
+ const eventName = getStringValue(eventProperty.value, constantMap);
134
+
135
+ // Create a modified properties node without the 'event' property
136
+ const modifiedPropertiesNode = {
137
+ ...firstArg,
138
+ properties: firstArg.properties.filter(prop =>
139
+ prop.key && (prop.key.name !== 'event' && prop.key.value !== 'event')
140
+ )
141
+ };
142
+
143
+ return { eventName, propertiesNode: modifiedPropertiesNode };
144
+ }
145
+
108
146
  /**
109
147
  * Default event extraction for standard providers
110
148
  * @param {Object} node - CallExpression node
@@ -75,6 +75,18 @@ const ANALYTICS_PROVIDERS = {
75
75
  name: 'googleanalytics',
76
76
  functionName: 'gtag',
77
77
  type: 'function'
78
+ },
79
+ DATADOG_RUM: {
80
+ name: 'datadog',
81
+ objectNames: ['datadogRum', 'DD_RUM'],
82
+ methodName: 'addAction',
83
+ type: 'member'
84
+ },
85
+ GOOGLE_TAG_MANAGER: {
86
+ name: 'gtm',
87
+ objectNames: ['dataLayer'],
88
+ methodName: 'push',
89
+ type: 'member'
78
90
  }
79
91
  };
80
92
 
@@ -87,8 +87,17 @@ function detectMemberBasedProvider(node) {
87
87
  return 'unknown';
88
88
  }
89
89
 
90
- const objectName = node.expression.expression?.escapedText;
91
90
  const methodName = node.expression.name?.escapedText;
91
+ let objectName = node.expression.expression?.escapedText;
92
+
93
+ // Handle nested member expressions like window.DD_RUM.addAction
94
+ if (!objectName && ts.isPropertyAccessExpression(node.expression.expression)) {
95
+ // For window.DD_RUM.addAction, we want to check if it matches DD_RUM.addAction pattern
96
+ const nestedObjectName = node.expression.expression.name?.escapedText;
97
+ if (nestedObjectName) {
98
+ objectName = nestedObjectName;
99
+ }
100
+ }
92
101
 
93
102
  if (!objectName || !methodName) {
94
103
  return 'unknown';
@@ -21,6 +21,7 @@ const EXTRACTION_STRATEGIES = {
21
21
  googleanalytics: extractGoogleAnalyticsEvent,
22
22
  snowplow: extractSnowplowEvent,
23
23
  mparticle: extractMparticleEvent,
24
+ gtm: extractGTMEvent,
24
25
  custom: extractCustomEvent,
25
26
  default: extractDefaultEvent
26
27
  };
@@ -126,6 +127,60 @@ function extractMparticleEvent(node, checker, sourceFile) {
126
127
  return { eventName, propertiesNode };
127
128
  }
128
129
 
130
+ /**
131
+ * Extracts Google Tag Manager event data
132
+ * @param {Object} node - CallExpression node
133
+ * @param {Object} checker - TypeScript type checker
134
+ * @param {Object} sourceFile - TypeScript source file
135
+ * @returns {EventData}
136
+ */
137
+ function extractGTMEvent(node, checker, sourceFile) {
138
+ if (!node.arguments || node.arguments.length === 0) {
139
+ return { eventName: null, propertiesNode: null };
140
+ }
141
+
142
+ // dataLayer.push({ event: 'event_name', property1: 'value1', property2: 'value2' })
143
+ const firstArg = node.arguments[0];
144
+
145
+ if (!ts.isObjectLiteralExpression(firstArg)) {
146
+ return { eventName: null, propertiesNode: null };
147
+ }
148
+
149
+ // Find the 'event' property
150
+ const eventProperty = findPropertyByKey(firstArg, 'event');
151
+ if (!eventProperty) {
152
+ return { eventName: null, propertiesNode: null };
153
+ }
154
+
155
+ const eventName = getStringValue(eventProperty.initializer, checker, sourceFile);
156
+
157
+ // Create a modified properties node without the 'event' property
158
+ const modifiedProperties = firstArg.properties.filter(prop => {
159
+ if (ts.isPropertyAssignment(prop) && prop.name) {
160
+ if (ts.isIdentifier(prop.name)) {
161
+ return prop.name.escapedText !== 'event';
162
+ }
163
+ if (ts.isStringLiteral(prop.name)) {
164
+ return prop.name.text !== 'event';
165
+ }
166
+ }
167
+ return true;
168
+ });
169
+
170
+ // Create a synthetic object literal with the filtered properties
171
+ const modifiedPropertiesNode = ts.factory.createObjectLiteralExpression(modifiedProperties);
172
+
173
+ // Copy source positions for proper analysis
174
+ if (firstArg.pos !== undefined) {
175
+ modifiedPropertiesNode.pos = firstArg.pos;
176
+ }
177
+ if (firstArg.end !== undefined) {
178
+ modifiedPropertiesNode.end = firstArg.end;
179
+ }
180
+
181
+ return { eventName, propertiesNode: modifiedPropertiesNode };
182
+ }
183
+
129
184
  /**
130
185
  * Custom extraction
131
186
  * @param {Object} node - CallExpression node