Skip to content

Commit 83d722b

Browse files
committed
add support for datadog rum
1 parent d05a313 commit 83d722b

File tree

14 files changed

+366
-114
lines changed

14 files changed

+366
-114
lines changed

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ See [schema.json](schema.json) for a JSON Schema of the output.
111111
| Pendo | ✅ | ❌ | ❌ | ❌ |
112112
| Heap | ✅ | ❌ | ❌ | ❌ |
113113
| Snowplow | ✅ | ✅ | ✅ | ✅ |
114+
| Datadog RUM | ✅ | ❌ | ❌ | ❌ |
114115
| Custom Function | ✅ | ✅ | ✅ | ✅ |
115116
116117
✳️ Rudderstack's SDKs often use the same format as Segment, so Rudderstack events may be detected as Segment events.
@@ -375,6 +376,27 @@ See [schema.json](schema.json) for a JSON Schema of the output.
375376

376377
</details>
377378

379+
<details>
380+
<summary>Datadog RUM</summary>
381+
382+
**JavaScript/TypeScript**
383+
```js
384+
datadogRum.addAction('<event_name>', {
385+
'<property_name>': '<property_value>'
386+
});
387+
388+
// Or via window
389+
window.DD_RUM.addAction('<event_name>', {
390+
'<property_name>': '<property_value>'
391+
});
392+
393+
// Or via global DD_RUM
394+
DD_RUM.addAction('<event_name>', {
395+
'<property_name>': '<property_value>'
396+
});
397+
```
398+
</details>
399+
378400
<details>
379401
<summary>Snowplow (Structured Events)</summary>
380402

schema.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
"pendo",
6868
"heap",
6969
"snowplow",
70+
"datadog",
7071
"custom",
7172
"unknown"
7273
],

src/analyze/javascript/constants.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,12 @@ const ANALYTICS_PROVIDERS = {
7575
name: 'googleanalytics',
7676
functionName: 'gtag',
7777
type: 'function'
78+
},
79+
DATADOG_RUM: {
80+
name: 'datadog',
81+
objectNames: ['datadogRum', 'DD_RUM'],
82+
methodName: 'addAction',
83+
type: 'member'
7884
}
7985
};
8086

src/analyze/javascript/detectors/analytics-source.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,17 @@ function detectMemberBasedProvider(node) {
131131
return 'unknown';
132132
}
133133

134-
const objectName = node.callee.object.name;
135134
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+
}
136145

137146
if (!objectName || !methodName) {
138147
return 'unknown';

src/analyze/typescript/constants.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,12 @@ const ANALYTICS_PROVIDERS = {
7575
name: 'googleanalytics',
7676
functionName: 'gtag',
7777
type: 'function'
78+
},
79+
DATADOG_RUM: {
80+
name: 'datadog',
81+
objectNames: ['datadogRum', 'DD_RUM'],
82+
methodName: 'addAction',
83+
type: 'member'
7884
}
7985
};
8086

src/analyze/typescript/detectors/analytics-source.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,17 @@ function detectMemberBasedProvider(node) {
8787
return 'unknown';
8888
}
8989

90-
const objectName = node.expression.expression?.escapedText;
9190
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+
}
92101

