Skip to content

Commit 54d3f79

Browse files
♻️ [RUM-9614] Decouple default context from assembly using hooks (#3498)
1 parent f25d53b commit 54d3f79

30 files changed

+374
-253
lines changed

packages/rum-core/src/boot/startRum.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import { startAccountContext } from '../domain/contexts/accountContext'
5353
import { startRumAssembly } from '../domain/assembly'
5454
import { startSessionContext } from '../domain/contexts/sessionContext'
5555
import { startConnectivityContext } from '../domain/contexts/connectivityContext'
56+
import { startDefaultContext } from '../domain/contexts/defaultContext'
5657
import type { RecorderApi, ProfilerApi } from './rumPublicApi'
5758

5859
export type StartRum = typeof startRum
@@ -128,6 +129,8 @@ export function startRum(
128129
const locationChangeObservable = createLocationChangeObservable(configuration, location)
129130
const { observable: windowOpenObservable, stop: stopWindowOpen } = createWindowOpenObservable()
130131
cleanupTasks.push(stopWindowOpen)
132+
133+
startDefaultContext(hooks, configuration)
131134
const pageStateHistory = startPageStateHistory(hooks, configuration)
132135
const viewHistory = startViewHistory(lifeCycle)
133136
cleanupTasks.push(() => viewHistory.stop())

packages/rum-core/src/domain/action/actionCollection.spec.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,18 +166,24 @@ describe('actionCollection', () => {
166166
it(`should add action properties on ${eventType} from the context`, () => {
167167
const actionId = '1'
168168
spyOn(actionContexts, 'findActionId').and.returnValue(actionId)
169-
const event = hooks.triggerHook(HookNames.Assemble, { eventType, startTime: 0 as RelativeTime })
169+
const defaultRumEventAttributes = hooks.triggerHook(HookNames.Assemble, {
170+
eventType,
171+
startTime: 0 as RelativeTime,
172+
})
170173

171-
expect(event).toEqual({ type: eventType, action: { id: actionId } })
174+
expect(defaultRumEventAttributes).toEqual({ type: eventType, action: { id: actionId } })
172175
})
173176
})
174177
;[RumEventType.VIEW, RumEventType.VITAL].forEach((eventType) => {
175178
it(`should not add action properties on ${eventType} from the context`, () => {
176179
const actionId = '1'
177180
spyOn(actionContexts, 'findActionId').and.returnValue(actionId)
178-
const event = hooks.triggerHook(HookNames.Assemble, { eventType, startTime: 0 as RelativeTime })
181+
const defaultRumEventAttributes = hooks.triggerHook(HookNames.Assemble, {
182+
eventType,
183+
startTime: 0 as RelativeTime,
184+
})
179185

180-
expect(event).toEqual(undefined)
186+
expect(defaultRumEventAttributes).toEqual(undefined)
181187
})
182188
})
183189
})

packages/rum-core/src/domain/action/actionCollection.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import type { LifeCycle, RawRumEventCollectedData } from '../lifeCycle'
77
import { LifeCycleEventType } from '../lifeCycle'
88
import type { RumConfiguration } from '../configuration'
99
import type { RumActionEventDomainContext } from '../../domainContext.types'
10-
import type { PartialRumEvent, Hooks } from '../../hooks'
11-
import { HookNames } from '../../hooks'
10+
import type { DefaultRumEventAttributes, Hooks } from '../../hooks'
11+
import { HookNames, SKIPPED } from '../../hooks'
1212
import type { ActionContexts, ClickAction } from './trackClickActions'
1313
import { trackClickActions } from './trackClickActions'
1414

@@ -35,18 +35,18 @@ export function startActionCollection(
3535
lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, processAction(action))
3636
)
3737

