diff --git a/packages/core/src/tools/utils.ts b/packages/core/src/tools/utils.ts
index 5637c53224..a1df461b71 100644
--- a/packages/core/src/tools/utils.ts
+++ b/packages/core/src/tools/utils.ts
@@ -527,6 +527,13 @@ type Combined = A extends null ? B : B extends null ? A : Merged
export function combine(a: A, b: B): Combined
export function combine(a: A, b: B, c: C): Combined, C>
export function combine(a: A, b: B, c: C, d: D): Combined, C>, D>
+export function combine(
+ a: A,
+ b: B,
+ c: C,
+ d: D,
+ e: E
+): Combined, C>, D>, E>
export function combine(...sources: any[]): unknown {
let destination: any
diff --git a/packages/rum-core/src/boot/startRum.spec.ts b/packages/rum-core/src/boot/startRum.spec.ts
index 0eac37e3e9..24ef7c432d 100644
--- a/packages/rum-core/src/boot/startRum.spec.ts
+++ b/packages/rum-core/src/boot/startRum.spec.ts
@@ -1,15 +1,16 @@
-import { RelativeTime, Configuration, Observable } from '@datadog/browser-core'
+import { RelativeTime, Configuration, Observable, noop, relativeNow } from '@datadog/browser-core'
import { RumSession } from '@datadog/browser-rum-core'
import { createRumSessionMock, RumSessionMock } from '../../test/mockRumSession'
import { isIE } from '../../../core/test/specHelper'
import { noopRecorderApi, setup, TestSetupBuilder } from '../../test/specHelper'
-import { RumPerformanceNavigationTiming } from '../browser/performanceCollection'
+import { RumPerformanceNavigationTiming, RumPerformanceEntry } from '../browser/performanceCollection'
import { LifeCycle, LifeCycleEventType } from '../domain/lifeCycle'
import { SESSION_KEEP_ALIVE_INTERVAL, THROTTLE_VIEW_UPDATE_PERIOD } from '../domain/rumEventsCollection/view/trackViews'
import { startViewCollection } from '../domain/rumEventsCollection/view/viewCollection'
import { RumEvent } from '../rumEvent.types'
import { LocationChange } from '../browser/locationChangeObservable'
+import { startLongTaskCollection } from '../domain/rumEventsCollection/longTask/longTaskCollection'
import { startRumEventCollection } from './startRum'
function collectServerEvents(lifeCycle: LifeCycle) {
@@ -33,7 +34,9 @@ function startRum(
applicationId,
lifeCycle,
configuration,
+ location,
session,
+ locationChangeObservable,
() => ({
context: {},
user: {},
@@ -48,6 +51,8 @@ function startRum(
foregroundContexts,
noopRecorderApi
)
+
+ startLongTaskCollection(lifeCycle)
return {
stop: () => {
rumEventCollectionStop()
@@ -190,7 +195,7 @@ describe('rum session keep alive', () => {
})
})
-describe('rum view url', () => {
+describe('rum events url', () => {
const FAKE_NAVIGATION_ENTRY: RumPerformanceNavigationTiming = {
domComplete: 456 as RelativeTime,
domContentLoadedEventEnd: 345 as RelativeTime,
@@ -232,6 +237,34 @@ describe('rum view url', () => {
setupBuilder.cleanup()
})
+ it('should attach the url corresponding to the start of the event', () => {
+ const { lifeCycle, clock, changeLocation } = setupBuilder
+ .withFakeClock()
+ .withFakeLocation('http://foo.com/')
+ .build()
+ clock.tick(10)
+ changeLocation('http://foo.com/?bar=bar')
+ clock.tick(10)
+ changeLocation('http://foo.com/?bar=qux')
+
+ lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRY_COLLECTED, {
+ entryType: 'longtask',
+ startTime: relativeNow() - 5,
+ toJSON: noop,
+ duration: 5,
+ } as RumPerformanceEntry)
+
+ clock.tick(THROTTLE_VIEW_UPDATE_PERIOD)
+
+ expect(serverRumEvents.length).toBe(3)
+ const [firstViewUpdate, longTaskEvent, lastViewUpdate] = serverRumEvents
+
+ expect(firstViewUpdate.view.url).toBe('http://foo.com/')
+ expect(lastViewUpdate.view.url).toBe('http://foo.com/')
+
+ expect(longTaskEvent.view.url).toBe('http://foo.com/?bar=bar')
+ })
+
it('should keep the same URL when updating a view ended by a URL change', () => {
const { changeLocation } = setupBuilder.withFakeLocation('http://foo.com/').build()
diff --git a/packages/rum-core/src/boot/startRum.ts b/packages/rum-core/src/boot/startRum.ts
index 4596a41ee3..3cfaeb44fd 100644
--- a/packages/rum-core/src/boot/startRum.ts
+++ b/packages/rum-core/src/boot/startRum.ts
@@ -1,4 +1,4 @@
-import { combine, Configuration, InternalMonitoring } from '@datadog/browser-core'
+import { combine, Configuration, InternalMonitoring, Observable } from '@datadog/browser-core'
import { createDOMMutationObservable } from '../browser/domMutationObservable'
import { startPerformanceCollection } from '../browser/performanceCollection'
import { startRumAssembly } from '../domain/assembly'
@@ -15,7 +15,8 @@ import { startViewCollection } from '../domain/rumEventsCollection/view/viewColl
import { RumSession, startRumSession } from '../domain/rumSession'
import { CommonContext } from '../rawRumEvent.types'
import { startRumBatch } from '../transport/batch'
-import { createLocationChangeObservable } from '../browser/locationChangeObservable'
+import { startUrlContexts } from '../domain/urlContexts'
+import { createLocationChangeObservable, LocationChange } from '../browser/locationChangeObservable'
import { RecorderApi, RumInitConfiguration } from './rumPublicApi'
export function startRum(
@@ -41,11 +42,13 @@ export function startRum(
)
)
- const { parentContexts, foregroundContexts } = startRumEventCollection(
+ const { parentContexts, foregroundContexts, urlContexts } = startRumEventCollection(
initConfiguration.applicationId,
lifeCycle,
configuration,
+ location,
session,
+ locationChangeObservable,
getCommonContext
)
@@ -69,7 +72,7 @@ export function startRum(
startRequestCollection(lifeCycle, configuration)
startPerformanceCollection(lifeCycle, configuration)
- const internalContext = startInternalContext(initConfiguration.applicationId, session, parentContexts)
+ const internalContext = startInternalContext(initConfiguration.applicationId, session, parentContexts, urlContexts)
return {
addAction,
@@ -87,18 +90,22 @@ export function startRumEventCollection(
applicationId: string,
lifeCycle: LifeCycle,
configuration: Configuration,
+ location: Location,
session: RumSession,
+ locationChangeObservable: Observable,
getCommonContext: () => CommonContext
) {
const parentContexts = startParentContexts(lifeCycle, session)
+ const urlContexts = startUrlContexts(lifeCycle, locationChangeObservable, location)
const foregroundContexts = startForegroundContexts()
const batch = startRumBatch(configuration, lifeCycle)
- startRumAssembly(applicationId, configuration, lifeCycle, session, parentContexts, getCommonContext)
+ startRumAssembly(applicationId, configuration, lifeCycle, session, parentContexts, urlContexts, getCommonContext)
return {
parentContexts,
foregroundContexts,
+ urlContexts,
stop: () => {
// prevent batch from previous tests to keep running and send unwanted requests
// could be replaced by stopping all the component when they will all have a stop method
diff --git a/packages/rum-core/src/domain/assembly.spec.ts b/packages/rum-core/src/domain/assembly.spec.ts
index e98aaffb57..4a121d0a18 100644
--- a/packages/rum-core/src/domain/assembly.spec.ts
+++ b/packages/rum-core/src/domain/assembly.spec.ts
@@ -35,17 +35,23 @@ describe('rum assembly', () => {
},
view: {
id: 'abcde',
- referrer: 'url',
- url: 'url',
},
}),
})
- .beforeBuild(({ applicationId, configuration, lifeCycle, session, parentContexts }) => {
+ .beforeBuild(({ applicationId, configuration, lifeCycle, session, parentContexts, urlContexts }) => {
serverRumEvents = []
lifeCycle.subscribe(LifeCycleEventType.RUM_EVENT_COLLECTED, (serverRumEvent) =>
serverRumEvents.push(serverRumEvent)
)
- startRumAssembly(applicationId, configuration, lifeCycle, session, parentContexts, () => commonContext)
+ startRumAssembly(
+ applicationId,
+ configuration,
+ lifeCycle,
+ session,
+ parentContexts,
+ urlContexts,
+ () => commonContext
+ )
})
})
@@ -431,15 +437,22 @@ describe('rum assembly', () => {
notifyRawRumEvent(lifeCycle, {
rawRumEvent: createRawRumEvent(RumEventType.ACTION),
})
- expect(serverRumEvents[0].view).toEqual({
- id: 'abcde',
- referrer: 'url',
- url: 'url',
- })
+ expect(serverRumEvents[0].view.id).toBe('abcde')
expect(serverRumEvents[0].session.id).toBe('1234')
})
})
+ describe('url context', () => {
+ it('should be merged with event attributes', () => {
+ const { lifeCycle, fakeLocation } = setupBuilder.build()
+ notifyRawRumEvent(lifeCycle, {
+ rawRumEvent: createRawRumEvent(RumEventType.ACTION),
+ })
+ expect(serverRumEvents[0].view.url).toBe(fakeLocation.href!)
+ expect(serverRumEvents[0].view.referrer).toBe(document.referrer)
+ })
+ })
+
describe('event generation condition', () => {
it('when tracked, it should generate event', () => {
const { lifeCycle } = setupBuilder.build()
diff --git a/packages/rum-core/src/domain/assembly.ts b/packages/rum-core/src/domain/assembly.ts
index a764b53b85..b27555e8ce 100644
--- a/packages/rum-core/src/domain/assembly.ts
+++ b/packages/rum-core/src/domain/assembly.ts
@@ -29,6 +29,7 @@ import { RumEvent } from '../rumEvent.types'
import { LifeCycle, LifeCycleEventType } from './lifeCycle'
import { ParentContexts } from './parentContexts'
import { RumSession, RumSessionPlan } from './rumSession'
+import { UrlContexts } from './urlContexts'
export interface BrowserWindow extends Window {
_DATADOG_SYNTHETICS_PUBLIC_ID?: string
@@ -65,6 +66,7 @@ export function startRumAssembly(
lifeCycle: LifeCycle,
session: RumSession,
parentContexts: ParentContexts,
+ urlContexts: UrlContexts,
getCommonContext: () => CommonContext
) {
const reportError = (error: RawError) => {
@@ -80,7 +82,8 @@ export function startRumAssembly(
LifeCycleEventType.RAW_RUM_EVENT_COLLECTED,
({ startTime, rawRumEvent, domainContext, savedCommonContext, customerContext }) => {
const viewContext = parentContexts.findView(startTime)
- if (session.isTracked() && viewContext && viewContext.session.id === session.getId()) {
+ const urlContext = urlContexts.findUrl(startTime)
+ if (session.isTracked() && viewContext && urlContext && viewContext.session.id === session.getId()) {
const actionContext = parentContexts.findAction(startTime)
const commonContext = savedCommonContext || getCommonContext()
const rumContext: RumContext = {
@@ -103,8 +106,8 @@ export function startRumAssembly(
synthetics: getSyntheticsContext(),
}
const serverRumEvent = (needToAssembleWithAction(rawRumEvent)
- ? combine(rumContext, viewContext, actionContext, rawRumEvent)
- : combine(rumContext, viewContext, rawRumEvent)) as RumEvent & Context
+ ? combine(rumContext, urlContext, viewContext, actionContext, rawRumEvent)
+ : combine(rumContext, urlContext, viewContext, rawRumEvent)) as RumEvent & Context
serverRumEvent.context = combine(commonContext.context, customerContext)
diff --git a/packages/rum-core/src/domain/contextHistory.spec.ts b/packages/rum-core/src/domain/contextHistory.spec.ts
index fef4021ada..29b870a454 100644
--- a/packages/rum-core/src/domain/contextHistory.spec.ts
+++ b/packages/rum-core/src/domain/contextHistory.spec.ts
@@ -5,12 +5,12 @@ import { CLEAR_OLD_CONTEXTS_INTERVAL, ContextHistory } from './contextHistory'
const EXPIRE_DELAY = 10 * ONE_MINUTE
describe('contextHistory', () => {
- let contextHistory: ContextHistory<{ value: string }, { value: string }>
+ let contextHistory: ContextHistory<{ value: string }>
let clock: Clock
beforeEach(() => {
clock = mockClock()
- contextHistory = new ContextHistory((raw) => ({ value: raw.value }), EXPIRE_DELAY)
+ contextHistory = new ContextHistory(EXPIRE_DELAY)
})
afterEach(() => {
diff --git a/packages/rum-core/src/domain/contextHistory.ts b/packages/rum-core/src/domain/contextHistory.ts
index 9f9bf92be1..22fb937369 100644
--- a/packages/rum-core/src/domain/contextHistory.ts
+++ b/packages/rum-core/src/domain/contextHistory.ts
@@ -8,22 +8,22 @@ interface PreviousContext {
export const CLEAR_OLD_CONTEXTS_INTERVAL = ONE_MINUTE
-export class ContextHistory {
- private current: Raw | undefined
+export class ContextHistory {
+ private current: Context | undefined
private currentStart: RelativeTime | undefined
- private previousContexts: Array> = []
+ private previousContexts: Array> = []
private clearOldContextsInterval: number
- constructor(private buildContext: (r: Raw) => Built, private expireDelay: number) {
+ constructor(private expireDelay: number) {
this.clearOldContextsInterval = setInterval(() => this.clearOldContexts(), CLEAR_OLD_CONTEXTS_INTERVAL)
}
find(startTime?: RelativeTime) {
- if (startTime === undefined) {
- return this.current ? this.buildContext(this.current) : undefined
- }
- if (this.current !== undefined && this.currentStart !== undefined && startTime >= this.currentStart) {
- return this.buildContext(this.current)
+ if (
+ startTime === undefined ||
+ (this.current !== undefined && this.currentStart !== undefined && startTime >= this.currentStart)
+ ) {
+ return this.current
}
for (const previousContext of this.previousContexts) {
if (startTime > previousContext.endTime) {
@@ -36,7 +36,7 @@ export class ContextHistory {
return undefined
}
- setCurrent(current: Raw, startTime: RelativeTime) {
+ setCurrent(current: Context, startTime: RelativeTime) {
this.current = current
this.currentStart = startTime
}
@@ -54,7 +54,7 @@ export class ContextHistory {
if (this.current !== undefined && this.currentStart !== undefined) {
this.previousContexts.unshift({
endTime,
- context: this.buildContext(this.current),
+ context: this.current,
startTime: this.currentStart,
})
this.clearCurrent()
diff --git a/packages/rum-core/src/domain/internalContext.spec.ts b/packages/rum-core/src/domain/internalContext.spec.ts
index 97b5cf7e4c..8cf6315199 100644
--- a/packages/rum-core/src/domain/internalContext.spec.ts
+++ b/packages/rum-core/src/domain/internalContext.spec.ts
@@ -1,11 +1,14 @@
import { createRumSessionMock } from 'packages/rum-core/test/mockRumSession'
+import { RelativeTime } from '@datadog/browser-core'
import { setup, TestSetupBuilder } from '../../test/specHelper'
import { startInternalContext } from './internalContext'
import { ParentContexts } from './parentContexts'
+import { UrlContexts } from './urlContexts'
describe('internal context', () => {
let setupBuilder: TestSetupBuilder
let parentContextsStub: Partial
+ let findUrlSpy: jasmine.Spy
let internalContext: ReturnType
beforeEach(() => {
@@ -21,15 +24,14 @@ describe('internal context', () => {
},
view: {
id: 'abcde',
- referrer: 'referrer',
- url: 'url',
},
}),
}
setupBuilder = setup()
.withParentContexts(parentContextsStub)
- .beforeBuild(({ applicationId, session, parentContexts }) => {
- internalContext = startInternalContext(applicationId, session, parentContexts)
+ .beforeBuild(({ applicationId, session, parentContexts, urlContexts }) => {
+ findUrlSpy = spyOn(urlContexts, 'findUrl').and.callThrough()
+ internalContext = startInternalContext(applicationId, session, parentContexts, urlContexts)
})
})
@@ -38,7 +40,7 @@ describe('internal context', () => {
})
it('should return current internal context', () => {
- setupBuilder.build()
+ const { fakeLocation } = setupBuilder.build()
expect(internalContext.get()).toEqual({
application_id: 'appId',
@@ -48,8 +50,8 @@ describe('internal context', () => {
},
view: {
id: 'abcde',
- referrer: 'referrer',
- url: 'url',
+ referrer: document.referrer,
+ url: fakeLocation.href!,
},
})
})
@@ -66,5 +68,6 @@ describe('internal context', () => {
expect(parentContextsStub.findView).toHaveBeenCalledWith(123)
expect(parentContextsStub.findAction).toHaveBeenCalledWith(123)
+ expect(findUrlSpy).toHaveBeenCalledWith(123 as RelativeTime)
})
})
diff --git a/packages/rum-core/src/domain/internalContext.ts b/packages/rum-core/src/domain/internalContext.ts
index e1251bdf2c..de275a948d 100644
--- a/packages/rum-core/src/domain/internalContext.ts
+++ b/packages/rum-core/src/domain/internalContext.ts
@@ -2,16 +2,23 @@ import { RelativeTime } from '@datadog/browser-core'
import { InternalContext } from '../rawRumEvent.types'
import { ParentContexts } from './parentContexts'
import { RumSession } from './rumSession'
+import { UrlContexts } from './urlContexts'
/**
* Internal context keep returning v1 format
* to not break compatibility with logs data format
*/
-export function startInternalContext(applicationId: string, session: RumSession, parentContexts: ParentContexts) {
+export function startInternalContext(
+ applicationId: string,
+ session: RumSession,
+ parentContexts: ParentContexts,
+ urlContexts: UrlContexts
+) {
return {
get: (startTime?: number): InternalContext | undefined => {
const viewContext = parentContexts.findView(startTime as RelativeTime)
- if (session.isTracked() && viewContext && viewContext.session.id) {
+ const urlContext = urlContexts.findUrl(startTime as RelativeTime)
+ if (session.isTracked() && viewContext && urlContext && viewContext.session.id) {
const actionContext = parentContexts.findAction(startTime as RelativeTime)
return {
application_id: applicationId,
@@ -21,7 +28,10 @@ export function startInternalContext(applicationId: string, session: RumSession,
id: actionContext.action.id,
}
: undefined,
- view: viewContext.view,
+ view: {
+ ...viewContext.view,
+ ...urlContext.view,
+ },
}
}
},
diff --git a/packages/rum-core/src/domain/parentContexts.spec.ts b/packages/rum-core/src/domain/parentContexts.spec.ts
index 7501752d76..a7168b313d 100644
--- a/packages/rum-core/src/domain/parentContexts.spec.ts
+++ b/packages/rum-core/src/domain/parentContexts.spec.ts
@@ -18,10 +18,8 @@ describe('parentContexts', () => {
function buildViewCreatedEvent(partialViewCreatedEvent: Partial = {}): ViewCreatedEvent {
return {
- location,
startClocks,
id: FAKE_ID,
- referrer: 'http://foo.com',
...partialViewCreatedEvent,
}
}
@@ -113,17 +111,6 @@ describe('parentContexts', () => {
expect(parentContexts.findView()!.view.id).toEqual(newViewId)
})
- it('should return the current url with the current view', () => {
- const { lifeCycle, fakeLocation, changeLocation } = setupBuilder.build()
-
- lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, buildViewCreatedEvent({ location: fakeLocation as Location }))
- expect(parentContexts.findView()!.view.url).toBe('http://fake-url.com/')
-
- changeLocation('/foo')
-
- expect(parentContexts.findView()!.view.url).toBe('http://fake-url.com/foo')
- })
-
it('should return the view name with the view', () => {
const { lifeCycle } = setupBuilder.build()
diff --git a/packages/rum-core/src/domain/parentContexts.ts b/packages/rum-core/src/domain/parentContexts.ts
index 8a8ce3969c..49d9056207 100644
--- a/packages/rum-core/src/domain/parentContexts.ts
+++ b/packages/rum-core/src/domain/parentContexts.ts
@@ -16,38 +16,20 @@ export interface ParentContexts {
}
export function startParentContexts(lifeCycle: LifeCycle, session: RumSession): ParentContexts {
- const viewContextHistory = new ContextHistory(
- buildCurrentViewContext,
- VIEW_CONTEXT_TIME_OUT_DELAY
- )
+ const viewContextHistory = new ContextHistory(VIEW_CONTEXT_TIME_OUT_DELAY)
- const actionContextHistory = new ContextHistory(
- buildCurrentActionContext,
- ACTION_CONTEXT_TIME_OUT_DELAY
- )
+ const actionContextHistory = new ContextHistory(ACTION_CONTEXT_TIME_OUT_DELAY)
lifeCycle.subscribe(LifeCycleEventType.VIEW_CREATED, (view) => {
- viewContextHistory.setCurrent(
- {
- sessionId: session.getId(),
- ...view,
- },
- view.startClocks.relative
- )
+ viewContextHistory.setCurrent(buildViewContext(view, session.getId()), view.startClocks.relative)
})
lifeCycle.subscribe(LifeCycleEventType.VIEW_UPDATED, (view) => {
// A view can be updated after its end. We have to ensure that the view being updated is the
// most recently created.
const current = viewContextHistory.getCurrent()
- if (current && current.id === view.id) {
- viewContextHistory.setCurrent(
- {
- sessionId: current.sessionId,
- ...view,
- },
- view.startClocks.relative
- )
+ if (current && current.view.id === view.id) {
+ viewContextHistory.setCurrent(buildViewContext(view, current.session.id), view.startClocks.relative)
}
})
@@ -56,7 +38,7 @@ export function startParentContexts(lifeCycle: LifeCycle, session: RumSession):
})
lifeCycle.subscribe(LifeCycleEventType.AUTO_ACTION_CREATED, (action) => {
- actionContextHistory.setCurrent(action, action.startClocks.relative)
+ actionContextHistory.setCurrent(buildActionContext(action), action.startClocks.relative)
})
lifeCycle.subscribe(LifeCycleEventType.AUTO_ACTION_COMPLETED, (action: AutoAction) => {
@@ -76,22 +58,20 @@ export function startParentContexts(lifeCycle: LifeCycle, session: RumSession):
actionContextHistory.reset()
})
- function buildCurrentViewContext(current: ViewCreatedEvent & { sessionId?: string }) {
+ function buildViewContext(view: ViewCreatedEvent, sessionId: string | undefined) {
return {
session: {
- id: current.sessionId,
+ id: sessionId,
},
view: {
- id: current.id,
- name: current.name,
- referrer: current.referrer,
- url: current.location.href,
+ id: view.id,
+ name: view.name,
},
}
}
- function buildCurrentActionContext(current: AutoActionCreatedEvent) {
- return { action: { id: current.id } }
+ function buildActionContext(action: AutoActionCreatedEvent) {
+ return { action: { id: action.id } }
}
return {
diff --git a/packages/rum-core/src/domain/rumEventsCollection/action/trackActions.spec.ts b/packages/rum-core/src/domain/rumEventsCollection/action/trackActions.spec.ts
index 6f72964caf..698ae61d09 100644
--- a/packages/rum-core/src/domain/rumEventsCollection/action/trackActions.spec.ts
+++ b/packages/rum-core/src/domain/rumEventsCollection/action/trackActions.spec.ts
@@ -80,9 +80,7 @@ describe('trackActions', () => {
expect(createSpy).toHaveBeenCalled()
lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, {
- location,
id: 'fake',
- referrer: 'http://foo.com',
startClocks: (jasmine.any(Object) as unknown) as ClocksState,
})
clock.tick(EXPIRE_DELAY)
diff --git a/packages/rum-core/src/domain/rumEventsCollection/view/trackViews.spec.ts b/packages/rum-core/src/domain/rumEventsCollection/view/trackViews.spec.ts
index 6184e5c830..5f7b86314a 100644
--- a/packages/rum-core/src/domain/rumEventsCollection/view/trackViews.spec.ts
+++ b/packages/rum-core/src/domain/rumEventsCollection/view/trackViews.spec.ts
@@ -45,44 +45,26 @@ describe('track views automatically', () => {
})
describe('location changes', () => {
- it('should update view location on search change', () => {
- const { changeLocation } = setupBuilder.build()
- const { getViewCreateCount, getViewCreate, getViewUpdate, getViewUpdateCount } = viewTest
-
- changeLocation('/foo?bar=qux')
-
- expect(getViewCreateCount()).toBe(1)
- expect(getViewCreate(0).location.href).toMatch(/\/foo$/)
-
- const lastUpdate = getViewUpdate(getViewUpdateCount() - 1)
- expect(lastUpdate.location.href).toMatch(/\/foo\?bar=qux$/)
- expect(lastUpdate.id).toBe(getViewCreate(0).id)
- })
-
it('should create new view on path change', () => {
const { changeLocation } = setupBuilder.build()
- const { getViewCreateCount, getViewCreate } = viewTest
+ const { getViewCreateCount } = viewTest
expect(getViewCreateCount()).toBe(1)
- expect(getViewCreate(0).location.href).toMatch(/\/foo$/)
changeLocation('/bar')
expect(getViewCreateCount()).toBe(2)
- expect(getViewCreate(1).location.href).toMatch(/\/bar$/)
})
it('should create new view on hash change from history', () => {
const { changeLocation } = setupBuilder.build()
- const { getViewCreateCount, getViewCreate } = viewTest
+ const { getViewCreateCount } = viewTest
expect(getViewCreateCount()).toBe(1)
- expect(getViewCreate(0).location.href).toMatch(/\/foo$/)
changeLocation('/foo#bar')
expect(getViewCreateCount()).toBe(2)
- expect(getViewCreate(1).location.href).toMatch(/\/foo#bar$/)
})
function mockGetElementById() {
@@ -115,124 +97,6 @@ describe('track views automatically', () => {
expect(getViewCreateCount()).toBe(2)
})
})
-
- describe('view referrer', () => {
- it('should set the document referrer as referrer for the initial view', () => {
- setupBuilder.build()
- const { getViewCreate } = viewTest
-
- expect(getViewCreate(0).referrer).toEqual(document.referrer)
- })
-
- it('should set the previous view URL as referrer when a route change occurs', () => {
- const { changeLocation } = setupBuilder.build()
- const { getViewCreate } = viewTest
-
- changeLocation('/bar')
-
- expect(getViewCreate(1).referrer).toEqual(jasmine.stringMatching(/\/foo$/))
- })
-
- it('should set the previous view URL as referrer when a the session is renewed', () => {
- const { lifeCycle } = setupBuilder.build()
- const { getViewCreate } = viewTest
-
- lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED)
-
- expect(getViewCreate(1).referrer).toEqual(jasmine.stringMatching(/\/foo$/))
- })
-
- it('should use the most up-to-date URL of the previous view as a referrer', () => {
- const { changeLocation } = setupBuilder.build()
- const { getViewCreate } = viewTest
-
- changeLocation('/foo?a=b')
- changeLocation('/bar')
-
- expect(getViewCreate(1).referrer).toEqual(jasmine.stringMatching(/\/foo\?a=b$/))
- })
- })
-})
-
-describe('track views manually', () => {
- let setupBuilder: TestSetupBuilder
- let viewTest: ViewTest
-
- beforeEach(() => {
- setupBuilder = setup()
- .withFakeLocation('/foo')
- .withConfiguration({ trackViewsManually: true })
- .beforeBuild((buildContext) => {
- viewTest = setupViewTest(buildContext)
- return viewTest
- })
- })
-
- afterEach(() => {
- setupBuilder.cleanup()
- })
-
- describe('location changes', () => {
- it('should update view location on search change', () => {
- const { changeLocation } = setupBuilder.build()
- const { getViewCreateCount, getViewCreate, getViewUpdate, getViewUpdateCount } = viewTest
-
- changeLocation('/foo?bar=qux')
-
- expect(getViewCreateCount()).toBe(1)
- expect(getViewCreate(0).location.href).toMatch(/\/foo$/)
-
- const lastUpdate = getViewUpdate(getViewUpdateCount() - 1)
- expect(lastUpdate.location.href).toMatch(/\/foo\?bar=qux$/)
- expect(lastUpdate.id).toBe(getViewCreate(0).id)
- })
-
- it('should update view location on path change', () => {
- const { changeLocation } = setupBuilder.build()
- const { getViewCreateCount, getViewCreate, getViewUpdate, getViewUpdateCount } = viewTest
-
- changeLocation('/bar')
-
- expect(getViewCreateCount()).toBe(1)
- expect(getViewCreate(0).location.href).toMatch(/\/foo$/)
-
- const lastUpdate = getViewUpdate(getViewUpdateCount() - 1)
- expect(lastUpdate.location.href).toMatch(/\/bar$/)
- expect(lastUpdate.id).toBe(getViewCreate(0).id)
- })
- })
-
- describe('view referrer', () => {
- it('should set the document referrer as referrer for the initial view', () => {
- setupBuilder.build()
- const { getViewCreate } = viewTest
-
- expect(getViewCreate(0).referrer).toEqual(document.referrer)
- })
-
- it('should set the previous view URL as referrer when starting a new view', () => {
- const { changeLocation } = setupBuilder.build()
- const { getViewUpdate, getViewUpdateCount, startView } = viewTest
-
- startView()
- changeLocation('/bar')
-
- const lastUpdate = getViewUpdate(getViewUpdateCount() - 1)
- expect(lastUpdate.referrer).toEqual(jasmine.stringMatching(/\/foo$/))
- expect(lastUpdate.location.href).toEqual(jasmine.stringMatching(/\/bar$/))
- })
-
- it('should use the most up-to-date URL of the previous view as a referrer', () => {
- const { changeLocation } = setupBuilder.build()
- const { getViewCreate, startView } = viewTest
-
- changeLocation('/foo?a=b')
- changeLocation('/bar')
- startView()
-
- expect(getViewCreate(1).referrer).toEqual(jasmine.stringMatching(/\/bar$/))
- })
- })
})
describe('initial view', () => {
diff --git a/packages/rum-core/src/domain/rumEventsCollection/view/trackViews.ts b/packages/rum-core/src/domain/rumEventsCollection/view/trackViews.ts
index 038fe5e392..fc1a86e995 100644
--- a/packages/rum-core/src/domain/rumEventsCollection/view/trackViews.ts
+++ b/packages/rum-core/src/domain/rumEventsCollection/view/trackViews.ts
@@ -12,6 +12,7 @@ import {
TimeStamp,
display,
Observable,
+ Subscription,
} from '@datadog/browser-core'
import { ViewLoadingType, ViewCustomTimings } from '../../../rawRumEvent.types'
@@ -25,7 +26,6 @@ export interface ViewEvent {
id: string
name?: string
location: Readonly
- referrer: string
timings: Timings
customTimings: ViewCustomTimings
eventCounts: EventCounts
@@ -41,8 +41,6 @@ export interface ViewEvent {
export interface ViewCreatedEvent {
id: string
name?: string
- location: Location
- referrer: string
startClocks: ClocksState
}
@@ -65,9 +63,11 @@ export function trackViews(
let currentView = initialView
const { stop: stopViewLifeCycle } = startViewLifeCycle()
- const { unsubscribe: stopViewCollectionMode } = areViewsTrackedAutomatically
- ? startAutomaticViewCollection(locationChangeObservable)
- : startManualViewCollection(locationChangeObservable)
+
+ let locationChangeSubscription: Subscription
+ if (areViewsTrackedAutomatically) {
+ locationChangeSubscription = renewViewOnLocationChange(locationChangeObservable)
+ }
function trackInitialView(name?: string) {
const initialView = newView(
@@ -75,7 +75,6 @@ export function trackViews(
domMutationObservable,
location,
ViewLoadingType.INITIAL_LOAD,
- document.referrer,
clocksOrigin(),
name
)
@@ -87,15 +86,7 @@ export function trackViews(
}
function trackViewChange(startClocks?: ClocksState, name?: string) {
- return newView(
- lifeCycle,
- domMutationObservable,
- location,
- ViewLoadingType.ROUTE_CHANGE,
- currentView.url,
- startClocks,
- name
- )
+ return newView(lifeCycle, domMutationObservable, location, ViewLoadingType.ROUTE_CHANGE, startClocks, name)
}
function startViewLifeCycle() {
@@ -127,24 +118,14 @@ export function trackViews(
}
}
- function startAutomaticViewCollection(locationChangeObservable: Observable) {
+ function renewViewOnLocationChange(locationChangeObservable: Observable) {
return locationChangeObservable.subscribe(({ oldLocation, newLocation }) => {
if (areDifferentLocation(oldLocation, newLocation)) {
- // Renew view on location changes
currentView.end()
currentView.triggerUpdate()
currentView = trackViewChange()
return
}
- currentView.updateLocation(newLocation)
- currentView.triggerUpdate()
- })
- }
-
- function startManualViewCollection(locationChangeObservable: Observable) {
- return locationChangeObservable.subscribe(({ newLocation }) => {
- currentView.updateLocation(newLocation)
- currentView.triggerUpdate()
})
}
@@ -159,7 +140,7 @@ export function trackViews(
currentView = trackViewChange(startClocks, name)
},
stop: () => {
- stopViewCollectionMode()
+ locationChangeSubscription?.unsubscribe()
stopInitialViewTracking()
stopViewLifeCycle()
currentView.end()
@@ -172,7 +153,6 @@ function newView(
domMutationObservable: Observable,
initialLocation: Location,
loadingType: ViewLoadingType,
- referrer: string,
startClocks: ClocksState = clocksNow(),
name?: string
) {
@@ -182,9 +162,9 @@ function newView(
const customTimings: ViewCustomTimings = {}
let documentVersion = 0
let endClocks: ClocksState | undefined
- let location = { ...initialLocation }
+ const location = { ...initialLocation }
- lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, { id, name, startClocks, location, referrer })
+ lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, { id, name, startClocks })
// Update the view every time the measures are changing
const { throttled: scheduleViewUpdate, cancel: cancelScheduleViewUpdate } = throttle(
@@ -216,7 +196,6 @@ function newView(
name,
loadingType,
location,
- referrer,
startClocks,
timings,
duration: elapsed(startClocks.timeStamp, currentEnd),
@@ -232,9 +211,6 @@ function newView(
stopViewMetricsTracking()
lifeCycle.notify(LifeCycleEventType.VIEW_ENDED, { endClocks })
},
- getLocation() {
- return location
- },
triggerUpdate() {
// cancel any pending view updates execution
cancelScheduleViewUpdate()
@@ -249,12 +225,6 @@ function newView(
addTiming(name: string, time: TimeStamp) {
customTimings[sanitizeTiming(name)] = elapsed(startClocks.timeStamp, time)
},
- updateLocation(newLocation: Location) {
- location = { ...newLocation }
- },
- get url() {
- return location.href
- },
}
}
diff --git a/packages/rum-core/src/domain/rumEventsCollection/view/viewCollection.spec.ts b/packages/rum-core/src/domain/rumEventsCollection/view/viewCollection.spec.ts
index abeab415ea..3b85b05a45 100644
--- a/packages/rum-core/src/domain/rumEventsCollection/view/viewCollection.spec.ts
+++ b/packages/rum-core/src/domain/rumEventsCollection/view/viewCollection.spec.ts
@@ -26,7 +26,6 @@ const VIEW: ViewEvent = {
loadingTime: 20 as Duration,
loadingType: ViewLoadingType.INITIAL_LOAD,
location: {} as Location,
- referrer: '',
startClocks: { relative: 1234 as RelativeTime, timeStamp: 123456789 as TimeStamp },
timings: {
domComplete: 10 as Duration,
diff --git a/packages/rum-core/src/domain/urlContexts.spec.ts b/packages/rum-core/src/domain/urlContexts.spec.ts
new file mode 100644
index 0000000000..857eb7eec6
--- /dev/null
+++ b/packages/rum-core/src/domain/urlContexts.spec.ts
@@ -0,0 +1,153 @@
+import { relativeToClocks, RelativeTime } from '@datadog/browser-core'
+import { setup, TestSetupBuilder } from '../../test/specHelper'
+import { startUrlContexts, UrlContexts } from './urlContexts'
+import { ViewCreatedEvent, ViewEndedEvent } from './rumEventsCollection/view/trackViews'
+import { LifeCycleEventType } from './lifeCycle'
+
+describe('urlContexts', () => {
+ let setupBuilder: TestSetupBuilder
+ let urlContexts: UrlContexts
+
+ beforeEach(() => {
+ setupBuilder = setup()
+ .withFakeLocation('http://fake-url.com')
+ .withFakeClock()
+ .beforeBuild(({ lifeCycle, locationChangeObservable, location }) => {
+ urlContexts = startUrlContexts(lifeCycle, locationChangeObservable, location)
+ return urlContexts
+ })
+ })
+
+ afterEach(() => {
+ setupBuilder.cleanup()
+ })
+
+ it('should return undefined before the initial view', () => {
+ setupBuilder.build()
+
+ expect(urlContexts.findUrl()).toBeUndefined()
+ })
+
+ it('should not create url context on location change before the initial view', () => {
+ const { changeLocation } = setupBuilder.build()
+
+ changeLocation('/foo')
+
+ expect(urlContexts.findUrl()).toBeUndefined()
+ })
+
+ it('should return current url and document referrer for initial view', () => {
+ const { lifeCycle } = setupBuilder.build()
+
+ lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, {
+ startClocks: relativeToClocks(0 as RelativeTime),
+ } as ViewCreatedEvent)
+
+ const urlContext = urlContexts.findUrl()!
+ expect(urlContext.view.url).toBe('http://fake-url.com/')
+ expect(urlContext.view.referrer).toBe(document.referrer)
+ })
+
+ it('should update url context on location change', () => {
+ const { lifeCycle, changeLocation } = setupBuilder.build()
+
+ lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, {
+ startClocks: relativeToClocks(0 as RelativeTime),
+ } as ViewCreatedEvent)
+ changeLocation('/foo')
+
+ const urlContext = urlContexts.findUrl()!
+ expect(urlContext.view.url).toContain('http://fake-url.com/foo')
+ expect(urlContext.view.referrer).toBe(document.referrer)
+ })
+
+ it('should update url context on new view', () => {
+ const { lifeCycle, changeLocation } = setupBuilder.build()
+
+ lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, {
+ startClocks: relativeToClocks(0 as RelativeTime),
+ } as ViewCreatedEvent)
+ changeLocation('/foo')
+ lifeCycle.notify(LifeCycleEventType.VIEW_ENDED, {
+ endClocks: relativeToClocks(10 as RelativeTime),
+ } as ViewEndedEvent)
+ lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, {
+ startClocks: relativeToClocks(10 as RelativeTime),
+ } as ViewCreatedEvent)
+
+ const urlContext = urlContexts.findUrl()!
+ expect(urlContext.view.url).toBe('http://fake-url.com/foo')
+ expect(urlContext.view.referrer).toBe('http://fake-url.com/')
+ })
+
+ it('should return the url context corresponding to the start time', () => {
+ const { lifeCycle, changeLocation, clock } = setupBuilder.build()
+
+ lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, {
+ startClocks: relativeToClocks(0 as RelativeTime),
+ } as ViewCreatedEvent)
+
+ clock.tick(10)
+ changeLocation('/foo')
+ lifeCycle.notify(LifeCycleEventType.VIEW_ENDED, {
+ endClocks: relativeToClocks(10 as RelativeTime),
+ } as ViewEndedEvent)
+ lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, {
+ startClocks: relativeToClocks(10 as RelativeTime),
+ } as ViewCreatedEvent)
+
+ clock.tick(10)
+ changeLocation('/foo#bar')
+
+ clock.tick(10)
+ changeLocation('/qux')
+ lifeCycle.notify(LifeCycleEventType.VIEW_ENDED, {
+ endClocks: relativeToClocks(30 as RelativeTime),
+ } as ViewEndedEvent)
+ lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, {
+ startClocks: relativeToClocks(30 as RelativeTime),
+ } as ViewCreatedEvent)
+
+ expect(urlContexts.findUrl(5 as RelativeTime)).toEqual({
+ view: {
+ url: 'http://fake-url.com/',
+ referrer: document.referrer,
+ },
+ })
+ expect(urlContexts.findUrl(15 as RelativeTime)).toEqual({
+ view: {
+ url: 'http://fake-url.com/foo',
+ referrer: 'http://fake-url.com/',
+ },
+ })
+ expect(urlContexts.findUrl(25 as RelativeTime)).toEqual({
+ view: {
+ url: 'http://fake-url.com/foo#bar',
+ referrer: 'http://fake-url.com/',
+ },
+ })
+ expect(urlContexts.findUrl(35 as RelativeTime)).toEqual({
+ view: {
+ url: 'http://fake-url.com/qux',
+ referrer: 'http://fake-url.com/foo',
+ },
+ })
+ })
+
+ /**
+ * It could happen if there is an event happening just between view end and view creation
+ * (which seems unlikely) and this event would anyway be rejected by lack of view id
+ */
+ it('should return undefined when no current view', () => {
+ const { lifeCycle } = setupBuilder.build()
+
+ lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, {
+ startClocks: relativeToClocks(0 as RelativeTime),
+ } as ViewCreatedEvent)
+ lifeCycle.notify(LifeCycleEventType.VIEW_ENDED, {
+ endClocks: relativeToClocks(10 as RelativeTime),
+ } as ViewEndedEvent)
+
+ expect(urlContexts.findUrl()).toBeUndefined()
+ })
+})
diff --git a/packages/rum-core/src/domain/urlContexts.ts b/packages/rum-core/src/domain/urlContexts.ts
new file mode 100644
index 0000000000..2121ab9ef2
--- /dev/null
+++ b/packages/rum-core/src/domain/urlContexts.ts
@@ -0,0 +1,76 @@
+import { RelativeTime, Observable, SESSION_TIME_OUT_DELAY, relativeNow } from '@datadog/browser-core'
+import { UrlContext } from '../rawRumEvent.types'
+import { LocationChange } from '../browser/locationChangeObservable'
+import { ContextHistory } from './contextHistory'
+import { LifeCycle, LifeCycleEventType } from './lifeCycle'
+
+/**
+ * We want to attach to an event:
+ * - the url corresponding to its start
+ * - the referrer corresponding to the previous view url (or document referrer for initial view)
+ */
+
+export const URL_CONTEXT_TIME_OUT_DELAY = SESSION_TIME_OUT_DELAY
+
+export interface UrlContexts {
+ findUrl: (startTime?: RelativeTime) => UrlContext | undefined
+ stop: () => void
+}
+
+export function startUrlContexts(
+ lifeCycle: LifeCycle,
+ locationChangeObservable: Observable,
+ location: Location
+) {
+ const urlContextHistory = new ContextHistory(URL_CONTEXT_TIME_OUT_DELAY)
+
+ let previousViewUrl: string | undefined
+
+ lifeCycle.subscribe(LifeCycleEventType.VIEW_ENDED, ({ endClocks }) => {
+ urlContextHistory.closeCurrent(endClocks.relative)
+ })
+
+ lifeCycle.subscribe(LifeCycleEventType.VIEW_CREATED, ({ startClocks }) => {
+ const viewUrl = location.href
+ urlContextHistory.setCurrent(
+ buildUrlContext({
+ url: viewUrl,
+ referrer: !previousViewUrl ? document.referrer : previousViewUrl,
+ }),
+ startClocks.relative
+ )
+ previousViewUrl = viewUrl
+ })
+
+ const locationChangeSubscription = locationChangeObservable.subscribe(({ newLocation }) => {
+ const current = urlContextHistory.getCurrent()
+ if (current) {
+ const changeTime = relativeNow()
+ urlContextHistory.closeCurrent(changeTime)
+ urlContextHistory.setCurrent(
+ buildUrlContext({
+ url: newLocation.href,
+ referrer: current.view.referrer,
+ }),
+ changeTime
+ )
+ }
+ })
+
+ function buildUrlContext({ url, referrer }: { url: string; referrer: string }) {
+ return {
+ view: {
+ url,
+ referrer,
+ },
+ }
+ }
+
+ return {
+ findUrl: (startTime?: RelativeTime) => urlContextHistory.find(startTime),
+ stop: () => {
+ locationChangeSubscription.unsubscribe()
+ urlContextHistory.stop()
+ },
+ }
+}
diff --git a/packages/rum-core/src/rawRumEvent.types.ts b/packages/rum-core/src/rawRumEvent.types.ts
index dd5d8ac88a..24ca0dab97 100644
--- a/packages/rum-core/src/rawRumEvent.types.ts
+++ b/packages/rum-core/src/rawRumEvent.types.ts
@@ -199,8 +199,6 @@ export interface ViewContext extends Context {
view: {
id: string
name?: string
- url: string
- referrer: string
}
}
@@ -210,6 +208,13 @@ export interface ActionContext extends Context {
}
}
+export interface UrlContext extends Context {
+ view: {
+ url: string
+ referrer: string
+ }
+}
+
export interface InternalContext {
application_id: string
session_id: string | undefined
diff --git a/packages/rum-core/test/specHelper.ts b/packages/rum-core/test/specHelper.ts
index fdba589e06..669bfe922e 100644
--- a/packages/rum-core/test/specHelper.ts
+++ b/packages/rum-core/test/specHelper.ts
@@ -14,8 +14,9 @@ import { LifeCycle, LifeCycleEventType, RawRumEventCollectedData } from '../src/
import { ParentContexts } from '../src/domain/parentContexts'
import { trackViews, ViewEvent } from '../src/domain/rumEventsCollection/view/trackViews'
import { RumSession, RumSessionPlan } from '../src/domain/rumSession'
-import { RawRumEvent, RumContext, ViewContext } from '../src/rawRumEvent.types'
+import { RawRumEvent, RumContext, ViewContext, UrlContext } from '../src/rawRumEvent.types'
import { LocationChange } from '../src/browser/locationChangeObservable'
+import { UrlContexts } from '../src/domain/urlContexts'
import { validateFormat } from './formatValidation'
import { createRumSessionMock } from './mockRumSession'
@@ -43,6 +44,7 @@ export interface BuildContext {
applicationId: string
parentContexts: ParentContexts
foregroundContexts: ForegroundContexts
+ urlContexts: UrlContexts
}
export interface TestIO {
@@ -67,6 +69,15 @@ export function setup(): TestSetupBuilder {
let clock: Clock
let fakeLocation: Partial = location
let parentContexts: ParentContexts
+ const urlContexts: UrlContexts = {
+ findUrl: () => ({
+ view: {
+ url: fakeLocation.href!,
+ referrer: document.referrer,
+ },
+ }),
+ stop: noop,
+ }
let foregroundContexts: ForegroundContexts = {
isInForegroundAt: () => undefined,
selectInForegroundPeriodsFor: () => undefined,
@@ -130,6 +141,7 @@ export function setup(): TestSetupBuilder {
domMutationObservable,
locationChangeObservable,
parentContexts,
+ urlContexts,
foregroundContexts,
session,
applicationId: FAKE_APP_ID,
@@ -162,7 +174,7 @@ export function setup(): TestSetupBuilder {
function validateRumEventFormat(rawRumEvent: RawRumEvent) {
const fakeId = '00000000-aaaa-0000-aaaa-000000000000'
- const fakeContext: RumContext & ViewContext = {
+ const fakeContext: RumContext & ViewContext & UrlContext = {
_dd: {
format_version: 2,
drift: 0,
diff --git a/packages/rum/src/boot/startRecording.spec.ts b/packages/rum/src/boot/startRecording.spec.ts
index 522ff5035e..891d779e33 100644
--- a/packages/rum/src/boot/startRecording.spec.ts
+++ b/packages/rum/src/boot/startRecording.spec.ts
@@ -45,8 +45,6 @@ describe('startRecording', () => {
},
view: {
id: viewId,
- referrer: '',
- url: 'http://example.org',
},
}
},
diff --git a/packages/rum/src/domain/segmentCollection/segmentCollection.spec.ts b/packages/rum/src/domain/segmentCollection/segmentCollection.spec.ts
index d5098665f9..5678d182cb 100644
--- a/packages/rum/src/domain/segmentCollection/segmentCollection.spec.ts
+++ b/packages/rum/src/domain/segmentCollection/segmentCollection.spec.ts
@@ -219,7 +219,7 @@ describe('startSegmentCollection', () => {
describe('computeSegmentContext', () => {
const DEFAULT_VIEW_CONTEXT: ViewContext = {
session: { id: '456' },
- view: { id: '123', url: 'http://foo.com', referrer: 'http://bar.com' },
+ view: { id: '123' },
}
const DEFAULT_SESSION = createRumSessionMock().setId('456')