93102
if (!objectName || !methodName) {
94103
return 'unknown';

tests/analyzeJavaScript.test.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ test.describe('analyzeJsFile', () => {
1616
// Sort events by line number for consistent ordering
1717
events.sort((a, b) => a.line - b.line);
1818

19-
assert.strictEqual(events.length, 12);
19+
assert.strictEqual(events.length, 15);
2020

2121
// Test Google Analytics event
2222
const gaEvent = events.find(e => e.eventName === 'purchase' && e.source === 'googleanalytics');
@@ -176,7 +176,7 @@ test.describe('analyzeJsFile', () => {
176176
assert.ok(snowplowEvent);
177177
assert.strictEqual(snowplowEvent.source, 'snowplow');
178178
assert.strictEqual(snowplowEvent.functionName, 'trackSnowplow');
179-
assert.strictEqual(snowplowEvent.line, 138);
179+
assert.strictEqual(snowplowEvent.line, 157);
180180
assert.deepStrictEqual(snowplowEvent.properties, {
181181
category: { type: 'string' },
182182
label: { type: 'string' },
@@ -189,7 +189,7 @@ test.describe('analyzeJsFile', () => {
189189
assert.ok(customEvent);
190190
assert.strictEqual(customEvent.source, 'custom');
191191
assert.strictEqual(customEvent.functionName, 'global');
192-
assert.strictEqual(customEvent.line, 152);
192+
assert.strictEqual(customEvent.line, 171);
193193
assert.deepStrictEqual(customEvent.properties, {
194194
userId: { type: 'string' },
195195
order_id: { type: 'string' },
@@ -232,7 +232,7 @@ test.describe('analyzeJsFile', () => {
232232
const events = analyzeJsFile(testFilePath, null);
233233

234234
// Should find all events except the custom one
235-
assert.strictEqual(events.length, 11);
235+
assert.strictEqual(events.length, 14);
236236
assert.strictEqual(events.find(e => e.source === 'custom'), undefined);
237237
});
238238

tests/analyzeTypeScript.test.js

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@ test.describe('analyzeTsFile', () => {
3737
// Sort events by line number for consistent ordering
3838
events.sort((a, b) => a.line - b.line);
3939

40-
assert.strictEqual(events.length, 20);
40+
assert.strictEqual(events.length, 23);
4141

4242
// Test Google Analytics event
4343
const gaEvent = events.find(e => e.eventName === 'order_completed' && e.source === 'googleanalytics');
4444
assert.ok(gaEvent);
4545
assert.strictEqual(gaEvent.source, 'googleanalytics');
4646
assert.strictEqual(gaEvent.functionName, 'trackOrderCompletedGA');
47-
assert.strictEqual(gaEvent.line, 105);
47+
assert.strictEqual(gaEvent.line, 119);
4848
assert.deepStrictEqual(gaEvent.properties, {
4949
order_id: { type: 'string' },
5050
products: {
@@ -76,7 +76,7 @@ test.describe('analyzeTsFile', () => {
7676
assert.ok(segmentEvent);
7777
assert.strictEqual(segmentEvent.source, 'segment');
7878
assert.strictEqual(segmentEvent.functionName, 'checkout');
79-
assert.strictEqual(segmentEvent.line, 121);
79+
assert.strictEqual(segmentEvent.line, 135);
8080
assert.deepStrictEqual(segmentEvent.properties, {
8181
stage: { type: 'string' },
8282
method: { type: 'string' },
@@ -88,7 +88,7 @@ test.describe('analyzeTsFile', () => {
8888
assert.ok(mixpanelEvent);
8989
assert.strictEqual(mixpanelEvent.source, 'mixpanel');
9090
assert.strictEqual(mixpanelEvent.functionName, 'confirmPurchaseMixpanel');
91-
assert.strictEqual(mixpanelEvent.line, 130);
91+
assert.strictEqual(mixpanelEvent.line, 144);
9292
assert.deepStrictEqual(mixpanelEvent.properties, {
9393
order_id: { type: 'string' },
9494
items: {
@@ -111,7 +111,7 @@ test.describe('analyzeTsFile', () => {
111111
assert.ok(amplitudeEvent);
112112
assert.strictEqual(amplitudeEvent.source, 'amplitude');
113113
assert.strictEqual(amplitudeEvent.functionName, 'checkout');
114-
assert.strictEqual(amplitudeEvent.line, 135);
114+
assert.strictEqual(amplitudeEvent.line, 149);
115115
assert.deepStrictEqual(amplitudeEvent.properties, {
116116
order_id: { type: 'string' },
117117
items: {
@@ -143,7 +143,7 @@ test.describe('analyzeTsFile', () => {
143143
assert.ok(rudderstackEvent);
144144
assert.strictEqual(rudderstackEvent.source, 'rudderstack');
145145
assert.strictEqual(rudderstackEvent.functionName, 'checkout');
146-
assert.strictEqual(rudderstackEvent.line, 150);
146+
assert.strictEqual(rudderstackEvent.line, 164);
147147
assert.deepStrictEqual(rudderstackEvent.properties, {
148148
order_id: { type: 'string' },
149149
items: {
@@ -174,7 +174,7 @@ test.describe('analyzeTsFile', () => {
174174
assert.ok(mparticleEvent);
175175
assert.strictEqual(mparticleEvent.source, 'mparticle');
176176
assert.strictEqual(mparticleEvent.functionName, 'checkout2');
177-
assert.strictEqual(mparticleEvent.line, 176);
177+
assert.strictEqual(mparticleEvent.line, 190);
178178
assert.deepStrictEqual(mparticleEvent.properties, {
179179
order_id: { type: 'string' },
180180
items: {
@@ -205,7 +205,7 @@ test.describe('analyzeTsFile', () => {
205205
assert.ok(posthogEvent);
206206
assert.strictEqual(posthogEvent.source, 'posthog');
207207
assert.strictEqual(posthogEvent.functionName, 'checkout2');
208-
assert.strictEqual(posthogEvent.line, 195);
208+
assert.strictEqual(posthogEvent.line, 209);
209209
assert.deepStrictEqual(posthogEvent.properties, {
210210
order_id: { type: 'string' },
211211
retry: { type: 'number' },
@@ -237,7 +237,7 @@ test.describe('analyzeTsFile', () => {
237237
assert.ok(pendoEvent);
238238
assert.strictEqual(pendoEvent.source, 'pendo');
239239
assert.strictEqual(pendoEvent.functionName, 'checkout3');
240-
assert.strictEqual(pendoEvent.line, 216);
240+
assert.strictEqual(pendoEvent.line, 230);
241241
assert.deepStrictEqual(pendoEvent.properties, {
242242
order_id: { type: 'string' },
243243
products: {
@@ -268,7 +268,7 @@ test.describe('analyzeTsFile', () => {
268268
assert.ok(heapEvent);
269269
assert.strictEqual(heapEvent.source, 'heap');
270270
assert.strictEqual(heapEvent.functionName, 'checkout3');
271-
assert.strictEqual(heapEvent.line, 230);
271+
assert.strictEqual(heapEvent.line, 244);
272272
assert.deepStrictEqual(heapEvent.properties, {
273273
user_id: { type: 'string' },
274274
email: { type: 'string' },
@@ -284,7 +284,7 @@ test.describe('analyzeTsFile', () => {
284284
assert.ok(snowplowEvent1);
285285
assert.strictEqual(snowplowEvent1.source, 'snowplow');
286286
assert.strictEqual(snowplowEvent1.functionName, 'trackSnowplow');
287-
assert.strictEqual(snowplowEvent1.line, 247);
287+
assert.strictEqual(snowplowEvent1.line, 282);
288288
assert.deepStrictEqual(snowplowEvent1.properties, {
289289
category: { type: 'string' },
290290
label: { type: 'string' },
@@ -296,14 +296,14 @@ test.describe('analyzeTsFile', () => {
296296
assert.ok(snowplowEvent2);
297297
assert.strictEqual(snowplowEvent2.source, 'snowplow');
298298
assert.strictEqual(snowplowEvent2.functionName, 'trackSnowplow2');
299-
assert.strictEqual(snowplowEvent2.line, 251);
299+
assert.strictEqual(snowplowEvent2.line, 286);
300300

301301
// Test custom function event
302302
const customEvent = events.find(e => e.eventName === 'custom_event_v2');
303303
assert.ok(customEvent);
304304
assert.strictEqual(customEvent.source, 'custom');
305305
assert.strictEqual(customEvent.functionName, 'global');
306-
assert.strictEqual(customEvent.line, 280);
306+
assert.strictEqual(customEvent.line, 315);
307307
assert.deepStrictEqual(customEvent.properties, {
308308
userId: { type: 'string' },
309309
order_id: { type: 'string' },
@@ -326,7 +326,7 @@ test.describe('analyzeTsFile', () => {
326326
assert.ok(constantEvent);
327327
assert.strictEqual(constantEvent.source, 'custom');
328328
assert.strictEqual(constantEvent.functionName, 'global');
329-
assert.strictEqual(constantEvent.line, 291);
329+
assert.strictEqual(constantEvent.line, 326);
330330
assert.deepStrictEqual(constantEvent.properties, {
331331
userId: { type: 'string' },
332332
orderId: { type: 'string' },
@@ -342,7 +342,7 @@ test.describe('analyzeTsFile', () => {
342342
assert.ok(importedConstantEvent);
343343
assert.strictEqual(importedConstantEvent.source, 'segment');
344344
assert.strictEqual(importedConstantEvent.functionName, 'global');
345-
assert.strictEqual(importedConstantEvent.line, 293);
345+
assert.strictEqual(importedConstantEvent.line, 328);
346346
assert.deepStrictEqual(importedConstantEvent.properties, {
347347
orderId: { type: 'string' },
348348
total: { type: 'number' },
@@ -357,7 +357,7 @@ test.describe('analyzeTsFile', () => {
357357
assert.ok(frozenConstantEvent);
358358
assert.strictEqual(frozenConstantEvent.source, 'mixpanel');
359359
assert.strictEqual(frozenConstantEvent.functionName, 'global');
360-
assert.strictEqual(frozenConstantEvent.line, 292);
360+
assert.strictEqual(frozenConstantEvent.line, 327);
361361
assert.deepStrictEqual(frozenConstantEvent.properties, {
362362
orderId: { type: 'string' },
363363
total: { type: 'number' },
@@ -405,7 +405,7 @@ test.describe('analyzeTsFile', () => {
405405
const events = analyzeTsFile(testFilePath, program, null);
406406

407407
// Should find all events except the custom ones
408-
assert.strictEqual(events.length, 13);
408+
assert.strictEqual(events.length, 16);
409409
assert.strictEqual(events.find(e => e.source === 'custom'), undefined);
410410
});
411411

tests/fixtures/javascript/main.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,25 @@ export const checkout3 = function() {
126126
email: 'user123@example.com',
127127
name: 'John Doe'
128128
});
129+
130+
// datadog tracking examples - all three patterns
131+
datadogRum.addAction('checkout', {
132+
total: 500,
133+
order_id: 'ABC123',
134+
currency: 'USD'
135+
});
136+
137+
window.DD_RUM.addAction('user_login', {
138+
user_id: 'user123',
139+
method: 'email',
140+
success: true
141+
});
142+
143+
DD_RUM.addAction('page_view', {
144+
page: '/checkout',
145+
section: 'payment',
146+
user_type: 'premium'
147+
});
129148
}
130149

131150
class MyClass {

0 commit comments

Comments
 (0)