38-
hooks.register(HookNames.Assemble, ({ startTime, eventType }): PartialRumEvent | undefined => {
38+
hooks.register(HookNames.Assemble, ({ startTime, eventType }): DefaultRumEventAttributes | SKIPPED => {
3939
if (
4040
eventType !== RumEventType.ERROR &&
4141
eventType !== RumEventType.RESOURCE &&
4242
eventType !== RumEventType.LONG_TASK
4343
) {
44-
return
44+
return SKIPPED
4545
}
4646

4747
const actionId = actionContexts.findActionId(startTime)
4848
if (!actionId) {
49-
return
49+
return SKIPPED
5050
}
5151

5252
return {

packages/rum-core/src/domain/assembly.spec.ts

Lines changed: 1 addition & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { ClocksState, RelativeTime, TimeStamp } from '@datadog/browser-core'
22
import { ErrorSource, ExperimentalFeature, ONE_MINUTE, display } from '@datadog/browser-core'
33
import type { Clock } from '@datadog/browser-core/test'
4-
import { mockEventBridge, mockExperimentalFeatures, registerCleanupTask, mockClock } from '@datadog/browser-core/test'
4+
import { mockExperimentalFeatures, registerCleanupTask, mockClock } from '@datadog/browser-core/test'
55
import {
66
createRumSessionManagerMock,
77
createRawRumEvent,
@@ -372,28 +372,6 @@ describe('rum assembly', () => {
372372
})
373373
})
374374

375-
describe('rum context', () => {
376-
it('should be merged with event attributes', () => {
377-
const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults()
378-
notifyRawRumEvent(lifeCycle, {
379-
rawRumEvent: createRawRumEvent(RumEventType.VIEW, undefined),
380-
})
381-
382-
expect(serverRumEvents[0].view.id).toBeDefined()
383-
expect(serverRumEvents[0].date).toBeDefined()
384-
expect(serverRumEvents[0].source).toBe('browser')
385-
})
386-
387-
it('should be overwritten by event attributes', () => {
388-
const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults()
389-
notifyRawRumEvent(lifeCycle, {
390-
rawRumEvent: createRawRumEvent(RumEventType.VIEW, { date: 10 }),
391-
})
392-
393-
expect(serverRumEvents[0].date).toBe(10)
394-
})
395-
})
396-
397375
describe('customer context', () => {
398376
it('should be merged with event attributes', () => {
399377
const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults()
@@ -502,50 +480,6 @@ describe('rum assembly', () => {
502480
expect(sessionManager.findTrackedSession).toHaveBeenCalledWith(123 as RelativeTime)
503481
})
504482
})
505-
506-
describe('configuration context', () => {
507-
it('should include the configured sample rates', () => {
508-
const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults()
509-
notifyRawRumEvent(lifeCycle, {
510-
rawRumEvent: createRawRumEvent(RumEventType.ACTION),
511-
})
512-
expect(serverRumEvents[0]._dd.configuration).toEqual({
513-
session_replay_sample_rate: 0,
514-
session_sample_rate: 100,
515-
})
516-
})
517-
518-
it('should round sample rates', () => {
519-
const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults({
520-
partialConfiguration: {
521-
sessionSampleRate: 1.2341,
522-
sessionReplaySampleRate: 6.7891,
523-
},
524-
})
525-
526-
notifyRawRumEvent(lifeCycle, {
527-
rawRumEvent: createRawRumEvent(RumEventType.ACTION),
528-
})
529-
expect(serverRumEvents[0]._dd.configuration).toEqual({
530-
session_sample_rate: 1.234,
531-
session_replay_sample_rate: 6.789,
532-
})
533-
})
534-
})
535-
536-
describe('if event bridge detected', () => {
537-
it('includes the browser sdk version', () => {
538-
const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults()
539-
notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW) })
540-
541-
mockEventBridge()
542-
543-
notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW) })
544-
545-
expect(serverRumEvents[0]._dd.browser_sdk_version).not.toBeDefined()
546-
expect(serverRumEvents[1]._dd.browser_sdk_version).toBeDefined()
547-
})
548-
})
549483
;[
550484
{
551485
eventType: RumEventType.ERROR,

packages/rum-core/src/domain/assembly.ts

Lines changed: 6 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,14 @@ import type { Context, RawError, EventRateLimiter } from '@datadog/browser-core'
22
import {
33
combine,
44
isEmptyObject,
5-
timeStampNow,
6-
currentDrift,
75
display,
86
createEventRateLimiter,
9-
canUseEventBridge,
10-
round,
117
isExperimentalFeatureEnabled,
128
ExperimentalFeature,
139
} from '@datadog/browser-core'
1410
import type { RumEventDomainContext } from '../domainContext.types'
1511
import { RumEventType } from '../rawRumEvent.types'
16-
import type { CommonProperties, RumEvent } from '../rumEvent.types'
12+
import type { RumEvent } from '../rumEvent.types'
1713
import type { Hooks } from '../hooks'
1814
import { DISCARDED, HookNames } from '../hooks'
1915
import type { LifeCycle } from './lifeCycle'
@@ -22,9 +18,6 @@ import type { RumConfiguration } from './configuration'
2218
import type { ModifiableFieldPaths } from './limitModification'
2319
import { limitModification } from './limitModification'
2420

25-
// replaced at build time
26-
declare const __BUILD_ENV__SDK_VERSION__: string
27-
2821
const VIEW_MODIFIABLE_FIELD_PATHS: ModifiableFieldPaths = {
2922
'view.name': 'string',
3023
'view.url': 'string',
@@ -110,39 +103,18 @@ export function startRumAssembly(
110103
lifeCycle.subscribe(
111104
LifeCycleEventType.RAW_RUM_EVENT_COLLECTED,
112105
({ startTime, duration, rawRumEvent, domainContext, customerContext }) => {
113-
const rumContext: Partial<CommonProperties> = {
114-
_dd: {
115-
format_version: 2,
116-
drift: currentDrift(),
117-
configuration: {
118-
session_sample_rate: round(configuration.sessionSampleRate, 3),
119-
session_replay_sample_rate: round(configuration.sessionReplaySampleRate, 3),
120-
},
121-
browser_sdk_version: canUseEventBridge() ? __BUILD_ENV__SDK_VERSION__ : undefined,
122-
},
123-
application: {
124-
id: configuration.applicationId,
125-
},
126-
date: timeStampNow(),
127-
source: 'browser',
128-
}
129-
130-
const assembledEvent = hooks.triggerHook(HookNames.Assemble, {
106+
const defaultRumEventAttributes = hooks.triggerHook(HookNames.Assemble, {
131107
eventType: rawRumEvent.type,
132108
startTime,
133109
duration,
134-
})
110+
})!
135111

136-
if (assembledEvent === DISCARDED) {
112+
if (defaultRumEventAttributes === DISCARDED) {
137113
return
138114
}
139115

140-
const serverRumEvent = combine(
141-
rumContext,
142-
assembledEvent,
143-
{ context: customerContext },
144-
rawRumEvent
145-
) as RumEvent & Context
116+
const serverRumEvent = combine(defaultRumEventAttributes, { context: customerContext }, rawRumEvent) as RumEvent &
117+
Context
146118

147119
if (shouldSend(serverRumEvent, configuration.beforeSend, domainContext, eventRateLimiters)) {
148120
if (isEmptyObject(serverRumEvent.context!)) {

packages/rum-core/src/domain/contexts/accountContext.spec.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,12 @@ describe('account context', () => {
3636
it('should set the account', () => {
3737
accountContext.setContext({ id: '123', foo: 'bar' })
3838

39-
const event = hooks.triggerHook(HookNames.Assemble, { eventType: 'view', startTime: 0 as RelativeTime })
39+
const defaultRumEventAttributes = hooks.triggerHook(HookNames.Assemble, {
40+
eventType: 'view',
41+
startTime: 0 as RelativeTime,
42+
})
4043

41-
expect(event).toEqual({
44+
expect(defaultRumEventAttributes).toEqual({
4245
type: 'view',
4346
account: {
4447
id: '123',
@@ -49,9 +52,12 @@ describe('account context', () => {
4952

5053
it('should not set the account when account.id is undefined', () => {
5154
accountContext.setContext({ foo: 'bar' })
52-
const event = hooks.triggerHook(HookNames.Assemble, { eventType: 'view', startTime: 0 as RelativeTime })
55+
const defaultRumEventAttributes = hooks.triggerHook(HookNames.Assemble, {
56+
eventType: 'view',
57+
startTime: 0 as RelativeTime,
58+
})
5359

54-
expect(event).toEqual(undefined)
60+
expect(defaultRumEventAttributes).toBeUndefined()
5561
})
5662
})
5763
})

packages/rum-core/src/domain/contexts/accountContext.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import type { Account } from '@datadog/browser-core'
22
import { createContextManager, CustomerDataType, isEmptyObject, storeContextManager } from '@datadog/browser-core'
33
import type { RumConfiguration } from '../configuration'
4-
import type { Hooks, PartialRumEvent } from '../../hooks'
5-
import { HookNames } from '../../hooks'
4+
import type { Hooks, DefaultRumEventAttributes } from '../../hooks'
5+
import { SKIPPED, HookNames } from '../../hooks'
66

77
export function startAccountContext(hooks: Hooks, configuration: RumConfiguration) {
88
const accountContextManager = buildAccountContextManager()
@@ -11,11 +11,11 @@ export function startAccountContext(hooks: Hooks, configuration: RumConfiguratio
1111
storeContextManager(configuration, accountContextManager, 'rum', CustomerDataType.Account)
1212
}
1313

14-
hooks.register(HookNames.Assemble, ({ eventType }): PartialRumEvent | undefined => {
14+
hooks.register(HookNames.Assemble, ({ eventType }): DefaultRumEventAttributes | SKIPPED => {
1515
const account = accountContextManager.getContext() as Account
1616

1717
if (isEmptyObject(account) || !account.id) {
18-
return
18+
return SKIPPED
1919
}
2020

2121
return {

packages/rum-core/src/domain/contexts/ciVisibilityContext.spec.ts

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,12 @@ describe('startCiVisibilityContext', () => {
2626
mockCiVisibilityValues('trace_id_value')
2727
;({ stop: stopCiVisibility } = startCiVisibilityContext(hooks, cookieObservable))
2828

29-
const event = hooks.triggerHook(HookNames.Assemble, { eventType: 'view', startTime: 0 as RelativeTime })
29+
const defaultRumEventAttributes = hooks.triggerHook(HookNames.Assemble, {
30+
eventType: 'view',
31+
startTime: 0 as RelativeTime,
32+
})
3033

31-
expect(event).toEqual({
34+
expect(defaultRumEventAttributes).toEqual({
3235
type: 'view',
3336
session: {
3437
type: SessionType.CI_TEST,
@@ -43,9 +46,12 @@ describe('startCiVisibilityContext', () => {
4346
mockCiVisibilityValues('trace_id_value', 'cookies')
4447
;({ stop: stopCiVisibility } = startCiVisibilityContext(hooks, cookieObservable))
4548

46-
const event = hooks.triggerHook(HookNames.Assemble, { eventType: 'view', startTime: 0 as RelativeTime })
49+
const defaultRumEventAttributes = hooks.triggerHook(HookNames.Assemble, {
50+
eventType: 'view',
51+
startTime: 0 as RelativeTime,
52+
})
4753

48-
expect(event).toEqual({
54+
expect(defaultRumEventAttributes).toEqual({
4955
type: 'view',
5056
session: {
5157
type: SessionType.CI_TEST,
@@ -61,9 +67,12 @@ describe('startCiVisibilityContext', () => {
6167
;({ stop: stopCiVisibility } = startCiVisibilityContext(hooks, cookieObservable))
6268
cookieObservable.notify('trace_id_value_updated')
6369

64-
const event = hooks.triggerHook(HookNames.Assemble, { eventType: 'view', startTime: 0 as RelativeTime })
70+
const defaultRumEventAttributes = hooks.triggerHook(HookNames.Assemble, {
71+
eventType: 'view',
72+
startTime: 0 as RelativeTime,
73+
})
6574

66-
expect(event).toEqual({
75+
expect(defaultRumEventAttributes).toEqual({
6776
type: 'view',
6877
session: {
6978
type: SessionType.CI_TEST,
@@ -78,18 +87,24 @@ describe('startCiVisibilityContext', () => {
7887
mockCiVisibilityValues(undefined)
7988
;({ stop: stopCiVisibility } = startCiVisibilityContext(hooks, cookieObservable))
8089

81-
const event = hooks.triggerHook(HookNames.Assemble, { eventType: 'view', startTime: 0 as RelativeTime })
90+
const defaultRumEventAttributes = hooks.triggerHook(HookNames.Assemble, {
91+
eventType: 'view',
92+
startTime: 0 as RelativeTime,
93+
})
8294

83-
expect(event).toBeUndefined()
95+
expect(defaultRumEventAttributes).toBeUndefined()
8496
})
8597

8698
it('should not set ci visibility context if it is not a string', () => {
8799
mockCiVisibilityValues({ key: 'value' })
88100
;({ stop: stopCiVisibility } = startCiVisibilityContext(hooks, cookieObservable))
89101

90-
const event = hooks.triggerHook(HookNames.Assemble, { eventType: 'view', startTime: 0 as RelativeTime })
102+
const defaultRumEventAttributes = hooks.triggerHook(HookNames.Assemble, {
103+
eventType: 'view',
104+
startTime: 0 as RelativeTime,
105+
})
91106

92-
expect(event).toBeUndefined()
107+
expect(defaultRumEventAttributes).toBeUndefined()
93108
})
94109
})
95110
})

packages/rum-core/src/domain/contexts/ciVisibilityContext.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { getInitCookie } from '@datadog/browser-core'
22
import { createCookieObservable } from '../../browser/cookieObservable'
3-
import type { Hooks, PartialRumEvent } from '../../hooks'
4-
import { HookNames } from '../../hooks'
3+
import type { Hooks, DefaultRumEventAttributes } from '../../hooks'
4+
import { SKIPPED, HookNames } from '../../hooks'
55
import { SessionType } from '../rumSessionManager'
66

77
export const CI_VISIBILITY_TEST_ID_COOKIE_NAME = 'datadog-ci-visibility-test-execution-id'
@@ -25,9 +25,9 @@ export function startCiVisibilityContext(
2525
testExecutionId = value
2626
})
2727

28-
hooks.register(HookNames.Assemble, ({ eventType }): PartialRumEvent | undefined => {
28+
hooks.register(HookNames.Assemble, ({ eventType }): DefaultRumEventAttributes | SKIPPED => {
2929
if (typeof testExecutionId !== 'string') {
30-
return
30+
return SKIPPED
3131
}
3232

3333
return {

0 commit comments

Comments
 (0)