From 356499a03deb0a6cfdfb83e30e65e62b44b580d8 Mon Sep 17 00:00:00 2001 From: Bastien Caudan <1331991+bcaudan@users.noreply.github.com> Date: Fri, 12 May 2023 09:56:05 +0200 Subject: [PATCH 01/27] =?UTF-8?q?=F0=9F=92=A5=20Promote=20track=20frustrat?= =?UTF-8?q?ion=20as=20default=20action=20behaviour=20(#2232)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rum-core/src/domain/configuration.spec.ts | 29 -- packages/rum-core/src/domain/configuration.ts | 7 +- .../action/trackClickActions.spec.ts | 329 +++++++----------- .../action/trackClickActions.ts | 34 +- .../scenario/recorder/recorder.scenario.ts | 4 +- test/e2e/scenario/rum/actions.scenario.ts | 32 +- 6 files changed, 149 insertions(+), 286 deletions(-) diff --git a/packages/rum-core/src/domain/configuration.spec.ts b/packages/rum-core/src/domain/configuration.spec.ts index 99947ee892..f1827ebbae 100644 --- a/packages/rum-core/src/domain/configuration.spec.ts +++ b/packages/rum-core/src/domain/configuration.spec.ts @@ -392,35 +392,6 @@ describe('validateAndBuildRumConfiguration', () => { }) }) - describe('trackFrustrations', () => { - it('defaults to false', () => { - expect(validateAndBuildRumConfiguration(DEFAULT_INIT_CONFIGURATION)!.trackFrustrations).toBeFalse() - }) - - it('the initialization parameter is set to provided value', () => { - expect( - validateAndBuildRumConfiguration({ ...DEFAULT_INIT_CONFIGURATION, trackFrustrations: true })!.trackFrustrations - ).toBeTrue() - expect( - validateAndBuildRumConfiguration({ ...DEFAULT_INIT_CONFIGURATION, trackFrustrations: false })!.trackFrustrations - ).toBeFalse() - }) - - it('the initialization parameter the provided value is cast to boolean', () => { - expect( - validateAndBuildRumConfiguration({ ...DEFAULT_INIT_CONFIGURATION, trackFrustrations: 'foo' as any })! - .trackFrustrations - ).toBeTrue() - }) - - it('implies "trackUserInteractions"', () => { - expect( - validateAndBuildRumConfiguration({ ...DEFAULT_INIT_CONFIGURATION, trackFrustrations: true })! - .trackUserInteractions - ).toBeTrue() - }) - }) - describe('trackViewsManually', () => { it('defaults to false', () => { expect(validateAndBuildRumConfiguration(DEFAULT_INIT_CONFIGURATION)!.trackViewsManually).toBeFalse() diff --git a/packages/rum-core/src/domain/configuration.ts b/packages/rum-core/src/domain/configuration.ts index 823d6c2b64..8bb664801b 100644 --- a/packages/rum-core/src/domain/configuration.ts +++ b/packages/rum-core/src/domain/configuration.ts @@ -54,7 +54,6 @@ export interface RumInitConfiguration extends InitConfiguration { */ trackInteractions?: boolean | undefined trackUserInteractions?: boolean | undefined - trackFrustrations?: boolean | undefined actionNameAttribute?: string | undefined // view options @@ -77,7 +76,6 @@ export interface RumConfiguration extends Configuration { oldPlansBehavior: boolean sessionReplaySampleRate: number trackUserInteractions: boolean - trackFrustrations: boolean trackViewsManually: boolean trackResources: boolean | undefined trackLongTasks: boolean | undefined @@ -136,7 +134,6 @@ export function validateAndBuildRumConfiguration( } const trackUserInteractions = !!(initConfiguration.trackUserInteractions ?? initConfiguration.trackInteractions) - const trackFrustrations = !!initConfiguration.trackFrustrations return assign( { @@ -148,8 +145,7 @@ export function validateAndBuildRumConfiguration( traceSampleRate, allowedTracingUrls, excludedActivityUrls: initConfiguration.excludedActivityUrls ?? [], - trackUserInteractions: trackUserInteractions || trackFrustrations, - trackFrustrations, + trackUserInteractions, trackViewsManually: !!initConfiguration.trackViewsManually, trackResources: initConfiguration.trackResources, trackLongTasks: initConfiguration.trackLongTasks, @@ -290,7 +286,6 @@ export function serializeRumConfiguration(configuration: RumInitConfiguration): default_privacy_level: configuration.defaultPrivacyLevel, use_excluded_activity_urls: Array.isArray(configuration.excludedActivityUrls) && configuration.excludedActivityUrls.length > 0, - track_frustrations: configuration.trackFrustrations, track_views_manually: configuration.trackViewsManually, track_user_interactions: configuration.trackUserInteractions ?? configuration.trackInteractions, }, diff --git a/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.spec.ts b/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.spec.ts index 198d89a428..3d70e81dee 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.spec.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.spec.ts @@ -167,268 +167,195 @@ describe('trackClickActions', () => { expect(events[0].name).toBe('test-1') }) - describe('without tracking frustrations', () => { - it('discards any click action with a negative duration', () => { - const { clock } = setupBuilder.build() - emulateClick({ activity: { delay: -1 } }) - expect(findActionId()).not.toBeUndefined() - clock.tick(EXPIRE_DELAY) + it('discards any click action with a negative duration', () => { + const { clock } = setupBuilder.build() + emulateClick({ activity: { delay: -1 } }) + expect(findActionId()!.length).toEqual(2) + clock.tick(EXPIRE_DELAY) - expect(events).toEqual([]) - expect(findActionId()).toBeUndefined() - }) + expect(events).toEqual([]) + expect(findActionId()).toEqual([]) + }) - it('discards ongoing click action on view ended', () => { - const { lifeCycle, clock } = setupBuilder.build() - emulateClick({ activity: {} }) - expect(findActionId()).not.toBeUndefined() + it('ongoing click action is stopped on view end', () => { + const { lifeCycle, clock } = setupBuilder.build() + emulateClick({ activity: { delay: BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY } }) - lifeCycle.notify(LifeCycleEventType.VIEW_ENDED, { - endClocks: clocksNow(), - }) - clock.tick(EXPIRE_DELAY) + clock.tick(BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY) - expect(events).toEqual([]) - expect(findActionId()).toBeUndefined() + lifeCycle.notify(LifeCycleEventType.VIEW_ENDED, { + endClocks: clocksNow(), }) - it('ignores any starting click action while another one is ongoing', () => { - const { clock } = setupBuilder.build() + expect(events.length).toBe(1) + expect(events[0].duration).toBe((2 * BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY) as Duration) + }) + it('collect click actions even if another one is ongoing', () => { + const { clock } = setupBuilder.build() + + const firstPointerDownTimeStamp = timeStampNow() + emulateClick({ activity: {} }) + const secondPointerDownTimeStamp = timeStampNow() + emulateClick({ activity: {} }) + + clock.tick(EXPIRE_DELAY) + expect(events.length).toBe(2) + expect(events[0].startClocks.timeStamp).toBe(addDuration(firstPointerDownTimeStamp, EMULATED_CLICK_DURATION)) + expect(events[1].startClocks.timeStamp).toBe(addDuration(secondPointerDownTimeStamp, EMULATED_CLICK_DURATION)) + }) + + it('collect click actions even if nothing happens after a click (dead click)', () => { + const { clock } = setupBuilder.build() + emulateClick() + + clock.tick(EXPIRE_DELAY) + expect(events.length).toBe(1) + expect(events[0].frustrationTypes).toEqual([FrustrationType.DEAD_CLICK]) + expect(findActionId()).toEqual([]) + }) + + it('does not set a duration for dead clicks', () => { + const { clock } = setupBuilder.build() + emulateClick() + + clock.tick(EXPIRE_DELAY) + expect(events.length).toBe(1) + expect(events[0].duration).toBeUndefined() + }) + + it('collect click actions even if it fails to find a name', () => { + const { clock } = setupBuilder.build() + emulateClick({ activity: {}, target: emptyElement }) + expect(findActionId()!.length).toBeGreaterThan(0) + clock.tick(EXPIRE_DELAY) + + expect(events.length).toBe(1) + }) + + describe('rage clicks', () => { + it('considers a chain of three clicks or more as a single action with "rage" frustration type', () => { + const { clock } = setupBuilder.build() const firstPointerDownTimeStamp = timeStampNow() - emulateClick({ activity: {} }) - emulateClick({ activity: {} }) + const activityDelay = 5 + emulateClick({ activity: { delay: activityDelay } }) + emulateClick({ activity: { delay: activityDelay } }) + emulateClick({ activity: { delay: activityDelay } }) clock.tick(EXPIRE_DELAY) expect(events.length).toBe(1) expect(events[0].startClocks.timeStamp).toBe(addDuration(firstPointerDownTimeStamp, EMULATED_CLICK_DURATION)) + expect(events[0].frustrationTypes).toEqual([FrustrationType.RAGE_CLICK]) + expect(events[0].duration).toBe( + (MAX_DURATION_BETWEEN_CLICKS + 2 * activityDelay + 2 * EMULATED_CLICK_DURATION) as Duration + ) }) - it('discards a click action when nothing happens after a click', () => { + it('should contain original events from of rage sequence', () => { const { clock } = setupBuilder.build() - emulateClick() + const activityDelay = 5 + emulateClick({ activity: { delay: activityDelay } }) + emulateClick({ activity: { delay: activityDelay } }) + emulateClick({ activity: { delay: activityDelay } }) clock.tick(EXPIRE_DELAY) - expect(events).toEqual([]) - expect(findActionId()).toBeUndefined() - }) - - it('ignores a click action if it fails to find a name', () => { - const { clock } = setupBuilder.build() - emulateClick({ activity: {}, target: emptyElement }) - expect(findActionId()).toBeUndefined() - clock.tick(EXPIRE_DELAY) - - expect(events).toEqual([]) + expect(events.length).toBe(1) + expect(events[0].frustrationTypes).toEqual([FrustrationType.RAGE_CLICK]) + expect(events[0].events?.length).toBe(3) }) - it('does not populate the frustrationTypes array', () => { + it('aggregates frustrationTypes from all clicks', () => { const { lifeCycle, clock } = setupBuilder.build() + // Dead + emulateClick() + clock.tick(PAGE_ACTIVITY_VALIDATION_DELAY) + + // Error emulateClick({ activity: {} }) lifeCycle.notify(LifeCycleEventType.RUM_EVENT_COLLECTED, createFakeErrorEvent()) + clock.tick(PAGE_ACTIVITY_VALIDATION_DELAY) + + // Third click to make a rage click + emulateClick({ activity: {} }) clock.tick(EXPIRE_DELAY) expect(events.length).toBe(1) - expect(events[0].frustrationTypes).toEqual([]) + expect(events[0].frustrationTypes).toEqual( + jasmine.arrayWithExactContents([ + FrustrationType.DEAD_CLICK, + FrustrationType.ERROR_CLICK, + FrustrationType.RAGE_CLICK, + ]) + ) }) }) - describe('when tracking frustrations', () => { - beforeEach(() => { - setupBuilder.withConfiguration({ trackFrustrations: true }) - }) - - it('discards any click action with a negative duration', () => { - const { clock } = setupBuilder.build() - emulateClick({ activity: { delay: -1 } }) - expect(findActionId()!.length).toEqual(2) - clock.tick(EXPIRE_DELAY) - - expect(events).toEqual([]) - expect(findActionId()).toEqual([]) - }) - - it('ongoing click action is stopped on view end', () => { + describe('error clicks', () => { + it('considers a "click with activity" followed by an error as a click action with "error" frustration type', () => { const { lifeCycle, clock } = setupBuilder.build() - emulateClick({ activity: { delay: BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY } }) - clock.tick(BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY) - - lifeCycle.notify(LifeCycleEventType.VIEW_ENDED, { - endClocks: clocksNow(), - }) + emulateClick({ activity: {} }) + lifeCycle.notify(LifeCycleEventType.RUM_EVENT_COLLECTED, createFakeErrorEvent()) + clock.tick(EXPIRE_DELAY) expect(events.length).toBe(1) - expect(events[0].duration).toBe((2 * BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY) as Duration) + expect(events[0].frustrationTypes).toEqual([FrustrationType.ERROR_CLICK]) }) - it('collect click actions even if another one is ongoing', () => { - const { clock } = setupBuilder.build() + it('considers a "click without activity" followed by an error as a click action with "error" (and "dead") frustration type', () => { + const { lifeCycle, clock } = setupBuilder.build() - const firstPointerDownTimeStamp = timeStampNow() - emulateClick({ activity: {} }) - const secondPointerDownTimeStamp = timeStampNow() - emulateClick({ activity: {} }) + emulateClick() + lifeCycle.notify(LifeCycleEventType.RUM_EVENT_COLLECTED, createFakeErrorEvent()) clock.tick(EXPIRE_DELAY) - expect(events.length).toBe(2) - expect(events[0].startClocks.timeStamp).toBe(addDuration(firstPointerDownTimeStamp, EMULATED_CLICK_DURATION)) - expect(events[1].startClocks.timeStamp).toBe(addDuration(secondPointerDownTimeStamp, EMULATED_CLICK_DURATION)) + expect(events.length).toBe(1) + expect(events[0].frustrationTypes).toEqual( + jasmine.arrayWithExactContents([FrustrationType.ERROR_CLICK, FrustrationType.DEAD_CLICK]) + ) }) + }) - it('collect click actions even if nothing happens after a click (dead click)', () => { + describe('dead clicks', () => { + it('considers a "click without activity" as a dead click', () => { const { clock } = setupBuilder.build() + emulateClick() clock.tick(EXPIRE_DELAY) expect(events.length).toBe(1) expect(events[0].frustrationTypes).toEqual([FrustrationType.DEAD_CLICK]) - expect(findActionId()).toEqual([]) }) - it('does not set a duration for dead clicks', () => { + it('does not consider a click with activity happening on pointerdown as a dead click', () => { const { clock } = setupBuilder.build() - emulateClick() - clock.tick(EXPIRE_DELAY) - expect(events.length).toBe(1) - expect(events[0].duration).toBeUndefined() - }) + emulateClick({ activity: { on: 'pointerdown' } }) - it('collect click actions even if it fails to find a name', () => { - const { clock } = setupBuilder.build() - emulateClick({ activity: {}, target: emptyElement }) - expect(findActionId()!.length).toBeGreaterThan(0) clock.tick(EXPIRE_DELAY) - expect(events.length).toBe(1) + expect(events[0].frustrationTypes).toEqual([]) }) - describe('rage clicks', () => { - it('considers a chain of three clicks or more as a single action with "rage" frustration type', () => { - const { clock } = setupBuilder.build() - const firstPointerDownTimeStamp = timeStampNow() - const activityDelay = 5 - emulateClick({ activity: { delay: activityDelay } }) - emulateClick({ activity: { delay: activityDelay } }) - emulateClick({ activity: { delay: activityDelay } }) - - clock.tick(EXPIRE_DELAY) - expect(events.length).toBe(1) - expect(events[0].startClocks.timeStamp).toBe(addDuration(firstPointerDownTimeStamp, EMULATED_CLICK_DURATION)) - expect(events[0].frustrationTypes).toEqual([FrustrationType.RAGE_CLICK]) - expect(events[0].duration).toBe( - (MAX_DURATION_BETWEEN_CLICKS + 2 * activityDelay + 2 * EMULATED_CLICK_DURATION) as Duration - ) - }) - - it('should contain original events from of rage sequence', () => { - const { clock } = setupBuilder.build() - const activityDelay = 5 - emulateClick({ activity: { delay: activityDelay } }) - emulateClick({ activity: { delay: activityDelay } }) - emulateClick({ activity: { delay: activityDelay } }) - - clock.tick(EXPIRE_DELAY) - expect(events.length).toBe(1) - expect(events[0].frustrationTypes).toEqual([FrustrationType.RAGE_CLICK]) - expect(events[0].events?.length).toBe(3) - }) - - it('aggregates frustrationTypes from all clicks', () => { - const { lifeCycle, clock } = setupBuilder.build() - - // Dead - emulateClick() - clock.tick(PAGE_ACTIVITY_VALIDATION_DELAY) - - // Error - emulateClick({ activity: {} }) - lifeCycle.notify(LifeCycleEventType.RUM_EVENT_COLLECTED, createFakeErrorEvent()) - clock.tick(PAGE_ACTIVITY_VALIDATION_DELAY) - - // Third click to make a rage click - emulateClick({ activity: {} }) - - clock.tick(EXPIRE_DELAY) - expect(events.length).toBe(1) - expect(events[0].frustrationTypes).toEqual( - jasmine.arrayWithExactContents([ - FrustrationType.DEAD_CLICK, - FrustrationType.ERROR_CLICK, - FrustrationType.RAGE_CLICK, - ]) - ) - }) - }) - - describe('error clicks', () => { - it('considers a "click with activity" followed by an error as a click action with "error" frustration type', () => { - const { lifeCycle, clock } = setupBuilder.build() - - emulateClick({ activity: {} }) - lifeCycle.notify(LifeCycleEventType.RUM_EVENT_COLLECTED, createFakeErrorEvent()) - - clock.tick(EXPIRE_DELAY) - expect(events.length).toBe(1) - expect(events[0].frustrationTypes).toEqual([FrustrationType.ERROR_CLICK]) - }) - - it('considers a "click without activity" followed by an error as a click action with "error" (and "dead") frustration type', () => { - const { lifeCycle, clock } = setupBuilder.build() + it('activity happening on pointerdown is not taken into account for the action duration', () => { + const { clock } = setupBuilder.build() - emulateClick() - lifeCycle.notify(LifeCycleEventType.RUM_EVENT_COLLECTED, createFakeErrorEvent()) + emulateClick({ activity: { on: 'pointerdown' } }) - clock.tick(EXPIRE_DELAY) - expect(events.length).toBe(1) - expect(events[0].frustrationTypes).toEqual( - jasmine.arrayWithExactContents([FrustrationType.ERROR_CLICK, FrustrationType.DEAD_CLICK]) - ) - }) + clock.tick(EXPIRE_DELAY) + expect(events.length).toBe(1) + expect(events[0].duration).toBe(0 as Duration) }) - describe('dead clicks', () => { - it('considers a "click without activity" as a dead click', () => { - const { clock } = setupBuilder.build() - - emulateClick() - - clock.tick(EXPIRE_DELAY) - expect(events.length).toBe(1) - expect(events[0].frustrationTypes).toEqual([FrustrationType.DEAD_CLICK]) - }) - - it('does not consider a click with activity happening on pointerdown as a dead click', () => { - const { clock } = setupBuilder.build() - - emulateClick({ activity: { on: 'pointerdown' } }) - - clock.tick(EXPIRE_DELAY) - expect(events.length).toBe(1) - expect(events[0].frustrationTypes).toEqual([]) - }) - - it('activity happening on pointerdown is not taken into account for the action duration', () => { - const { clock } = setupBuilder.build() - - emulateClick({ activity: { on: 'pointerdown' } }) - - clock.tick(EXPIRE_DELAY) - expect(events.length).toBe(1) - expect(events[0].duration).toBe(0 as Duration) - }) - - it('does not consider a click with activity happening on pointerup as a dead click', () => { - const { clock } = setupBuilder.build() + it('does not consider a click with activity happening on pointerup as a dead click', () => { + const { clock } = setupBuilder.build() - emulateClick({ activity: { on: 'pointerup' } }) + emulateClick({ activity: { on: 'pointerup' } }) - clock.tick(EXPIRE_DELAY) - expect(events.length).toBe(1) - expect(events[0].frustrationTypes).toEqual([]) - }) + clock.tick(EXPIRE_DELAY) + expect(events.length).toBe(1) + expect(events[0].frustrationTypes).toEqual([]) }) }) diff --git a/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.ts b/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.ts index 357c8d6b4e..4b5a58fd76 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.ts @@ -82,7 +82,7 @@ export function trackClickActions( hadActivityOnPointerDown: () => boolean }>({ onPointerDown: (pointerDownEvent) => - processPointerDown(configuration, lifeCycle, domMutationObservable, history, pointerDownEvent), + processPointerDown(configuration, lifeCycle, domMutationObservable, pointerDownEvent), onPointerUp: ({ clickActionBase, hadActivityOnPointerDown }, startEvent, getUserActivity) => startClickAction( configuration, @@ -99,8 +99,7 @@ export function trackClickActions( }) const actionContexts: ActionContexts = { - findActionId: (startTime?: RelativeTime) => - configuration.trackFrustrations ? history.findAll(startTime) : history.find(startTime), + findActionId: (startTime?: RelativeTime) => history.findAll(startTime), } return { @@ -132,21 +131,9 @@ function processPointerDown( configuration: RumConfiguration, lifeCycle: LifeCycle, domMutationObservable: Observable, - history: ClickActionIdHistory, pointerDownEvent: MouseEventOnElement ) { - if (!configuration.trackFrustrations && history.find()) { - // TODO: remove this in a future major version. To keep retrocompatibility, ignore any new - // action if another one is already occurring. - return - } - const clickActionBase = computeClickActionBase(pointerDownEvent, configuration.actionNameAttribute) - if (!configuration.trackFrustrations && !clickActionBase.name) { - // TODO: remove this in a future major version. To keep retrocompatibility, ignore any action - // with a blank name - return - } let hadActivityOnPointerDown = false @@ -178,10 +165,7 @@ function startClickAction( hadActivityOnPointerDown: () => boolean ) { const click = newClick(lifeCycle, history, getUserActivity, clickActionBase, startEvent) - - if (configuration.trackFrustrations) { - appendClickToClickChain(click) - } + appendClickToClickChain(click) const { stop: stopWaitPageActivityEnd } = waitPageActivityEnd( lifeCycle, @@ -203,18 +187,6 @@ function startClickAction( } else { click.stop() } - - // Validate or discard the click only if we don't track frustrations. It'll be done when - // the click chain is finalized. - if (!configuration.trackFrustrations) { - if (!pageActivityEndEvent.hadActivity) { - // If we are not tracking frustrations, we should discard the click to keep backward - // compatibility. - click.discard() - } else { - click.validate() - } - } } }, CLICK_ACTION_MAX_DURATION diff --git a/test/e2e/scenario/recorder/recorder.scenario.ts b/test/e2e/scenario/recorder/recorder.scenario.ts index bead027797..6a2699ed5c 100644 --- a/test/e2e/scenario/recorder/recorder.scenario.ts +++ b/test/e2e/scenario/recorder/recorder.scenario.ts @@ -691,7 +691,7 @@ describe('recorder', () => { describe('frustration records', () => { createTest('should detect a dead click and match it to mouse interaction record') - .withRum({ trackFrustrations: true }) + .withRum({ trackUserInteractions: true }) .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .run(async ({ serverEvents }) => { @@ -715,7 +715,7 @@ describe('recorder', () => { }) createTest('should detect a rage click and match it to mouse interaction records') - .withRum({ trackFrustrations: true }) + .withRum({ trackUserInteractions: true }) .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( diff --git a/test/e2e/scenario/rum/actions.scenario.ts b/test/e2e/scenario/rum/actions.scenario.ts index c3e8c288e6..66d52955d0 100644 --- a/test/e2e/scenario/rum/actions.scenario.ts +++ b/test/e2e/scenario/rum/actions.scenario.ts @@ -62,7 +62,7 @@ describe('action collection', () => { }) createTest('compute action target information before the UI changes') - .withRum({ trackFrustrations: true }) + .withRum({ trackUserInteractions: true }) .withBody( html` @@ -91,7 +91,7 @@ describe('action collection', () => { // click event. Skip this test. if (getBrowserName() !== 'firefox') { createTest('does not report a click on the body when the target element changes between mousedown and mouseup') - .withRum({ trackFrustrations: true }) + .withRum({ trackUserInteractions: true }) .withBody( html` @@ -160,15 +160,13 @@ describe('action collection', () => { }) expect(resourceEvents.length).toBe(1) - expect(resourceEvents[0].action!.id).toBe(actionEvents[0].action.id!) + // resource action id should contain the collected action id + the discarded rage click id + expect(resourceEvents[0].action!.id.length).toBe(2) + expect(resourceEvents[0].action!.id).toContain(actionEvents[0].action.id!) }) createTest('increment the view.action.count of the view active when the action started') - .withRum({ - // Frustrations need to be collected for this test case, else actions leading to a new view - // are ignored - trackFrustrations: true, - }) + .withRum({ trackUserInteractions: true }) .withBody( html` @@ -195,7 +193,7 @@ describe('action collection', () => { }) createTest('collect an "error click"') - .withRum({ trackFrustrations: true }) + .withRum({ trackUserInteractions: true }) .withBody( html` @@ -226,7 +224,7 @@ describe('action collection', () => { }) createTest('collect a "dead click"') - .withRum({ trackFrustrations: true }) + .withRum({ trackUserInteractions: true }) .withBody(html` `) .run(async ({ serverEvents }) => { const button = await $('button') @@ -241,7 +239,7 @@ describe('action collection', () => { }) createTest('do not consider a click on a checkbox as "dead_click"') - .withRum({ trackFrustrations: true }) + .withRum({ trackUserInteractions: true }) .withBody(html` `) .run(async ({ serverEvents }) => { const input = await $('input') @@ -254,7 +252,7 @@ describe('action collection', () => { }) createTest('do not consider a click to change the value of a "range" input as "dead_click"') - .withRum({ trackFrustrations: true }) + .withRum({ trackUserInteractions: true }) .withBody(html` `) .run(async ({ serverEvents }) => { const input = await $('input') @@ -267,7 +265,7 @@ describe('action collection', () => { }) createTest('consider a click on an already checked "radio" input as "dead_click"') - .withRum({ trackFrustrations: true }) + .withRum({ trackUserInteractions: true }) .withBody(html` `) .run(async ({ serverEvents }) => { const input = await $('input') @@ -280,7 +278,7 @@ describe('action collection', () => { }) createTest('do not consider a click on text input as "dead_click"') - .withRum({ trackFrustrations: true }) + .withRum({ trackUserInteractions: true }) .withBody(html` `) .run(async ({ serverEvents }) => { const input = await $('input') @@ -293,7 +291,7 @@ describe('action collection', () => { }) createTest('do not consider a click that open a new window as "dead_click"') - .withRum({ trackFrustrations: true }) + .withRum({ trackUserInteractions: true }) .withBody( html` @@ -322,7 +320,7 @@ describe('action collection', () => { }) createTest('collect a "rage click"') - .withRum({ trackFrustrations: true }) + .withRum({ trackUserInteractions: true }) .withBody( html` @@ -345,7 +343,7 @@ describe('action collection', () => { }) createTest('collect multiple frustrations in one action') - .withRum({ trackFrustrations: true }) + .withRum({ trackUserInteractions: true }) .withBody( html` From 503751f82c3cfbdb1dbd91c37697b7f2b956d477 Mon Sep 17 00:00:00 2001 From: Bastien Caudan <1331991+bcaudan@users.noreply.github.com> Date: Fri, 12 May 2023 15:40:43 +0200 Subject: [PATCH 02/27] =?UTF-8?q?=F0=9F=92=A5[RUMF-1554]=20Drop=20some=20d?= =?UTF-8?q?eprecated=20config=20parameters=20(#2238)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * πŸ’₯ drop proxyUrl * πŸ’₯ drop sampleRate * πŸ’₯ drop allowedTracingOrigins * πŸ’₯ drop tracingSampleRate * πŸ’₯ drop trackInteractions * πŸ‘Œ typo Co-authored-by: Yannick Adam * πŸ‘Œremove outdated comment --------- Co-authored-by: Yannick Adam --- .../configuration/configuration.spec.ts | 17 ---- .../src/domain/configuration/configuration.ts | 18 +--- .../configuration/endpointBuilder.spec.ts | 40 --------- .../domain/configuration/endpointBuilder.ts | 12 +-- .../transportConfiguration.spec.ts | 60 ++++++------- .../rum-core/src/domain/configuration.spec.ts | 90 ------------------- packages/rum-core/src/domain/configuration.ts | 87 ++---------------- performances/src/main.ts | 4 +- test/e2e/scenario/rum/tracing.scenario.ts | 6 +- 9 files changed, 42 insertions(+), 292 deletions(-) diff --git a/packages/core/src/domain/configuration/configuration.spec.ts b/packages/core/src/domain/configuration/configuration.spec.ts index b0b6d69045..54392c36b1 100644 --- a/packages/core/src/domain/configuration/configuration.spec.ts +++ b/packages/core/src/domain/configuration/configuration.spec.ts @@ -65,23 +65,6 @@ describe('validateAndBuildConfiguration', () => { expect(displaySpy).not.toHaveBeenCalled() }) - it('requires deprecated sampleRate to be a percentage', () => { - expect( - validateAndBuildConfiguration({ clientToken, sampleRate: 'foo' } as unknown as InitConfiguration) - ).toBeUndefined() - expect(displaySpy).toHaveBeenCalledOnceWith('Session Sample Rate should be a number between 0 and 100') - - displaySpy.calls.reset() - expect( - validateAndBuildConfiguration({ clientToken, sampleRate: 200 } as unknown as InitConfiguration) - ).toBeUndefined() - expect(displaySpy).toHaveBeenCalledOnceWith('Session Sample Rate should be a number between 0 and 100') - - displaySpy.calls.reset() - validateAndBuildConfiguration({ clientToken: 'yes', sampleRate: 1 }) - expect(displaySpy).not.toHaveBeenCalled() - }) - it('requires sessionSampleRate to be a percentage', () => { expect( validateAndBuildConfiguration({ clientToken, sessionSampleRate: 'foo' } as unknown as InitConfiguration) diff --git a/packages/core/src/domain/configuration/configuration.ts b/packages/core/src/domain/configuration/configuration.ts index c2f57f639e..2d0911aabe 100644 --- a/packages/core/src/domain/configuration/configuration.ts +++ b/packages/core/src/domain/configuration/configuration.ts @@ -24,10 +24,6 @@ export interface InitConfiguration { // global options clientToken: string beforeSend?: GenericBeforeSendCallback | undefined - /** - * @deprecated use sessionSampleRate instead - */ - sampleRate?: number | undefined sessionSampleRate?: number | undefined telemetrySampleRate?: number | undefined silentMultipleInit?: boolean | undefined @@ -36,10 +32,6 @@ export interface InitConfiguration { // transport options proxy?: string | undefined - /** - * @deprecated use `proxy` instead - */ - proxyUrl?: string | undefined site?: string | undefined // tag and context options @@ -97,8 +89,7 @@ export function validateAndBuildConfiguration(initConfiguration: InitConfigurati return } - const sessionSampleRate = initConfiguration.sessionSampleRate ?? initConfiguration.sampleRate - if (sessionSampleRate !== undefined && !isPercentage(sessionSampleRate)) { + if (initConfiguration.sessionSampleRate !== undefined && !isPercentage(initConfiguration.sessionSampleRate)) { display.error('Session Sample Rate should be a number between 0 and 100') return } @@ -130,7 +121,7 @@ export function validateAndBuildConfiguration(initConfiguration: InitConfigurati beforeSend: initConfiguration.beforeSend && catchUserErrors(initConfiguration.beforeSend, 'beforeSend threw an error:'), cookieOptions: buildCookieOptions(initConfiguration), - sessionSampleRate: sessionSampleRate ?? 100, + sessionSampleRate: initConfiguration.sessionSampleRate ?? 100, telemetrySampleRate: initConfiguration.telemetrySampleRate ?? 20, telemetryConfigurationSampleRate: initConfiguration.telemetryConfigurationSampleRate ?? 5, service: initConfiguration.service, @@ -179,15 +170,14 @@ function mustUseSecureCookie(initConfiguration: InitConfiguration) { } export function serializeConfiguration(configuration: InitConfiguration): Partial { - const proxy = configuration.proxy ?? configuration.proxyUrl return { - session_sample_rate: configuration.sessionSampleRate ?? configuration.sampleRate, + session_sample_rate: configuration.sessionSampleRate, telemetry_sample_rate: configuration.telemetrySampleRate, telemetry_configuration_sample_rate: configuration.telemetryConfigurationSampleRate, use_before_send: !!configuration.beforeSend, use_cross_site_session_cookie: configuration.useCrossSiteSessionCookie, use_secure_session_cookie: configuration.useSecureSessionCookie, - use_proxy: proxy !== undefined ? !!proxy : undefined, + use_proxy: !!configuration.proxy, silent_multiple_init: configuration.silentMultipleInit, track_session_across_subdomains: configuration.trackSessionAcrossSubdomains, track_resources: configuration.trackResources, diff --git a/packages/core/src/domain/configuration/endpointBuilder.spec.ts b/packages/core/src/domain/configuration/endpointBuilder.spec.ts index 53ac1efbac..1a7bcfd22a 100644 --- a/packages/core/src/domain/configuration/endpointBuilder.spec.ts +++ b/packages/core/src/domain/configuration/endpointBuilder.spec.ts @@ -63,46 +63,6 @@ describe('endpointBuilder', () => { ) ).toBeTrue() }) - - it('uses `proxy` over `proxyUrl`', () => { - expect( - createEndpointBuilder( - { ...initConfiguration, proxy: 'https://proxy.io/path', proxyUrl: 'https://legacy-proxy.io/path' }, - 'rum', - [] - ).build('xhr') - ).toMatch(/^https:\/\/proxy.io\/path\?/) - - expect( - createEndpointBuilder( - { ...initConfiguration, proxy: false as any, proxyUrl: 'https://legacy-proxy.io/path' }, - 'rum', - [] - ).build('xhr') - ).toMatch(/^https:\/\/rum.browser-intake-datadoghq.com\//) - }) - }) - - describe('deprecated proxyUrl configuration', () => { - it('should replace the full intake endpoint by the proxyUrl and set it in the attribute ddforward', () => { - expect( - createEndpointBuilder({ ...initConfiguration, proxyUrl: 'https://proxy.io/path' }, 'rum', []).build('xhr') - ).toMatch( - `https://proxy.io/path\\?ddforward=${encodeURIComponent( - `https://rum.browser-intake-datadoghq.com/api/v2/rum?ddsource=(.*)&ddtags=(.*)&dd-api-key=${clientToken}` + - '&dd-evp-origin-version=(.*)&dd-evp-origin=browser&dd-request-id=(.*)&batch_time=(.*)' - )}` - ) - }) - - it('normalizes the proxy url', () => { - expect( - startsWith( - createEndpointBuilder({ ...initConfiguration, proxyUrl: '/path' }, 'rum', []).build('xhr'), - `${location.origin}/path?ddforward` - ) - ).toBeTrue() - }) }) describe('tags', () => { diff --git a/packages/core/src/domain/configuration/endpointBuilder.ts b/packages/core/src/domain/configuration/endpointBuilder.ts index f7676c77bf..3a454dfa1d 100644 --- a/packages/core/src/domain/configuration/endpointBuilder.ts +++ b/packages/core/src/domain/configuration/endpointBuilder.ts @@ -59,22 +59,12 @@ function createEndpointUrlWithParametersBuilder( endpointType: EndpointType ): (parameters: string) => string { const path = `/api/v2/${INTAKE_TRACKS[endpointType]}` - - const { proxy, proxyUrl } = initConfiguration + const proxy = initConfiguration.proxy if (proxy) { const normalizedProxyUrl = normalizeUrl(proxy) return (parameters) => `${normalizedProxyUrl}?ddforward=${encodeURIComponent(`${path}?${parameters}`)}` } - const host = buildEndpointHost(initConfiguration, endpointType) - - if (proxy === undefined && proxyUrl) { - // TODO: remove this in a future major. - const normalizedProxyUrl = normalizeUrl(proxyUrl) - return (parameters) => - `${normalizedProxyUrl}?ddforward=${encodeURIComponent(`https://${host}${path}?${parameters}`)}` - } - return (parameters) => `https://${host}${path}?${parameters}` } diff --git a/packages/core/src/domain/configuration/transportConfiguration.spec.ts b/packages/core/src/domain/configuration/transportConfiguration.spec.ts index a33da4f3b6..cbd5aaaa1f 100644 --- a/packages/core/src/domain/configuration/transportConfiguration.spec.ts +++ b/packages/core/src/domain/configuration/transportConfiguration.spec.ts @@ -97,44 +97,34 @@ describe('transportConfiguration', () => { const configuration = computeTransportConfiguration({ clientToken }) expect(configuration.isIntakeUrl('https://www.foo.com')).toBe(false) }) - ;[ - { - proxyConfigurationName: 'proxy' as const, - intakeUrl: '/api/v2/rum', - }, - { - proxyConfigurationName: 'proxyUrl' as const, - intakeUrl: 'https://rum.browser-intake-datadoghq.com/api/v2/rum', - }, - ].forEach(({ proxyConfigurationName, intakeUrl }) => { - describe(`${proxyConfigurationName} configuration`, () => { - it('should detect proxy intake request', () => { - let configuration = computeTransportConfiguration({ - clientToken, - [proxyConfigurationName]: 'https://www.proxy.com', - }) - expect( - configuration.isIntakeUrl(`https://www.proxy.com/?ddforward=${encodeURIComponent(`${intakeUrl}?foo=bar`)}`) - ).toBe(true) - - configuration = computeTransportConfiguration({ - clientToken, - [proxyConfigurationName]: 'https://www.proxy.com/custom/path', - }) - expect( - configuration.isIntakeUrl( - `https://www.proxy.com/custom/path?ddforward=${encodeURIComponent(`${intakeUrl}?foo=bar`)}` - ) - ).toBe(true) + + describe('proxy configuration', () => { + it('should detect proxy intake request', () => { + let configuration = computeTransportConfiguration({ + clientToken, + proxy: 'https://www.proxy.com', }) + expect( + configuration.isIntakeUrl(`https://www.proxy.com/?ddforward=${encodeURIComponent('/api/v2/rum?foo=bar')}`) + ).toBe(true) - it('should not detect request done on the same host as the proxy', () => { - const configuration = computeTransportConfiguration({ - clientToken, - [proxyConfigurationName]: 'https://www.proxy.com', - }) - expect(configuration.isIntakeUrl('https://www.proxy.com/foo')).toBe(false) + configuration = computeTransportConfiguration({ + clientToken, + proxy: 'https://www.proxy.com/custom/path', + }) + expect( + configuration.isIntakeUrl( + `https://www.proxy.com/custom/path?ddforward=${encodeURIComponent('/api/v2/rum?foo=bar')}` + ) + ).toBe(true) + }) + + it('should not detect request done on the same host as the proxy', () => { + const configuration = computeTransportConfiguration({ + clientToken, + proxy: 'https://www.proxy.com', }) + expect(configuration.isIntakeUrl('https://www.proxy.com/foo')).toBe(false) }) }) ;[ diff --git a/packages/rum-core/src/domain/configuration.spec.ts b/packages/rum-core/src/domain/configuration.spec.ts index f1827ebbae..a2a9056838 100644 --- a/packages/rum-core/src/domain/configuration.spec.ts +++ b/packages/rum-core/src/domain/configuration.spec.ts @@ -141,31 +141,6 @@ describe('validateAndBuildRumConfiguration', () => { }) }) - describe('deprecated tracingSampleRate', () => { - it('defaults to undefined if the option is not provided', () => { - expect(validateAndBuildRumConfiguration(DEFAULT_INIT_CONFIGURATION)!.traceSampleRate).toBeUndefined() - }) - - it('is set to provided value', () => { - expect( - validateAndBuildRumConfiguration({ ...DEFAULT_INIT_CONFIGURATION, tracingSampleRate: 50 })!.traceSampleRate - ).toBe(50) - }) - - it('does not validate the configuration if an incorrect value is provided', () => { - expect( - validateAndBuildRumConfiguration({ ...DEFAULT_INIT_CONFIGURATION, tracingSampleRate: 'foo' as any }) - ).toBeUndefined() - expect(displayErrorSpy).toHaveBeenCalledOnceWith('Trace Sample Rate should be a number between 0 and 100') - - displayErrorSpy.calls.reset() - expect( - validateAndBuildRumConfiguration({ ...DEFAULT_INIT_CONFIGURATION, tracingSampleRate: 200 }) - ).toBeUndefined() - expect(displayErrorSpy).toHaveBeenCalledOnceWith('Trace Sample Rate should be a number between 0 and 100') - }) - }) - describe('traceSampleRate', () => { it('defaults to undefined if the option is not provided', () => { expect(validateAndBuildRumConfiguration(DEFAULT_INIT_CONFIGURATION)!.traceSampleRate).toBeUndefined() @@ -189,47 +164,6 @@ describe('validateAndBuildRumConfiguration', () => { }) }) - describe('allowedTracingOrigins', () => { - it('is set to provided value', () => { - expect( - validateAndBuildRumConfiguration({ - ...DEFAULT_INIT_CONFIGURATION, - allowedTracingOrigins: ['foo'], - service: 'bar', - })!.allowedTracingUrls - ).toEqual([{ match: 'foo', propagatorTypes: ['datadog'] }]) - }) - - it('accepts functions', () => { - const originMatchSpy = jasmine.createSpy<(origin: string) => boolean>() - - const tracingUrlOptionMatch = validateAndBuildRumConfiguration({ - ...DEFAULT_INIT_CONFIGURATION, - allowedTracingOrigins: [originMatchSpy], - service: 'bar', - })!.allowedTracingUrls[0].match as (url: string) => boolean - - expect(typeof tracingUrlOptionMatch).toBe('function') - // Replicating behavior from allowedTracingOrigins, new function will treat the origin part of the URL - tracingUrlOptionMatch('https://my.origin.com/api') - expect(originMatchSpy).toHaveBeenCalledWith('https://my.origin.com') - }) - - it('does not validate the configuration if a value is provided and service is undefined', () => { - expect( - validateAndBuildRumConfiguration({ ...DEFAULT_INIT_CONFIGURATION, allowedTracingOrigins: ['foo'] }) - ).toBeUndefined() - expect(displayErrorSpy).toHaveBeenCalledOnceWith('Service needs to be configured when tracing is enabled') - }) - - it('does not validate the configuration if an incorrect value is provided', () => { - expect( - validateAndBuildRumConfiguration({ ...DEFAULT_INIT_CONFIGURATION, allowedTracingOrigins: 'foo' as any }) - ).toBeUndefined() - expect(displayErrorSpy).toHaveBeenCalledOnceWith('Allowed Tracing Origins should be an array') - }) - }) - describe('allowedTracingUrls', () => { it('defaults to an empty array', () => { expect(validateAndBuildRumConfiguration(DEFAULT_INIT_CONFIGURATION)!.allowedTracingUrls).toEqual([]) @@ -344,30 +278,6 @@ describe('validateAndBuildRumConfiguration', () => { }) }) - describe('deprecated trackInteractions', () => { - it('defaults to false', () => { - expect(validateAndBuildRumConfiguration(DEFAULT_INIT_CONFIGURATION)!.trackUserInteractions).toBeFalse() - }) - - it('is set to provided value', () => { - expect( - validateAndBuildRumConfiguration({ ...DEFAULT_INIT_CONFIGURATION, trackInteractions: true })! - .trackUserInteractions - ).toBeTrue() - expect( - validateAndBuildRumConfiguration({ ...DEFAULT_INIT_CONFIGURATION, trackInteractions: false })! - .trackUserInteractions - ).toBeFalse() - }) - - it('the provided value is cast to boolean', () => { - expect( - validateAndBuildRumConfiguration({ ...DEFAULT_INIT_CONFIGURATION, trackInteractions: 'foo' as any })! - .trackUserInteractions - ).toBeTrue() - }) - }) - describe('trackUserInteractions', () => { it('defaults to false', () => { expect(validateAndBuildRumConfiguration(DEFAULT_INIT_CONFIGURATION)!.trackUserInteractions).toBeFalse() diff --git a/packages/rum-core/src/domain/configuration.ts b/packages/rum-core/src/domain/configuration.ts index 8bb664801b..6453947535 100644 --- a/packages/rum-core/src/domain/configuration.ts +++ b/packages/rum-core/src/domain/configuration.ts @@ -2,7 +2,6 @@ import type { Configuration, InitConfiguration, MatchOption, RawTelemetryConfigu import { getType, arrayFrom, - getOrigin, isMatchOption, serializeConfiguration, assign, @@ -28,15 +27,7 @@ export interface RumInitConfiguration extends InitConfiguration { excludedActivityUrls?: MatchOption[] | undefined // tracing options - /** - * @deprecated use allowedTracingUrls instead - */ - allowedTracingOrigins?: MatchOption[] | undefined allowedTracingUrls?: Array | undefined - /** - * @deprecated use traceSampleRate instead - */ - tracingSampleRate?: number | undefined traceSampleRate?: number | undefined // replay options @@ -49,10 +40,6 @@ export interface RumInitConfiguration extends InitConfiguration { sessionReplaySampleRate?: number | undefined // action options - /** - * @deprecated use trackUserInteractions instead - */ - trackInteractions?: boolean | undefined trackUserInteractions?: boolean | undefined actionNameAttribute?: string | undefined @@ -112,8 +99,7 @@ export function validateAndBuildRumConfiguration( return } - const traceSampleRate = initConfiguration.traceSampleRate ?? initConfiguration.tracingSampleRate - if (traceSampleRate !== undefined && !isPercentage(traceSampleRate)) { + if (initConfiguration.traceSampleRate !== undefined && !isPercentage(initConfiguration.traceSampleRate)) { display.error('Trace Sample Rate should be a number between 0 and 100') return } @@ -133,8 +119,6 @@ export function validateAndBuildRumConfiguration( return } - const trackUserInteractions = !!(initConfiguration.trackUserInteractions ?? initConfiguration.trackInteractions) - return assign( { applicationId: initConfiguration.applicationId, @@ -142,10 +126,10 @@ export function validateAndBuildRumConfiguration( actionNameAttribute: initConfiguration.actionNameAttribute, sessionReplaySampleRate: initConfiguration.sessionReplaySampleRate ?? premiumSampleRate ?? 100, oldPlansBehavior: initConfiguration.sessionReplaySampleRate === undefined, - traceSampleRate, + traceSampleRate: initConfiguration.traceSampleRate, allowedTracingUrls, excludedActivityUrls: initConfiguration.excludedActivityUrls ?? [], - trackUserInteractions, + trackUserInteractions: !!initConfiguration.trackUserInteractions, trackViewsManually: !!initConfiguration.trackViewsManually, trackResources: initConfiguration.trackResources, trackLongTasks: initConfiguration.trackLongTasks, @@ -160,16 +144,9 @@ export function validateAndBuildRumConfiguration( } /** - * Handles allowedTracingUrls and processes legacy allowedTracingOrigins + * Validates allowedTracingUrls and converts match options to tracing options */ function validateAndBuildTracingOptions(initConfiguration: RumInitConfiguration): TracingOption[] | undefined { - // Advise about parameters precedence. - if (initConfiguration.allowedTracingUrls !== undefined && initConfiguration.allowedTracingOrigins !== undefined) { - display.warn( - 'Both allowedTracingUrls and allowedTracingOrigins (deprecated) have been defined. The parameter allowedTracingUrls will override allowedTracingOrigins.' - ) - } - // Handle allowedTracingUrls first if (initConfiguration.allowedTracingUrls !== undefined) { if (!Array.isArray(initConfiguration.allowedTracingUrls)) { display.error('Allowed Tracing URLs should be an array') @@ -197,55 +174,11 @@ function validateAndBuildTracingOptions(initConfiguration: RumInitConfiguration) return tracingOptions } - // Handle conversion of allowedTracingOrigins to allowedTracingUrls - if (initConfiguration.allowedTracingOrigins !== undefined) { - if (!Array.isArray(initConfiguration.allowedTracingOrigins)) { - display.error('Allowed Tracing Origins should be an array') - return - } - if (initConfiguration.allowedTracingOrigins.length !== 0 && initConfiguration.service === undefined) { - display.error('Service needs to be configured when tracing is enabled') - return - } - - const tracingOptions: TracingOption[] = [] - initConfiguration.allowedTracingOrigins.forEach((legacyMatchOption) => { - const tracingOption = convertLegacyMatchOptionToTracingOption(legacyMatchOption) - if (tracingOption) { - tracingOptions.push(tracingOption) - } - }) - return tracingOptions - } - return [] } /** - * Converts parameters from the deprecated allowedTracingOrigins - * to allowedTracingUrls. Handles the change from origin to full URLs. - */ -function convertLegacyMatchOptionToTracingOption(item: MatchOption): TracingOption | undefined { - let match: MatchOption | undefined - if (typeof item === 'string') { - match = item - } else if (item instanceof RegExp) { - match = (url) => item.test(getOrigin(url)) - } else if (typeof item === 'function') { - match = (url) => item(getOrigin(url)) - } - - if (match === undefined) { - display.warn('Allowed Tracing Origins parameters should be a string, RegExp or function. Ignoring parameter', item) - return undefined - } - - return { match, propagatorTypes: ['datadog'] } -} - -/** - * Combines the selected tracing propagators from the different options in allowedTracingUrls, - * and assumes 'datadog' has been selected when using allowedTracingOrigins + * Combines the selected tracing propagators from the different options in allowedTracingUrls */ function getSelectedTracingPropagators(configuration: RumInitConfiguration): PropagatorType[] { const usedTracingPropagators = new Set() @@ -261,10 +194,6 @@ function getSelectedTracingPropagators(configuration: RumInitConfiguration): Pro }) } - if (Array.isArray(configuration.allowedTracingOrigins) && configuration.allowedTracingOrigins.length > 0) { - usedTracingPropagators.add('datadog') - } - return arrayFrom(usedTracingPropagators) } @@ -276,10 +205,8 @@ export function serializeRumConfiguration(configuration: RumInitConfiguration): premium_sample_rate: configuration.premiumSampleRate, replay_sample_rate: configuration.replaySampleRate, session_replay_sample_rate: configuration.sessionReplaySampleRate, - trace_sample_rate: configuration.traceSampleRate ?? configuration.tracingSampleRate, + trace_sample_rate: configuration.traceSampleRate, action_name_attribute: configuration.actionNameAttribute, - use_allowed_tracing_origins: - Array.isArray(configuration.allowedTracingOrigins) && configuration.allowedTracingOrigins.length > 0, use_allowed_tracing_urls: Array.isArray(configuration.allowedTracingUrls) && configuration.allowedTracingUrls.length > 0, selected_tracing_propagators: getSelectedTracingPropagators(configuration), @@ -287,7 +214,7 @@ export function serializeRumConfiguration(configuration: RumInitConfiguration): use_excluded_activity_urls: Array.isArray(configuration.excludedActivityUrls) && configuration.excludedActivityUrls.length > 0, track_views_manually: configuration.trackViewsManually, - track_user_interactions: configuration.trackUserInteractions ?? configuration.trackInteractions, + track_user_interactions: configuration.trackUserInteractions, }, baseSerializedConfiguration ) diff --git a/performances/src/main.ts b/performances/src/main.ts index a17ec9f738..51834c77b8 100644 --- a/performances/src/main.ts +++ b/performances/src/main.ts @@ -81,8 +81,8 @@ async function setupSDK(page: Page, options: ProfilingOptions) { clientToken: 'xxx', applicationId: 'xxx', site: 'datadoghq.com', - trackInteractions: true, - proxyUrl: ${JSON.stringify(options.proxy.origin)} + trackUserInteractions: true, + proxy: ${JSON.stringify(options.proxy.origin)} }) ${options.startRecording ? 'window.DD_RUM.startSessionReplayRecording()' : ''} }) diff --git a/test/e2e/scenario/rum/tracing.scenario.ts b/test/e2e/scenario/rum/tracing.scenario.ts index af0943847b..4a0ad23fc7 100644 --- a/test/e2e/scenario/rum/tracing.scenario.ts +++ b/test/e2e/scenario/rum/tracing.scenario.ts @@ -4,7 +4,7 @@ import { browserExecuteAsync, sendXhr } from '../../lib/helpers/browser' describe('tracing', () => { createTest('trace xhr') - .withRum({ service: 'service', allowedTracingOrigins: ['LOCATION_ORIGIN'] }) + .withRum({ service: 'service', allowedTracingUrls: ['LOCATION_ORIGIN'] }) .run(async ({ serverEvents }) => { const rawHeaders = await sendXhr('/headers', [ ['x-foo', 'bar'], @@ -16,7 +16,7 @@ describe('tracing', () => { }) createTest('trace fetch') - .withRum({ service: 'service', allowedTracingOrigins: ['LOCATION_ORIGIN'] }) + .withRum({ service: 'service', allowedTracingUrls: ['LOCATION_ORIGIN'] }) .run(async ({ serverEvents }) => { const rawHeaders = await browserExecuteAsync((done) => { window @@ -39,7 +39,7 @@ describe('tracing', () => { }) createTest('trace fetch with Request argument') - .withRum({ service: 'service', allowedTracingOrigins: ['LOCATION_ORIGIN'] }) + .withRum({ service: 'service', allowedTracingUrls: ['LOCATION_ORIGIN'] }) .run(async ({ serverEvents }) => { const rawHeaders = await browserExecuteAsync((done) => { window From fe261e6af6c983cfac38f61b893fbf405cf424c1 Mon Sep 17 00:00:00 2001 From: Bastien Caudan <1331991+bcaudan@users.noreply.github.com> Date: Mon, 15 May 2023 10:23:26 +0200 Subject: [PATCH 03/27] =?UTF-8?q?=F0=9F=92=A5=20[RUMF-1554]=20Drop=20some?= =?UTF-8?q?=20deprecated=20public=20APIs=20(#2241)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * πŸ’₯ Drop removeUser * πŸ’₯ Drop (Rum|Logger)GlobalContext APIs --- developer-extension/src/panel/hooks/useSdkInfos.ts | 4 ++-- packages/logs/src/boot/logsPublicApi.spec.ts | 6 +++--- packages/logs/src/boot/logsPublicApi.ts | 8 -------- packages/rum-core/src/boot/rumPublicApi.spec.ts | 10 +++++----- packages/rum-core/src/boot/rumPublicApi.ts | 10 ---------- test/e2e/scenario/rum/init.scenario.ts | 4 ++-- 6 files changed, 12 insertions(+), 30 deletions(-) diff --git a/developer-extension/src/panel/hooks/useSdkInfos.ts b/developer-extension/src/panel/hooks/useSdkInfos.ts index 26a4ed3118..8a9b516658 100644 --- a/developer-extension/src/panel/hooks/useSdkInfos.ts +++ b/developer-extension/src/panel/hooks/useSdkInfos.ts @@ -61,12 +61,12 @@ async function getInfos(): Promise { version: window.DD_RUM?.version, config: window.DD_RUM?.getInitConfiguration?.(), internalContext: window.DD_RUM?.getInternalContext?.(), - globalContext: window.DD_RUM?.getRumGlobalContext?.(), + globalContext: window.DD_RUM?.getGlobalContext?.(), } const logs = window.DD_RUM && { version: window.DD_LOGS?.version, config: window.DD_LOGS?.getInitConfiguration?.(), - globalContext: window.DD_LOGS?.getLoggerGlobalContext?.(), + globalContext: window.DD_LOGS?.getGlobalContext?.(), } return { rum, logs, cookie } ` diff --git a/packages/logs/src/boot/logsPublicApi.spec.ts b/packages/logs/src/boot/logsPublicApi.spec.ts index b383e68f98..0c34049d3a 100644 --- a/packages/logs/src/boot/logsPublicApi.spec.ts +++ b/packages/logs/src/boot/logsPublicApi.spec.ts @@ -213,9 +213,9 @@ describe('logs entry', () => { }) it('stores a deep copy of the global context', () => { - LOGS.addLoggerGlobalContext('foo', 'bar') + LOGS.setGlobalContextProperty('foo', 'bar') LOGS.logger.log('message') - LOGS.addLoggerGlobalContext('foo', 'baz') + LOGS.setGlobalContextProperty('foo', 'baz') LOGS.init(DEFAULT_INIT_CONFIGURATION) @@ -322,7 +322,7 @@ describe('logs entry', () => { LOGS = makeLogsPublicApi(startLogs) }) - it('should return undefined if not initalized', () => { + it('should return undefined if not initialized', () => { expect(LOGS.getInternalContext()).toBeUndefined() }) diff --git a/packages/logs/src/boot/logsPublicApi.ts b/packages/logs/src/boot/logsPublicApi.ts index 67144dd5d1..a49b1a8988 100644 --- a/packages/logs/src/boot/logsPublicApi.ts +++ b/packages/logs/src/boot/logsPublicApi.ts @@ -99,20 +99,12 @@ export function makeLogsPublicApi(startLogsImpl: StartLogs) { isAlreadyInitialized = true }), - /** @deprecated: use getGlobalContext instead */ - getLoggerGlobalContext: monitor(globalContextManager.get), getGlobalContext: monitor(globalContextManager.getContext), - /** @deprecated: use setGlobalContext instead */ - setLoggerGlobalContext: monitor(globalContextManager.set), setGlobalContext: monitor(globalContextManager.setContext), - /** @deprecated: use setGlobalContextProperty instead */ - addLoggerGlobalContext: monitor(globalContextManager.add), setGlobalContextProperty: monitor(globalContextManager.setContextProperty), - /** @deprecated: use removeGlobalContextProperty instead */ - removeLoggerGlobalContext: monitor(globalContextManager.remove), removeGlobalContextProperty: monitor(globalContextManager.removeContextProperty), clearGlobalContext: monitor(globalContextManager.clearContext), diff --git a/packages/rum-core/src/boot/rumPublicApi.spec.ts b/packages/rum-core/src/boot/rumPublicApi.spec.ts index 455f13fdd6..3941b3ad7e 100644 --- a/packages/rum-core/src/boot/rumPublicApi.spec.ts +++ b/packages/rum-core/src/boot/rumPublicApi.spec.ts @@ -263,9 +263,9 @@ describe('rum public api', () => { }) it('stores a deep copy of the global context', () => { - rumPublicApi.addRumGlobalContext('foo', 'bar') + rumPublicApi.setGlobalContextProperty('foo', 'bar') rumPublicApi.addAction('message') - rumPublicApi.addRumGlobalContext('foo', 'baz') + rumPublicApi.setGlobalContextProperty('foo', 'baz') rumPublicApi.init(DEFAULT_INIT_CONFIGURATION) @@ -365,9 +365,9 @@ describe('rum public api', () => { }) it('stores a deep copy of the global context', () => { - rumPublicApi.addRumGlobalContext('foo', 'bar') + rumPublicApi.setGlobalContextProperty('foo', 'bar') rumPublicApi.addError(new Error('message')) - rumPublicApi.addRumGlobalContext('foo', 'baz') + rumPublicApi.setGlobalContextProperty('foo', 'baz') rumPublicApi.init(DEFAULT_INIT_CONFIGURATION) @@ -460,7 +460,7 @@ describe('rum public api', () => { it('should remove the user', () => { const user = { id: 'foo', name: 'bar', email: 'qux' } rumPublicApi.setUser(user) - rumPublicApi.removeUser() + rumPublicApi.clearUser() rumPublicApi.addAction('message') rumPublicApi.init(DEFAULT_INIT_CONFIGURATION) diff --git a/packages/rum-core/src/boot/rumPublicApi.ts b/packages/rum-core/src/boot/rumPublicApi.ts index 584e8341fb..284cb6bddb 100644 --- a/packages/rum-core/src/boot/rumPublicApi.ts +++ b/packages/rum-core/src/boot/rumPublicApi.ts @@ -188,20 +188,12 @@ export function makeRumPublicApi( const rumPublicApi = makePublicApi({ init: monitor(initRum), - /** @deprecated: use setGlobalContextProperty instead */ - addRumGlobalContext: monitor(globalContextManager.add), setGlobalContextProperty: monitor(globalContextManager.setContextProperty), - /** @deprecated: use removeGlobalContextProperty instead */ - removeRumGlobalContext: monitor(globalContextManager.remove), removeGlobalContextProperty: monitor(globalContextManager.removeContextProperty), - /** @deprecated: use getGlobalContext instead */ - getRumGlobalContext: monitor(globalContextManager.get), getGlobalContext: monitor(globalContextManager.getContext), - /** @deprecated: use setGlobalContext instead */ - setRumGlobalContext: monitor(globalContextManager.set), setGlobalContext: monitor(globalContextManager.setContext), clearGlobalContext: monitor(globalContextManager.clearContext), @@ -249,8 +241,6 @@ export function makeRumPublicApi( removeUserProperty: monitor(userContextManager.removeContextProperty), - /** @deprecated: renamed to clearUser */ - removeUser: monitor(userContextManager.clearContext), clearUser: monitor(userContextManager.clearContext), startView, diff --git a/test/e2e/scenario/rum/init.scenario.ts b/test/e2e/scenario/rum/init.scenario.ts index 145f9a2a41..7a44e334b8 100644 --- a/test/e2e/scenario/rum/init.scenario.ts +++ b/test/e2e/scenario/rum/init.scenario.ts @@ -131,8 +131,8 @@ describe('beforeSend', () => { .withRumSlim() .withRumInit((configuration) => { window.DD_RUM!.init(configuration) - window.DD_RUM!.addRumGlobalContext('foo', 'baz') - window.DD_RUM!.addRumGlobalContext('zig', 'zag') + window.DD_RUM!.setGlobalContextProperty('foo', 'baz') + window.DD_RUM!.setGlobalContextProperty('zig', 'zag') }) .run(async ({ serverEvents }) => { await flushEvents() From aad4d979b1880cdd1af794ae5bd9ebe893dde705 Mon Sep 17 00:00:00 2001 From: Bastien Caudan <1331991+bcaudan@users.noreply.github.com> Date: Fri, 26 May 2023 14:02:15 +0200 Subject: [PATCH 04/27] =?UTF-8?q?=F0=9F=92=A5=20[RUMF-1587]=20Remove=20`pr?= =?UTF-8?q?emiumSampleRate`=20and=20`replaySampleRate`=20(#2256)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * πŸ”₯ [RUMF-1587] Remove `premiumSampleRate` and `replaySampleRate` * πŸ‘Œ wording Co-authored-by: Aymeric * πŸ‘Œuse trackResources/LongTasks directly from configuration * πŸ‘Œremove useless config copy --------- Co-authored-by: Aymeric --- packages/rum-core/src/boot/startRum.spec.ts | 2 +- packages/rum-core/src/boot/startRum.ts | 2 +- .../rum-core/src/domain/configuration.spec.ts | 94 +----------- packages/rum-core/src/domain/configuration.ts | 34 +---- .../longTask/longTaskCollection.spec.ts | 19 ++- .../longTask/longTaskCollection.ts | 9 +- .../resource/resourceCollection.spec.ts | 19 +-- .../resource/resourceCollection.ts | 12 +- .../src/domain/rumSessionManager.spec.ts | 139 ------------------ .../rum-core/src/domain/rumSessionManager.ts | 14 +- .../rum-core/test/mockRumSessionManager.ts | 14 -- packages/rum-core/test/testSetupBuilder.ts | 7 +- 12 files changed, 54 insertions(+), 311 deletions(-) diff --git a/packages/rum-core/src/boot/startRum.spec.ts b/packages/rum-core/src/boot/startRum.spec.ts index e40b67b287..bfbbfff0c9 100644 --- a/packages/rum-core/src/boot/startRum.spec.ts +++ b/packages/rum-core/src/boot/startRum.spec.ts @@ -77,7 +77,7 @@ function startRumStub( noopRecorderApi ) - startLongTaskCollection(lifeCycle, sessionManager) + startLongTaskCollection(lifeCycle, configuration, sessionManager) return { stop: () => { rumEventCollectionStop() diff --git a/packages/rum-core/src/boot/startRum.ts b/packages/rum-core/src/boot/startRum.ts index 948a5d0562..1ab730a04b 100644 --- a/packages/rum-core/src/boot/startRum.ts +++ b/packages/rum-core/src/boot/startRum.ts @@ -116,7 +116,7 @@ export function startRum( addTelemetryConfiguration(serializeRumConfiguration(initConfiguration)) - startLongTaskCollection(lifeCycle, session) + startLongTaskCollection(lifeCycle, configuration, session) startResourceCollection(lifeCycle, configuration, session, pageStateHistory) const { addTiming, startView } = startViewCollection( lifeCycle, diff --git a/packages/rum-core/src/domain/configuration.spec.ts b/packages/rum-core/src/domain/configuration.spec.ts index a2a9056838..bfdc3c6bcd 100644 --- a/packages/rum-core/src/domain/configuration.spec.ts +++ b/packages/rum-core/src/domain/configuration.spec.ts @@ -36,38 +36,6 @@ describe('validateAndBuildRumConfiguration', () => { ).toBe(50) }) - it('is set to `premiumSampleRate` provided value', () => { - expect( - validateAndBuildRumConfiguration({ ...DEFAULT_INIT_CONFIGURATION, premiumSampleRate: 50 })! - .sessionReplaySampleRate - ).toBe(50) - }) - - it('is set to `replaySampleRate` provided value', () => { - expect( - validateAndBuildRumConfiguration({ ...DEFAULT_INIT_CONFIGURATION, replaySampleRate: 50 })! - .sessionReplaySampleRate - ).toBe(50) - }) - - it('is set with precedence `sessionReplaySampleRate` > `premiumSampleRate` > `replaySampleRate`', () => { - expect( - validateAndBuildRumConfiguration({ - ...DEFAULT_INIT_CONFIGURATION, - replaySampleRate: 25, - premiumSampleRate: 50, - sessionReplaySampleRate: 75, - })!.sessionReplaySampleRate - ).toBe(75) - expect( - validateAndBuildRumConfiguration({ - ...DEFAULT_INIT_CONFIGURATION, - replaySampleRate: 25, - premiumSampleRate: 50, - })!.sessionReplaySampleRate - ).toBe(50) - }) - it('does not validate the configuration if an incorrect value is provided', () => { expect( validateAndBuildRumConfiguration({ ...DEFAULT_INIT_CONFIGURATION, sessionReplaySampleRate: 'foo' as any }) @@ -84,60 +52,6 @@ describe('validateAndBuildRumConfiguration', () => { expect(displayErrorSpy).toHaveBeenCalledOnceWith( 'Session Replay Sample Rate should be a number between 0 and 100' ) - - displayErrorSpy.calls.reset() - - expect( - validateAndBuildRumConfiguration({ ...DEFAULT_INIT_CONFIGURATION, premiumSampleRate: 'foo' as any }) - ).toBeUndefined() - expect(displayErrorSpy).toHaveBeenCalledOnceWith('Premium Sample Rate should be a number between 0 and 100') - - displayErrorSpy.calls.reset() - - expect( - validateAndBuildRumConfiguration({ ...DEFAULT_INIT_CONFIGURATION, premiumSampleRate: 200 }) - ).toBeUndefined() - expect(displayErrorSpy).toHaveBeenCalledOnceWith('Premium Sample Rate should be a number between 0 and 100') - - displayErrorSpy.calls.reset() - - expect( - validateAndBuildRumConfiguration({ ...DEFAULT_INIT_CONFIGURATION, replaySampleRate: 'foo' as any }) - ).toBeUndefined() - expect(displayErrorSpy).toHaveBeenCalledOnceWith('Premium Sample Rate should be a number between 0 and 100') - - displayErrorSpy.calls.reset() - - expect(validateAndBuildRumConfiguration({ ...DEFAULT_INIT_CONFIGURATION, replaySampleRate: 200 })).toBeUndefined() - expect(displayErrorSpy).toHaveBeenCalledOnceWith('Premium Sample Rate should be a number between 0 and 100') - - displayErrorSpy.calls.reset() - }) - - it('should validate and display a warn if both `sessionReplaySampleRate` and `premiumSampleRate` are set', () => { - expect( - validateAndBuildRumConfiguration({ - ...DEFAULT_INIT_CONFIGURATION, - sessionReplaySampleRate: 100, - premiumSampleRate: 100, - }) - ).toBeDefined() - expect(displayWarnSpy).toHaveBeenCalledOnceWith( - 'Ignoring Premium Sample Rate because Session Replay Sample Rate is set' - ) - }) - }) - - describe('oldPlansBehavior', () => { - it('should be true by default', () => { - expect(validateAndBuildRumConfiguration(DEFAULT_INIT_CONFIGURATION)!.oldPlansBehavior).toBeTrue() - }) - - it('should be false if `sessionReplaySampleRate` is set', () => { - expect( - validateAndBuildRumConfiguration({ ...DEFAULT_INIT_CONFIGURATION, sessionReplaySampleRate: 100 })! - .oldPlansBehavior - ).toBeFalse() }) }) @@ -364,8 +278,8 @@ describe('validateAndBuildRumConfiguration', () => { }) describe('trackResources', () => { - it('defaults to undefined', () => { - expect(validateAndBuildRumConfiguration(DEFAULT_INIT_CONFIGURATION)!.trackResources).toBeUndefined() + it('defaults to false', () => { + expect(validateAndBuildRumConfiguration(DEFAULT_INIT_CONFIGURATION)!.trackResources).toBeFalse() }) it('is set to provided value', () => { @@ -379,8 +293,8 @@ describe('validateAndBuildRumConfiguration', () => { }) describe('trackLongTasks', () => { - it('defaults to undefined', () => { - expect(validateAndBuildRumConfiguration(DEFAULT_INIT_CONFIGURATION)!.trackLongTasks).toBeUndefined() + it('defaults to false', () => { + expect(validateAndBuildRumConfiguration(DEFAULT_INIT_CONFIGURATION)!.trackLongTasks).toBeFalse() }) it('is set to provided value', () => { diff --git a/packages/rum-core/src/domain/configuration.ts b/packages/rum-core/src/domain/configuration.ts index 6453947535..34c87bd089 100644 --- a/packages/rum-core/src/domain/configuration.ts +++ b/packages/rum-core/src/domain/configuration.ts @@ -20,10 +20,6 @@ export interface RumInitConfiguration extends InitConfiguration { // global options applicationId: string beforeSend?: ((event: RumEvent, context: RumEventDomainContext) => void | boolean) | undefined - /** - * @deprecated use sessionReplaySampleRate instead - */ - premiumSampleRate?: number | undefined excludedActivityUrls?: MatchOption[] | undefined // tracing options @@ -33,10 +29,6 @@ export interface RumInitConfiguration extends InitConfiguration { // replay options defaultPrivacyLevel?: DefaultPrivacyLevel | undefined subdomain?: string - /** - * @deprecated use sessionReplaySampleRate instead - */ - replaySampleRate?: number | undefined sessionReplaySampleRate?: number | undefined // action options @@ -60,12 +52,11 @@ export interface RumConfiguration extends Configuration { excludedActivityUrls: MatchOption[] applicationId: string defaultPrivacyLevel: DefaultPrivacyLevel - oldPlansBehavior: boolean sessionReplaySampleRate: number trackUserInteractions: boolean trackViewsManually: boolean - trackResources: boolean | undefined - trackLongTasks: boolean | undefined + trackResources: boolean + trackLongTasks: boolean version?: string subdomain?: string customerDataTelemetrySampleRate: number @@ -87,18 +78,6 @@ export function validateAndBuildRumConfiguration( return } - // TODO remove fallback in next major - let premiumSampleRate = initConfiguration.premiumSampleRate ?? initConfiguration.replaySampleRate - if (premiumSampleRate !== undefined && initConfiguration.sessionReplaySampleRate !== undefined) { - display.warn('Ignoring Premium Sample Rate because Session Replay Sample Rate is set') - premiumSampleRate = undefined - } - - if (premiumSampleRate !== undefined && !isPercentage(premiumSampleRate)) { - display.error('Premium Sample Rate should be a number between 0 and 100') - return - } - if (initConfiguration.traceSampleRate !== undefined && !isPercentage(initConfiguration.traceSampleRate)) { display.error('Trace Sample Rate should be a number between 0 and 100') return @@ -124,15 +103,14 @@ export function validateAndBuildRumConfiguration( applicationId: initConfiguration.applicationId, version: initConfiguration.version, actionNameAttribute: initConfiguration.actionNameAttribute, - sessionReplaySampleRate: initConfiguration.sessionReplaySampleRate ?? premiumSampleRate ?? 100, - oldPlansBehavior: initConfiguration.sessionReplaySampleRate === undefined, + sessionReplaySampleRate: initConfiguration.sessionReplaySampleRate ?? 100, traceSampleRate: initConfiguration.traceSampleRate, allowedTracingUrls, excludedActivityUrls: initConfiguration.excludedActivityUrls ?? [], trackUserInteractions: !!initConfiguration.trackUserInteractions, trackViewsManually: !!initConfiguration.trackViewsManually, - trackResources: initConfiguration.trackResources, - trackLongTasks: initConfiguration.trackLongTasks, + trackResources: !!initConfiguration.trackResources, + trackLongTasks: !!initConfiguration.trackLongTasks, subdomain: initConfiguration.subdomain, defaultPrivacyLevel: objectHasValue(DefaultPrivacyLevel, initConfiguration.defaultPrivacyLevel) ? initConfiguration.defaultPrivacyLevel @@ -202,8 +180,6 @@ export function serializeRumConfiguration(configuration: RumInitConfiguration): return assign( { - premium_sample_rate: configuration.premiumSampleRate, - replay_sample_rate: configuration.replaySampleRate, session_replay_sample_rate: configuration.sessionReplaySampleRate, trace_sample_rate: configuration.traceSampleRate, action_name_attribute: configuration.actionNameAttribute, diff --git a/packages/rum-core/src/domain/rumEventsCollection/longTask/longTaskCollection.spec.ts b/packages/rum-core/src/domain/rumEventsCollection/longTask/longTaskCollection.spec.ts index c700d934e7..19b1247f0b 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/longTask/longTaskCollection.spec.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/longTask/longTaskCollection.spec.ts @@ -18,12 +18,15 @@ const LONG_TASK: RumPerformanceLongTaskTiming = { describe('long task collection', () => { let setupBuilder: TestSetupBuilder let sessionManager: RumSessionManagerMock + let trackLongTasks: boolean + beforeEach(() => { + trackLongTasks = true sessionManager = createRumSessionManagerMock() setupBuilder = setup() .withSessionManager(sessionManager) - .beforeBuild(({ lifeCycle, sessionManager }) => { - startLongTaskCollection(lifeCycle, sessionManager) + .beforeBuild(({ lifeCycle, sessionManager, configuration }) => { + startLongTaskCollection(lifeCycle, { ...configuration, trackLongTasks }, sessionManager) }) }) @@ -43,16 +46,20 @@ describe('long task collection', () => { expect(rawRumEvents.length).toBe(1) }) - it('should only collect when session allows long tasks', () => { + it('should collect when trackLongTasks=true', () => { + trackLongTasks = true const { lifeCycle, rawRumEvents } = setupBuilder.build() - sessionManager.setLongTaskAllowed(true) lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [LONG_TASK]) expect(rawRumEvents.length).toBe(1) + }) + + it('should not collect when trackLongTasks=false', () => { + trackLongTasks = false + const { lifeCycle, rawRumEvents } = setupBuilder.build() - sessionManager.setLongTaskAllowed(false) lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [LONG_TASK]) - expect(rawRumEvents.length).toBe(1) + expect(rawRumEvents.length).toBe(0) }) it('should create raw rum event from performance entry', () => { diff --git a/packages/rum-core/src/domain/rumEventsCollection/longTask/longTaskCollection.ts b/packages/rum-core/src/domain/rumEventsCollection/longTask/longTaskCollection.ts index a9cba29c9a..464e10199f 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/longTask/longTaskCollection.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/longTask/longTaskCollection.ts @@ -4,15 +4,20 @@ import { RumEventType } from '../../../rawRumEvent.types' import type { LifeCycle } from '../../lifeCycle' import { LifeCycleEventType } from '../../lifeCycle' import type { RumSessionManager } from '../../rumSessionManager' +import type { RumConfiguration } from '../../configuration' -export function startLongTaskCollection(lifeCycle: LifeCycle, sessionManager: RumSessionManager) { +export function startLongTaskCollection( + lifeCycle: LifeCycle, + configuration: RumConfiguration, + sessionManager: RumSessionManager +) { lifeCycle.subscribe(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, (entries) => { for (const entry of entries) { if (entry.entryType !== 'longtask') { break } const session = sessionManager.findTrackedSession(entry.startTime) - if (!session || !session.longTaskAllowed) { + if (!session || !configuration.trackLongTasks) { break } const startClocks = relativeToClocks(entry.startTime) diff --git a/packages/rum-core/src/domain/rumEventsCollection/resource/resourceCollection.spec.ts b/packages/rum-core/src/domain/rumEventsCollection/resource/resourceCollection.spec.ts index 38c0f88af3..8f3c704dbd 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/resource/resourceCollection.spec.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/resource/resourceCollection.spec.ts @@ -21,17 +21,14 @@ import { startResourceCollection } from './resourceCollection' describe('resourceCollection', () => { let setupBuilder: TestSetupBuilder + let trackResources: boolean let pageStateHistorySpy: jasmine.Spy beforeEach(() => { - setupBuilder = setup().beforeBuild(({ lifeCycle, sessionManager, pageStateHistory }) => { + trackResources = true + setupBuilder = setup().beforeBuild(({ lifeCycle, sessionManager, pageStateHistory, configuration }) => { pageStateHistorySpy = spyOn(pageStateHistory, 'findAll') - startResourceCollection( - lifeCycle, - validateAndBuildRumConfiguration({ clientToken: 'xxx', applicationId: 'xxx' })!, - sessionManager, - pageStateHistory - ) + startResourceCollection(lifeCycle, { ...configuration, trackResources }, sessionManager, pageStateHistory) }) }) @@ -367,8 +364,8 @@ describe('resourceCollection', () => { expect((rawRumEvents[0].rawRumEvent as RawRumResourceEvent)._dd.discarded).toBeTrue() }) - it('should be discarded=true if session does not allow resources', () => { - setupBuilder.withSessionManager(createRumSessionManagerMock().setResourceAllowed(false)) + it('should be discarded=true when trackResources is disabled', () => { + trackResources = false const { lifeCycle, rawRumEvents } = setupBuilder.build() lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [createResourceEntry()]) @@ -376,8 +373,8 @@ describe('resourceCollection', () => { expect((rawRumEvents[0].rawRumEvent as RawRumResourceEvent)._dd.discarded).toBeTrue() }) - it('should be discarded=false if session allows resources', () => { - setupBuilder.withSessionManager(createRumSessionManagerMock().setResourceAllowed(true)) + it('should be discarded=false when trackResources is enabled', () => { + trackResources = true const { lifeCycle, rawRumEvents } = setupBuilder.build() lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [createResourceEntry()]) diff --git a/packages/rum-core/src/domain/rumEventsCollection/resource/resourceCollection.ts b/packages/rum-core/src/domain/rumEventsCollection/resource/resourceCollection.ts index f54ad4802d..84f8c0404f 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/resource/resourceCollection.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/resource/resourceCollection.ts @@ -72,7 +72,7 @@ function processRequest( const correspondingTimingOverrides = matchingTiming ? computePerformanceEntryMetrics(matchingTiming) : undefined const tracingInfo = computeRequestTracingInfo(request, configuration) - const indexingInfo = computeIndexingInfo(sessionManager, startClocks) + const indexingInfo = computeIndexingInfo(configuration, sessionManager, startClocks) const duration = toServerDuration(request.duration) const pageStateInfo = computePageStateInfo( @@ -125,7 +125,7 @@ function processResourceEntry( const startClocks = relativeToClocks(entry.startTime) const tracingInfo = computeEntryTracingInfo(entry, configuration) - const indexingInfo = computeIndexingInfo(sessionManager, startClocks) + const indexingInfo = computeIndexingInfo(configuration, sessionManager, startClocks) const pageStateInfo = computePageStateInfo(pageStateHistory, startClocks, entry.duration) const resourceEvent = combine( @@ -203,11 +203,15 @@ function getRulePsr(configuration: RumConfiguration) { return isNumber(configuration.traceSampleRate) ? configuration.traceSampleRate / 100 : undefined } -function computeIndexingInfo(sessionManager: RumSessionManager, resourceStart: ClocksState) { +function computeIndexingInfo( + configuration: RumConfiguration, + sessionManager: RumSessionManager, + resourceStart: ClocksState +) { const session = sessionManager.findTrackedSession(resourceStart.relative) return { _dd: { - discarded: !session || !session.resourceAllowed, + discarded: !session || !configuration.trackResources, }, } } diff --git a/packages/rum-core/src/domain/rumSessionManager.spec.ts b/packages/rum-core/src/domain/rumSessionManager.spec.ts index c59eccbb84..08f7eb4e71 100644 --- a/packages/rum-core/src/domain/rumSessionManager.spec.ts +++ b/packages/rum-core/src/domain/rumSessionManager.spec.ts @@ -190,172 +190,33 @@ describe('rum session manager', () => { describe('session behaviors', () => { ;[ - { - description: - 'WITH_SESSION_REPLAY plan without trackResources/LongTasks should have replay, no resources and no long tasks', - trackedWithSessionReplay: true, - oldPlansBehavior: false, - trackResources: undefined, - trackLongTasks: undefined, - expectSessionReplay: true, - expectResources: false, - expectLongTasks: false, - }, - { - description: - 'WITHOUT_SESSION_REPLAY plan without trackResources/LongTasks should have no replay, no resources and no long tasks', - trackedWithSessionReplay: false, - oldPlansBehavior: false, - trackResources: undefined, - trackLongTasks: undefined, - expectSessionReplay: false, - expectResources: false, - expectLongTasks: false, - }, { description: 'WITH_SESSION_REPLAY plan with trackResources/LongTasks=false should have replay, no resources and no long tasks', trackedWithSessionReplay: true, - oldPlansBehavior: false, - trackResources: false, - trackLongTasks: false, expectSessionReplay: true, - expectResources: false, - expectLongTasks: false, }, { description: 'WITHOUT_SESSION_REPLAY plan with trackResources/LongTasks=false should have no replay, no resources and no long tasks', trackedWithSessionReplay: false, - oldPlansBehavior: false, - trackResources: false, - trackLongTasks: false, - expectSessionReplay: false, - expectResources: false, - expectLongTasks: false, - }, - { - description: - 'WITH_SESSION_REPLAY plan with trackResources/LongTasks=true should have replay, resources and long tasks', - trackedWithSessionReplay: true, - oldPlansBehavior: true, - trackResources: true, - trackLongTasks: true, - expectSessionReplay: true, - expectResources: true, - expectLongTasks: true, - }, - { - description: - 'WITHOUT_SESSION_REPLAY plan with trackResources/LongTasks=true should have no replay, resources and long tasks', - trackedWithSessionReplay: false, - oldPlansBehavior: false, - trackResources: true, - trackLongTasks: true, expectSessionReplay: false, - expectResources: true, - expectLongTasks: true, - }, - { - description: - 'old WITH_SESSION_REPLAY plan without trackResources/LongTasks should have replay, resources and long tasks', - trackedWithSessionReplay: true, - oldPlansBehavior: true, - trackResources: undefined, - trackLongTasks: undefined, - expectSessionReplay: true, - expectResources: true, - expectLongTasks: true, - }, - { - description: - 'old WITHOUT_SESSION_REPLAY plan without trackResources/LongTasks should have no replay, no resources and no long tasks', - trackedWithSessionReplay: false, - oldPlansBehavior: true, - trackResources: undefined, - trackLongTasks: undefined, - expectSessionReplay: false, - expectResources: false, - expectLongTasks: false, - }, - { - description: - 'old WITH_SESSION_REPLAY plan with trackResources/LongTasks=false should have replay, no resources and no long tasks', - trackedWithSessionReplay: true, - oldPlansBehavior: true, - trackResources: false, - trackLongTasks: false, - expectSessionReplay: true, - expectResources: false, - expectLongTasks: false, - }, - { - description: - 'old WITHOUT_SESSION_REPLAY plan with trackResources/LongTasks=false should have no replay, no resources and no long tasks', - trackedWithSessionReplay: false, - oldPlansBehavior: true, - trackResources: false, - trackLongTasks: false, - expectSessionReplay: false, - expectResources: false, - expectLongTasks: false, - }, - { - description: - 'old WITH_SESSION_REPLAY plan with trackResources/LongTasks=true should have replay, resources and long tasks', - trackedWithSessionReplay: true, - oldPlansBehavior: true, - trackResources: true, - trackLongTasks: true, - expectSessionReplay: true, - expectResources: true, - expectLongTasks: true, - }, - { - description: - 'old WITHOUT_SESSION_REPLAY plan with trackResources/LongTasks=true should have no replay, resources and long tasks', - trackedWithSessionReplay: false, - oldPlansBehavior: true, - trackResources: true, - trackLongTasks: true, - expectSessionReplay: false, - expectResources: true, - expectLongTasks: true, }, ].forEach( ({ description, trackedWithSessionReplay, - oldPlansBehavior, - trackResources, - trackLongTasks, expectSessionReplay, - expectResources, - expectLongTasks, }: { description: string trackedWithSessionReplay: boolean - oldPlansBehavior: boolean - trackResources: boolean | undefined - trackLongTasks: boolean | undefined expectSessionReplay: boolean - expectResources: boolean - expectLongTasks: boolean }) => { it(description, () => { - configuration = { - ...configuration, - trackResources, - trackLongTasks, - oldPlansBehavior, - } - setupDraws({ tracked: true, trackedWithSessionReplay }) const rumSessionManager = startRumSessionManager(configuration, lifeCycle) expect(rumSessionManager.findTrackedSession()!.sessionReplayAllowed).toBe(expectSessionReplay) - expect(rumSessionManager.findTrackedSession()!.resourceAllowed).toBe(expectResources) - expect(rumSessionManager.findTrackedSession()!.longTaskAllowed).toBe(expectLongTasks) }) } ) diff --git a/packages/rum-core/src/domain/rumSessionManager.ts b/packages/rum-core/src/domain/rumSessionManager.ts index e78c294833..acd6d970af 100644 --- a/packages/rum-core/src/domain/rumSessionManager.ts +++ b/packages/rum-core/src/domain/rumSessionManager.ts @@ -16,8 +16,6 @@ export type RumSession = { id: string plan: RumSessionPlan sessionReplayAllowed: boolean - longTaskAllowed: boolean - resourceAllowed: boolean } export const enum RumSessionPlan { @@ -28,7 +26,7 @@ export const enum RumSessionPlan { export const enum RumTrackingType { NOT_TRACKED = '0', // Note: the "tracking type" value (stored in the session cookie) does not match the "session - // plan" value (sent in RUM events). This is expected, and was done to keep retrocompatibility + // plan" value (sent in RUM events). This is expected, and was done to keep backward-compatibility // with active sessions when upgrading the SDK. TRACKED_WITH_SESSION_REPLAY = '1', TRACKED_WITHOUT_SESSION_REPLAY = '2', @@ -61,14 +59,6 @@ export function startRumSessionManager(configuration: RumConfiguration, lifeCycl id: session.id, plan, sessionReplayAllowed: plan === RumSessionPlan.WITH_SESSION_REPLAY, - longTaskAllowed: - configuration.trackLongTasks !== undefined - ? configuration.trackLongTasks - : configuration.oldPlansBehavior && plan === RumSessionPlan.WITH_SESSION_REPLAY, - resourceAllowed: - configuration.trackResources !== undefined - ? configuration.trackResources - : configuration.oldPlansBehavior && plan === RumSessionPlan.WITH_SESSION_REPLAY, } }, expire: sessionManager.expire, @@ -84,8 +74,6 @@ export function startRumSessionManagerStub(): RumSessionManager { id: '00000000-aaaa-0000-aaaa-000000000000', plan: RumSessionPlan.WITHOUT_SESSION_REPLAY, // plan value should not be taken into account for mobile sessionReplayAllowed: false, - longTaskAllowed: true, - resourceAllowed: true, } return { findTrackedSession: () => session, diff --git a/packages/rum-core/test/mockRumSessionManager.ts b/packages/rum-core/test/mockRumSessionManager.ts index eb8c78df0c..9966b5b17e 100644 --- a/packages/rum-core/test/mockRumSessionManager.ts +++ b/packages/rum-core/test/mockRumSessionManager.ts @@ -7,8 +7,6 @@ export interface RumSessionManagerMock extends RumSessionManager { setNotTracked(): RumSessionManagerMock setPlanWithoutSessionReplay(): RumSessionManagerMock setPlanWithSessionReplay(): RumSessionManagerMock - setLongTaskAllowed(longTaskAllowed: boolean): RumSessionManagerMock - setResourceAllowed(resourceAllowed: boolean): RumSessionManagerMock } const DEFAULT_ID = 'session-id' @@ -22,8 +20,6 @@ const enum SessionStatus { export function createRumSessionManagerMock(): RumSessionManagerMock { let id = DEFAULT_ID let sessionStatus: SessionStatus = SessionStatus.TRACKED_WITH_SESSION_REPLAY - let resourceAllowed = true - let longTaskAllowed = true return { findTrackedSession() { if ( @@ -39,8 +35,6 @@ export function createRumSessionManagerMock(): RumSessionManagerMock { ? RumSessionPlan.WITH_SESSION_REPLAY : RumSessionPlan.WITHOUT_SESSION_REPLAY, sessionReplayAllowed: sessionStatus === SessionStatus.TRACKED_WITH_SESSION_REPLAY, - longTaskAllowed, - resourceAllowed, } }, expire() { @@ -64,13 +58,5 @@ export function createRumSessionManagerMock(): RumSessionManagerMock { sessionStatus = SessionStatus.TRACKED_WITH_SESSION_REPLAY return this }, - setLongTaskAllowed(isAllowed: boolean) { - longTaskAllowed = isAllowed - return this - }, - setResourceAllowed(isAllowed: boolean) { - resourceAllowed = isAllowed - return this - }, } } diff --git a/packages/rum-core/test/testSetupBuilder.ts b/packages/rum-core/test/testSetupBuilder.ts index c0bc3b58ef..acba7e5e1a 100644 --- a/packages/rum-core/test/testSetupBuilder.ts +++ b/packages/rum-core/test/testSetupBuilder.ts @@ -109,7 +109,12 @@ export function setup(): TestSetupBuilder { } const FAKE_APP_ID = 'appId' const configuration: RumConfiguration = { - ...validateAndBuildRumConfiguration({ clientToken: 'xxx', applicationId: FAKE_APP_ID })!, + ...validateAndBuildRumConfiguration({ + clientToken: 'xxx', + applicationId: FAKE_APP_ID, + trackResources: true, + trackLongTasks: true, + })!, ...SPEC_ENDPOINTS, } From 808d65268307e77da9984e3a922d611e8b7eec7d Mon Sep 17 00:00:00 2001 From: Bastien Caudan <1331991+bcaudan@users.noreply.github.com> Date: Tue, 30 May 2023 11:13:42 +0200 Subject: [PATCH 05/27] =?UTF-8?q?=F0=9F=92=A5=20[RUMF-1588]=20Update=20def?= =?UTF-8?q?ault=20session=20replay=20behaviour=20(#2257)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * πŸ’₯ update default sessionReplaySampleRate * πŸ’₯ update default defaultPrivacyLevel * tweak obfuscation scenarios --- .../rum-core/src/boot/rumPublicApi.spec.ts | 10 ++++----- .../rum-core/src/domain/configuration.spec.ts | 10 ++++----- packages/rum-core/src/domain/configuration.ts | 4 ++-- test/e2e/lib/framework/createTest.ts | 2 ++ .../scenario/recorder/recorder.scenario.ts | 21 +++++++++---------- 5 files changed, 24 insertions(+), 23 deletions(-) diff --git a/packages/rum-core/src/boot/rumPublicApi.spec.ts b/packages/rum-core/src/boot/rumPublicApi.spec.ts index 3941b3ad7e..e741cb6068 100644 --- a/packages/rum-core/src/boot/rumPublicApi.spec.ts +++ b/packages/rum-core/src/boot/rumPublicApi.spec.ts @@ -842,17 +842,17 @@ describe('rum public api', () => { it('recording is started with the default defaultPrivacyLevel', () => { rumPublicApi.init(DEFAULT_INIT_CONFIGURATION) - expect(recorderApiOnRumStartSpy.calls.mostRecent().args[1].defaultPrivacyLevel).toBe( - DefaultPrivacyLevel.MASK_USER_INPUT - ) + expect(recorderApiOnRumStartSpy.calls.mostRecent().args[1].defaultPrivacyLevel).toBe(DefaultPrivacyLevel.MASK) }) it('recording is started with the configured defaultPrivacyLevel', () => { rumPublicApi.init({ ...DEFAULT_INIT_CONFIGURATION, - defaultPrivacyLevel: DefaultPrivacyLevel.MASK, + defaultPrivacyLevel: DefaultPrivacyLevel.MASK_USER_INPUT, }) - expect(recorderApiOnRumStartSpy.calls.mostRecent().args[1].defaultPrivacyLevel).toBe(DefaultPrivacyLevel.MASK) + expect(recorderApiOnRumStartSpy.calls.mostRecent().args[1].defaultPrivacyLevel).toBe( + DefaultPrivacyLevel.MASK_USER_INPUT + ) }) }) diff --git a/packages/rum-core/src/domain/configuration.spec.ts b/packages/rum-core/src/domain/configuration.spec.ts index bfdc3c6bcd..963a4c8295 100644 --- a/packages/rum-core/src/domain/configuration.spec.ts +++ b/packages/rum-core/src/domain/configuration.spec.ts @@ -25,8 +25,8 @@ describe('validateAndBuildRumConfiguration', () => { }) describe('sessionReplaySampleRate', () => { - it('defaults to 100 if the option is not provided', () => { - expect(validateAndBuildRumConfiguration(DEFAULT_INIT_CONFIGURATION)!.sessionReplaySampleRate).toBe(100) + it('defaults to 0 if the option is not provided', () => { + expect(validateAndBuildRumConfiguration(DEFAULT_INIT_CONFIGURATION)!.sessionReplaySampleRate).toBe(0) }) it('is set to `sessionReplaySampleRate` provided value', () => { @@ -254,9 +254,9 @@ describe('validateAndBuildRumConfiguration', () => { }) describe('defaultPrivacyLevel', () => { - it('defaults to MASK_USER_INPUT', () => { + it('defaults to MASK', () => { expect(validateAndBuildRumConfiguration(DEFAULT_INIT_CONFIGURATION)!.defaultPrivacyLevel).toBe( - DefaultPrivacyLevel.MASK_USER_INPUT + DefaultPrivacyLevel.MASK ) }) @@ -273,7 +273,7 @@ describe('validateAndBuildRumConfiguration', () => { expect( validateAndBuildRumConfiguration({ ...DEFAULT_INIT_CONFIGURATION, defaultPrivacyLevel: 'foo' as any })! .defaultPrivacyLevel - ).toBe(DefaultPrivacyLevel.MASK_USER_INPUT) + ).toBe(DefaultPrivacyLevel.MASK) }) }) diff --git a/packages/rum-core/src/domain/configuration.ts b/packages/rum-core/src/domain/configuration.ts index 34c87bd089..845eb47f09 100644 --- a/packages/rum-core/src/domain/configuration.ts +++ b/packages/rum-core/src/domain/configuration.ts @@ -103,7 +103,7 @@ export function validateAndBuildRumConfiguration( applicationId: initConfiguration.applicationId, version: initConfiguration.version, actionNameAttribute: initConfiguration.actionNameAttribute, - sessionReplaySampleRate: initConfiguration.sessionReplaySampleRate ?? 100, + sessionReplaySampleRate: initConfiguration.sessionReplaySampleRate ?? 0, traceSampleRate: initConfiguration.traceSampleRate, allowedTracingUrls, excludedActivityUrls: initConfiguration.excludedActivityUrls ?? [], @@ -114,7 +114,7 @@ export function validateAndBuildRumConfiguration( subdomain: initConfiguration.subdomain, defaultPrivacyLevel: objectHasValue(DefaultPrivacyLevel, initConfiguration.defaultPrivacyLevel) ? initConfiguration.defaultPrivacyLevel - : DefaultPrivacyLevel.MASK_USER_INPUT, + : DefaultPrivacyLevel.MASK, customerDataTelemetrySampleRate: 1, }, baseConfiguration diff --git a/test/e2e/lib/framework/createTest.ts b/test/e2e/lib/framework/createTest.ts index 0d8c35ab97..cd87f9e054 100644 --- a/test/e2e/lib/framework/createTest.ts +++ b/test/e2e/lib/framework/createTest.ts @@ -1,5 +1,6 @@ import type { LogsInitConfiguration } from '@datadog/browser-logs' import type { RumInitConfiguration } from '@datadog/browser-rum-core' +import { DefaultPrivacyLevel } from '@datadog/browser-rum' import { getRunId } from '../../../envUtils' import { deleteAllCookies, getBrowserName, withBrowserLogs } from '../helpers/browser' import { APPLICATION_ID, CLIENT_TOKEN } from '../helpers/constants' @@ -18,6 +19,7 @@ const DEFAULT_RUM_CONFIGURATION = { applicationId: APPLICATION_ID, clientToken: CLIENT_TOKEN, sessionReplaySampleRate: 100, + defaultPrivacyLevel: DefaultPrivacyLevel.ALLOW, trackResources: true, trackLongTasks: true, telemetrySampleRate: 100, diff --git a/test/e2e/scenario/recorder/recorder.scenario.ts b/test/e2e/scenario/recorder/recorder.scenario.ts index 6a2699ed5c..5652aad37d 100644 --- a/test/e2e/scenario/recorder/recorder.scenario.ts +++ b/test/e2e/scenario/recorder/recorder.scenario.ts @@ -83,11 +83,11 @@ describe('recorder', () => { .withSetup(bundleSetup) .withBody( html` -
foo
-

bar

- baz - - +
displayed
+

hidden

+ hidden + + ` ) .run(async ({ serverEvents }) => { @@ -99,7 +99,7 @@ describe('recorder', () => { const node = findElementWithIdAttribute(fullSnapshot.data.node, 'not-obfuscated') expect(node).toBeTruthy() - expect(findTextContent(node!)).toBe('foo') + expect(findTextContent(node!)).toBe('displayed') const hiddenNodeByAttribute = findElement(fullSnapshot.data.node, (node) => node.tagName === 'p') expect(hiddenNodeByAttribute).toBeTruthy() @@ -107,15 +107,14 @@ describe('recorder', () => { expect(hiddenNodeByAttribute!.childNodes.length).toBe(0) const hiddenNodeByClassName = findElement(fullSnapshot.data.node, (node) => node.tagName === 'span') - expect(hiddenNodeByClassName).toBeTruthy() expect(hiddenNodeByClassName!.attributes.class).toBeUndefined() expect(hiddenNodeByClassName!.attributes['data-dd-privacy']).toBe('hidden') expect(hiddenNodeByClassName!.childNodes.length).toBe(0) - const inputIgnored = findElementWithIdAttribute(fullSnapshot.data.node, 'input-ignored') + const inputIgnored = findElementWithIdAttribute(fullSnapshot.data.node, 'input-not-obfuscated') expect(inputIgnored).toBeTruthy() - expect(inputIgnored!.attributes.value).toBe('***') + expect(inputIgnored!.attributes.value).toBe('displayed') const inputMasked = findElementWithIdAttribute(fullSnapshot.data.node, 'input-masked') expect(inputMasked).toBeTruthy() @@ -579,8 +578,8 @@ describe('recorder', () => { .withSetup(bundleSetup) .withBody( html` - - + + ` ) .run(async ({ serverEvents }) => { From 638b655313f0ff6932cf5534a833c6a6f48ef879 Mon Sep 17 00:00:00 2001 From: Aymeric Date: Tue, 30 May 2023 14:33:19 +0200 Subject: [PATCH 06/27] =?UTF-8?q?=F0=9F=90=9B=20[RUMF-1499]=20Don't=20send?= =?UTF-8?q?=20duration=20for=20resources=20crossing=20a=20page=20frozen=20?= =?UTF-8?q?state=20(#2271)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/src/tools/experimentalFeatures.ts | 1 - .../resource/resourceCollection.spec.ts | 17 +---------------- .../resource/resourceCollection.ts | 5 ----- 3 files changed, 1 insertion(+), 22 deletions(-) diff --git a/packages/core/src/tools/experimentalFeatures.ts b/packages/core/src/tools/experimentalFeatures.ts index c0027b81e0..3bc5286f28 100644 --- a/packages/core/src/tools/experimentalFeatures.ts +++ b/packages/core/src/tools/experimentalFeatures.ts @@ -16,7 +16,6 @@ export enum ExperimentalFeature { RESOURCE_PAGE_STATES = 'resource_page_states', PAGE_STATES = 'page_states', COLLECT_FLUSH_REASON = 'collect_flush_reason', - NO_RESOURCE_DURATION_FROZEN_STATE = 'no_resource_duration_frozen_state', } const enabledExperimentalFeatures: Set = new Set() diff --git a/packages/rum-core/src/domain/rumEventsCollection/resource/resourceCollection.spec.ts b/packages/rum-core/src/domain/rumEventsCollection/resource/resourceCollection.spec.ts index 169e0cd1cc..1ec2b64976 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/resource/resourceCollection.spec.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/resource/resourceCollection.spec.ts @@ -132,8 +132,7 @@ describe('resourceCollection', () => { expect(rawRumResourceEventEntry._dd.page_states).toEqual(jasmine.objectContaining(mockPageStates)) }) - it('should not have a duration if a frozen state happens during the request and no performance entry matches when NO_RESOURCE_DURATION_FROZEN_STATE enabled', () => { - addExperimentalFeatures([ExperimentalFeature.NO_RESOURCE_DURATION_FROZEN_STATE]) + it('should not have a duration if a frozen state happens during the request and no performance entry matches', () => { const { lifeCycle, rawRumEvents } = setupBuilder.build() const mockPageStates = [{ state: PageState.FROZEN, startTime: 0 as RelativeTime }] const mockXHR = createCompletedRequest() @@ -146,19 +145,6 @@ describe('resourceCollection', () => { expect(rawRumResourceEventFetch.resource.duration).toBeUndefined() }) - it('should have a duration if a frozen state happens during the request and no performance entry matches when NO_RESOURCE_DURATION_FROZEN_STATE disabled', () => { - const { lifeCycle, rawRumEvents } = setupBuilder.build() - const mockPageStates = [{ state: PageState.FROZEN, startTime: 0 as RelativeTime }] - const mockXHR = createCompletedRequest() - - pageStateHistorySpy.and.returnValue(mockPageStates) - - lifeCycle.notify(LifeCycleEventType.REQUEST_COMPLETED, mockXHR) - - const rawRumResourceEventFetch = rawRumEvents[0].rawRumEvent as RawRumResourceEvent - expect(rawRumResourceEventFetch.resource.duration).toBeDefined() - }) - it('should not collect page states on resources when ff resource_page_states disabled', () => { const { lifeCycle, rawRumEvents } = setupBuilder.build() const mockPageStates = [{ state: PageState.ACTIVE, startTime: 0 as RelativeTime }] @@ -173,7 +159,6 @@ describe('resourceCollection', () => { const rawRumResourceEventFetch = rawRumEvents[0].rawRumEvent as RawRumResourceEvent const rawRumResourceEventEntry = rawRumEvents[1].rawRumEvent as RawRumResourceEvent - expect(pageStateHistorySpy).not.toHaveBeenCalled() expect(rawRumResourceEventFetch._dd.page_states).not.toBeDefined() expect(rawRumResourceEventEntry._dd.page_states).not.toBeDefined() }) diff --git a/packages/rum-core/src/domain/rumEventsCollection/resource/resourceCollection.ts b/packages/rum-core/src/domain/rumEventsCollection/resource/resourceCollection.ts index 9ab57ed2a6..4f8d059223 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/resource/resourceCollection.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/resource/resourceCollection.ts @@ -231,11 +231,6 @@ function computePageStateInfo(pageStateHistory: PageStateHistory, startClocks: C } function computeRequestDuration(pageStateHistory: PageStateHistory, startClocks: ClocksState, duration: Duration) { - // TODO remove FF in next major - if (!isExperimentalFeatureEnabled(ExperimentalFeature.NO_RESOURCE_DURATION_FROZEN_STATE)) { - return toServerDuration(duration) - } - const requestCrossedFrozenState = pageStateHistory .findAll(startClocks.relative, duration) ?.some((pageState) => pageState.state === PageState.FROZEN) From f875c2b50d464eafe6186e903c2f837dda520954 Mon Sep 17 00:00:00 2001 From: Bastien Caudan <1331991+bcaudan@users.noreply.github.com> Date: Mon, 5 Jun 2023 14:25:52 +0200 Subject: [PATCH 07/27] =?UTF-8?q?=F0=9F=92=A5=20[RUMF-1589]=20automaticall?= =?UTF-8?q?y=20start=20recording=20(#2275)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ♻️ buffer start/stop recorder API calls before init * ✨add option to opt out of automatic recording * πŸ’₯automatically start recording by default * ♻️ cleanup scenarios using replay * πŸ‘Œfix test description --- .../rum-core/src/boot/rumPublicApi.spec.ts | 35 ++++- packages/rum-core/src/boot/rumPublicApi.ts | 16 ++- .../rum-core/src/domain/configuration.spec.ts | 28 ++++ packages/rum-core/src/domain/configuration.ts | 4 + packages/rum/src/boot/recorderApi.spec.ts | 126 +++++++++--------- packages/rum/src/boot/recorderApi.ts | 5 +- test/e2e/lib/helpers/replay.ts | 6 - .../scenario/recorder/recorder.scenario.ts | 21 +-- .../scenario/recorder/shadowDom.scenario.ts | 11 -- .../scenario/recorder/viewports.scenario.ts | 10 -- test/e2e/scenario/rum/sessions.scenario.ts | 7 +- 11 files changed, 152 insertions(+), 117 deletions(-) diff --git a/packages/rum-core/src/boot/rumPublicApi.spec.ts b/packages/rum-core/src/boot/rumPublicApi.spec.ts index e741cb6068..171069c7de 100644 --- a/packages/rum-core/src/boot/rumPublicApi.spec.ts +++ b/packages/rum-core/src/boot/rumPublicApi.spec.ts @@ -829,10 +829,12 @@ describe('rum public api', () => { let recorderApiOnRumStartSpy: jasmine.Spy let setupBuilder: TestSetupBuilder let rumPublicApi: RumPublicApi + let recorderApi: RecorderApi beforeEach(() => { recorderApiOnRumStartSpy = jasmine.createSpy('recorderApiOnRumStart') - rumPublicApi = makeRumPublicApi(noopStartRum, { ...noopRecorderApi, onRumStart: recorderApiOnRumStartSpy }) + recorderApi = { ...noopRecorderApi, onRumStart: recorderApiOnRumStartSpy } + rumPublicApi = makeRumPublicApi(noopStartRum, recorderApi) setupBuilder = setup() }) @@ -840,12 +842,12 @@ describe('rum public api', () => { setupBuilder.cleanup() }) - it('recording is started with the default defaultPrivacyLevel', () => { + it('is started with the default defaultPrivacyLevel', () => { rumPublicApi.init(DEFAULT_INIT_CONFIGURATION) expect(recorderApiOnRumStartSpy.calls.mostRecent().args[1].defaultPrivacyLevel).toBe(DefaultPrivacyLevel.MASK) }) - it('recording is started with the configured defaultPrivacyLevel', () => { + it('is started with the configured defaultPrivacyLevel', () => { rumPublicApi.init({ ...DEFAULT_INIT_CONFIGURATION, defaultPrivacyLevel: DefaultPrivacyLevel.MASK_USER_INPUT, @@ -854,6 +856,33 @@ describe('rum public api', () => { DefaultPrivacyLevel.MASK_USER_INPUT ) }) + + it('api calls before init are performed after onRumStart', () => { + // in order to let recording initial state to be defined by init configuration + const callOrders: string[] = [] + spyOn(recorderApi, 'start').and.callFake(() => callOrders.push('start')) + spyOn(recorderApi, 'stop').and.callFake(() => callOrders.push('stop')) + recorderApiOnRumStartSpy.and.callFake(() => callOrders.push('onRumStart')) + + rumPublicApi.startSessionReplayRecording() + rumPublicApi.stopSessionReplayRecording() + rumPublicApi.init(DEFAULT_INIT_CONFIGURATION) + + expect(callOrders).toEqual(['onRumStart', 'start', 'stop']) + }) + + it('is started with the default startSessionReplayRecordingManually', () => { + rumPublicApi.init(DEFAULT_INIT_CONFIGURATION) + expect(recorderApiOnRumStartSpy.calls.mostRecent().args[1].startSessionReplayRecordingManually).toBe(false) + }) + + it('is started with the configured startSessionReplayRecordingManually', () => { + rumPublicApi.init({ + ...DEFAULT_INIT_CONFIGURATION, + startSessionReplayRecordingManually: true, + }) + expect(recorderApiOnRumStartSpy.calls.mostRecent().args[1].startSessionReplayRecordingManually).toBe(true) + }) }) it('should provide sdk version', () => { diff --git a/packages/rum-core/src/boot/rumPublicApi.ts b/packages/rum-core/src/boot/rumPublicApi.ts index 284cb6bddb..7be43fdf9e 100644 --- a/packages/rum-core/src/boot/rumPublicApi.ts +++ b/packages/rum-core/src/boot/rumPublicApi.ts @@ -94,6 +94,14 @@ export function makeRumPublicApi( bufferApiCalls.add(() => addErrorStrategy(providedError, commonContext)) } + let recorderStartStrategy: typeof recorderApi.start = () => { + bufferApiCalls.add(() => recorderStartStrategy()) + } + + let recorderStopStrategy: typeof recorderApi.stop = () => { + bufferApiCalls.add(() => recorderStopStrategy()) + } + let addFeatureFlagEvaluationStrategy: StartRumResult['addFeatureFlagEvaluation'] = (key: string, value: any) => { bufferApiCalls.add(() => addFeatureFlagEvaluationStrategy(key, value)) } @@ -158,6 +166,8 @@ export function makeRumPublicApi( ) getSessionReplayLinkStrategy = () => recorderApi.getSessionReplayLink(configuration, startRumResults.session, startRumResults.viewContexts) + recorderStartStrategy = recorderApi.start + recorderStopStrategy = recorderApi.stop ;({ startView: startViewStrategy, addAction: addActionStrategy, @@ -167,7 +177,6 @@ export function makeRumPublicApi( getInternalContext: getInternalContextStrategy, stopSession: stopSessionStrategy, } = startRumResults) - bufferApiCalls.drain() recorderApi.onRumStart( startRumResults.lifeCycle, @@ -175,6 +184,7 @@ export function makeRumPublicApi( startRumResults.session, startRumResults.viewContexts ) + bufferApiCalls.drain() } const startView: { @@ -249,8 +259,8 @@ export function makeRumPublicApi( stopSessionStrategy() }), - startSessionReplayRecording: monitor(recorderApi.start), - stopSessionReplayRecording: monitor(recorderApi.stop), + startSessionReplayRecording: monitor(() => recorderStartStrategy()), + stopSessionReplayRecording: monitor(() => recorderStopStrategy()), /** * This feature is currently in beta. For more information see the full [feature flag tracking guide](https://docs.datadoghq.com/real_user_monitoring/feature_flag_tracking/). diff --git a/packages/rum-core/src/domain/configuration.spec.ts b/packages/rum-core/src/domain/configuration.spec.ts index 963a4c8295..b3dbd9489a 100644 --- a/packages/rum-core/src/domain/configuration.spec.ts +++ b/packages/rum-core/src/domain/configuration.spec.ts @@ -240,6 +240,34 @@ describe('validateAndBuildRumConfiguration', () => { }) }) + describe('startSessionReplayRecordingManually', () => { + it('defaults to false', () => { + expect( + validateAndBuildRumConfiguration(DEFAULT_INIT_CONFIGURATION)!.startSessionReplayRecordingManually + ).toBeFalse() + }) + + it('is set to provided value', () => { + expect( + validateAndBuildRumConfiguration({ ...DEFAULT_INIT_CONFIGURATION, startSessionReplayRecordingManually: true })! + .startSessionReplayRecordingManually + ).toBeTrue() + expect( + validateAndBuildRumConfiguration({ ...DEFAULT_INIT_CONFIGURATION, startSessionReplayRecordingManually: false })! + .startSessionReplayRecordingManually + ).toBeFalse() + }) + + it('the provided value is cast to boolean', () => { + expect( + validateAndBuildRumConfiguration({ + ...DEFAULT_INIT_CONFIGURATION, + startSessionReplayRecordingManually: 'foo' as any, + })!.startSessionReplayRecordingManually + ).toBeTrue() + }) + }) + describe('actionNameAttribute', () => { it('defaults to undefined', () => { expect(validateAndBuildRumConfiguration(DEFAULT_INIT_CONFIGURATION)!.actionNameAttribute).toBeUndefined() diff --git a/packages/rum-core/src/domain/configuration.ts b/packages/rum-core/src/domain/configuration.ts index 845eb47f09..eba13ac2c4 100644 --- a/packages/rum-core/src/domain/configuration.ts +++ b/packages/rum-core/src/domain/configuration.ts @@ -30,6 +30,7 @@ export interface RumInitConfiguration extends InitConfiguration { defaultPrivacyLevel?: DefaultPrivacyLevel | undefined subdomain?: string sessionReplaySampleRate?: number | undefined + startSessionReplayRecordingManually?: boolean | undefined // action options trackUserInteractions?: boolean | undefined @@ -53,6 +54,7 @@ export interface RumConfiguration extends Configuration { applicationId: string defaultPrivacyLevel: DefaultPrivacyLevel sessionReplaySampleRate: number + startSessionReplayRecordingManually: boolean trackUserInteractions: boolean trackViewsManually: boolean trackResources: boolean @@ -104,6 +106,7 @@ export function validateAndBuildRumConfiguration( version: initConfiguration.version, actionNameAttribute: initConfiguration.actionNameAttribute, sessionReplaySampleRate: initConfiguration.sessionReplaySampleRate ?? 0, + startSessionReplayRecordingManually: !!initConfiguration.startSessionReplayRecordingManually, traceSampleRate: initConfiguration.traceSampleRate, allowedTracingUrls, excludedActivityUrls: initConfiguration.excludedActivityUrls ?? [], @@ -181,6 +184,7 @@ export function serializeRumConfiguration(configuration: RumInitConfiguration): return assign( { session_replay_sample_rate: configuration.sessionReplaySampleRate, + start_session_replay_recording_manually: configuration.startSessionReplayRecordingManually, trace_sample_rate: configuration.traceSampleRate, action_name_attribute: configuration.actionNameAttribute, use_allowed_tracing_urls: diff --git a/packages/rum/src/boot/recorderApi.spec.ts b/packages/rum/src/boot/recorderApi.spec.ts index 77598cff30..3be3b0a583 100644 --- a/packages/rum/src/boot/recorderApi.spec.ts +++ b/packages/rum/src/boot/recorderApi.spec.ts @@ -32,11 +32,13 @@ describe('makeRecorderApi', () => { } let rumInit: () => void + let startSessionReplayRecordingManually: boolean beforeEach(() => { if (isIE()) { pending('IE not supported') } + startSessionReplayRecordingManually = false setupBuilder = setup().beforeBuild(({ lifeCycle, sessionManager }) => { stopRecordingSpy = jasmine.createSpy('stopRecording') startRecordingSpy = jasmine.createSpy('startRecording').and.callFake(() => ({ @@ -46,7 +48,12 @@ describe('makeRecorderApi', () => { startDeflateWorkerWith(FAKE_WORKER) recorderApi = makeRecorderApi(startRecordingSpy, startDeflateWorkerSpy) rumInit = () => { - recorderApi.onRumStart(lifeCycle, {} as RumConfiguration, sessionManager, {} as ViewContexts) + recorderApi.onRumStart( + lifeCycle, + { startSessionReplayRecordingManually } as RumConfiguration, + sessionManager, + {} as ViewContexts + ) } }) }) @@ -55,26 +62,48 @@ describe('makeRecorderApi', () => { setupBuilder.cleanup() }) - describe('boot', () => { - it('does not start recording when init() is called', () => { - setupBuilder.build() - expect(startRecordingSpy).not.toHaveBeenCalled() - rumInit() - expect(startRecordingSpy).not.toHaveBeenCalled() + describe('recorder boot', () => { + describe('with automatic start', () => { + it('starts recording when init() is called', () => { + setupBuilder.build() + expect(startRecordingSpy).not.toHaveBeenCalled() + rumInit() + expect(startRecordingSpy).toHaveBeenCalled() + }) + + it('starts recording after the DOM is loaded', () => { + setupBuilder.build() + const { triggerOnDomLoaded } = mockDocumentReadyState() + rumInit() + expect(startRecordingSpy).not.toHaveBeenCalled() + triggerOnDomLoaded() + expect(startRecordingSpy).toHaveBeenCalled() + }) }) - it('does not start recording after the DOM is loaded', () => { - setupBuilder.build() - const { triggerOnDomLoaded } = mockDocumentReadyState() - rumInit() - expect(startRecordingSpy).not.toHaveBeenCalled() - triggerOnDomLoaded() - expect(startRecordingSpy).not.toHaveBeenCalled() + describe('with manual start', () => { + it('does not start recording when init() is called', () => { + startSessionReplayRecordingManually = true + setupBuilder.build() + expect(startRecordingSpy).not.toHaveBeenCalled() + rumInit() + expect(startRecordingSpy).not.toHaveBeenCalled() + }) + + it('does not start recording after the DOM is loaded', () => { + startSessionReplayRecordingManually = true + setupBuilder.build() + const { triggerOnDomLoaded } = mockDocumentReadyState() + rumInit() + expect(startRecordingSpy).not.toHaveBeenCalled() + triggerOnDomLoaded() + expect(startRecordingSpy).not.toHaveBeenCalled() + }) }) }) - describe('startSessionReplayRecording()', () => { - it('ignores calls while recording is already started', () => { + describe('recorder start', () => { + it('ignores additional start calls while recording is already started', () => { setupBuilder.build() rumInit() recorderApi.start() @@ -83,32 +112,24 @@ describe('makeRecorderApi', () => { expect(startRecordingSpy).toHaveBeenCalledTimes(1) }) - it('starts recording if called before init()', () => { - setupBuilder.build() - recorderApi.start() - rumInit() - expect(startRecordingSpy).toHaveBeenCalled() - }) - - it('does not start recording multiple times if restarted before the DOM is loaded', () => { + it('ignores restart before the DOM is loaded', () => { setupBuilder.build() const { triggerOnDomLoaded } = mockDocumentReadyState() rumInit() - recorderApi.start() recorderApi.stop() recorderApi.start() triggerOnDomLoaded() expect(startRecordingSpy).toHaveBeenCalledTimes(1) }) - it('ignores calls if the session is not tracked', () => { + it('ignores start calls if the session is not tracked', () => { setupBuilder.withSessionManager(createRumSessionManagerMock().setNotTracked()).build() rumInit() recorderApi.start() expect(startRecordingSpy).not.toHaveBeenCalled() }) - it('ignores calls if the session plan is WITHOUT_REPLAY', () => { + it('ignores start calls if the session plan is WITHOUT_REPLAY', () => { setupBuilder.withSessionManager(createRumSessionManagerMock().setPlanWithoutSessionReplay()).build() rumInit() recorderApi.start() @@ -117,17 +138,15 @@ describe('makeRecorderApi', () => { it('do not start recording if worker fails to be instantiated', () => { setupBuilder.build() - rumInit() startDeflateWorkerWith(undefined) - recorderApi.start() + rumInit() expect(startRecordingSpy).not.toHaveBeenCalled() }) it('does not start recording multiple times if restarted before worker is initialized', () => { setupBuilder.build() - rumInit() stopDeflateWorker() - recorderApi.start() + rumInit() recorderApi.stop() callLastRegisteredInitialisationCallback() @@ -174,30 +193,20 @@ describe('makeRecorderApi', () => { }) }) - describe('stopSessionReplayRecording()', () => { + describe('recorder stop', () => { it('ignores calls while recording is already stopped', () => { setupBuilder.build() rumInit() - recorderApi.start() recorderApi.stop() recorderApi.stop() recorderApi.stop() expect(stopRecordingSpy).toHaveBeenCalledTimes(1) }) - it('does not start recording if called before init()', () => { - setupBuilder.build() - recorderApi.start() - recorderApi.stop() - rumInit() - expect(startRecordingSpy).not.toHaveBeenCalled() - }) - it('prevents recording to start when the DOM is loaded', () => { setupBuilder.build() const { triggerOnDomLoaded } = mockDocumentReadyState() rumInit() - recorderApi.start() recorderApi.stop() triggerOnDomLoaded() expect(startRecordingSpy).not.toHaveBeenCalled() @@ -211,7 +220,6 @@ describe('makeRecorderApi', () => { sessionManager = createRumSessionManagerMock() setupBuilder.withSessionManager(sessionManager) ;({ lifeCycle } = setupBuilder.build()) - rumInit() }) describe('from WITHOUT_REPLAY to WITH_REPLAY', () => { @@ -220,7 +228,7 @@ describe('makeRecorderApi', () => { }) it('starts recording if startSessionReplayRecording was called', () => { - recorderApi.start() + rumInit() sessionManager.setPlanWithSessionReplay() lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) expect(startRecordingSpy).not.toHaveBeenCalled() @@ -230,7 +238,7 @@ describe('makeRecorderApi', () => { }) it('does not starts recording if stopSessionReplayRecording was called', () => { - recorderApi.start() + rumInit() recorderApi.stop() sessionManager.setPlanWithSessionReplay() lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) @@ -245,7 +253,7 @@ describe('makeRecorderApi', () => { }) it('keeps not recording if startSessionReplayRecording was called', () => { - recorderApi.start() + rumInit() sessionManager.setNotTracked() lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED) @@ -260,7 +268,7 @@ describe('makeRecorderApi', () => { }) it('keeps not recording if startSessionReplayRecording was called', () => { - recorderApi.start() + rumInit() lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED) expect(startRecordingSpy).not.toHaveBeenCalled() @@ -274,7 +282,7 @@ describe('makeRecorderApi', () => { }) it('stops recording if startSessionReplayRecording was called', () => { - recorderApi.start() + rumInit() expect(startRecordingSpy).toHaveBeenCalledTimes(1) sessionManager.setPlanWithoutSessionReplay() lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) @@ -284,10 +292,8 @@ describe('makeRecorderApi', () => { }) it('prevents session recording to start if the session is renewed before the DOM is loaded', () => { - setupBuilder.build() const { triggerOnDomLoaded } = mockDocumentReadyState() rumInit() - recorderApi.start() sessionManager.setPlanWithoutSessionReplay() lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED) @@ -302,7 +308,7 @@ describe('makeRecorderApi', () => { }) it('stops recording if startSessionReplayRecording was called', () => { - recorderApi.start() + rumInit() expect(startRecordingSpy).toHaveBeenCalledTimes(1) sessionManager.setNotTracked() lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) @@ -318,7 +324,7 @@ describe('makeRecorderApi', () => { }) it('keeps recording if startSessionReplayRecording was called', () => { - recorderApi.start() + rumInit() expect(startRecordingSpy).toHaveBeenCalledTimes(1) lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) expect(stopRecordingSpy).toHaveBeenCalled() @@ -327,7 +333,7 @@ describe('makeRecorderApi', () => { }) it('does not starts recording if stopSessionReplayRecording was called', () => { - recorderApi.start() + rumInit() expect(startRecordingSpy).toHaveBeenCalledTimes(1) recorderApi.stop() expect(stopRecordingSpy).toHaveBeenCalledTimes(1) @@ -344,7 +350,7 @@ describe('makeRecorderApi', () => { }) it('starts recording if startSessionReplayRecording was called', () => { - recorderApi.start() + rumInit() sessionManager.setPlanWithSessionReplay() lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED) @@ -353,7 +359,7 @@ describe('makeRecorderApi', () => { }) it('does not starts recording if stopSessionReplayRecording was called', () => { - recorderApi.start() + rumInit() recorderApi.stop() sessionManager.setPlanWithSessionReplay() lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) @@ -369,7 +375,7 @@ describe('makeRecorderApi', () => { }) it('keeps not recording if startSessionReplayRecording was called', () => { - recorderApi.start() + rumInit() sessionManager.setPlanWithoutSessionReplay() lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED) @@ -384,7 +390,7 @@ describe('makeRecorderApi', () => { }) it('keeps not recording if startSessionReplayRecording was called', () => { - recorderApi.start() + rumInit() lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED) expect(startRecordingSpy).not.toHaveBeenCalled() @@ -397,11 +403,11 @@ describe('makeRecorderApi', () => { it('is true only if recording', () => { setupBuilder.build() rumInit() - expect(recorderApi.isRecording()).toBeFalse() - recorderApi.start() expect(recorderApi.isRecording()).toBeTrue() recorderApi.stop() expect(recorderApi.isRecording()).toBeFalse() + recorderApi.start() + expect(recorderApi.isRecording()).toBeTrue() }) it('is false before the DOM is loaded', () => { @@ -409,8 +415,6 @@ describe('makeRecorderApi', () => { const { triggerOnDomLoaded } = mockDocumentReadyState() rumInit() expect(recorderApi.isRecording()).toBeFalse() - recorderApi.start() - expect(recorderApi.isRecording()).toBeFalse() triggerOnDomLoaded() expect(recorderApi.isRecording()).toBeTrue() }) diff --git a/packages/rum/src/boot/recorderApi.ts b/packages/rum/src/boot/recorderApi.ts index 65a8a2a6d9..5e057eb1b2 100644 --- a/packages/rum/src/boot/recorderApi.ts +++ b/packages/rum/src/boot/recorderApi.ts @@ -58,7 +58,7 @@ export function makeRecorderApi( } let state: RecorderState = { - status: RecorderStatus.Stopped, + status: RecorderStatus.IntentToStart, } let startStrategy = () => { @@ -79,6 +79,9 @@ export function makeRecorderApi( sessionManager: RumSessionManager, viewContexts: ViewContexts ) => { + if (configuration.startSessionReplayRecordingManually) { + state = { status: RecorderStatus.Stopped } + } lifeCycle.subscribe(LifeCycleEventType.SESSION_EXPIRED, () => { if (state.status === RecorderStatus.Starting || state.status === RecorderStatus.Started) { stopStrategy() diff --git a/test/e2e/lib/helpers/replay.ts b/test/e2e/lib/helpers/replay.ts index 3ce6ef93f4..1a0331ef1f 100644 --- a/test/e2e/lib/helpers/replay.ts +++ b/test/e2e/lib/helpers/replay.ts @@ -1,4 +1,3 @@ -import type { RumInitConfiguration } from '@datadog/browser-rum-core' import type { EventRegistry } from '../framework' export function getFirstSegment(events: EventRegistry) { @@ -8,8 +7,3 @@ export function getFirstSegment(events: EventRegistry) { export function getLastSegment(events: EventRegistry) { return events.sessionReplay[events.sessionReplay.length - 1].segment.data } - -export function initRumAndStartRecording(initConfiguration: RumInitConfiguration) { - window.DD_RUM!.init(initConfiguration) - window.DD_RUM!.startSessionReplayRecording() -} diff --git a/test/e2e/scenario/recorder/recorder.scenario.ts b/test/e2e/scenario/recorder/recorder.scenario.ts index 5652aad37d..4a26a129bd 100644 --- a/test/e2e/scenario/recorder/recorder.scenario.ts +++ b/test/e2e/scenario/recorder/recorder.scenario.ts @@ -20,7 +20,7 @@ import { } from '@datadog/browser-rum/test' import { flushEvents, createTest, bundleSetup, html } from '../../lib/framework' import { browserExecute, browserExecuteAsync } from '../../lib/helpers/browser' -import { getFirstSegment, getLastSegment, initRumAndStartRecording } from '../../lib/helpers/replay' +import { getFirstSegment, getLastSegment } from '../../lib/helpers/replay' const TIMESTAMP_RE = /^\d{13}$/ const UUID_RE = /^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$/ @@ -28,7 +28,6 @@ const UUID_RE = /^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$/ describe('recorder', () => { createTest('record mouse move') .withRum() - .withRumInit(initRumAndStartRecording) .run(async ({ serverEvents }) => { await browserExecute(() => document.documentElement.outerHTML) const html = await $('html') @@ -79,7 +78,6 @@ describe('recorder', () => { describe('full snapshot', () => { createTest('obfuscate elements') .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( html` @@ -125,7 +123,6 @@ describe('recorder', () => { describe('mutations observer', () => { createTest('record mutations') .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( html` @@ -172,7 +169,6 @@ describe('recorder', () => { createTest('record character data mutations') .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( html` @@ -225,7 +221,6 @@ describe('recorder', () => { createTest('record attributes mutations') .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( html` @@ -270,7 +265,6 @@ describe('recorder', () => { createTest("don't record hidden elements mutations") .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( html` @@ -298,7 +292,6 @@ describe('recorder', () => { createTest('record DOM node movement 1') .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( // prettier-ignore @@ -347,7 +340,6 @@ describe('recorder', () => { createTest('record DOM node movement 2') .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( // prettier-ignore @@ -399,7 +391,6 @@ describe('recorder', () => { createTest('serialize node before record') .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( // prettier-ignore @@ -455,7 +446,6 @@ describe('recorder', () => { .withRum({ defaultPrivacyLevel: DefaultPrivacyLevel.ALLOW, }) - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( html` @@ -537,7 +527,6 @@ describe('recorder', () => { .withRum({ defaultPrivacyLevel: DefaultPrivacyLevel.ALLOW, }) - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( html` @@ -574,7 +563,6 @@ describe('recorder', () => { createTest('replace masked values by asterisks') .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( html` @@ -608,7 +596,6 @@ describe('recorder', () => { describe('stylesheet rules observer', () => { createTest('record dynamic CSS changes') .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( html` @@ -643,7 +630,6 @@ describe('recorder', () => { createTest('record nested css rules changes') .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( html` @@ -691,7 +677,6 @@ describe('recorder', () => { describe('frustration records', () => { createTest('should detect a dead click and match it to mouse interaction record') .withRum({ trackUserInteractions: true }) - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .run(async ({ serverEvents }) => { const html = await $('html') @@ -715,7 +700,6 @@ describe('recorder', () => { createTest('should detect a rage click and match it to mouse interaction records') .withRum({ trackUserInteractions: true }) - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( html` @@ -748,7 +732,8 @@ describe('recorder', () => { describe('scroll positions', () => { createTest('should be recorded across navigation') - .withRum() + // to control initial position before recording + .withRum({ startSessionReplayRecordingManually: true }) .withSetup(bundleSetup) .withBody( html` diff --git a/test/e2e/scenario/recorder/shadowDom.scenario.ts b/test/e2e/scenario/recorder/shadowDom.scenario.ts index b01f4ed545..3b9057e0ee 100644 --- a/test/e2e/scenario/recorder/shadowDom.scenario.ts +++ b/test/e2e/scenario/recorder/shadowDom.scenario.ts @@ -1,4 +1,3 @@ -import type { RumInitConfiguration } from '@datadog/browser-rum-core' import { IncrementalSource, NodeType } from '@datadog/browser-rum/src/types' import type { DocumentFragmentNode, MouseInteractionData, SerializedNodeWithId } from '@datadog/browser-rum/src/types' @@ -113,7 +112,6 @@ class DivWithStyle extends HTMLElement { describe('recorder with shadow DOM', () => { createTest('can record fullsnapshot with the detail inside the shadow root') .withRum({ defaultPrivacyLevel: 'allow' }) - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( html` @@ -136,7 +134,6 @@ describe('recorder with shadow DOM', () => { createTest('can record fullsnapshot with adoptedStylesheet') .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( html` @@ -164,7 +161,6 @@ describe('recorder with shadow DOM', () => { createTest('can apply privacy level set from outside or inside the shadow DOM') .withRum({ defaultPrivacyLevel: 'allow' }) - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( html` @@ -202,7 +198,6 @@ describe('recorder with shadow DOM', () => { createTest('can record click with target from inside the shadow root') .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( html` @@ -228,7 +223,6 @@ describe('recorder with shadow DOM', () => { createTest('can record mutation from inside the shadow root') .withRum({ defaultPrivacyLevel: 'allow' }) - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( html` @@ -287,11 +281,6 @@ function getFirstSegment(events: EventRegistry) { return events.sessionReplay[0].segment.data } -function initRumAndStartRecording(initConfiguration: RumInitConfiguration) { - window.DD_RUM!.init(initConfiguration) - window.DD_RUM!.startSessionReplayRecording() -} - async function getNodeInsideShadowDom(hostTag: string, selector: string) { const host = await $(hostTag) return host.shadow$(selector) diff --git a/test/e2e/scenario/recorder/viewports.scenario.ts b/test/e2e/scenario/recorder/viewports.scenario.ts index fc30e31e6a..65d429299b 100644 --- a/test/e2e/scenario/recorder/viewports.scenario.ts +++ b/test/e2e/scenario/recorder/viewports.scenario.ts @@ -1,6 +1,5 @@ import type { ViewportResizeData, ScrollData } from '@datadog/browser-rum/cjs/types' import { IncrementalSource } from '@datadog/browser-rum/cjs/types' -import type { RumInitConfiguration } from '@datadog/browser-rum-core' import { findAllIncrementalSnapshots, findAllVisualViewports } from '@datadog/browser-rum/test' import type { EventRegistry } from '../../lib/framework' @@ -26,7 +25,6 @@ describe('recorder', () => { describe('layout viewport properties', () => { createTest('getWindowWidth/Height should not be affected by pinch zoom') .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody(html`${VIEWPORT_META_TAGS}`) .run(async ({ serverEvents }) => { @@ -57,7 +55,6 @@ describe('recorder', () => { */ createTest('getScrollX/Y should not be affected by pinch scroll') .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody(html`${VIEWPORT_META_TAGS}`) .run(async ({ serverEvents }) => { @@ -99,7 +96,6 @@ describe('recorder', () => { describe('visual viewport properties', () => { createTest('pinch zoom "scroll" event reports visual viewport position') .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody(html`${VIEWPORT_META_TAGS}`) .run(async ({ serverEvents }) => { @@ -114,7 +110,6 @@ describe('recorder', () => { createTest('pinch zoom "resize" event reports visual viewport scale') .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody(html`${VIEWPORT_META_TAGS}`) .run(async ({ serverEvents }) => { @@ -130,11 +125,6 @@ function getLastSegment(serverEvents: EventRegistry) { return serverEvents.sessionReplay[serverEvents.sessionReplay.length - 1].segment.data } -function initRumAndStartRecording(initConfiguration: RumInitConfiguration) { - window.DD_RUM!.init(initConfiguration) - window.DD_RUM!.startSessionReplayRecording() -} - const isGestureUnsupported = () => /firefox|safari|edge/.test(getBrowserName()) || /windows|linux/.test(getPlatformName()) diff --git a/test/e2e/scenario/rum/sessions.scenario.ts b/test/e2e/scenario/rum/sessions.scenario.ts index c912930dbf..01523b457d 100644 --- a/test/e2e/scenario/rum/sessions.scenario.ts +++ b/test/e2e/scenario/rum/sessions.scenario.ts @@ -2,7 +2,7 @@ import { RecordType } from '@datadog/browser-rum/src/types' import { expireSession, findSessionCookie, renewSession } from '../../lib/helpers/session' import { bundleSetup, createTest, flushEvents, waitForRequests } from '../../lib/framework' import { browserExecute, browserExecuteAsync, sendXhr } from '../../lib/helpers/browser' -import { getLastSegment, initRumAndStartRecording } from '../../lib/helpers/replay' +import { getLastSegment } from '../../lib/helpers/replay' describe('rum sessions', () => { describe('session renewal', () => { @@ -23,7 +23,6 @@ describe('rum sessions', () => { createTest('a single fullSnapshot is taken when the session is renewed') .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .run(async ({ serverEvents }) => { await renewSession() @@ -43,7 +42,8 @@ describe('rum sessions', () => { describe('session expiration', () => { createTest("don't send events when session is expired") - .withRum() + // prevent recording start to generate late events + .withRum({ startSessionReplayRecordingManually: true }) .run(async ({ serverEvents }) => { await expireSession() serverEvents.empty() @@ -98,7 +98,6 @@ describe('rum sessions', () => { createTest('flush events when the session expires') .withRum() .withLogs() - .withRumInit(initRumAndStartRecording) .run(async ({ serverEvents }) => { expect(serverEvents.rumViews.length).toBe(0) expect(serverEvents.logs.length).toBe(0) From 892bc2373ba140847f0e20c7f731187a009285f3 Mon Sep 17 00:00:00 2001 From: Bastien Caudan <1331991+bcaudan@users.noreply.github.com> Date: Tue, 6 Jun 2023 16:08:46 +0200 Subject: [PATCH 08/27] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Remove=20deprecated?= =?UTF-8?q?=20context=20manager=20APIs=20(#2284)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../serialisation/contextManager.spec.ts | 37 ++++++++----------- .../src/tools/serialisation/contextManager.ts | 22 +---------- packages/logs/src/domain/logger.ts | 14 ++++--- .../domain/startCustomerDataTelemetry.spec.ts | 2 +- .../src/domain/startCustomerDataTelemetry.ts | 4 +- 5 files changed, 27 insertions(+), 52 deletions(-) diff --git a/packages/core/src/tools/serialisation/contextManager.spec.ts b/packages/core/src/tools/serialisation/contextManager.spec.ts index f57f0cd7df..2f4b1ba225 100644 --- a/packages/core/src/tools/serialisation/contextManager.spec.ts +++ b/packages/core/src/tools/serialisation/contextManager.spec.ts @@ -20,42 +20,35 @@ describe('createContextManager', () => { it('starts with an empty context', () => { const manager = createContextManager(CustomerDataType.User) - expect(manager.get()).toEqual({}) + expect(manager.getContext()).toEqual({}) }) it('updates the context', () => { const manager = createContextManager(CustomerDataType.User) - manager.set({ bar: 'foo' }) - - expect(manager.get()).toEqual({ bar: 'foo' }) - }) + manager.setContext({ bar: 'foo' }) - it('updates the context without copy', () => { - const manager = createContextManager(CustomerDataType.User) - const context = {} - manager.set(context) - expect(manager.get()).toBe(context) + expect(manager.getContext()).toEqual({ bar: 'foo' }) }) it('completely replaces the context', () => { const manager = createContextManager(CustomerDataType.User) - manager.set({ a: 'foo' }) - expect(manager.get()).toEqual({ a: 'foo' }) - manager.set({ b: 'foo' }) - expect(manager.get()).toEqual({ b: 'foo' }) + manager.setContext({ a: 'foo' }) + expect(manager.getContext()).toEqual({ a: 'foo' }) + manager.setContext({ b: 'foo' }) + expect(manager.getContext()).toEqual({ b: 'foo' }) }) it('sets a context value', () => { const manager = createContextManager(CustomerDataType.User) - manager.add('foo', 'bar') - expect(manager.get()).toEqual({ foo: 'bar' }) + manager.setContextProperty('foo', 'bar') + expect(manager.getContext()).toEqual({ foo: 'bar' }) }) it('removes a context value', () => { const manager = createContextManager(CustomerDataType.User) - manager.set({ a: 'foo', b: 'bar' }) - manager.remove('a') - expect(manager.get()).toEqual({ b: 'bar' }) + manager.setContext({ a: 'foo', b: 'bar' }) + manager.removeContextProperty('a') + expect(manager.getContext()).toEqual({ b: 'bar' }) manager.removeContextProperty('b') expect(manager.getContext()).toEqual({}) }) @@ -97,13 +90,13 @@ describe('createContextManager', () => { const computeBytesCountStub = jasmine.createSpy('computeBytesCountStub').and.returnValue(1) const manager = createContextManager(CustomerDataType.User, computeBytesCountStub) - manager.add('foo', 'bar') + manager.setContextProperty('foo', 'bar') clock.tick(BYTES_COMPUTATION_THROTTLING_DELAY) - manager.remove('foo') + manager.removeContextProperty('foo') clock.tick(BYTES_COMPUTATION_THROTTLING_DELAY) - manager.set({ foo: 'bar' }) + manager.setContext({ foo: 'bar' }) clock.tick(BYTES_COMPUTATION_THROTTLING_DELAY) manager.setContextProperty('foo', 'bar') diff --git a/packages/core/src/tools/serialisation/contextManager.ts b/packages/core/src/tools/serialisation/contextManager.ts index 983cae9812..9a5c712e37 100644 --- a/packages/core/src/tools/serialisation/contextManager.ts +++ b/packages/core/src/tools/serialisation/contextManager.ts @@ -5,7 +5,7 @@ import { jsonStringify } from './jsonStringify' import { sanitize } from './sanitize' import { warnIfCustomerDataLimitReached } from './heavyCustomerDataWarning' import type { CustomerDataType } from './heavyCustomerDataWarning' -import type { Context, ContextValue } from './context' +import type { Context } from './context' export const BYTES_COMPUTATION_THROTTLING_DELAY = 200 @@ -27,26 +27,6 @@ export function createContextManager(customerDataType: CustomerDataType, compute return { getBytesCount: () => bytesCountCache, - /** @deprecated use getContext instead */ - get: () => context, - - /** @deprecated use setContextProperty instead */ - add: (key: string, value: any) => { - context[key] = value as ContextValue - computeBytesCountThrottled(context) - }, - - /** @deprecated renamed to removeContextProperty */ - remove: (key: string) => { - delete context[key] - computeBytesCountThrottled(context) - }, - - /** @deprecated use setContext instead */ - set: (newContext: object) => { - context = newContext as Context - computeBytesCountThrottled(context) - }, getContext: () => deepClone(context), diff --git a/packages/logs/src/domain/logger.ts b/packages/logs/src/domain/logger.ts index de626a879f..f4cc6a742e 100644 --- a/packages/logs/src/domain/logger.ts +++ b/packages/logs/src/domain/logger.ts @@ -5,7 +5,6 @@ import { ErrorHandling, computeStackTrace, CustomerDataType, - assign, combine, createContextManager, ErrorSource, @@ -50,7 +49,10 @@ export class Logger { private level: StatusType = StatusType.debug, loggerContext: object = {} ) { - this.contextManager.set(assign({}, loggerContext, name ? { logger: { name } } : undefined)) + this.contextManager.setContext(loggerContext as Context) + if (name) { + this.contextManager.setContextProperty('logger', { name }) + } } @monitored @@ -114,19 +116,19 @@ export class Logger { } setContext(context: object) { - this.contextManager.set(context) + this.contextManager.setContext(context as Context) } getContext() { - return this.contextManager.get() + return this.contextManager.getContext() } addContext(key: string, value: any) { - this.contextManager.add(key, value) + this.contextManager.setContextProperty(key, value) } removeContext(key: string) { - this.contextManager.remove(key) + this.contextManager.removeContextProperty(key) } setHandler(handler: HandlerType | HandlerType[]) { diff --git a/packages/rum-core/src/domain/startCustomerDataTelemetry.spec.ts b/packages/rum-core/src/domain/startCustomerDataTelemetry.spec.ts index 37e43b21fa..55b96a857c 100644 --- a/packages/rum-core/src/domain/startCustomerDataTelemetry.spec.ts +++ b/packages/rum-core/src/domain/startCustomerDataTelemetry.spec.ts @@ -43,7 +43,7 @@ describe('customerDataTelemetry', () => { } function spyOnContextManager(contextManager: ContextManager) { - spyOn(contextManager, 'get').and.callFake(() => fakeContext) + spyOn(contextManager, 'getContext').and.callFake(() => fakeContext) spyOn(contextManager, 'getBytesCount').and.callFake(() => fakeContextBytesCount) } diff --git a/packages/rum-core/src/domain/startCustomerDataTelemetry.ts b/packages/rum-core/src/domain/startCustomerDataTelemetry.ts index 3158019e1e..a0c58aa842 100644 --- a/packages/rum-core/src/domain/startCustomerDataTelemetry.ts +++ b/packages/rum-core/src/domain/startCustomerDataTelemetry.ts @@ -57,12 +57,12 @@ export function startCustomerDataTelemetry( batchHasRumEvent = true updateMeasure( currentBatchMeasures.globalContextBytes, - !isEmptyObject(globalContextManager.get()) ? globalContextManager.getBytesCount() : 0 + !isEmptyObject(globalContextManager.getContext()) ? globalContextManager.getBytesCount() : 0 ) updateMeasure( currentBatchMeasures.userContextBytes, - !isEmptyObject(userContextManager.get()) ? userContextManager.getBytesCount() : 0 + !isEmptyObject(userContextManager.getContext()) ? userContextManager.getBytesCount() : 0 ) const featureFlagContext = featureFlagContexts.findFeatureFlagEvaluations() From 982e7dbce0a4122320b5843b4f0d2a2fe4af6237 Mon Sep 17 00:00:00 2001 From: Bastien Caudan <1331991+bcaudan@users.noreply.github.com> Date: Tue, 6 Jun 2023 16:42:45 +0200 Subject: [PATCH 09/27] =?UTF-8?q?=F0=9F=92=A5=20[RUMF-1555]=20Remove=20`ev?= =?UTF-8?q?ent`=20in=20action=20domain=20context=20(#2286)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rumEventsCollection/action/actionCollection.spec.ts | 1 - .../src/domain/rumEventsCollection/action/actionCollection.ts | 2 +- packages/rum-core/src/domainContext.types.ts | 4 ---- .../src/domain/record/observers/frustrationObserver.spec.ts | 2 +- 4 files changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/rum-core/src/domain/rumEventsCollection/action/actionCollection.spec.ts b/packages/rum-core/src/domain/rumEventsCollection/action/actionCollection.spec.ts index a6b2ca4c77..4484189979 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/action/actionCollection.spec.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/action/actionCollection.spec.ts @@ -91,7 +91,6 @@ describe('actionCollection', () => { }, }) expect(rawRumEvents[0].domainContext).toEqual({ - event, events: [event], }) }) diff --git a/packages/rum-core/src/domain/rumEventsCollection/action/actionCollection.ts b/packages/rum-core/src/domain/rumEventsCollection/action/actionCollection.ts index ac0208f383..02e930fc19 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/action/actionCollection.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/action/actionCollection.ts @@ -106,7 +106,7 @@ function processAction( customerContext, rawRumEvent: actionEvent, startTime: action.startClocks.relative, - domainContext: isAutoAction(action) ? { event: action.event, events: action.events } : {}, + domainContext: isAutoAction(action) ? { events: action.events } : {}, } } diff --git a/packages/rum-core/src/domainContext.types.ts b/packages/rum-core/src/domainContext.types.ts index 3b8bd2a2df..25b14bc3b0 100644 --- a/packages/rum-core/src/domainContext.types.ts +++ b/packages/rum-core/src/domainContext.types.ts @@ -21,10 +21,6 @@ export interface RumViewEventDomainContext { } export interface RumActionEventDomainContext { - /** - * @deprecated use events array instead - */ - event?: Event events?: Event[] } diff --git a/packages/rum/src/domain/record/observers/frustrationObserver.spec.ts b/packages/rum/src/domain/record/observers/frustrationObserver.spec.ts index 50cdc267cd..9c1e8ebd7c 100644 --- a/packages/rum/src/domain/record/observers/frustrationObserver.spec.ts +++ b/packages/rum/src/domain/record/observers/frustrationObserver.spec.ts @@ -40,7 +40,7 @@ describe('initFrustrationObserver', () => { }, }, }, - domainContext: { event: mouseEvent, events: [mouseEvent] }, + domainContext: { events: [mouseEvent] }, } }) From c928b60a9a5419605bb52111fcb4452dd6a9b3ed Mon Sep 17 00:00:00 2001 From: Bastien Caudan <1331991+bcaudan@users.noreply.github.com> Date: Tue, 6 Jun 2023 16:43:10 +0200 Subject: [PATCH 10/27] =?UTF-8?q?=F0=9F=94=A5=20[RUMF-1555]=20Remove=20`st?= =?UTF-8?q?artTime`=20in=20xhr=20start=20context=20(#2287)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/src/browser/xhrObservable.spec.ts | 10 ---------- packages/core/src/browser/xhrObservable.ts | 8 +++----- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/packages/core/src/browser/xhrObservable.spec.ts b/packages/core/src/browser/xhrObservable.spec.ts index e0270e16ea..38b2ad5065 100644 --- a/packages/core/src/browser/xhrObservable.spec.ts +++ b/packages/core/src/browser/xhrObservable.spec.ts @@ -47,7 +47,6 @@ describe('xhr observable', () => { expect(request.url).toContain('/ok') expect(request.status).toBe(200) expect(request.isAborted).toBe(false) - expect(request.startTime).toEqual(jasmine.any(Number)) expect(request.duration).toEqual(jasmine.any(Number)) done() }, @@ -67,7 +66,6 @@ describe('xhr observable', () => { expect(request.url).toContain('/expected-404') expect(request.status).toBe(404) expect(request.isAborted).toBe(false) - expect(request.startTime).toEqual(jasmine.any(Number)) expect(request.duration).toEqual(jasmine.any(Number)) done() }, @@ -87,7 +85,6 @@ describe('xhr observable', () => { expect(request.url).toContain('/throw') expect(request.status).toBe(500) expect(request.isAborted).toBe(false) - expect(request.startTime).toEqual(jasmine.any(Number)) expect(request.duration).toEqual(jasmine.any(Number)) done() }, @@ -107,7 +104,6 @@ describe('xhr observable', () => { expect(request.url).toBe('http://foo.bar/qux') expect(request.status).toBe(0) expect(request.isAborted).toBe(false) - expect(request.startTime).toEqual(jasmine.any(Number)) expect(request.duration).toEqual(jasmine.any(Number)) done() }, @@ -133,7 +129,6 @@ describe('xhr observable', () => { expect(request.method).toBe('GET') expect(request.url).toContain('/ok') expect(request.status).toBe(200) - expect(request.startTime).toEqual(jasmine.any(Number)) expect(request.duration).toEqual(jasmine.any(Number)) expect(request.isAborted).toBe(false) expect(xhr.status).toBe(0) @@ -155,7 +150,6 @@ describe('xhr observable', () => { expect(request.method).toBe('GET') expect(request.url).toContain('/ok') expect(request.status).toBe(0) - expect(request.startTime).toEqual(jasmine.any(Number)) expect(request.duration).toEqual(jasmine.any(Number)) expect(request.isAborted).toBe(true) expect(xhr.status).toBe(0) @@ -178,7 +172,6 @@ describe('xhr observable', () => { expect(request.url).toContain('/ok') expect(request.status).toBe(200) expect(request.isAborted).toBe(false) - expect(request.startTime).toEqual(jasmine.any(Number)) expect(request.duration).toEqual(jasmine.any(Number)) expect(xhr.onreadystatechange).toHaveBeenCalled() done() @@ -200,7 +193,6 @@ describe('xhr observable', () => { expect(request.url).toContain('/ok') expect(request.status).toBe(200) expect(request.isAborted).toBe(false) - expect(request.startTime).toEqual(jasmine.any(Number)) expect(request.duration).toEqual(jasmine.any(Number)) expect(xhr.onreadystatechange).toHaveBeenCalled() done() @@ -273,7 +265,6 @@ describe('xhr observable', () => { expect(firstRequest.url).toContain('/ok?request=1') expect(firstRequest.status).toBe(200) expect(firstRequest.isAborted).toBe(false) - expect(firstRequest.startTime).toEqual(jasmine.any(Number)) expect(firstRequest.duration).toEqual(jasmine.any(Number)) const secondRequest = requests[1] @@ -281,7 +272,6 @@ describe('xhr observable', () => { expect(secondRequest.url).toContain('/ok?request=2') expect(secondRequest.status).toBe(400) expect(secondRequest.isAborted).toBe(false) - expect(secondRequest.startTime).toEqual(jasmine.any(Number)) expect(secondRequest.duration).toEqual(jasmine.any(Number)) expect(xhr.onreadystatechange).toHaveBeenCalledTimes(2) diff --git a/packages/core/src/browser/xhrObservable.ts b/packages/core/src/browser/xhrObservable.ts index b0b7f73026..c07b19364b 100644 --- a/packages/core/src/browser/xhrObservable.ts +++ b/packages/core/src/browser/xhrObservable.ts @@ -1,7 +1,7 @@ import { instrumentMethodAndCallOriginal } from '../tools/instrumentMethod' import { Observable } from '../tools/observable' -import type { Duration, RelativeTime, ClocksState } from '../tools/utils/timeUtils' -import { elapsed, relativeNow, clocksNow, timeStampNow } from '../tools/utils/timeUtils' +import type { Duration, ClocksState } from '../tools/utils/timeUtils' +import { elapsed, clocksNow, timeStampNow } from '../tools/utils/timeUtils' import { normalizeUrl } from '../tools/utils/urlPolyfill' import { shallowClone } from '../tools/utils/objectUtils' import { addEventListener } from './addEventListener' @@ -14,7 +14,6 @@ export interface XhrOpenContext { export interface XhrStartContext extends Omit { state: 'start' - startTime: RelativeTime // deprecated startClocks: ClocksState isAborted: boolean xhr: XMLHttpRequest @@ -79,7 +78,6 @@ function sendXhr(this: XMLHttpRequest, observable: Observable) { const startContext = context as XhrStartContext startContext.state = 'start' - startContext.startTime = relativeNow() startContext.startClocks = clocksNow() startContext.isAborted = false startContext.xhr = this @@ -91,7 +89,7 @@ function sendXhr(this: XMLHttpRequest, observable: Observable) { if (this.readyState === XMLHttpRequest.DONE) { // Try to report the XHR as soon as possible, because the XHR may be mutated by the // application during a future event. For example, Angular is calling .abort() on - // completed requests during a onreadystatechange event, so the status becomes '0' + // completed requests during an onreadystatechange event, so the status becomes '0' // before the request is collected. onEnd() } From fad84dd4de01218475c054b1351fb6b7f16fcee7 Mon Sep 17 00:00:00 2001 From: Bastien Caudan <1331991+bcaudan@users.noreply.github.com> Date: Wed, 7 Jun 2023 11:17:21 +0200 Subject: [PATCH 11/27] =?UTF-8?q?=F0=9F=92=A5[RUMF-1152]=20sanitize=20reso?= =?UTF-8?q?urce=20method=20names=20(#2288)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/src/browser/fetchObservable.spec.ts | 4 +++- packages/core/src/browser/fetchObservable.ts | 3 ++- packages/core/src/browser/xhrObservable.spec.ts | 15 +++++++++++++++ packages/core/src/browser/xhrObservable.ts | 2 +- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/packages/core/src/browser/fetchObservable.spec.ts b/packages/core/src/browser/fetchObservable.spec.ts index 9629b6d077..b233552725 100644 --- a/packages/core/src/browser/fetchObservable.spec.ts +++ b/packages/core/src/browser/fetchObservable.spec.ts @@ -113,6 +113,7 @@ describe('fetch proxy', () => { fetchStub(new Request(FAKE_URL, { method: 'PUT' }), { method: 'POST' }).resolveWith({ status: 500 }) fetchStub(new Request(FAKE_URL), { method: 'POST' }).resolveWith({ status: 500 }) fetchStub(FAKE_URL, { method: 'POST' }).resolveWith({ status: 500 }) + fetchStub(FAKE_URL, { method: 'post' }).resolveWith({ status: 500 }) fetchStub(null as any).resolveWith({ status: 500 }) fetchStub({ method: 'POST' } as any).resolveWith({ status: 500 }) @@ -123,8 +124,9 @@ describe('fetch proxy', () => { expect(requests[3].method).toEqual('POST') expect(requests[4].method).toEqual('POST') expect(requests[5].method).toEqual('POST') - expect(requests[6].method).toEqual('GET') + expect(requests[6].method).toEqual('POST') expect(requests[7].method).toEqual('GET') + expect(requests[8].method).toEqual('GET') done() }) }) diff --git a/packages/core/src/browser/fetchObservable.ts b/packages/core/src/browser/fetchObservable.ts index 02716d8b3c..7ede80e839 100644 --- a/packages/core/src/browser/fetchObservable.ts +++ b/packages/core/src/browser/fetchObservable.ts @@ -69,7 +69,8 @@ function createFetchObservable() { } function beforeSend(observable: Observable, input: unknown, init?: RequestInit) { - const method = (init && init.method) || (input instanceof Request && input.method) || 'GET' + const methodFromParams = (init && init.method) || (input instanceof Request && input.method) + const method = methodFromParams ? methodFromParams.toUpperCase() : 'GET' const url = input instanceof Request ? input.url : normalizeUrl(String(input)) const startClocks = clocksNow() diff --git a/packages/core/src/browser/xhrObservable.spec.ts b/packages/core/src/browser/xhrObservable.spec.ts index 38b2ad5065..dd11ca1837 100644 --- a/packages/core/src/browser/xhrObservable.spec.ts +++ b/packages/core/src/browser/xhrObservable.spec.ts @@ -53,6 +53,21 @@ describe('xhr observable', () => { }) }) + it('should sanitize request method', (done) => { + withXhr({ + setup(xhr) { + xhr.open('get', '/ok') + xhr.send() + xhr.complete(200, 'ok') + }, + onComplete() { + const request = requests[0] + expect(request.method).toBe('GET') + done() + }, + }) + }) + it('should track client error', (done) => { withXhr({ setup(xhr) { diff --git a/packages/core/src/browser/xhrObservable.ts b/packages/core/src/browser/xhrObservable.ts index c07b19364b..ee6be47548 100644 --- a/packages/core/src/browser/xhrObservable.ts +++ b/packages/core/src/browser/xhrObservable.ts @@ -65,7 +65,7 @@ function createXhrObservable() { function openXhr(this: XMLHttpRequest, method: string, url: string | URL | undefined | null) { xhrContexts.set(this, { state: 'open', - method, + method: method.toUpperCase(), url: normalizeUrl(String(url)), }) } From 512137b550c032afe5e2fe3f1b376130b511f4a1 Mon Sep 17 00:00:00 2001 From: Bastien Caudan <1331991+bcaudan@users.noreply.github.com> Date: Wed, 7 Jun 2023 12:08:43 +0200 Subject: [PATCH 12/27] =?UTF-8?q?=F0=9F=92=A5=20[RUMF-1555]=20Rework=20log?= =?UTF-8?q?ger=20context=20APIs=20(#2285)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/logs/src/domain/logger.spec.ts | 31 +++++++++++++++++++++++++ packages/logs/src/domain/logger.ts | 8 +++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/packages/logs/src/domain/logger.spec.ts b/packages/logs/src/domain/logger.spec.ts index 35996fb0f7..04eca49041 100644 --- a/packages/logs/src/domain/logger.spec.ts +++ b/packages/logs/src/domain/logger.spec.ts @@ -94,6 +94,37 @@ describe('Logger', () => { }) }) + describe('context methods', () => { + beforeEach(() => { + const loggerContext = { foo: 'bar' } + logger = new Logger(handleLogSpy, undefined, HandlerType.http, StatusType.debug, loggerContext) + }) + + it('getContext should return the context', () => { + expect(logger.getContext()).toEqual({ foo: 'bar' }) + }) + + it('setContext should overwrite the whole context', () => { + logger.setContext({ qux: 'qix' }) + expect(logger.getContext()).toEqual({ qux: 'qix' }) + }) + + it('setContextProperty should set a context value', () => { + logger.setContextProperty('qux', 'qix') + expect(logger.getContext()).toEqual({ foo: 'bar', qux: 'qix' }) + }) + + it('removeContextProperty should remove a context value', () => { + logger.removeContextProperty('foo') + expect(logger.getContext()).toEqual({}) + }) + + it('clearContext should clear the context', () => { + logger.clearContext() + expect(logger.getContext()).toEqual({}) + }) + }) + describe('contexts', () => { it('logger context should be deep copied', () => { const loggerContext = { foo: 'bar' } diff --git a/packages/logs/src/domain/logger.ts b/packages/logs/src/domain/logger.ts index f4cc6a742e..0ddc32d393 100644 --- a/packages/logs/src/domain/logger.ts +++ b/packages/logs/src/domain/logger.ts @@ -123,14 +123,18 @@ export class Logger { return this.contextManager.getContext() } - addContext(key: string, value: any) { + setContextProperty(key: string, value: any) { this.contextManager.setContextProperty(key, value) } - removeContext(key: string) { + removeContextProperty(key: string) { this.contextManager.removeContextProperty(key) } + clearContext() { + this.contextManager.clearContext() + } + setHandler(handler: HandlerType | HandlerType[]) { this.handlerType = handler } From e2fb6c1d34193c51c2c860068c0aa4b568ce61b5 Mon Sep 17 00:00:00 2001 From: Aymeric Date: Fri, 9 Jun 2023 16:26:24 +0200 Subject: [PATCH 13/27] =?UTF-8?q?=F0=9F=92=A5[RUMF-1228]=20Remove=20consol?= =?UTF-8?q?e=20error=20message=20prefix=20(#2289)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/console/consoleObservable.spec.ts | 18 +++++++++--------- .../src/domain/console/consoleObservable.ts | 4 +--- test/e2e/scenario/logs.scenario.ts | 2 +- test/e2e/scenario/rum/errors.scenario.ts | 4 ++-- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/packages/core/src/domain/console/consoleObservable.spec.ts b/packages/core/src/domain/console/consoleObservable.spec.ts index 8aeec39631..4064eeaab0 100644 --- a/packages/core/src/domain/console/consoleObservable.spec.ts +++ b/packages/core/src/domain/console/consoleObservable.spec.ts @@ -8,12 +8,12 @@ import { initConsoleObservable } from './consoleObservable' // prettier: avoid formatting issue // cf https://github.com/prettier/prettier/issues/12211 ;[ - { api: ConsoleApiName.log, prefix: '' }, - { api: ConsoleApiName.info, prefix: '' }, - { api: ConsoleApiName.warn, prefix: '' }, - { api: ConsoleApiName.debug, prefix: '' }, - { api: ConsoleApiName.error, prefix: 'console error: ' }, -].forEach(({ api, prefix }) => { + { api: ConsoleApiName.log }, + { api: ConsoleApiName.info }, + { api: ConsoleApiName.warn }, + { api: ConsoleApiName.debug }, + { api: ConsoleApiName.error }, +].forEach(({ api }) => { describe(`console ${api} observable`, () => { let consoleStub: jasmine.Spy let consoleSubscription: Subscription @@ -37,7 +37,7 @@ import { initConsoleObservable } from './consoleObservable' expect(consoleLog).toEqual( jasmine.objectContaining({ - message: `${prefix}foo bar`, + message: 'foo bar', api, }) ) @@ -52,13 +52,13 @@ import { initConsoleObservable } from './consoleObservable' it('should format error instance', () => { console[api](new TypeError('hello')) const consoleLog = notifyLog.calls.mostRecent().args[0] - expect(consoleLog.message).toBe(`${prefix}TypeError: hello`) + expect(consoleLog.message).toBe('TypeError: hello') }) it('should stringify object parameters', () => { console[api]('Hello', { foo: 'bar' }) const consoleLog = notifyLog.calls.mostRecent().args[0] - expect(consoleLog.message).toBe(`${prefix}Hello {\n "foo": "bar"\n}`) + expect(consoleLog.message).toBe('Hello {\n "foo": "bar"\n}') }) it('should allow multiple callers', () => { diff --git a/packages/core/src/domain/console/consoleObservable.ts b/packages/core/src/domain/console/consoleObservable.ts index bbf8f7c027..21c7ecab86 100644 --- a/packages/core/src/domain/console/consoleObservable.ts +++ b/packages/core/src/domain/console/consoleObservable.ts @@ -54,8 +54,7 @@ function createConsoleObservable(api: ConsoleApiName) { } function buildConsoleLog(params: unknown[], api: ConsoleApiName, handlingStack: string): ConsoleLog { - // Todo: remove console error prefix in the next major version - let message = params.map((param) => formatConsoleParameters(param)).join(' ') + const message = params.map((param) => formatConsoleParameters(param)).join(' ') let stack let fingerprint @@ -63,7 +62,6 @@ function buildConsoleLog(params: unknown[], api: ConsoleApiName, handlingStack: const firstErrorParam = find(params, (param: unknown): param is Error => param instanceof Error) stack = firstErrorParam ? toStackTraceString(computeStackTrace(firstErrorParam)) : undefined fingerprint = tryToGetFingerprint(firstErrorParam) - message = `console error: ${message}` } return { diff --git a/test/e2e/scenario/logs.scenario.ts b/test/e2e/scenario/logs.scenario.ts index 171db742dc..c672213290 100644 --- a/test/e2e/scenario/logs.scenario.ts +++ b/test/e2e/scenario/logs.scenario.ts @@ -23,7 +23,7 @@ describe('logs', () => { }) await flushEvents() expect(serverEvents.logs.length).toBe(1) - expect(serverEvents.logs[0].message).toBe('console error: oh snap') + expect(serverEvents.logs[0].message).toBe('oh snap') await withBrowserLogs((browserLogs) => { expect(browserLogs.length).toEqual(1) }) diff --git a/test/e2e/scenario/rum/errors.scenario.ts b/test/e2e/scenario/rum/errors.scenario.ts index 52dd03c1d1..10899b2bb2 100644 --- a/test/e2e/scenario/rum/errors.scenario.ts +++ b/test/e2e/scenario/rum/errors.scenario.ts @@ -30,7 +30,7 @@ describe('rum errors', () => { await flushEvents() expect(serverEvents.rumErrors.length).toBe(1) expectError(serverEvents.rumErrors[0].error, { - message: 'console error: oh snap', + message: 'oh snap', source: 'console', handlingStack: ['Error: ', `handler @ ${baseUrl}/:`], handling: 'handled', @@ -50,7 +50,7 @@ describe('rum errors', () => { await flushEvents() expect(serverEvents.rumErrors.length).toBe(1) expectError(serverEvents.rumErrors[0].error, { - message: 'console error: Foo: Error: oh snap', + message: 'Foo: Error: oh snap', source: 'console', stack: ['Error: oh snap', `at foo @ ${baseUrl}/:`, `handler @ ${baseUrl}/:`], handlingStack: ['Error: ', `handler @ ${baseUrl}/:`], From 76872fb0b14fe230dc01a075057a4a26090423ff Mon Sep 17 00:00:00 2001 From: Bastien Caudan <1331991+bcaudan@users.noreply.github.com> Date: Thu, 15 Jun 2023 15:58:48 +0200 Subject: [PATCH 14/27] =?UTF-8?q?=F0=9F=92=A5[RUMF-1229]=20Logs:=20remove?= =?UTF-8?q?=20`error.origin`=20attribute=20(#2294)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Improve types * Remove error.origin * πŸ‘Œadd test for all statuses/apis * πŸ‘Œremove console override --- packages/logs/src/boot/startLogs.ts | 3 - packages/logs/src/domain/assembly.spec.ts | 13 +++++ packages/logs/src/domain/assembly.ts | 2 +- packages/logs/src/domain/logger.spec.ts | 10 +--- packages/logs/src/domain/logger.ts | 6 -- .../console/consoleCollection.spec.ts | 57 +++++++++++-------- .../console/consoleCollection.ts | 3 +- .../networkErrorCollection.spec.ts | 1 - .../networkError/networkErrorCollection.ts | 1 - .../report/reportCollection.spec.ts | 1 - .../logsCollection/report/reportCollection.ts | 1 - .../runtimeErrorCollection.spec.ts | 2 +- .../runtimeError/runtimeErrorCollection.ts | 1 - packages/logs/src/logsEvent.types.ts | 6 +- packages/logs/src/rawLogsEvent.types.ts | 3 +- test/e2e/scenario/logs.scenario.ts | 6 +- 16 files changed, 57 insertions(+), 59 deletions(-) diff --git a/packages/logs/src/boot/startLogs.ts b/packages/logs/src/boot/startLogs.ts index 5a4ef0c402..0ac9b21cfb 100644 --- a/packages/logs/src/boot/startLogs.ts +++ b/packages/logs/src/boot/startLogs.ts @@ -45,9 +45,6 @@ export function startLogs( rawLogsEvent: { message: error.message, date: error.startClocks.timeStamp, - error: { - origin: ErrorSource.AGENT, // Todo: Remove in the next major release - }, origin: ErrorSource.AGENT, status: StatusType.error, }, diff --git a/packages/logs/src/domain/assembly.spec.ts b/packages/logs/src/domain/assembly.spec.ts index baef7c4c37..01dce3524c 100644 --- a/packages/logs/src/domain/assembly.spec.ts +++ b/packages/logs/src/domain/assembly.spec.ts @@ -386,6 +386,19 @@ describe('logs limitation', () => { clock.cleanup() serverLogs = [] }) + it('should not apply to agent logs', () => { + lifeCycle.notify(LifeCycleEventType.RAW_LOG_COLLECTED, { + rawLogsEvent: { ...DEFAULT_MESSAGE, origin: ErrorSource.AGENT, status: 'error', message: 'foo' }, + }) + lifeCycle.notify(LifeCycleEventType.RAW_LOG_COLLECTED, { + rawLogsEvent: { ...DEFAULT_MESSAGE, origin: ErrorSource.AGENT, status: 'error', message: 'bar' }, + }) + + expect(serverLogs.length).toEqual(2) + expect(reportErrorSpy).not.toHaveBeenCalled() + expect(serverLogs[0].message).toBe('foo') + expect(serverLogs[1].message).toBe('bar') + }) ;[ { status: StatusType.error, messageContext: {}, message: 'Reached max number of errors by minute: 1' }, { status: StatusType.warn, messageContext: {}, message: 'Reached max number of warns by minute: 1' }, diff --git a/packages/logs/src/domain/assembly.ts b/packages/logs/src/domain/assembly.ts index 16f7ff21a1..720feac396 100644 --- a/packages/logs/src/domain/assembly.ts +++ b/packages/logs/src/domain/assembly.ts @@ -63,7 +63,7 @@ export function startLogsAssembly( // Todo: [RUMF-1230] Move this check to the logger collection in the next major release !isAuthorized(rawLogsEvent.status, HandlerType.http, logger) || configuration.beforeSend?.(log) === false || - (log.error?.origin !== ErrorSource.AGENT && + (log.origin !== ErrorSource.AGENT && (logRateLimiters[log.status] ?? logRateLimiters['custom']).isLimitReached()) ) { return diff --git a/packages/logs/src/domain/logger.spec.ts b/packages/logs/src/domain/logger.spec.ts index 04eca49041..bd6836c2c2 100644 --- a/packages/logs/src/domain/logger.spec.ts +++ b/packages/logs/src/domain/logger.spec.ts @@ -37,7 +37,6 @@ describe('Logger', () => { expect(getLoggedMessage(0).context).toEqual({ error: { - origin: 'logger', kind: 'SyntaxError', message: 'My Error', stack: jasmine.stringMatching(/^SyntaxError: My Error/), @@ -69,7 +68,6 @@ describe('Logger', () => { message: 'message', context: { error: { - origin: 'logger', kind: undefined, message: 'Provided "My Error"', stack: NO_ERROR_STACK_PRESENT_MESSAGE, @@ -79,17 +77,13 @@ describe('Logger', () => { }) }) - it("'logger.error' should populate an error context with origin even if no Error object is provided", () => { + it("'logger.error' should have an empty context if no Error object is provided", () => { logger.error('message') expect(getLoggedMessage(0)).toEqual({ message: 'message', - context: { - error: { - origin: 'logger', - }, - }, status: 'error', + context: undefined, }) }) }) diff --git a/packages/logs/src/domain/logger.ts b/packages/logs/src/domain/logger.ts index 0ddc32d393..2629ae8f38 100644 --- a/packages/logs/src/domain/logger.ts +++ b/packages/logs/src/domain/logger.ts @@ -59,11 +59,6 @@ export class Logger { log(message: string, messageContext?: object, status: StatusType = StatusType.info, error?: Error) { let errorContext: LogsEvent['error'] - if (status === StatusType.error) { - // Always add origin if status is error (backward compatibility - Remove in next major) - errorContext = { origin: ErrorSource.LOGGER } - } - if (error !== undefined && error !== null) { const stackTrace = error instanceof Error ? computeStackTrace(error) : undefined const rawError = computeRawError({ @@ -76,7 +71,6 @@ export class Logger { }) errorContext = { - origin: ErrorSource.LOGGER, // Remove in next major stack: rawError.stack, kind: rawError.type, message: rawError.message, diff --git a/packages/logs/src/domain/logsCollection/console/consoleCollection.spec.ts b/packages/logs/src/domain/logsCollection/console/consoleCollection.spec.ts index 38ea5f667f..7cc72636cb 100644 --- a/packages/logs/src/domain/logsCollection/console/consoleCollection.spec.ts +++ b/packages/logs/src/domain/logsCollection/console/consoleCollection.spec.ts @@ -1,14 +1,13 @@ -import { ErrorSource, noop } from '@datadog/browser-core' +import { ErrorSource, noop, objectEntries } from '@datadog/browser-core' import type { RawConsoleLogsEvent } from '../../../rawLogsEvent.types' import { validateAndBuildLogsConfiguration } from '../../configuration' import type { RawLogsEventCollectedData } from '../../lifeCycle' import { LifeCycle, LifeCycleEventType } from '../../lifeCycle' -import { StatusType } from '../../logger' -import { startConsoleCollection } from './consoleCollection' +import { startConsoleCollection, LogStatusForApi } from './consoleCollection' describe('console collection', () => { const initConfiguration = { clientToken: 'xxx', service: 'service' } - let consoleLogSpy: jasmine.Spy + let consoleSpies: { [key: string]: jasmine.Spy } let stopConsoleCollection: () => void let lifeCycle: LifeCycle let rawLogsEvents: Array> @@ -20,32 +19,39 @@ describe('console collection', () => { rawLogsEvents.push(rawLogsEvent as RawLogsEventCollectedData) ) stopConsoleCollection = noop - consoleLogSpy = spyOn(console, 'log').and.callFake(() => true) - spyOn(console, 'error').and.callFake(() => true) + consoleSpies = { + log: spyOn(console, 'log').and.callFake(() => true), + debug: spyOn(console, 'debug').and.callFake(() => true), + info: spyOn(console, 'info').and.callFake(() => true), + warn: spyOn(console, 'warn').and.callFake(() => true), + error: spyOn(console, 'error').and.callFake(() => true), + } }) afterEach(() => { stopConsoleCollection() }) - it('should send console logs', () => { - ;({ stop: stopConsoleCollection } = startConsoleCollection( - validateAndBuildLogsConfiguration({ ...initConfiguration, forwardConsoleLogs: ['log'] })!, - lifeCycle - )) + objectEntries(LogStatusForApi).forEach(([api, status]) => { + it(`should collect ${status} logs from console.${api}`, () => { + ;({ stop: stopConsoleCollection } = startConsoleCollection( + validateAndBuildLogsConfiguration({ ...initConfiguration, forwardConsoleLogs: 'all' })!, + lifeCycle + )) - /* eslint-disable-next-line no-console */ - console.log('foo', 'bar') + /* eslint-disable-next-line no-console */ + console[api as keyof typeof LogStatusForApi]('foo', 'bar') - expect(rawLogsEvents[0].rawLogsEvent).toEqual({ - date: jasmine.any(Number), - message: 'foo bar', - status: StatusType.info, - origin: ErrorSource.CONSOLE, - error: undefined, - }) + expect(rawLogsEvents[0].rawLogsEvent).toEqual({ + date: jasmine.any(Number), + message: 'foo bar', + status, + origin: ErrorSource.CONSOLE, + error: whatever(), + }) - expect(consoleLogSpy).toHaveBeenCalled() + expect(consoleSpies[api]).toHaveBeenCalled() + }) }) it('console error should have an error object defined', () => { @@ -58,7 +64,6 @@ describe('console collection', () => { console.error('foo', 'bar') expect(rawLogsEvents[0].rawLogsEvent.error).toEqual({ - origin: ErrorSource.CONSOLE, stack: undefined, fingerprint: undefined, }) @@ -79,9 +84,15 @@ describe('console collection', () => { console.error(error) expect(rawLogsEvents[0].rawLogsEvent.error).toEqual({ - origin: ErrorSource.CONSOLE, stack: jasmine.any(String), fingerprint: 'my-fingerprint', }) }) }) + +function whatever() { + return { + asymmetricMatch: () => true, + jasmineToString: () => '', + } +} diff --git a/packages/logs/src/domain/logsCollection/console/consoleCollection.ts b/packages/logs/src/domain/logsCollection/console/consoleCollection.ts index 8df499b3a9..b83fb5fc7c 100644 --- a/packages/logs/src/domain/logsCollection/console/consoleCollection.ts +++ b/packages/logs/src/domain/logsCollection/console/consoleCollection.ts @@ -12,7 +12,7 @@ export interface ProvidedError { handlingStack: string } -const LogStatusForApi = { +export const LogStatusForApi = { [ConsoleApiName.log]: StatusType.info, [ConsoleApiName.debug]: StatusType.debug, [ConsoleApiName.info]: StatusType.info, @@ -29,7 +29,6 @@ export function startConsoleCollection(configuration: LogsConfiguration, lifeCyc error: log.api === ConsoleApiName.error ? { - origin: ErrorSource.CONSOLE, // Todo: Remove in the next major release stack: log.stack, fingerprint: log.fingerprint, } diff --git a/packages/logs/src/domain/logsCollection/networkError/networkErrorCollection.spec.ts b/packages/logs/src/domain/logsCollection/networkError/networkErrorCollection.spec.ts index 44cfa550d5..1183068415 100644 --- a/packages/logs/src/domain/logsCollection/networkError/networkErrorCollection.spec.ts +++ b/packages/logs/src/domain/logsCollection/networkError/networkErrorCollection.spec.ts @@ -72,7 +72,6 @@ describe('network error collection', () => { status: StatusType.error, origin: ErrorSource.NETWORK, error: { - origin: ErrorSource.NETWORK, stack: 'Server error', }, http: { diff --git a/packages/logs/src/domain/logsCollection/networkError/networkErrorCollection.ts b/packages/logs/src/domain/logsCollection/networkError/networkErrorCollection.ts index c99cb56a2d..06789fc60e 100644 --- a/packages/logs/src/domain/logsCollection/networkError/networkErrorCollection.ts +++ b/packages/logs/src/domain/logsCollection/networkError/networkErrorCollection.ts @@ -50,7 +50,6 @@ export function startNetworkErrorCollection(configuration: LogsConfiguration, li message: `${format(type)} error ${request.method} ${request.url}`, date: request.startClocks.timeStamp, error: { - origin: ErrorSource.NETWORK, // Todo: Remove in the next major release stack: (responseData as string) || 'Failed to load', }, http: { diff --git a/packages/logs/src/domain/logsCollection/report/reportCollection.spec.ts b/packages/logs/src/domain/logsCollection/report/reportCollection.spec.ts index 5b5a4e0610..47f617be20 100644 --- a/packages/logs/src/domain/logsCollection/report/reportCollection.spec.ts +++ b/packages/logs/src/domain/logsCollection/report/reportCollection.spec.ts @@ -39,7 +39,6 @@ describe('reports', () => { expect(rawLogsEvents[0].rawLogsEvent).toEqual({ error: { kind: 'NavigatorVibrate', - origin: ErrorSource.REPORT, stack: jasmine.any(String), }, date: jasmine.any(Number), diff --git a/packages/logs/src/domain/logsCollection/report/reportCollection.ts b/packages/logs/src/domain/logsCollection/report/reportCollection.ts index b48fb9e7f1..937869a2ce 100644 --- a/packages/logs/src/domain/logsCollection/report/reportCollection.ts +++ b/packages/logs/src/domain/logsCollection/report/reportCollection.ts @@ -32,7 +32,6 @@ export function startReportCollection(configuration: LogsConfiguration, lifeCycl if (status === StatusType.error) { error = { kind: report.subtype, - origin: ErrorSource.REPORT, // Todo: Remove in the next major release stack: report.stack, } } else if (report.stack) { diff --git a/packages/logs/src/domain/logsCollection/runtimeError/runtimeErrorCollection.spec.ts b/packages/logs/src/domain/logsCollection/runtimeError/runtimeErrorCollection.spec.ts index f2c5ce4190..90b0034db5 100644 --- a/packages/logs/src/domain/logsCollection/runtimeError/runtimeErrorCollection.spec.ts +++ b/packages/logs/src/domain/logsCollection/runtimeError/runtimeErrorCollection.spec.ts @@ -39,7 +39,7 @@ describe('runtime error collection', () => { setTimeout(() => { expect(rawLogsEvents[0].rawLogsEvent).toEqual({ date: jasmine.any(Number), - error: { origin: ErrorSource.SOURCE, kind: 'Error', stack: jasmine.any(String) }, + error: { kind: 'Error', stack: jasmine.any(String) }, message: 'error!', status: StatusType.error, origin: ErrorSource.SOURCE, diff --git a/packages/logs/src/domain/logsCollection/runtimeError/runtimeErrorCollection.ts b/packages/logs/src/domain/logsCollection/runtimeError/runtimeErrorCollection.ts index 9d5fbac792..8b1bb4ef03 100644 --- a/packages/logs/src/domain/logsCollection/runtimeError/runtimeErrorCollection.ts +++ b/packages/logs/src/domain/logsCollection/runtimeError/runtimeErrorCollection.ts @@ -28,7 +28,6 @@ export function startRuntimeErrorCollection(configuration: LogsConfiguration, li date: rawError.startClocks.timeStamp, error: { kind: rawError.type, - origin: ErrorSource.SOURCE, // Todo: Remove in the next major release stack: rawError.stack, }, origin: ErrorSource.SOURCE, diff --git a/packages/logs/src/logsEvent.types.ts b/packages/logs/src/logsEvent.types.ts index e90a110475..d1286f6dda 100644 --- a/packages/logs/src/logsEvent.types.ts +++ b/packages/logs/src/logsEvent.types.ts @@ -14,7 +14,7 @@ export interface LogsEvent { /** * Origin of the log */ - origin?: 'network' | 'source' | 'console' | 'logger' | 'agent' | 'report' | 'custom' + origin: 'network' | 'source' | 'console' | 'logger' | 'agent' | 'report' /** * UUID of the application */ @@ -65,10 +65,6 @@ export interface LogsEvent { * Kind of the error */ kind?: string - /** - * Origin of the error - */ - origin: 'network' | 'source' | 'console' | 'logger' | 'agent' | 'report' | 'custom' /** * Stacktrace of the error */ diff --git a/packages/logs/src/rawLogsEvent.types.ts b/packages/logs/src/rawLogsEvent.types.ts index 276078c5ae..0d0ee3eaf9 100644 --- a/packages/logs/src/rawLogsEvent.types.ts +++ b/packages/logs/src/rawLogsEvent.types.ts @@ -11,7 +11,6 @@ export type RawLogsEvent = type Error = { kind?: string - origin: ErrorSource // Todo: Remove in the next major release stack?: string fingerprint?: string [k: string]: unknown @@ -22,6 +21,7 @@ interface CommonRawLogsEvent { message: string status: StatusType error?: Error + origin: 'network' | 'source' | 'console' | 'logger' | 'agent' | 'report' } export interface RawConsoleLogsEvent extends CommonRawLogsEvent { @@ -57,7 +57,6 @@ export interface RawRuntimeLogsEvent extends CommonRawLogsEvent { export interface RawAgentLogsEvent extends CommonRawLogsEvent { origin: typeof ErrorSource.AGENT status: typeof StatusType.error - error: Error } export interface CommonContext { diff --git a/test/e2e/scenario/logs.scenario.ts b/test/e2e/scenario/logs.scenario.ts index c672213290..0f247954d2 100644 --- a/test/e2e/scenario/logs.scenario.ts +++ b/test/e2e/scenario/logs.scenario.ts @@ -42,7 +42,7 @@ describe('logs', () => { await flushEvents() expect(serverEvents.logs.length).toBe(1) expect(serverEvents.logs[0].message).toBe(`XHR error GET ${UNREACHABLE_URL}`) - expect(serverEvents.logs[0].error?.origin).toBe('network') + expect(serverEvents.logs[0].origin).toBe('network') await withBrowserLogs((browserLogs) => { // Some browser report two errors: @@ -64,7 +64,7 @@ describe('logs', () => { await flushEvents() expect(serverEvents.logs.length).toBe(1) expect(serverEvents.logs[0].message).toBe(`Fetch error GET ${UNREACHABLE_URL}`) - expect(serverEvents.logs[0].error?.origin).toBe('network') + expect(serverEvents.logs[0].origin).toBe('network') await withBrowserLogs((browserLogs) => { // Some browser report two errors: @@ -84,7 +84,7 @@ describe('logs', () => { await flushEvents() expect(serverEvents.logs.length).toBe(1) expect(serverEvents.logs[0].message).toBe(`Fetch error GET ${baseUrl}/throw-large-response`) - expect(serverEvents.logs[0].error?.origin).toBe('network') + expect(serverEvents.logs[0].origin).toBe('network') const ellipsisSize = 3 expect(serverEvents.logs[0].error?.stack?.length).toBe(DEFAULT_REQUEST_ERROR_RESPONSE_LENGTH_LIMIT + ellipsisSize) From 02f6a03c3d11da5dab503dece702777740af50d9 Mon Sep 17 00:00:00 2001 From: Bastien Caudan <1331991+bcaudan@users.noreply.github.com> Date: Fri, 23 Jun 2023 14:56:25 +0200 Subject: [PATCH 15/27] =?UTF-8?q?=F0=9F=92=A5[RUMF-1230]=20Only=20apply=20?= =?UTF-8?q?main=20logger=20configuration=20to=20its=20own=20logs=20(#2298)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * πŸ’₯ add main logger context only to main logger logs * πŸ’₯ apply main logger level only to main logger logs * ♻️ merge logger context with message context in logger collection --- packages/logs/src/boot/logsPublicApi.ts | 3 +- packages/logs/src/boot/startLogs.spec.ts | 20 +++-- packages/logs/src/boot/startLogs.ts | 6 +- packages/logs/src/domain/assembly.spec.ts | 38 ++-------- packages/logs/src/domain/assembly.ts | 10 +-- packages/logs/src/domain/lifeCycle.ts | 2 - .../logger/loggerCollection.spec.ts | 75 +++++++++++++------ .../logsCollection/logger/loggerCollection.ts | 27 +++---- 8 files changed, 89 insertions(+), 92 deletions(-) diff --git a/packages/logs/src/boot/logsPublicApi.ts b/packages/logs/src/boot/logsPublicApi.ts index a49b1a8988..533377e473 100644 --- a/packages/logs/src/boot/logsPublicApi.ts +++ b/packages/logs/src/boot/logsPublicApi.ts @@ -90,8 +90,7 @@ export function makeLogsPublicApi(startLogsImpl: StartLogs) { ;({ handleLog: handleLogStrategy, getInternalContext: getInternalContextStrategy } = startLogsImpl( initConfiguration, configuration, - buildCommonContext, - mainLogger + buildCommonContext )) beforeInitLoggerLog.drain() diff --git a/packages/logs/src/boot/startLogs.spec.ts b/packages/logs/src/boot/startLogs.spec.ts index cca957bc5c..5cc2eafaf7 100644 --- a/packages/logs/src/boot/startLogs.spec.ts +++ b/packages/logs/src/boot/startLogs.spec.ts @@ -69,7 +69,7 @@ describe('logs', () => { describe('request', () => { it('should send the needed data', () => { - ;({ handleLog: handleLog } = startLogs(initConfiguration, baseConfiguration, () => COMMON_CONTEXT, logger)) + ;({ handleLog: handleLog } = startLogs(initConfiguration, baseConfiguration, () => COMMON_CONTEXT)) handleLog({ message: 'message', status: StatusType.warn, context: { foo: 'bar' } }, logger, COMMON_CONTEXT) @@ -94,8 +94,7 @@ describe('logs', () => { ;({ handleLog } = startLogs( initConfiguration, { ...baseConfiguration, batchMessagesLimit: 3 }, - () => COMMON_CONTEXT, - logger + () => COMMON_CONTEXT )) handleLog(DEFAULT_MESSAGE, logger) @@ -107,7 +106,7 @@ describe('logs', () => { it('should send bridge event when bridge is present', () => { const sendSpy = spyOn(initEventBridgeStub(), 'send') - ;({ handleLog: handleLog } = startLogs(initConfiguration, baseConfiguration, () => COMMON_CONTEXT, logger)) + ;({ handleLog: handleLog } = startLogs(initConfiguration, baseConfiguration, () => COMMON_CONTEXT)) handleLog(DEFAULT_MESSAGE, logger) @@ -126,13 +125,13 @@ describe('logs', () => { const sendSpy = spyOn(initEventBridgeStub(), 'send') let configuration = { ...baseConfiguration, sessionSampleRate: 0 } - ;({ handleLog } = startLogs(initConfiguration, configuration, () => COMMON_CONTEXT, logger)) + ;({ handleLog } = startLogs(initConfiguration, configuration, () => COMMON_CONTEXT)) handleLog(DEFAULT_MESSAGE, logger) expect(sendSpy).not.toHaveBeenCalled() configuration = { ...baseConfiguration, sessionSampleRate: 100 } - ;({ handleLog } = startLogs(initConfiguration, configuration, () => COMMON_CONTEXT, logger)) + ;({ handleLog } = startLogs(initConfiguration, configuration, () => COMMON_CONTEXT)) handleLog(DEFAULT_MESSAGE, logger) expect(sendSpy).toHaveBeenCalled() @@ -144,8 +143,7 @@ describe('logs', () => { ;({ handleLog } = startLogs( initConfiguration, { ...baseConfiguration, forwardConsoleLogs: ['log'] }, - () => COMMON_CONTEXT, - logger + () => COMMON_CONTEXT )) /* eslint-disable-next-line no-console */ @@ -161,21 +159,21 @@ describe('logs', () => { }) it('creates a session on normal conditions', () => { - ;({ handleLog } = startLogs(initConfiguration, baseConfiguration, () => COMMON_CONTEXT, logger)) + ;({ handleLog } = startLogs(initConfiguration, baseConfiguration, () => COMMON_CONTEXT)) expect(getCookie(SESSION_STORE_KEY)).not.toBeUndefined() }) it('does not create a session if event bridge is present', () => { initEventBridgeStub() - ;({ handleLog } = startLogs(initConfiguration, baseConfiguration, () => COMMON_CONTEXT, logger)) + ;({ handleLog } = startLogs(initConfiguration, baseConfiguration, () => COMMON_CONTEXT)) expect(getCookie(SESSION_STORE_KEY)).toBeUndefined() }) it('does not create a session if synthetics worker will inject RUM', () => { mockSyntheticsWorkerValues({ injectsRum: true }) - ;({ handleLog } = startLogs(initConfiguration, baseConfiguration, () => COMMON_CONTEXT, logger)) + ;({ handleLog } = startLogs(initConfiguration, baseConfiguration, () => COMMON_CONTEXT)) expect(getCookie(SESSION_STORE_KEY)).toBeUndefined() }) diff --git a/packages/logs/src/boot/startLogs.ts b/packages/logs/src/boot/startLogs.ts index 34711af773..d542ea7363 100644 --- a/packages/logs/src/boot/startLogs.ts +++ b/packages/logs/src/boot/startLogs.ts @@ -25,15 +25,13 @@ import { startLoggerCollection } from '../domain/logsCollection/logger/loggerCol import type { CommonContext } from '../rawLogsEvent.types' import { startLogsBatch } from '../transport/startLogsBatch' import { startLogsBridge } from '../transport/startLogsBridge' -import type { Logger } from '../domain/logger' import { StatusType } from '../domain/logger' import { startInternalContext } from '../domain/internalContext' export function startLogs( initConfiguration: LogsInitConfiguration, configuration: LogsConfiguration, - buildCommonContext: () => CommonContext, - mainLogger: Logger + buildCommonContext: () => CommonContext ) { const lifeCycle = new LifeCycle() @@ -77,7 +75,7 @@ export function startLogs( startReportCollection(configuration, lifeCycle) const { handleLog } = startLoggerCollection(lifeCycle) - startLogsAssembly(session, configuration, lifeCycle, buildCommonContext, mainLogger, reportError) + startLogsAssembly(session, configuration, lifeCycle, buildCommonContext, reportError) if (!canUseEventBridge()) { startLogsBatch(configuration, lifeCycle, reportError, pageExitObservable, session.expireObservable) diff --git a/packages/logs/src/domain/assembly.spec.ts b/packages/logs/src/domain/assembly.spec.ts index 01dce3524c..5b233d2ad2 100644 --- a/packages/logs/src/domain/assembly.spec.ts +++ b/packages/logs/src/domain/assembly.spec.ts @@ -62,7 +62,7 @@ describe('startLogsAssembly', () => { } beforeSend = noop mainLogger = new Logger(() => noop) - startLogsAssembly(sessionManager, configuration, lifeCycle, () => COMMON_CONTEXT, mainLogger, noop) + startLogsAssembly(sessionManager, configuration, lifeCycle, () => COMMON_CONTEXT, noop) window.DD_RUM = { getInternalContext: noop, } @@ -153,22 +153,11 @@ describe('startLogsAssembly', () => { expect(serverLogs[0].common_context_key).toBeUndefined() }) - it('should include main logger context', () => { + it('should not include main logger context', () => { mainLogger.setContext({ foo: 'from-main-logger' }) lifeCycle.notify(LifeCycleEventType.RAW_LOG_COLLECTED, { rawLogsEvent: DEFAULT_MESSAGE }) - expect(serverLogs[0].foo).toEqual('from-main-logger') - }) - - it('should include logger context instead of main logger context when present', () => { - const logger = new Logger(() => noop) - mainLogger.setContext({ foo: 'from-main-logger', bar: 'from-main-logger' }) - logger.setContext({ foo: 'from-logger' }) - - lifeCycle.notify(LifeCycleEventType.RAW_LOG_COLLECTED, { rawLogsEvent: DEFAULT_MESSAGE, logger }) - - expect(serverLogs[0].foo).toEqual('from-logger') - expect(serverLogs[0].bar).toBeUndefined() + expect(serverLogs[0].foo).toBeUndefined() }) it('should include rum internal context related to the error time', () => { @@ -238,15 +227,7 @@ describe('startLogsAssembly', () => { expect(serverLogs[0].message).toEqual('message') }) - it('logger context should take precedence over raw log', () => { - mainLogger.setContext({ message: 'from-main-logger' }) - - lifeCycle.notify(LifeCycleEventType.RAW_LOG_COLLECTED, { rawLogsEvent: DEFAULT_MESSAGE }) - - expect(serverLogs[0].message).toEqual('from-main-logger') - }) - - it('message context should take precedence over logger context', () => { + it('message context should take precedence over raw log', () => { lifeCycle.notify(LifeCycleEventType.RAW_LOG_COLLECTED, { rawLogsEvent: DEFAULT_MESSAGE, messageContext: { message: 'from-message-context' }, @@ -296,7 +277,6 @@ describe('user management', () => { let serverLogs: Array = [] const beforeSend: (event: LogsEvent) => void | boolean = noop - const mainLogger = new Logger(() => noop) const configuration = { ...validateAndBuildLogsConfiguration(initConfiguration)!, beforeSend: (x: LogsEvent) => beforeSend(x), @@ -314,14 +294,14 @@ describe('user management', () => { }) it('should not output usr key if user is not set', () => { - startLogsAssembly(sessionManager, configuration, lifeCycle, () => COMMON_CONTEXT, mainLogger, noop) + startLogsAssembly(sessionManager, configuration, lifeCycle, () => COMMON_CONTEXT, noop) lifeCycle.notify(LifeCycleEventType.RAW_LOG_COLLECTED, { rawLogsEvent: DEFAULT_MESSAGE }) expect(serverLogs[0].usr).toBeUndefined() }) it('should include user data when user has been set', () => { - startLogsAssembly(sessionManager, configuration, lifeCycle, () => COMMON_CONTEXT_WITH_USER, mainLogger, noop) + startLogsAssembly(sessionManager, configuration, lifeCycle, () => COMMON_CONTEXT_WITH_USER, noop) lifeCycle.notify(LifeCycleEventType.RAW_LOG_COLLECTED, { rawLogsEvent: DEFAULT_MESSAGE }) expect(serverLogs[0].usr).toEqual({ @@ -342,7 +322,7 @@ describe('user management', () => { }, }, } - startLogsAssembly(sessionManager, configuration, lifeCycle, () => globalContextWithUser, mainLogger, noop) + startLogsAssembly(sessionManager, configuration, lifeCycle, () => globalContextWithUser, noop) lifeCycle.notify(LifeCycleEventType.RAW_LOG_COLLECTED, { rawLogsEvent: DEFAULT_MESSAGE }) expect(serverLogs[0].usr).toEqual({ @@ -363,7 +343,6 @@ describe('logs limitation', () => { let beforeSend: (event: LogsEvent) => void | boolean let lifeCycle: LifeCycle let serverLogs: Array = [] - let mainLogger: Logger let reportErrorSpy: jasmine.Spy beforeEach(() => { @@ -376,9 +355,8 @@ describe('logs limitation', () => { beforeSend: (x: LogsEvent) => beforeSend(x), } beforeSend = noop - mainLogger = new Logger(() => noop) reportErrorSpy = jasmine.createSpy('reportError') - startLogsAssembly(sessionManager, configuration, lifeCycle, () => COMMON_CONTEXT, mainLogger, reportErrorSpy) + startLogsAssembly(sessionManager, configuration, lifeCycle, () => COMMON_CONTEXT, reportErrorSpy) clock = mockClock() }) diff --git a/packages/logs/src/domain/assembly.ts b/packages/logs/src/domain/assembly.ts index 720feac396..6e6143fe6d 100644 --- a/packages/logs/src/domain/assembly.ts +++ b/packages/logs/src/domain/assembly.ts @@ -14,9 +14,7 @@ import type { CommonContext } from '../rawLogsEvent.types' import type { LogsConfiguration } from './configuration' import type { LifeCycle } from './lifeCycle' import { LifeCycleEventType } from './lifeCycle' -import type { Logger } from './logger' -import { STATUSES, HandlerType } from './logger' -import { isAuthorized } from './logsCollection/logger/loggerCollection' +import { STATUSES } from './logger' import type { LogsSessionManager } from './logsSessionManager' export function startLogsAssembly( @@ -24,7 +22,6 @@ export function startLogsAssembly( configuration: LogsConfiguration, lifeCycle: LifeCycle, buildCommonContext: () => CommonContext, - mainLogger: Logger, // Todo: [RUMF-1230] Remove this parameter in the next major release reportError: (error: RawError) => void ) { const statusWithCustom = (STATUSES as string[]).concat(['custom']) @@ -35,7 +32,7 @@ export function startLogsAssembly( lifeCycle.subscribe( LifeCycleEventType.RAW_LOG_COLLECTED, - ({ rawLogsEvent, messageContext = undefined, savedCommonContext = undefined, logger = mainLogger }) => { + ({ rawLogsEvent, messageContext = undefined, savedCommonContext = undefined }) => { const startTime = getRelativeTime(rawLogsEvent.date) const session = sessionManager.findTrackedSession(startTime) @@ -55,13 +52,10 @@ export function startLogsAssembly( commonContext.context, getRUMInternalContext(startTime), rawLogsEvent, - logger.getContext(), messageContext ) if ( - // Todo: [RUMF-1230] Move this check to the logger collection in the next major release - !isAuthorized(rawLogsEvent.status, HandlerType.http, logger) || configuration.beforeSend?.(log) === false || (log.origin !== ErrorSource.AGENT && (logRateLimiters[log.status] ?? logRateLimiters['custom']).isLimitReached()) diff --git a/packages/logs/src/domain/lifeCycle.ts b/packages/logs/src/domain/lifeCycle.ts index 312056cb32..116d89d796 100644 --- a/packages/logs/src/domain/lifeCycle.ts +++ b/packages/logs/src/domain/lifeCycle.ts @@ -2,7 +2,6 @@ import { AbstractLifeCycle } from '@datadog/browser-core' import type { Context } from '@datadog/browser-core' import type { LogsEvent } from '../logsEvent.types' import type { CommonContext, RawLogsEvent } from '../rawLogsEvent.types' -import type { Logger } from './logger' export const enum LifeCycleEventType { RAW_LOG_COLLECTED, @@ -21,5 +20,4 @@ export interface RawLogsEventCollectedData { clock.cleanup() }) - it('should send logger logs', () => { - handleLog({ message: 'message', status: StatusType.error }, logger, COMMON_CONTEXT) - - expect(rawLogsEvents[0].rawLogsEvent).toEqual({ - date: timeStampNow(), - origin: ErrorSource.LOGGER, - message: 'message', - status: StatusType.error, - }) - }) - - it('should send the saved date when present', () => { - handleLog({ message: 'message', status: StatusType.error }, logger, COMMON_CONTEXT, FAKE_DATE) - - expect(rawLogsEvents[0].rawLogsEvent.date).toEqual(FAKE_DATE) - }) - describe('when handle type is set to "console"', () => { beforeEach(() => { logger.setHandler(HandlerType.console) @@ -62,19 +45,18 @@ describe('logger collection', () => { }) it('should print the log message and context to the console', () => { - logger.setContext({ 'logger-context': 'foo' }) + logger.setContext({ foo: 'from-logger', bar: 'from-logger' }) handleLog( - { message: 'message', status: StatusType.error, context: { 'log-context': 'bar' } }, + { message: 'message', status: StatusType.error, context: { bar: 'from-message' } }, logger, COMMON_CONTEXT ) expect(display.error).toHaveBeenCalledOnceWith('message', { - 'logger-context': 'foo', - 'log-context': 'bar', + foo: 'from-logger', + bar: 'from-message', }) - expect(rawLogsEvents.length).toEqual(1) }) for (const { status, api } of [ @@ -107,4 +89,53 @@ describe('logger collection', () => { expect(display.debug).not.toHaveBeenCalled() }) }) + + describe('when handle type is set to "http"', () => { + beforeEach(() => { + logger.setHandler(HandlerType.http) + }) + + it('should send the log message and context', () => { + logger.setContext({ foo: 'from-logger', bar: 'from-logger' }) + + handleLog( + { message: 'message', status: StatusType.error, context: { bar: 'from-message' } }, + logger, + COMMON_CONTEXT + ) + + expect(rawLogsEvents[0]).toEqual({ + rawLogsEvent: { + date: timeStampNow(), + origin: ErrorSource.LOGGER, + message: 'message', + status: StatusType.error, + }, + messageContext: { + foo: 'from-logger', + bar: 'from-message', + }, + savedCommonContext: COMMON_CONTEXT, + }) + }) + + it('should send the saved date when present', () => { + handleLog({ message: 'message', status: StatusType.error }, logger, COMMON_CONTEXT, FAKE_DATE) + + expect(rawLogsEvents[0].rawLogsEvent.date).toEqual(FAKE_DATE) + }) + + it('does not send the log if its status is below the logger level', () => { + logger.setLevel(StatusType.warn) + handleLog({ message: 'message', status: StatusType.info }, logger, COMMON_CONTEXT) + + expect(rawLogsEvents.length).toBe(0) + }) + + it('does not send the log and does not crash if its status is unknown', () => { + handleLog({ message: 'message', status: 'unknown' as StatusType }, logger, COMMON_CONTEXT) + + expect(rawLogsEvents.length).toBe(0) + }) + }) }) diff --git a/packages/logs/src/domain/logsCollection/logger/loggerCollection.ts b/packages/logs/src/domain/logsCollection/logger/loggerCollection.ts index 798faeeaf6..09748eb542 100644 --- a/packages/logs/src/domain/logsCollection/logger/loggerCollection.ts +++ b/packages/logs/src/domain/logsCollection/logger/loggerCollection.ts @@ -20,23 +20,24 @@ export function startLoggerCollection(lifeCycle: LifeCycle) { savedCommonContext?: CommonContext, savedDate?: TimeStamp ) { - const messageContext = logsMessage.context + const messageContext = combine(logger.getContext(), logsMessage.context) if (isAuthorized(logsMessage.status, HandlerType.console, logger)) { - display(logsMessage.status, logsMessage.message, combine(logger.getContext(), messageContext)) + display(logsMessage.status, logsMessage.message, messageContext) } - lifeCycle.notify(LifeCycleEventType.RAW_LOG_COLLECTED, { - rawLogsEvent: { - date: savedDate || timeStampNow(), - message: logsMessage.message, - status: logsMessage.status, - origin: ErrorSource.LOGGER, - }, - messageContext, - savedCommonContext, - logger, - }) + if (isAuthorized(logsMessage.status, HandlerType.http, logger)) { + lifeCycle.notify(LifeCycleEventType.RAW_LOG_COLLECTED, { + rawLogsEvent: { + date: savedDate || timeStampNow(), + message: logsMessage.message, + status: logsMessage.status, + origin: ErrorSource.LOGGER, + }, + messageContext, + savedCommonContext, + }) + } } return { From fe1d663ed856626ceec4d14a2c5afbb4451ad351 Mon Sep 17 00:00:00 2001 From: Bastien Caudan <1331991+bcaudan@users.noreply.github.com> Date: Mon, 26 Jun 2023 11:10:20 +0200 Subject: [PATCH 16/27] =?UTF-8?q?=F0=9F=92=A5=20Typings:=20consistent=20be?= =?UTF-8?q?foreSend=20return=20type=20(#2303)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/logs/src/domain/assembly.spec.ts | 16 ++++++++++ packages/logs/src/domain/configuration.ts | 2 +- packages/rum-core/src/domain/assembly.spec.ts | 32 +++++++++++++++++++ packages/rum-core/src/domain/configuration.ts | 2 +- test/e2e/scenario/logs.scenario.ts | 1 + test/e2e/scenario/rum/init.scenario.ts | 2 ++ 6 files changed, 53 insertions(+), 2 deletions(-) diff --git a/packages/logs/src/domain/assembly.spec.ts b/packages/logs/src/domain/assembly.spec.ts index 5b233d2ad2..d0c2108f91 100644 --- a/packages/logs/src/domain/assembly.spec.ts +++ b/packages/logs/src/domain/assembly.spec.ts @@ -73,6 +73,22 @@ describe('startLogsAssembly', () => { serverLogs = [] }) + it('should send if beforeSend returned true', () => { + beforeSend = () => true + lifeCycle.notify(LifeCycleEventType.RAW_LOG_COLLECTED, { + rawLogsEvent: DEFAULT_MESSAGE, + }) + expect(serverLogs.length).toEqual(1) + }) + + it('should send if beforeSend returned undefined', () => { + beforeSend = () => undefined + lifeCycle.notify(LifeCycleEventType.RAW_LOG_COLLECTED, { + rawLogsEvent: DEFAULT_MESSAGE, + }) + expect(serverLogs.length).toEqual(1) + }) + it('should not send if beforeSend returned false', () => { beforeSend = () => false lifeCycle.notify(LifeCycleEventType.RAW_LOG_COLLECTED, { diff --git a/packages/logs/src/domain/configuration.ts b/packages/logs/src/domain/configuration.ts index f554913101..f2c8959f71 100644 --- a/packages/logs/src/domain/configuration.ts +++ b/packages/logs/src/domain/configuration.ts @@ -14,7 +14,7 @@ import { import type { LogsEvent } from '../logsEvent.types' export interface LogsInitConfiguration extends InitConfiguration { - beforeSend?: ((event: LogsEvent) => void | boolean) | undefined + beforeSend?: ((event: LogsEvent) => boolean) | undefined forwardErrorsToLogs?: boolean | undefined forwardConsoleLogs?: ConsoleApiName[] | 'all' | undefined forwardReports?: RawReportType[] | 'all' | undefined diff --git a/packages/rum-core/src/domain/assembly.spec.ts b/packages/rum-core/src/domain/assembly.spec.ts index fcfcc287cc..95272060b5 100644 --- a/packages/rum-core/src/domain/assembly.spec.ts +++ b/packages/rum-core/src/domain/assembly.spec.ts @@ -329,6 +329,38 @@ describe('rum assembly', () => { expect(displaySpy).toHaveBeenCalledWith("Can't dismiss view events using beforeSend!") }) }) + + it('should not dismiss when true is returned', () => { + const { lifeCycle } = setupBuilder + .withConfiguration({ + beforeSend: () => true, + }) + .build() + + notifyRawRumEvent(lifeCycle, { + rawRumEvent: createRawRumEvent(RumEventType.ACTION, { + view: { id: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' }, + }), + }) + + expect(serverRumEvents.length).toBe(1) + }) + + it('should not dismiss when undefined is returned', () => { + const { lifeCycle } = setupBuilder + .withConfiguration({ + beforeSend: () => undefined, + }) + .build() + + notifyRawRumEvent(lifeCycle, { + rawRumEvent: createRawRumEvent(RumEventType.ACTION, { + view: { id: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' }, + }), + }) + + expect(serverRumEvents.length).toBe(1) + }) }) describe('rum context', () => { diff --git a/packages/rum-core/src/domain/configuration.ts b/packages/rum-core/src/domain/configuration.ts index eba13ac2c4..413e1b049c 100644 --- a/packages/rum-core/src/domain/configuration.ts +++ b/packages/rum-core/src/domain/configuration.ts @@ -19,7 +19,7 @@ import type { PropagatorType, TracingOption } from './tracing/tracer.types' export interface RumInitConfiguration extends InitConfiguration { // global options applicationId: string - beforeSend?: ((event: RumEvent, context: RumEventDomainContext) => void | boolean) | undefined + beforeSend?: ((event: RumEvent, context: RumEventDomainContext) => boolean) | undefined excludedActivityUrls?: MatchOption[] | undefined // tracing options diff --git a/test/e2e/scenario/logs.scenario.ts b/test/e2e/scenario/logs.scenario.ts index 0f247954d2..fdf9e7716a 100644 --- a/test/e2e/scenario/logs.scenario.ts +++ b/test/e2e/scenario/logs.scenario.ts @@ -159,6 +159,7 @@ describe('logs', () => { .withLogs({ beforeSend: (event) => { event.foo = 'bar' + return true }, }) .run(async ({ serverEvents }) => { diff --git a/test/e2e/scenario/rum/init.scenario.ts b/test/e2e/scenario/rum/init.scenario.ts index 7a44e334b8..846a34a630 100644 --- a/test/e2e/scenario/rum/init.scenario.ts +++ b/test/e2e/scenario/rum/init.scenario.ts @@ -110,6 +110,7 @@ describe('beforeSend', () => { .withRum({ beforeSend: (event: any) => { event.context!.foo = 'bar' + return true }, }) .withRumSlim() @@ -126,6 +127,7 @@ describe('beforeSend', () => { .withRum({ beforeSend: (event) => { event.context = { foo: 'bar' } + return true }, }) .withRumSlim() From d1882c754246236dc94a8e116d0aac5c91cef951 Mon Sep 17 00:00:00 2001 From: Bastien Caudan <1331991+bcaudan@users.noreply.github.com> Date: Mon, 26 Jun 2023 11:55:07 +0200 Subject: [PATCH 17/27] =?UTF-8?q?=F0=9F=92=A5=20beforeSend=20domain=20cont?= =?UTF-8?q?ext:=20use=20PerformanceEntry=20(#2300)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/browser/performanceCollection.spec.ts | 6 ++++++ .../src/browser/performanceCollection.ts | 8 +++++--- .../longTask/longTaskCollection.spec.ts | 9 ++++++++- .../longTask/longTaskCollection.ts | 2 +- .../resource/resourceCollection.ts | 17 ++++------------- packages/rum-core/src/domainContext.types.ts | 15 ++++----------- packages/rum-core/test/fixtures.ts | 2 +- 7 files changed, 29 insertions(+), 30 deletions(-) diff --git a/packages/rum-core/src/browser/performanceCollection.spec.ts b/packages/rum-core/src/browser/performanceCollection.spec.ts index af56a4029f..df34050635 100644 --- a/packages/rum-core/src/browser/performanceCollection.spec.ts +++ b/packages/rum-core/src/browser/performanceCollection.spec.ts @@ -18,6 +18,12 @@ describe('rum initial document resource', () => { retrieveInitialDocumentResourceTiming((timing) => { expect(timing.entryType).toBe('resource') expect(timing.duration).toBeGreaterThan(0) + + // generate a performance entry like structure + const toJsonTiming = timing.toJSON() + expect(toJsonTiming.entryType).toEqual(timing.entryType) + expect(toJsonTiming.duration).toEqual(timing.duration) + expect((toJsonTiming as any).toJSON).toBeUndefined() done() }) }) diff --git a/packages/rum-core/src/browser/performanceCollection.ts b/packages/rum-core/src/browser/performanceCollection.ts index 031be3bce2..4aac72ebaa 100644 --- a/packages/rum-core/src/browser/performanceCollection.ts +++ b/packages/rum-core/src/browser/performanceCollection.ts @@ -19,7 +19,6 @@ import { LifeCycleEventType } from '../domain/lifeCycle' import { FAKE_INITIAL_DOCUMENT, isAllowedRequestUrl } from '../domain/rumEventsCollection/resource/resourceUtils' import { getDocumentTraceId } from '../domain/tracing/getDocumentTraceId' -import type { PerformanceEntryRepresentation } from '../domainContext.types' export interface RumPerformanceResourceTiming { entryType: 'resource' @@ -40,13 +39,15 @@ export interface RumPerformanceResourceTiming { redirectEnd: RelativeTime decodedBodySize: number traceId?: string + toJSON(): Omit } export interface RumPerformanceLongTaskTiming { + name: string entryType: 'longtask' startTime: RelativeTime duration: Duration - toJSON(): PerformanceEntryRepresentation + toJSON(): Omit } export interface RumPerformancePaintTiming { @@ -167,10 +168,11 @@ export function retrieveInitialDocumentResourceTiming(callback: (timing: RumPerf entryType: 'resource' as const, initiatorType: FAKE_INITIAL_DOCUMENT, traceId: getDocumentTraceId(document), + toJSON: () => assign({}, timing, { toJSON: undefined }), } if (supportPerformanceTimingEvent('navigation') && performance.getEntriesByType('navigation').length > 0) { const navigationEntry = performance.getEntriesByType('navigation')[0] - timing = assign(navigationEntry.toJSON(), forcedAttributes) + timing = assign(navigationEntry.toJSON() as RumPerformanceResourceTiming, forcedAttributes) } else { const relativePerformanceTiming = computeRelativePerformanceTiming() timing = assign( diff --git a/packages/rum-core/src/domain/rumEventsCollection/longTask/longTaskCollection.spec.ts b/packages/rum-core/src/domain/rumEventsCollection/longTask/longTaskCollection.spec.ts index 19b1247f0b..4d1643b5c8 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/longTask/longTaskCollection.spec.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/longTask/longTaskCollection.spec.ts @@ -7,6 +7,7 @@ import { LifeCycleEventType } from '../../lifeCycle' import { startLongTaskCollection } from './longTaskCollection' const LONG_TASK: RumPerformanceLongTaskTiming = { + name: 'self', duration: 100 as Duration, entryType: 'longtask', startTime: 1234 as RelativeTime, @@ -79,7 +80,13 @@ describe('long task collection', () => { }, }) expect(rawRumEvents[0].domainContext).toEqual({ - performanceEntry: { name: 'self', duration: 100, entryType: 'longtask', startTime: 1234 }, + performanceEntry: { + name: 'self', + duration: 100, + entryType: 'longtask', + startTime: 1234, + toJSON: jasmine.any(Function), + }, }) }) }) diff --git a/packages/rum-core/src/domain/rumEventsCollection/longTask/longTaskCollection.ts b/packages/rum-core/src/domain/rumEventsCollection/longTask/longTaskCollection.ts index 464e10199f..f1916c4ea3 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/longTask/longTaskCollection.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/longTask/longTaskCollection.ts @@ -35,7 +35,7 @@ export function startLongTaskCollection( lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { rawRumEvent, startTime: startClocks.relative, - domainContext: { performanceEntry: entry.toJSON() }, + domainContext: { performanceEntry: entry }, }) } }) diff --git a/packages/rum-core/src/domain/rumEventsCollection/resource/resourceCollection.ts b/packages/rum-core/src/domain/rumEventsCollection/resource/resourceCollection.ts index 4f8d059223..b79916913d 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/resource/resourceCollection.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/resource/resourceCollection.ts @@ -12,12 +12,8 @@ import { } from '@datadog/browser-core' import type { ClocksState, Duration } from '@datadog/browser-core' import type { RumConfiguration } from '../../configuration' -import type { RumPerformanceEntry, RumPerformanceResourceTiming } from '../../../browser/performanceCollection' -import type { - PerformanceEntryRepresentation, - RumXhrResourceEventDomainContext, - RumFetchResourceEventDomainContext, -} from '../../../domainContext.types' +import type { RumPerformanceResourceTiming } from '../../../browser/performanceCollection' +import type { RumXhrResourceEventDomainContext, RumFetchResourceEventDomainContext } from '../../../domainContext.types' import type { RawRumResourceEvent } from '../../../rawRumEvent.types' import { RumEventType } from '../../../rawRumEvent.types' import type { LifeCycle, RawRumEventCollectedData } from '../../lifeCycle' @@ -105,7 +101,7 @@ function processRequest( startTime: startClocks.relative, rawRumEvent: resourceEvent, domainContext: { - performanceEntry: matchingTiming && toPerformanceEntryRepresentation(matchingTiming), + performanceEntry: matchingTiming, xhr: request.xhr, response: request.response, requestInput: request.input, @@ -148,7 +144,7 @@ function processResourceEntry( startTime: startClocks.relative, rawRumEvent: resourceEvent, domainContext: { - performanceEntry: toPerformanceEntryRepresentation(entry), + performanceEntry: entry, }, } } @@ -192,11 +188,6 @@ function computeEntryTracingInfo(entry: RumPerformanceResourceTiming, configurat } } -// TODO next major: use directly PerformanceEntry type in domain context -function toPerformanceEntryRepresentation(entry: RumPerformanceEntry): PerformanceEntryRepresentation { - return entry as PerformanceEntryRepresentation -} - /** * @returns number between 0 and 1 which represents trace sample rate */ diff --git a/packages/rum-core/src/domainContext.types.ts b/packages/rum-core/src/domainContext.types.ts index 25b14bc3b0..c4d390291a 100644 --- a/packages/rum-core/src/domainContext.types.ts +++ b/packages/rum-core/src/domainContext.types.ts @@ -29,16 +29,16 @@ export interface RumFetchResourceEventDomainContext { requestInput: RequestInfo response?: Response error?: Error - performanceEntry?: PerformanceEntryRepresentation + performanceEntry?: PerformanceEntry } export interface RumXhrResourceEventDomainContext { xhr: XMLHttpRequest - performanceEntry?: PerformanceEntryRepresentation + performanceEntry?: PerformanceEntry } export interface RumOtherResourceEventDomainContext { - performanceEntry: PerformanceEntryRepresentation + performanceEntry: PerformanceEntry } export interface RumErrorEventDomainContext { @@ -46,12 +46,5 @@ export interface RumErrorEventDomainContext { } export interface RumLongTaskEventDomainContext { - performanceEntry: PerformanceEntryRepresentation + performanceEntry: PerformanceEntry } - -/** - * Symbolizes the type of the value returned by performanceEntry.toJSON(). Can also be built - * manually to represent other kind of performance entries (ex: initial_document) or polyfilled - * based on `performance.timing`. - */ -export type PerformanceEntryRepresentation = Omit diff --git a/packages/rum-core/test/fixtures.ts b/packages/rum-core/test/fixtures.ts index deb22f9417..f186b71297 100644 --- a/packages/rum-core/test/fixtures.ts +++ b/packages/rum-core/test/fixtures.ts @@ -120,6 +120,6 @@ export function createResourceEntry( startTime: 200 as RelativeTime, ...overrides, } - entry.toJSON = () => entry + entry.toJSON = () => entry as RumPerformanceResourceTiming & PerformanceResourceTiming return entry as RumPerformanceResourceTiming & PerformanceResourceTiming } From ec6207553b88e753e2bdb899d65bc8fa7dad3c99 Mon Sep 17 00:00:00 2001 From: Bastien Caudan <1331991+bcaudan@users.noreply.github.com> Date: Wed, 28 Jun 2023 16:06:35 +0200 Subject: [PATCH 18/27] =?UTF-8?q?=F0=9F=92=A5[RUMF-1564]=20remove=20intake?= =?UTF-8?q?=20subdomains=20(#2309)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../configuration/endpointBuilder.spec.ts | 2 +- .../domain/configuration/endpointBuilder.ts | 39 +++++++------------ .../core/src/domain/configuration/index.ts | 2 +- .../src/domain/configuration/intakeSites.ts | 1 - .../transportConfiguration.spec.ts | 26 +++++-------- .../configuration/transportConfiguration.ts | 2 +- packages/core/src/index.ts | 1 - packages/core/src/transport/httpRequest.ts | 2 +- .../src/transport/sendWithRetryStrategy.ts | 20 +++++----- 9 files changed, 37 insertions(+), 58 deletions(-) diff --git a/packages/core/src/domain/configuration/endpointBuilder.spec.ts b/packages/core/src/domain/configuration/endpointBuilder.spec.ts index 1a7bcfd22a..4a32fd3109 100644 --- a/packages/core/src/domain/configuration/endpointBuilder.spec.ts +++ b/packages/core/src/domain/configuration/endpointBuilder.spec.ts @@ -31,7 +31,7 @@ describe('endpointBuilder', () => { it('should not add batch_time for logs and replay endpoints', () => { expect(createEndpointBuilder(initConfiguration, 'logs', []).build('xhr')).not.toContain('&batch_time=') - expect(createEndpointBuilder(initConfiguration, 'sessionReplay', []).build('xhr')).not.toContain('&batch_time=') + expect(createEndpointBuilder(initConfiguration, 'replay', []).build('xhr')).not.toContain('&batch_time=') }) it('should not start with ddsource for internal analytics mode', () => { diff --git a/packages/core/src/domain/configuration/endpointBuilder.ts b/packages/core/src/domain/configuration/endpointBuilder.ts index 3a454dfa1d..df1bc8ff39 100644 --- a/packages/core/src/domain/configuration/endpointBuilder.ts +++ b/packages/core/src/domain/configuration/endpointBuilder.ts @@ -4,39 +4,27 @@ import { normalizeUrl } from '../../tools/utils/urlPolyfill' import { ExperimentalFeature, isExperimentalFeatureEnabled } from '../../tools/experimentalFeatures' import { generateUUID } from '../../tools/utils/stringUtils' import type { InitConfiguration } from './configuration' -import { INTAKE_SITE_AP1, INTAKE_SITE_US1 } from './intakeSites' +import { INTAKE_SITE_US1 } from './intakeSites' // replaced at build time declare const __BUILD_ENV__SDK_VERSION__: string -export const ENDPOINTS = { - logs: 'logs', - rum: 'rum', - sessionReplay: 'session-replay', -} as const - -const INTAKE_TRACKS = { - logs: 'logs', - rum: 'rum', - sessionReplay: 'replay', -} - -export type EndpointType = keyof typeof ENDPOINTS +export type TrackType = 'logs' | 'rum' | 'replay' export type EndpointBuilder = ReturnType export function createEndpointBuilder( initConfiguration: InitConfiguration, - endpointType: EndpointType, + trackType: TrackType, configurationTags: string[] ) { - const buildUrlWithParameters = createEndpointUrlWithParametersBuilder(initConfiguration, endpointType) + const buildUrlWithParameters = createEndpointUrlWithParametersBuilder(initConfiguration, trackType) return { build(api: 'xhr' | 'fetch' | 'beacon', flushReason?: FlushReason, retry?: RetryInfo) { const parameters = buildEndpointParameters( initConfiguration, - endpointType, + trackType, configurationTags, api, flushReason, @@ -45,7 +33,7 @@ export function createEndpointBuilder( return buildUrlWithParameters(parameters) }, urlPrefix: buildUrlWithParameters(''), - endpointType, + trackType, } } @@ -56,19 +44,19 @@ export function createEndpointBuilder( */ function createEndpointUrlWithParametersBuilder( initConfiguration: InitConfiguration, - endpointType: EndpointType + trackType: TrackType ): (parameters: string) => string { - const path = `/api/v2/${INTAKE_TRACKS[endpointType]}` + const path = `/api/v2/${trackType}` const proxy = initConfiguration.proxy if (proxy) { const normalizedProxyUrl = normalizeUrl(proxy) return (parameters) => `${normalizedProxyUrl}?ddforward=${encodeURIComponent(`${path}?${parameters}`)}` } - const host = buildEndpointHost(initConfiguration, endpointType) + const host = buildEndpointHost(initConfiguration) return (parameters) => `https://${host}${path}?${parameters}` } -function buildEndpointHost(initConfiguration: InitConfiguration, endpointType: EndpointType) { +function buildEndpointHost(initConfiguration: InitConfiguration) { const { site = INTAKE_SITE_US1, internalAnalyticsSubdomain } = initConfiguration if (internalAnalyticsSubdomain && site === INTAKE_SITE_US1) { @@ -77,8 +65,7 @@ function buildEndpointHost(initConfiguration: InitConfiguration, endpointType: E const domainParts = site.split('.') const extension = domainParts.pop() - const subdomain = site !== INTAKE_SITE_AP1 ? `${ENDPOINTS[endpointType]}.` : '' - return `${subdomain}browser-intake-${domainParts.join('-')}.${extension!}` + return `browser-intake-${domainParts.join('-')}.${extension!}` } /** @@ -87,7 +74,7 @@ function buildEndpointHost(initConfiguration: InitConfiguration, endpointType: E */ function buildEndpointParameters( { clientToken, internalAnalyticsSubdomain }: InitConfiguration, - endpointType: EndpointType, + trackType: TrackType, configurationTags: string[], api: 'xhr' | 'fetch' | 'beacon', flushReason: FlushReason | undefined, @@ -109,7 +96,7 @@ function buildEndpointParameters( `dd-request-id=${generateUUID()}`, ] - if (endpointType === 'rum') { + if (trackType === 'rum') { parameters.push(`batch_time=${timeStampNow()}`) } if (internalAnalyticsSubdomain) { diff --git a/packages/core/src/domain/configuration/index.ts b/packages/core/src/domain/configuration/index.ts index 3ebfe3d4cd..42f6c28387 100644 --- a/packages/core/src/domain/configuration/index.ts +++ b/packages/core/src/domain/configuration/index.ts @@ -5,5 +5,5 @@ export { validateAndBuildConfiguration, serializeConfiguration, } from './configuration' -export { createEndpointBuilder, EndpointBuilder, EndpointType } from './endpointBuilder' +export { createEndpointBuilder, EndpointBuilder, TrackType } from './endpointBuilder' export * from './intakeSites' diff --git a/packages/core/src/domain/configuration/intakeSites.ts b/packages/core/src/domain/configuration/intakeSites.ts index 96915a3331..71308ddb20 100644 --- a/packages/core/src/domain/configuration/intakeSites.ts +++ b/packages/core/src/domain/configuration/intakeSites.ts @@ -1,5 +1,4 @@ export const INTAKE_SITE_STAGING = 'datad0g.com' export const INTAKE_SITE_US1 = 'datadoghq.com' export const INTAKE_SITE_EU1 = 'datadoghq.eu' -export const INTAKE_SITE_AP1 = 'ap1.datadoghq.com' export const INTAKE_SITE_US1_FED = 'ddog-gov.com' diff --git a/packages/core/src/domain/configuration/transportConfiguration.spec.ts b/packages/core/src/domain/configuration/transportConfiguration.spec.ts index cbd5aaaa1f..3080ba721b 100644 --- a/packages/core/src/domain/configuration/transportConfiguration.spec.ts +++ b/packages/core/src/domain/configuration/transportConfiguration.spec.ts @@ -63,25 +63,19 @@ describe('transportConfiguration', () => { describe('isIntakeUrl', () => { ;[ - { expectSubdomain: true, site: 'datadoghq.eu', intakeDomain: 'browser-intake-datadoghq.eu' }, - { expectSubdomain: true, site: 'datadoghq.com', intakeDomain: 'browser-intake-datadoghq.com' }, - { expectSubdomain: true, site: 'us3.datadoghq.com', intakeDomain: 'browser-intake-us3-datadoghq.com' }, - { expectSubdomain: true, site: 'us5.datadoghq.com', intakeDomain: 'browser-intake-us5-datadoghq.com' }, - { expectSubdomain: true, site: 'ddog-gov.com', intakeDomain: 'browser-intake-ddog-gov.com' }, - { expectSubdomain: false, site: 'ap1.datadoghq.com', intakeDomain: 'browser-intake-ap1-datadoghq.com' }, - ].forEach(({ site, intakeDomain, expectSubdomain }) => { + { site: 'datadoghq.eu', intakeDomain: 'browser-intake-datadoghq.eu' }, + { site: 'datadoghq.com', intakeDomain: 'browser-intake-datadoghq.com' }, + { site: 'us3.datadoghq.com', intakeDomain: 'browser-intake-us3-datadoghq.com' }, + { site: 'us5.datadoghq.com', intakeDomain: 'browser-intake-us5-datadoghq.com' }, + { site: 'ddog-gov.com', intakeDomain: 'browser-intake-ddog-gov.com' }, + { site: 'ap1.datadoghq.com', intakeDomain: 'browser-intake-ap1-datadoghq.com' }, + ].forEach(({ site, intakeDomain }) => { it(`should detect intake request for ${site} site`, () => { const configuration = computeTransportConfiguration({ clientToken, site }) - expect(configuration.isIntakeUrl(`https://rum.${intakeDomain}/api/v2/rum?xxx`)).toBe(expectSubdomain) - expect(configuration.isIntakeUrl(`https://logs.${intakeDomain}/api/v2/logs?xxx`)).toBe(expectSubdomain) - expect(configuration.isIntakeUrl(`https://session-replay.${intakeDomain}/api/v2/replay?xxx`)).toBe( - expectSubdomain - ) - - expect(configuration.isIntakeUrl(`https://${intakeDomain}/api/v2/rum?xxx`)).toBe(!expectSubdomain) - expect(configuration.isIntakeUrl(`https://${intakeDomain}/api/v2/logs?xxx`)).toBe(!expectSubdomain) - expect(configuration.isIntakeUrl(`https://${intakeDomain}/api/v2/replay?xxx`)).toBe(!expectSubdomain) + expect(configuration.isIntakeUrl(`https://${intakeDomain}/api/v2/rum?xxx`)).toBe(true) + expect(configuration.isIntakeUrl(`https://${intakeDomain}/api/v2/logs?xxx`)).toBe(true) + expect(configuration.isIntakeUrl(`https://${intakeDomain}/api/v2/replay?xxx`)).toBe(true) }) }) diff --git a/packages/core/src/domain/configuration/transportConfiguration.ts b/packages/core/src/domain/configuration/transportConfiguration.ts index 057fc356e8..929f0318f2 100644 --- a/packages/core/src/domain/configuration/transportConfiguration.ts +++ b/packages/core/src/domain/configuration/transportConfiguration.ts @@ -42,7 +42,7 @@ function computeEndpointBuilders(initConfiguration: InitConfiguration, tags: str return { logsEndpointBuilder: createEndpointBuilder(initConfiguration, 'logs', tags), rumEndpointBuilder: createEndpointBuilder(initConfiguration, 'rum', tags), - sessionReplayEndpointBuilder: createEndpointBuilder(initConfiguration, 'sessionReplay', tags), + sessionReplayEndpointBuilder: createEndpointBuilder(initConfiguration, 'replay', tags), } } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 4338cb3c6f..f35f23d374 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -5,7 +5,6 @@ export { DefaultPrivacyLevel, EndpointBuilder, serializeConfiguration, - INTAKE_SITE_AP1, INTAKE_SITE_STAGING, INTAKE_SITE_US1, INTAKE_SITE_US1_FED, diff --git a/packages/core/src/transport/httpRequest.ts b/packages/core/src/transport/httpRequest.ts index f796f49121..f9693f80f5 100644 --- a/packages/core/src/transport/httpRequest.ts +++ b/packages/core/src/transport/httpRequest.ts @@ -46,7 +46,7 @@ export function createHttpRequest( return { send: (payload: Payload) => { - sendWithRetryStrategy(payload, retryState, sendStrategyForRetry, endpointBuilder.endpointType, reportError) + sendWithRetryStrategy(payload, retryState, sendStrategyForRetry, endpointBuilder.trackType, reportError) }, /** * Since fetch keepalive behaves like regular fetch on Firefox, diff --git a/packages/core/src/transport/sendWithRetryStrategy.ts b/packages/core/src/transport/sendWithRetryStrategy.ts index c294b0421e..2560fe2f3a 100644 --- a/packages/core/src/transport/sendWithRetryStrategy.ts +++ b/packages/core/src/transport/sendWithRetryStrategy.ts @@ -1,4 +1,4 @@ -import type { EndpointType } from '../domain/configuration' +import type { TrackType } from '../domain/configuration' import { setTimeout } from '../tools/timer' import { clocksNow, ONE_MINUTE, ONE_SECOND } from '../tools/utils/timeUtils' import { ONE_MEBI_BYTE, ONE_KIBI_BYTE } from '../tools/utils/byteUtils' @@ -38,7 +38,7 @@ export function sendWithRetryStrategy( payload: Payload, state: RetryState, sendStrategy: SendStrategy, - endpointType: EndpointType, + trackType: TrackType, reportError: (error: RawError) => void ) { if ( @@ -47,10 +47,10 @@ export function sendWithRetryStrategy( state.bandwidthMonitor.canHandle(payload) ) { send(payload, state, sendStrategy, { - onSuccess: () => retryQueuedPayloads(RetryReason.AFTER_SUCCESS, state, sendStrategy, endpointType, reportError), + onSuccess: () => retryQueuedPayloads(RetryReason.AFTER_SUCCESS, state, sendStrategy, trackType, reportError), onFailure: () => { state.queuedPayloads.enqueue(payload) - scheduleRetry(state, sendStrategy, endpointType, reportError) + scheduleRetry(state, sendStrategy, trackType, reportError) }, }) } else { @@ -61,7 +61,7 @@ export function sendWithRetryStrategy( function scheduleRetry( state: RetryState, sendStrategy: SendStrategy, - endpointType: EndpointType, + trackType: TrackType, reportError: (error: RawError) => void ) { if (state.transportStatus !== TransportStatus.DOWN) { @@ -73,11 +73,11 @@ function scheduleRetry( onSuccess: () => { state.queuedPayloads.dequeue() state.currentBackoffTime = INITIAL_BACKOFF_TIME - retryQueuedPayloads(RetryReason.AFTER_RESUME, state, sendStrategy, endpointType, reportError) + retryQueuedPayloads(RetryReason.AFTER_RESUME, state, sendStrategy, trackType, reportError) }, onFailure: () => { state.currentBackoffTime = Math.min(MAX_BACKOFF_TIME, state.currentBackoffTime * 2) - scheduleRetry(state, sendStrategy, endpointType, reportError) + scheduleRetry(state, sendStrategy, trackType, reportError) }, }) }, state.currentBackoffTime) @@ -112,12 +112,12 @@ function retryQueuedPayloads( reason: RetryReason, state: RetryState, sendStrategy: SendStrategy, - endpointType: EndpointType, + trackType: TrackType, reportError: (error: RawError) => void ) { if (reason === RetryReason.AFTER_SUCCESS && state.queuedPayloads.isFull() && !state.queueFullReported) { reportError({ - message: `Reached max ${endpointType} events size queued for upload: ${MAX_QUEUE_BYTES_COUNT / ONE_MEBI_BYTE}MiB`, + message: `Reached max ${trackType} events size queued for upload: ${MAX_QUEUE_BYTES_COUNT / ONE_MEBI_BYTE}MiB`, source: ErrorSource.AGENT, startClocks: clocksNow(), }) @@ -126,7 +126,7 @@ function retryQueuedPayloads( const previousQueue = state.queuedPayloads state.queuedPayloads = newPayloadQueue() while (previousQueue.size() > 0) { - sendWithRetryStrategy(previousQueue.dequeue()!, state, sendStrategy, endpointType, reportError) + sendWithRetryStrategy(previousQueue.dequeue()!, state, sendStrategy, trackType, reportError) } } From 682d40b505850f72b7d358ecc6814b3007f30e7e Mon Sep 17 00:00:00 2001 From: Aymeric Date: Mon, 3 Jul 2023 15:40:47 +0200 Subject: [PATCH 19/27] =?UTF-8?q?=F0=9F=92=A5=20[RUMF-1473]=20Ignore=20unt?= =?UTF-8?q?rusted=20event=20(#2308)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/src/browser/addEventListener.spec.ts | 35 +++++++++++- packages/core/src/browser/addEventListener.ts | 23 +++++--- .../core/src/browser/xhrObservable.spec.ts | 2 +- .../src/domain/session/sessionManager.spec.ts | 14 ++--- packages/core/src/tools/getGlobalObject.ts | 2 +- .../src/tools/serialisation/sanitize.spec.ts | 9 +-- packages/core/src/tools/timer.ts | 10 ++-- packages/core/test/emulate/createNewEvent.ts | 17 ++++-- packages/core/test/emulate/stubReportApis.ts | 5 +- packages/core/test/requests.ts | 5 +- .../src/domain/logsSessionManager.spec.ts | 5 +- .../src/domain/contexts/pageStateHistory.ts | 6 +- .../src/domain/rumSessionManager.spec.ts | 5 +- .../mouseInteractionObserver.spec.ts | 16 +++--- packages/rum/test/mockWorker.ts | 57 +++++++++++-------- test/e2e/lib/framework/pageSetups.ts | 18 +++++- 16 files changed, 147 insertions(+), 82 deletions(-) diff --git a/packages/core/src/browser/addEventListener.spec.ts b/packages/core/src/browser/addEventListener.spec.ts index a79ef082f1..bda902127d 100644 --- a/packages/core/src/browser/addEventListener.spec.ts +++ b/packages/core/src/browser/addEventListener.spec.ts @@ -1,4 +1,4 @@ -import { stubZoneJs } from '../../test' +import { createNewEvent, stubZoneJs } from '../../test' import { noop } from '../tools/utils/functionUtils' import { addEventListener, DOM_EVENT } from './addEventListener' @@ -33,4 +33,37 @@ describe('addEventListener', () => { expect(zoneJsPatchedRemoveEventListener).not.toHaveBeenCalled() }) }) + + describe('Untrusted event', () => { + it('should be ignored if __ddIsTrusted is absent', () => { + const listener = jasmine.createSpy() + const eventTarget = document.createElement('div') + addEventListener(eventTarget, DOM_EVENT.CLICK, listener) + + const event = createNewEvent(DOM_EVENT.CLICK, { __ddIsTrusted: undefined }) + eventTarget.dispatchEvent(event) + expect(listener).not.toHaveBeenCalled() + }) + + it('should be ignored if __ddIsTrusted is false', () => { + const listener = jasmine.createSpy() + const eventTarget = document.createElement('div') + addEventListener(eventTarget, DOM_EVENT.CLICK, listener) + + const event = createNewEvent(DOM_EVENT.CLICK, { __ddIsTrusted: false }) + eventTarget.dispatchEvent(event) + expect(listener).not.toHaveBeenCalled() + }) + + it('should not be ignored if __ddIsTrusted is true', () => { + const listener = jasmine.createSpy() + const eventTarget = document.createElement('div') + addEventListener(eventTarget, DOM_EVENT.CLICK, listener) + + const event = createNewEvent(DOM_EVENT.CLICK, { __ddIsTrusted: true }) + eventTarget.dispatchEvent(event) + + expect(listener).toHaveBeenCalled() + }) + }) }) diff --git a/packages/core/src/browser/addEventListener.ts b/packages/core/src/browser/addEventListener.ts index c0bba5f2f3..e393e0b945 100644 --- a/packages/core/src/browser/addEventListener.ts +++ b/packages/core/src/browser/addEventListener.ts @@ -2,7 +2,11 @@ import { monitor } from '../tools/monitor' import { getZoneJsOriginalValue } from '../tools/getZoneJsOriginalValue' import type { VisualViewport, VisualViewportEventMap } from './types' -export const enum DOM_EVENT { +export type TrustableEvent = E & { __ddIsTrusted?: boolean } + +// We want to use a real enum (i.e. not a const enum) here, to be able to iterate over it to automatically add _ddIsTrusted in e2e tests +// eslint-disable-next-line no-restricted-syntax +export enum DOM_EVENT { BEFORE_UNLOAD = 'beforeunload', CLICK = 'click', DBL_CLICK = 'dblclick', @@ -108,14 +112,15 @@ export function addEventListeners[EventName]) => void, { once, capture, passive }: AddEventListenerOptions = {} ) { - const wrappedListener = monitor( - once - ? (event: Event) => { - stop() - listener(event as EventMapFor[EventName]) - } - : (listener as (event: Event) => void) - ) + const wrappedListener = monitor((event: TrustableEvent) => { + if (!event.isTrusted && !event.__ddIsTrusted) { + return + } + if (once) { + stop() + } + listener(event as EventMapFor[EventName]) + }) const options = passive ? { capture, passive } : capture diff --git a/packages/core/src/browser/xhrObservable.spec.ts b/packages/core/src/browser/xhrObservable.spec.ts index dd11ca1837..9aaf5fd74d 100644 --- a/packages/core/src/browser/xhrObservable.spec.ts +++ b/packages/core/src/browser/xhrObservable.spec.ts @@ -254,7 +254,7 @@ describe('xhr observable', () => { }) it('should track multiple requests with the same xhr instance', (done) => { - let listeners: { [k: string]: Array<() => void> } + let listeners: { [k: string]: Array<(event: Event) => void> } withXhr({ setup(xhr) { const secondOnload = () => { diff --git a/packages/core/src/domain/session/sessionManager.spec.ts b/packages/core/src/domain/session/sessionManager.spec.ts index 36e48bebb0..cee406eb41 100644 --- a/packages/core/src/domain/session/sessionManager.spec.ts +++ b/packages/core/src/domain/session/sessionManager.spec.ts @@ -160,7 +160,7 @@ describe('startSessionManager', () => { expectSessionIdToNotBeDefined(sessionManager) expectTrackingTypeToNotBeDefined(sessionManager, FIRST_PRODUCT_KEY) - document.dispatchEvent(new CustomEvent('click')) + document.dispatchEvent(createNewEvent(DOM_EVENT.CLICK)) expect(renewSessionSpy).toHaveBeenCalled() expectSessionIdToBeDefined(sessionManager) @@ -196,7 +196,7 @@ describe('startSessionManager', () => { startSessionManager(STORE_TYPE, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) // schedule an expandOrRenewSession - document.dispatchEvent(new CustomEvent('click')) + document.dispatchEvent(createNewEvent(DOM_EVENT.CLICK)) clock.tick(STORAGE_POLL_DELAY / 2) @@ -244,7 +244,7 @@ describe('startSessionManager', () => { expect(renewSessionASpy).not.toHaveBeenCalled() expect(renewSessionBSpy).not.toHaveBeenCalled() - document.dispatchEvent(new CustomEvent('click')) + document.dispatchEvent(createNewEvent(DOM_EVENT.CLICK)) expect(renewSessionASpy).toHaveBeenCalled() expect(renewSessionBSpy).toHaveBeenCalled() @@ -317,7 +317,7 @@ describe('startSessionManager', () => { expectSessionIdToBeDefined(sessionManager) clock.tick(SESSION_EXPIRATION_DELAY - 10) - document.dispatchEvent(new CustomEvent('click')) + document.dispatchEvent(createNewEvent(DOM_EVENT.CLICK)) clock.tick(10) expectSessionIdToBeDefined(sessionManager) @@ -336,7 +336,7 @@ describe('startSessionManager', () => { expectTrackingTypeToBe(sessionManager, FIRST_PRODUCT_KEY, FakeTrackingType.NOT_TRACKED) clock.tick(SESSION_EXPIRATION_DELAY - 10) - document.dispatchEvent(new CustomEvent('click')) + document.dispatchEvent(createNewEvent(DOM_EVENT.CLICK)) clock.tick(10) expectTrackingTypeToBe(sessionManager, FIRST_PRODUCT_KEY, FakeTrackingType.NOT_TRACKED) @@ -432,7 +432,7 @@ describe('startSessionManager', () => { sessionManager.expire() - document.dispatchEvent(new CustomEvent('click')) + document.dispatchEvent(createNewEvent(DOM_EVENT.CLICK)) expectSessionIdToBeDefined(sessionManager) }) @@ -466,7 +466,7 @@ describe('startSessionManager', () => { clock.tick(10 * ONE_SECOND) // 20s to end: second session - document.dispatchEvent(new CustomEvent('click')) + document.dispatchEvent(createNewEvent(DOM_EVENT.CLICK)) clock.tick(10 * ONE_SECOND) const secondSessionId = sessionManager.findActiveSession()!.id const secondSessionTrackingType = sessionManager.findActiveSession()!.trackingType diff --git a/packages/core/src/tools/getGlobalObject.ts b/packages/core/src/tools/getGlobalObject.ts index 755754c8dc..2b049e7949 100644 --- a/packages/core/src/tools/getGlobalObject.ts +++ b/packages/core/src/tools/getGlobalObject.ts @@ -2,7 +2,7 @@ * inspired by https://mathiasbynens.be/notes/globalthis */ -export function getGlobalObject(): T { +export function getGlobalObject(): T { if (typeof globalThis === 'object') { return globalThis as unknown as T } diff --git a/packages/core/src/tools/serialisation/sanitize.spec.ts b/packages/core/src/tools/serialisation/sanitize.spec.ts index 53b85df228..22ba57ec51 100644 --- a/packages/core/src/tools/serialisation/sanitize.spec.ts +++ b/packages/core/src/tools/serialisation/sanitize.spec.ts @@ -1,5 +1,6 @@ import { isIE } from '../utils/browserDetection' import { display } from '../display' +import { createNewEvent } from '../../../test' import { sanitize } from './sanitize' describe('sanitize', () => { @@ -72,13 +73,7 @@ describe('sanitize', () => { }) it('should serialize events', () => { - let event: CustomEvent - if (isIE()) { - event = document.createEvent('CustomEvent') - event.initCustomEvent('MyEvent', false, false, {}) - } else { - event = new CustomEvent('MyEvent') - } + const event = createNewEvent('click') expect(sanitize(event)).toEqual({ isTrusted: false, diff --git a/packages/core/src/tools/timer.ts b/packages/core/src/tools/timer.ts index fec65efff7..73183f3dce 100644 --- a/packages/core/src/tools/timer.ts +++ b/packages/core/src/tools/timer.ts @@ -2,20 +2,20 @@ import { getZoneJsOriginalValue } from './getZoneJsOriginalValue' import { monitor } from './monitor' import { getGlobalObject } from './getGlobalObject' -export type TimeoutId = ReturnType +export type TimeoutId = ReturnType export function setTimeout(callback: () => void, delay?: number): TimeoutId { - return getZoneJsOriginalValue(getGlobalObject(), 'setTimeout')(monitor(callback), delay) + return getZoneJsOriginalValue(getGlobalObject(), 'setTimeout')(monitor(callback), delay) } export function clearTimeout(timeoutId: TimeoutId | undefined) { - getZoneJsOriginalValue(getGlobalObject(), 'clearTimeout')(timeoutId) + getZoneJsOriginalValue(getGlobalObject(), 'clearTimeout')(timeoutId) } export function setInterval(callback: () => void, delay?: number): TimeoutId { - return getZoneJsOriginalValue(window, 'setInterval')(monitor(callback), delay) + return getZoneJsOriginalValue(getGlobalObject(), 'setInterval')(monitor(callback), delay) } export function clearInterval(timeoutId: TimeoutId | undefined) { - getZoneJsOriginalValue(window, 'clearInterval')(timeoutId) + getZoneJsOriginalValue(getGlobalObject(), 'clearInterval')(timeoutId) } diff --git a/packages/core/test/emulate/createNewEvent.ts b/packages/core/test/emulate/createNewEvent.ts index e5a9089000..4d9d298802 100644 --- a/packages/core/test/emulate/createNewEvent.ts +++ b/packages/core/test/emulate/createNewEvent.ts @@ -1,19 +1,26 @@ +import type { TrustableEvent } from '../../src' import { objectEntries } from '../../src' -export function createNewEvent

>(eventName: 'click', properties?: P): MouseEvent & P -export function createNewEvent

>( +export function createNewEvent(eventName: 'click', properties?: Partial): MouseEvent +export function createNewEvent( eventName: 'pointerup', - properties?: P -): PointerEvent & P + properties?: Partial +): PointerEvent & { target: Element } +export function createNewEvent(eventName: 'message', properties?: Partial): MessageEvent +export function createNewEvent( + eventName: 'securitypolicyviolation', + properties?: Partial +): SecurityPolicyViolationEvent export function createNewEvent(eventName: string, properties?: { [name: string]: unknown }): Event export function createNewEvent(eventName: string, properties: { [name: string]: unknown } = {}) { - let event: Event + let event: TrustableEvent if (typeof Event === 'function') { event = new Event(eventName) } else { event = document.createEvent('Event') event.initEvent(eventName, true, true) } + event.__ddIsTrusted = true objectEntries(properties).forEach(([name, value]) => { // Setting values directly or with a `value` descriptor seems unsupported in IE11 Object.defineProperty(event, name, { diff --git a/packages/core/test/emulate/stubReportApis.ts b/packages/core/test/emulate/stubReportApis.ts index 72be8c8260..61249b43dc 100644 --- a/packages/core/test/emulate/stubReportApis.ts +++ b/packages/core/test/emulate/stubReportApis.ts @@ -1,5 +1,6 @@ import type { InterventionReport, ReportType } from '../../src/domain/report/browser.types' import { noop } from '../../src/tools/utils/functionUtils' +import { createNewEvent } from './createNewEvent' export function stubReportingObserver() { const originalReportingObserver = window.ReportingObserver @@ -56,7 +57,7 @@ export function stubCspEventListener() { } } -export const FAKE_CSP_VIOLATION_EVENT = { +export const FAKE_CSP_VIOLATION_EVENT = createNewEvent('securitypolicyviolation', { blockedURI: 'blob', columnNumber: 8, documentURI: 'blob', @@ -67,7 +68,7 @@ export const FAKE_CSP_VIOLATION_EVENT = { sourceFile: 'http://foo.bar/index.js', statusCode: 200, violatedDirective: 'worker-src', -} as SecurityPolicyViolationEvent +}) export const FAKE_REPORT: InterventionReport = { type: 'intervention', diff --git a/packages/core/test/requests.ts b/packages/core/test/requests.ts index 33c5a60c0e..e72ab147c1 100644 --- a/packages/core/test/requests.ts +++ b/packages/core/test/requests.ts @@ -1,5 +1,6 @@ import type { EndpointBuilder } from '../src' import { noop, isServerError } from '../src' +import { createNewEvent } from './emulate/createNewEvent' export const SPEC_ENDPOINTS = { logsEndpointBuilder: stubEndpointBuilder('https://logs-intake.com/v1/input/abcde?foo=bar'), @@ -249,7 +250,7 @@ export interface FetchStubPromise extends Promise { } class StubEventEmitter { - public listeners: { [k: string]: Array<() => void> } = {} + public listeners: { [k: string]: Array<(event: Event) => void> } = {} addEventListener(name: string, callback: () => void) { if (!this.listeners[name]) { @@ -271,7 +272,7 @@ class StubEventEmitter { if (!this.listeners[name]) { return } - this.listeners[name].forEach((listener) => listener.call(this)) + this.listeners[name].forEach((listener) => listener.apply(this, [createNewEvent(name)])) } } diff --git a/packages/logs/src/domain/logsSessionManager.spec.ts b/packages/logs/src/domain/logsSessionManager.spec.ts index 62f7aea34d..47cd85ab21 100644 --- a/packages/logs/src/domain/logsSessionManager.spec.ts +++ b/packages/logs/src/domain/logsSessionManager.spec.ts @@ -6,9 +6,10 @@ import { setCookie, stopSessionManager, ONE_SECOND, + DOM_EVENT, } from '@datadog/browser-core' import type { Clock } from '@datadog/browser-core/test' -import { mockClock } from '@datadog/browser-core/test' +import { createNewEvent, mockClock } from '@datadog/browser-core/test' import type { LogsConfiguration } from './configuration' import { @@ -84,7 +85,7 @@ describe('logs session manager', () => { clock.tick(STORAGE_POLL_DELAY) tracked = true - document.body.click() + document.body.dispatchEvent(createNewEvent(DOM_EVENT.CLICK)) expect(getCookie(SESSION_STORE_KEY)).toMatch(/id=[a-f0-9-]+/) expect(getCookie(SESSION_STORE_KEY)).toContain(`${LOGS_SESSION_KEY}=${LoggerTrackingType.TRACKED}`) diff --git a/packages/rum-core/src/domain/contexts/pageStateHistory.ts b/packages/rum-core/src/domain/contexts/pageStateHistory.ts index 61e2fc517d..bc8ef7e69f 100644 --- a/packages/rum-core/src/domain/contexts/pageStateHistory.ts +++ b/packages/rum-core/src/domain/contexts/pageStateHistory.ts @@ -54,11 +54,7 @@ export function startPageStateHistory( DOM_EVENT.PAGE_HIDE, ], (event) => { - // Only get events fired by the browser to avoid false currentPageState changes done with custom events - // cf: developer extension auto flush: https://github.com/DataDog/browser-sdk/blob/2f72bf05a672794c9e33965351964382a94c72ba/developer-extension/src/panel/flushEvents.ts#L11-L12 - if (event.isTrusted) { - addPageState(computePageState(event), event.timeStamp as RelativeTime) - } + addPageState(computePageState(event), event.timeStamp as RelativeTime) }, { capture: true } ) diff --git a/packages/rum-core/src/domain/rumSessionManager.spec.ts b/packages/rum-core/src/domain/rumSessionManager.spec.ts index 866eefc18f..e53baff3ab 100644 --- a/packages/rum-core/src/domain/rumSessionManager.spec.ts +++ b/packages/rum-core/src/domain/rumSessionManager.spec.ts @@ -7,9 +7,10 @@ import { setCookie, stopSessionManager, ONE_SECOND, + DOM_EVENT, } from '@datadog/browser-core' import type { Clock } from '@datadog/browser-core/test' -import { mockClock } from '@datadog/browser-core/test' +import { createNewEvent, mockClock } from '@datadog/browser-core/test' import type { RumConfiguration } from './configuration' import { validateAndBuildRumConfiguration } from './configuration' @@ -134,7 +135,7 @@ describe('rum session manager', () => { clock.tick(STORAGE_POLL_DELAY) setupDraws({ tracked: true, trackedWithSessionReplay: true }) - document.dispatchEvent(new CustomEvent('click')) + document.dispatchEvent(createNewEvent(DOM_EVENT.CLICK)) expect(expireSessionSpy).toHaveBeenCalled() expect(renewSessionSpy).toHaveBeenCalled() diff --git a/packages/rum/src/domain/record/observers/mouseInteractionObserver.spec.ts b/packages/rum/src/domain/record/observers/mouseInteractionObserver.spec.ts index 22cca63a57..b70b7aefce 100644 --- a/packages/rum/src/domain/record/observers/mouseInteractionObserver.spec.ts +++ b/packages/rum/src/domain/record/observers/mouseInteractionObserver.spec.ts @@ -1,4 +1,4 @@ -import { DefaultPrivacyLevel, isIE } from '@datadog/browser-core' +import { DOM_EVENT, DefaultPrivacyLevel, isIE } from '@datadog/browser-core' import { createNewEvent } from '@datadog/browser-core/test' import { IncrementalSource, MouseInteractionType, RecordType } from '../../../types' import { serializeDocument, SerializationContextStatus } from '../serialization' @@ -26,7 +26,7 @@ describe('initMouseInteractionObserver', () => { a.setAttribute('tabindex', '0') // make the element focusable sandbox.appendChild(a) document.body.appendChild(sandbox) - a.focus() + a.dispatchEvent(createNewEvent(DOM_EVENT.FOCUS)) serializeDocument(document, DEFAULT_CONFIGURATION, { shadowRootsController: DEFAULT_SHADOW_ROOT_CONTROLLER, @@ -45,7 +45,7 @@ describe('initMouseInteractionObserver', () => { }) it('should generate click record', () => { - a.click() + a.dispatchEvent(createNewEvent(DOM_EVENT.CLICK, { clientX: 0, clientY: 0 })) expect(mouseInteractionCallbackSpy).toHaveBeenCalledWith({ id: jasmine.any(Number), @@ -62,7 +62,7 @@ describe('initMouseInteractionObserver', () => { }) it('should generate mouseup record on pointerup DOM event', () => { - const pointerupEvent = createNewEvent('pointerup', { clientX: 1, clientY: 2 }) + const pointerupEvent = createNewEvent(DOM_EVENT.POINTER_UP, { clientX: 1, clientY: 2 }) a.dispatchEvent(pointerupEvent) expect(mouseInteractionCallbackSpy).toHaveBeenCalledWith({ @@ -80,14 +80,14 @@ describe('initMouseInteractionObserver', () => { }) it('should not generate click record if x/y are missing', () => { - const clickEvent = createNewEvent('click') + const clickEvent = createNewEvent(DOM_EVENT.CLICK) a.dispatchEvent(clickEvent) expect(mouseInteractionCallbackSpy).not.toHaveBeenCalled() }) it('should generate blur record', () => { - a.blur() + a.dispatchEvent(createNewEvent(DOM_EVENT.BLUR)) expect(mouseInteractionCallbackSpy).toHaveBeenCalledWith({ id: jasmine.any(Number), @@ -125,12 +125,12 @@ describe('initMouseInteractionObserver', () => { }) it('should compute x/y coordinates for click record', () => { - a.click() + a.dispatchEvent(createNewEvent(DOM_EVENT.CLICK)) expect(coordinatesComputed).toBeTrue() }) it('should not compute x/y coordinates for blur record', () => { - a.blur() + a.dispatchEvent(createNewEvent(DOM_EVENT.BLUR)) expect(coordinatesComputed).toBeFalse() }) }) diff --git a/packages/rum/test/mockWorker.ts b/packages/rum/test/mockWorker.ts index 72fedeeac2..6576358588 100644 --- a/packages/rum/test/mockWorker.ts +++ b/packages/rum/test/mockWorker.ts @@ -1,3 +1,4 @@ +import { createNewEvent } from '../../core/test' import type { DeflateWorker, DeflateWorkerAction, DeflateWorkerListener } from '../src/domain/segmentCollection' export class MockWorker implements DeflateWorker { @@ -64,25 +65,29 @@ export class MockWorker implements DeflateWorker { switch (message.action) { case 'init': this.listeners.message.forEach((listener) => - listener({ - data: { - type: 'initialized', - }, - }) + listener( + createNewEvent('message', { + data: { + type: 'initialized', + }, + }) + ) ) break case 'write': { const additionalBytesCount = this.pushData(message.data) this.listeners.message.forEach((listener) => - listener({ - data: { - type: 'wrote', - id: message.id, - compressedBytesCount: uint8ArraysSize(this.deflatedData), - additionalBytesCount, - }, - }) + listener( + createNewEvent('message', { + data: { + type: 'wrote', + id: message.id, + compressedBytesCount: uint8ArraysSize(this.deflatedData), + additionalBytesCount, + }, + }) + ) ) } break @@ -90,15 +95,17 @@ export class MockWorker implements DeflateWorker { { const additionalBytesCount = this.pushData(message.data) this.listeners.message.forEach((listener) => - listener({ - data: { - type: 'flushed', - id: message.id, - result: mergeUint8Arrays(this.deflatedData), - rawBytesCount: this.rawBytesCount, - additionalBytesCount, - }, - }) + listener( + createNewEvent('message', { + data: { + type: 'flushed', + id: message.id, + result: mergeUint8Arrays(this.deflatedData), + rawBytesCount: this.rawBytesCount, + additionalBytesCount, + }, + }) + ) ) this.deflatedData.length = 0 this.rawBytesCount = 0 @@ -109,12 +116,14 @@ export class MockWorker implements DeflateWorker { } dispatchErrorEvent() { - const error = new ErrorEvent('worker') + const error = createNewEvent('worker') this.listeners.error.forEach((listener) => listener(error)) } dispatchErrorMessage(error: Error | string) { - this.listeners.message.forEach((listener) => listener({ data: { type: 'errored', error } })) + this.listeners.message.forEach((listener) => + listener(createNewEvent('message', { data: { type: 'errored', error } })) + ) } private pushData(data?: string) { diff --git a/test/e2e/lib/framework/pageSetups.ts b/test/e2e/lib/framework/pageSetups.ts index bf0af1ff1b..0756523ffc 100644 --- a/test/e2e/lib/framework/pageSetups.ts +++ b/test/e2e/lib/framework/pageSetups.ts @@ -1,5 +1,6 @@ import type { LogsInitConfiguration } from '@datadog/browser-logs' import type { RumInitConfiguration } from '@datadog/browser-rum-core' +import { DOM_EVENT } from '@datadog/browser-core/src/browser/addEventListener' import type { Servers } from './httpServers' export interface SetupOptions { @@ -161,7 +162,7 @@ export function basePage({ header, body }: { header?: string; body?: string }) { - ${header || ''} + ${setupTrustedEvents()} ${header || ''} ${body || ''} @@ -170,6 +171,21 @@ export function basePage({ header, body }: { header?: string; body?: string }) { ` } +// Adds __ddIsTrusted: true to all events generated by e2e tests so we can track untrusted events generated by wdio +export function setupTrustedEvents() { + return html` ` +} + // html is a simple template string tag to allow prettier to format various setups as HTML export function html(parts: readonly string[], ...vars: string[]) { return parts.reduce((full, part, index) => full + vars[index - 1] + part) From ae5aac616833a92d8d1e827d53233f48c68c5d81 Mon Sep 17 00:00:00 2001 From: Aymeric Date: Tue, 4 Jul 2023 16:52:22 +0200 Subject: [PATCH 20/27] =?UTF-8?q?=F0=9F=92=A5=20[RUMF-1577]=20Stop=20colle?= =?UTF-8?q?cting=20foreground=20periods=20(#2311)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/src/tools/experimentalFeatures.ts | 1 - .../contexts/foregroundContexts.spec.ts | 249 ------------------ .../src/domain/contexts/foregroundContexts.ts | 32 --- .../view/viewCollection.spec.ts | 27 +- .../view/viewCollection.ts | 15 +- packages/rum-core/src/rawRumEvent.types.ts | 6 - rum-events-format | 2 +- 7 files changed, 8 insertions(+), 324 deletions(-) delete mode 100644 packages/rum-core/src/domain/contexts/foregroundContexts.spec.ts delete mode 100644 packages/rum-core/src/domain/contexts/foregroundContexts.ts diff --git a/packages/core/src/tools/experimentalFeatures.ts b/packages/core/src/tools/experimentalFeatures.ts index 7d4de3ef76..e6c90f7a68 100644 --- a/packages/core/src/tools/experimentalFeatures.ts +++ b/packages/core/src/tools/experimentalFeatures.ts @@ -14,7 +14,6 @@ export enum ExperimentalFeature { PAGEHIDE = 'pagehide', FEATURE_FLAGS = 'feature_flags', RESOURCE_PAGE_STATES = 'resource_page_states', - PAGE_STATES = 'page_states', COLLECT_FLUSH_REASON = 'collect_flush_reason', SCROLLMAP = 'scrollmap', } diff --git a/packages/rum-core/src/domain/contexts/foregroundContexts.spec.ts b/packages/rum-core/src/domain/contexts/foregroundContexts.spec.ts deleted file mode 100644 index 140372ad84..0000000000 --- a/packages/rum-core/src/domain/contexts/foregroundContexts.spec.ts +++ /dev/null @@ -1,249 +0,0 @@ -import type { RelativeTime, Duration, ServerDuration } from '@datadog/browser-core' -import { relativeNow } from '@datadog/browser-core' -import type { TestSetupBuilder } from '../../../test' -import { setup } from '../../../test' -import { mapToForegroundPeriods } from './foregroundContexts' -import type { PageStateHistory } from './pageStateHistory' -import { PageState, startPageStateHistory } from './pageStateHistory' - -const FOCUS_PERIOD_LENGTH = 10 as Duration -const BLUR_PERIOD_LENGTH = 5 as Duration - -describe('foreground context', () => { - let setupBuilder: TestSetupBuilder - let pageStateHistory: PageStateHistory - - function addNewForegroundPeriod() { - pageStateHistory.addPageState(PageState.ACTIVE) - } - - function closeForegroundPeriod() { - pageStateHistory.addPageState(PageState.PASSIVE) - } - - function selectInForegroundPeriodsFor(startTime: RelativeTime, duration: Duration) { - const pageStates = pageStateHistory.findAll(startTime, duration) - return mapToForegroundPeriods(pageStates || [], duration) - } - - function isInForegroundAt(startTime: RelativeTime) { - return pageStateHistory.isInActivePageStateAt(startTime) - } - - beforeEach(() => { - setupBuilder = setup() - .withFakeClock() - .beforeBuild(() => { - pageStateHistory = startPageStateHistory() - return pageStateHistory - }) - }) - - afterEach(() => { - setupBuilder.cleanup() - }) - - describe('when the page do not have the focus when starting', () => { - beforeEach(() => { - spyOn(Document.prototype, 'hasFocus').and.callFake(() => false) - pageStateHistory = startPageStateHistory() - }) - describe('without any focus nor blur event', () => { - describe('isInForegroundAt', () => { - it('should return false', () => { - const { clock } = setupBuilder.build() - - clock.tick(1_000) - - expect(isInForegroundAt(relativeNow())).toEqual(false) - }) - }) - - describe('selectInForegroundPeriodsFor', () => { - it('should an empty array', () => { - const { clock } = setupBuilder.build() - - clock.tick(1_000) - - expect(selectInForegroundPeriodsFor(relativeNow(), 0 as Duration)).toEqual([]) - }) - }) - }) - - describe('with two closed focus period & one active one', () => { - /* - events F B F B F - periods <------> <-------> <---- - - - - time 0 5 10 15 20 25 30 35 40 45 - */ - beforeEach(() => { - const { clock } = setupBuilder.build() - clock.tick(BLUR_PERIOD_LENGTH) - addNewForegroundPeriod() - clock.tick(FOCUS_PERIOD_LENGTH) - closeForegroundPeriod() - clock.tick(BLUR_PERIOD_LENGTH) - addNewForegroundPeriod() - clock.tick(FOCUS_PERIOD_LENGTH) - closeForegroundPeriod() - clock.tick(BLUR_PERIOD_LENGTH) - addNewForegroundPeriod() - clock.tick(FOCUS_PERIOD_LENGTH) - }) - - it('isInForegroundAt should match the focused/burred period', () => { - // first blurred period - expect(isInForegroundAt(2 as RelativeTime)).toEqual(false) - - // first focused period - expect(isInForegroundAt(10 as RelativeTime)).toEqual(true) - - // second blurred period - expect(isInForegroundAt(17 as RelativeTime)).toEqual(false) - - // second focused period - expect(isInForegroundAt(25 as RelativeTime)).toEqual(true) - - // third blurred period - expect(isInForegroundAt(32 as RelativeTime)).toEqual(false) - - // current focused periods - expect(isInForegroundAt(42 as RelativeTime)).toEqual(true) - }) - - describe('selectInForegroundPeriodsFor', () => { - it('should have 3 in foreground periods for the whole period', () => { - const periods = selectInForegroundPeriodsFor(0 as RelativeTime, 50 as Duration) - - expect(periods).toHaveSize(3) - expect(periods[0]).toEqual({ - start: (5 * 1e6) as ServerDuration, - duration: (10 * 1e6) as ServerDuration, - }) - expect(periods[1]).toEqual({ - start: (20 * 1e6) as ServerDuration, - duration: (10 * 1e6) as ServerDuration, - }) - expect(periods[2]).toEqual({ - start: (35 * 1e6) as ServerDuration, - duration: (15 * 1e6) as ServerDuration, - }) - }) - - it('should have 2 in foreground periods when in between the two full periods', () => { - const periods = selectInForegroundPeriodsFor(10 as RelativeTime, 15 as Duration) - - expect(periods).toHaveSize(2) - expect(periods[0]).toEqual({ - start: 0 as ServerDuration, - duration: (5 * 1e6) as ServerDuration, - }) - expect(periods[1]).toEqual({ - start: (10 * 1e6) as ServerDuration, - duration: (5 * 1e6) as ServerDuration, - }) - }) - - it('should have 2 periods, when in between the the full period and ongoing periods', () => { - const periods = selectInForegroundPeriodsFor(25 as RelativeTime, 20 as Duration) - - expect(periods).toHaveSize(2) - expect(periods[0]).toEqual({ - start: 0 as ServerDuration, - duration: (5 * 1e6) as ServerDuration, - }) - expect(periods[1]).toEqual({ - start: (10 * 1e6) as ServerDuration, - duration: (10 * 1e6) as ServerDuration, - }) - }) - }) - }) - - describe('with one missing blur event. with two closed focus period every 5 seconds lasting 10 seconds', () => { - /* - events F F B - periods <------><-------> - time 0 5 10 15 20 25 - */ - beforeEach(() => { - const { clock } = setupBuilder.build() - clock.tick(BLUR_PERIOD_LENGTH) - addNewForegroundPeriod() - clock.tick(FOCUS_PERIOD_LENGTH) - addNewForegroundPeriod() - clock.tick(FOCUS_PERIOD_LENGTH) - closeForegroundPeriod() - clock.tick(BLUR_PERIOD_LENGTH) - }) - it('isInForegroundAt should match the focused/burred period', () => { - expect(isInForegroundAt(2 as RelativeTime)).toEqual(false) - expect(isInForegroundAt(10 as RelativeTime)).toEqual(true) - expect(isInForegroundAt(20 as RelativeTime)).toEqual(true) - expect(isInForegroundAt(30 as RelativeTime)).toEqual(false) - }) - }) - - it('should not be in foreground, when the periods is closed twice', () => { - const { clock } = setupBuilder.build() - addNewForegroundPeriod() - clock.tick(BLUR_PERIOD_LENGTH) - pageStateHistory.addPageState(PageState.PASSIVE) - - expect(isInForegroundAt(relativeNow())).toEqual(false) - }) - - it('after starting with a blur even, should not be in foreground', () => { - pageStateHistory.addPageState(PageState.PASSIVE) - - expect(isInForegroundAt(relativeNow())).toEqual(false) - }) - }) - - describe('when the page has focus when starting', () => { - beforeEach(() => { - spyOn(Document.prototype, 'hasFocus').and.callFake(() => true) - pageStateHistory = startPageStateHistory() - }) - - describe('when there is no focus event', () => { - it('should return true during the focused period', () => { - const { clock } = setupBuilder.build() - clock.tick(FOCUS_PERIOD_LENGTH) - closeForegroundPeriod() - - expect(isInForegroundAt(2 as RelativeTime)).toEqual(true) - }) - - it('should return false after the first focused period', () => { - const { clock } = setupBuilder.build() - clock.tick(FOCUS_PERIOD_LENGTH) - closeForegroundPeriod() - - expect(isInForegroundAt(12 as RelativeTime)).toEqual(false) - }) - }) - - describe('when still getting the first focus event and closing the first periods after 10 seconds', () => { - it('should return true during the focused period', () => { - const { clock } = setupBuilder.build() - clock.tick(FOCUS_PERIOD_LENGTH / 2) - addNewForegroundPeriod() - clock.tick(FOCUS_PERIOD_LENGTH / 2) - closeForegroundPeriod() - - expect(isInForegroundAt(2 as RelativeTime)).toEqual(true) - }) - - it('should return false after the focused period', () => { - const { clock } = setupBuilder.build() - clock.tick(FOCUS_PERIOD_LENGTH / 2) - addNewForegroundPeriod() - clock.tick(FOCUS_PERIOD_LENGTH / 2) - closeForegroundPeriod() - - expect(isInForegroundAt(12 as RelativeTime)).toEqual(false) - }) - }) - }) -}) diff --git a/packages/rum-core/src/domain/contexts/foregroundContexts.ts b/packages/rum-core/src/domain/contexts/foregroundContexts.ts deleted file mode 100644 index ce21728ea5..0000000000 --- a/packages/rum-core/src/domain/contexts/foregroundContexts.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { RelativeTime, Duration, ServerDuration } from '@datadog/browser-core' -import { toServerDuration } from '@datadog/browser-core' -import type { InForegroundPeriod, PageStateServerEntry } from '../../rawRumEvent.types' -import { PageState } from './pageStateHistory' - -export interface ForegroundPeriod { - start: RelativeTime - end?: RelativeTime -} - -// Todo: Remove in the next major release -export function mapToForegroundPeriods( - pageStateServerEntries: PageStateServerEntry[], - duration: Duration -): InForegroundPeriod[] { - const foregroundPeriods: InForegroundPeriod[] = [] - for (let i = 0; i < pageStateServerEntries.length; i++) { - const current = pageStateServerEntries[i] - const next = pageStateServerEntries[i + 1] - - if (current.state === PageState.ACTIVE) { - const start = current.start >= 0 ? current.start : (0 as ServerDuration) - const end = next ? next.start : toServerDuration(duration) - foregroundPeriods.push({ - start, - duration: (end - start) as ServerDuration, - }) - } - } - - return foregroundPeriods -} 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 44b8c939fa..56e605a89a 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/view/viewCollection.spec.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/view/viewCollection.spec.ts @@ -1,5 +1,5 @@ import type { Duration, RelativeTime, ServerDuration, TimeStamp } from '@datadog/browser-core' -import { resetExperimentalFeatures, ExperimentalFeature, addExperimentalFeatures } from '@datadog/browser-core' +import { resetExperimentalFeatures } from '@datadog/browser-core' import type { RecorderApi } from '../../../boot/rumPublicApi' import type { TestSetupBuilder } from '../../../../test' import { setup, noopRecorderApi } from '../../../../test' @@ -105,7 +105,10 @@ describe('viewCollection', () => { _dd: { document_version: 3, replay_stats: undefined, - page_states: undefined, + page_states: [ + { start: 0 as ServerDuration, state: PageState.ACTIVE }, + { start: 10 as ServerDuration, state: PageState.PASSIVE }, + ], }, date: jasmine.any(Number), type: RumEventType.VIEW, @@ -144,7 +147,6 @@ describe('viewCollection', () => { count: 10, }, time_spent: (100 * 1e6) as ServerDuration, - in_foreground_periods: [{ start: 0 as ServerDuration, duration: 10 as ServerDuration }], }, session: { has_replay: undefined, @@ -207,25 +209,6 @@ describe('viewCollection', () => { expect(rawRumViewEvent.view.loading_time).toBeUndefined() }) - it('should include page_states but not in_foreground_periods when PAGE_STATES ff is enabled', () => { - addExperimentalFeatures([ExperimentalFeature.PAGE_STATES]) - const { lifeCycle, rawRumEvents } = setupBuilder.build() - lifeCycle.notify(LifeCycleEventType.VIEW_UPDATED, VIEW) - const rawRumViewEvent = rawRumEvents[rawRumEvents.length - 1].rawRumEvent as RawRumViewEvent - - expect(rawRumViewEvent._dd.page_states).toBeDefined() - expect(rawRumViewEvent.view.in_foreground_periods).toBeUndefined() - }) - - it('should include in_foreground_periods but not page_states when PAGE_STATES ff is disabled', () => { - const { lifeCycle, rawRumEvents } = setupBuilder.build() - lifeCycle.notify(LifeCycleEventType.VIEW_UPDATED, VIEW) - const rawRumViewEvent = rawRumEvents[rawRumEvents.length - 1].rawRumEvent as RawRumViewEvent - - expect(rawRumViewEvent._dd.page_states).toBeUndefined() - expect(rawRumViewEvent.view.in_foreground_periods).toBeDefined() - }) - it('should not include scroll metrics when there are not scroll metrics in the raw event', () => { const { lifeCycle, rawRumEvents } = setupBuilder.build() lifeCycle.notify(LifeCycleEventType.VIEW_UPDATED, { ...VIEW, scrollMetrics: undefined }) diff --git a/packages/rum-core/src/domain/rumEventsCollection/view/viewCollection.ts b/packages/rum-core/src/domain/rumEventsCollection/view/viewCollection.ts index 3ef63f2456..2b330ac0ff 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/view/viewCollection.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/view/viewCollection.ts @@ -1,18 +1,10 @@ import type { Duration, ServerDuration, Observable } from '@datadog/browser-core' -import { - isExperimentalFeatureEnabled, - ExperimentalFeature, - isEmptyObject, - mapValues, - toServerDuration, - isNumber, -} from '@datadog/browser-core' +import { isEmptyObject, mapValues, toServerDuration, isNumber } from '@datadog/browser-core' import type { RecorderApi } from '../../../boot/rumPublicApi' import type { RawRumViewEvent } from '../../../rawRumEvent.types' import { RumEventType } from '../../../rawRumEvent.types' import type { LifeCycle, RawRumEventCollectedData } from '../../lifeCycle' import { LifeCycleEventType } from '../../lifeCycle' -import { mapToForegroundPeriods } from '../../contexts/foregroundContexts' import type { LocationChange } from '../../../browser/locationChangeObservable' import type { RumConfiguration } from '../../configuration' import type { FeatureFlagContexts } from '../../contexts/featureFlagContext' @@ -59,13 +51,12 @@ function processViewUpdate( ): RawRumEventCollectedData { const replayStats = recorderApi.getReplayStats(view.id) const featureFlagContext = featureFlagContexts.findFeatureFlagEvaluations(view.startClocks.relative) - const pageStatesEnabled = isExperimentalFeatureEnabled(ExperimentalFeature.PAGE_STATES) const pageStates = pageStateHistory.findAll(view.startClocks.relative, view.duration) const viewEvent: RawRumViewEvent = { _dd: { document_version: view.documentVersion, replay_stats: replayStats, - page_states: pageStatesEnabled ? pageStates : undefined, + page_states: pageStates, }, date: view.startClocks.timeStamp, type: RumEventType.VIEW, @@ -100,8 +91,6 @@ function processViewUpdate( count: view.eventCounts.resourceCount, }, time_spent: toServerDuration(view.duration), - in_foreground_periods: - !pageStatesEnabled && pageStates ? mapToForegroundPeriods(pageStates, view.duration) : undefined, // Todo: Remove in the next major release }, feature_flags: featureFlagContext && !isEmptyObject(featureFlagContext) ? featureFlagContext : undefined, display: view.scrollMetrics diff --git a/packages/rum-core/src/rawRumEvent.types.ts b/packages/rum-core/src/rawRumEvent.types.ts index 1064893791..aa1f61c814 100644 --- a/packages/rum-core/src/rawRumEvent.types.ts +++ b/packages/rum-core/src/rawRumEvent.types.ts @@ -101,7 +101,6 @@ export interface RawRumViewEvent { long_task: Count resource: Count frustration: Count - in_foreground_periods?: InForegroundPeriod[] } session: { has_replay: true | undefined @@ -128,11 +127,6 @@ interface ViewDisplay { } } -export interface InForegroundPeriod { - start: ServerDuration - duration: ServerDuration -} - export type PageStateServerEntry = { state: PageState; start: ServerDuration } export const enum ViewLoadingType { diff --git a/rum-events-format b/rum-events-format index 427239c247..1c5eaa897c 160000 --- a/rum-events-format +++ b/rum-events-format @@ -1 +1 @@ -Subproject commit 427239c2479c13535aacff4f01d2b4085dd4c0d5 +Subproject commit 1c5eaa897c065e5f790a5f8aaf6fc8782d706051 From 251a4846dccf61712d990a85410a399a91e74398 Mon Sep 17 00:00:00 2001 From: Bastien Caudan <1331991+bcaudan@users.noreply.github.com> Date: Fri, 7 Jul 2023 09:38:54 +0200 Subject: [PATCH 21/27] v5.0.0-alpha.0 (#2321) --- CHANGELOG.md | 23 +++++++++++++++++++++++ developer-extension/package.json | 2 +- lerna.json | 2 +- packages/core/package.json | 2 +- packages/logs/package.json | 6 +++--- packages/rum-core/package.json | 4 ++-- packages/rum-slim/package.json | 8 ++++---- packages/rum/package.json | 8 ++++---- performances/package.json | 2 +- test/app/yarn.lock | 12 ++++++------ yarn.lock | 22 +++++++++++----------- 11 files changed, 57 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0f7c724de..15812e8a65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,29 @@ --- +## v5.0.0-alpha.0 + +- πŸ’₯ [RUMF-1577] Stop collecting foreground periods ([#2311](https://github.com/DataDog/browser-sdk/pull/2311)) +- πŸ’₯ [RUMF-1473] Ignore untrusted event ([#2308](https://github.com/DataDog/browser-sdk/pull/2308)) +- πŸ’₯ [RUMF-1564] remove intake subdomains ([#2309](https://github.com/DataDog/browser-sdk/pull/2309)) +- πŸ’₯ [RUMF-1557] beforeSend domain context: use PerformanceEntry ([#2300](https://github.com/DataDog/browser-sdk/pull/2300)) +- πŸ’₯ [RUMF-1556] Typings: consistent beforeSend return type ([#2303](https://github.com/DataDog/browser-sdk/pull/2303)) +- πŸ’₯ [RUMF-1230] Only apply main logger configuration to its own logs ([#2298](https://github.com/DataDog/browser-sdk/pull/2298)) +- πŸ’₯ [RUMF-1229] Logs: remove `error.origin` attribute ([#2294](https://github.com/DataDog/browser-sdk/pull/2294)) +- πŸ’₯ [RUMF-1228] Remove console error message prefix ([#2289](https://github.com/DataDog/browser-sdk/pull/2289)) +- πŸ’₯ [RUMF-1555] Rework logger context APIs ([#2285](https://github.com/DataDog/browser-sdk/pull/2285)) +- πŸ’₯ [RUMF-1152] sanitize resource method names ([#2288](https://github.com/DataDog/browser-sdk/pull/2288)) +- πŸ’₯ [RUMF-1555] Remove `event` in action domain context ([#2286](https://github.com/DataDog/browser-sdk/pull/2286)) +- πŸ’₯ [RUMF-1589] automatically start recording ([#2275](https://github.com/DataDog/browser-sdk/pull/2275)) +- πŸ’₯ [RUMF-1588] Update default session replay behaviour ([#2257](https://github.com/DataDog/browser-sdk/pull/2257)) +- πŸ’₯ [RUMF-1587] Remove `premiumSampleRate` and `replaySampleRate` ([#2256](https://github.com/DataDog/browser-sdk/pull/2256)) +- πŸ’₯ [RUMF-1554] Drop some deprecated public APIs ([#2241](https://github.com/DataDog/browser-sdk/pull/2241)) +- πŸ’₯ [RUMF-1554] Drop some deprecated config parameters ([#2238](https://github.com/DataDog/browser-sdk/pull/2238)) +- πŸ’₯ [RUMF-1578] Promote track frustration as default action behaviour ([#2232](https://github.com/DataDog/browser-sdk/pull/2232)) +- πŸ› [RUMF-1499] Don't send duration for resources crossing a page frozen state ([#2271](https://github.com/DataDog/browser-sdk/pull/2271)) +- πŸ”₯ [RUMF-1555] Remove `startTime` in xhr start context ([#2287](https://github.com/DataDog/browser-sdk/pull/2287)) +- ♻️ [RUMF-1555] Remove deprecated context manager APIs ([#2284](https://github.com/DataDog/browser-sdk/pull/2284)) + ## v4.44.0 - ✨ Collect replay privacy level in views ([#2299](https://github.com/DataDog/browser-sdk/pull/2299)) diff --git a/developer-extension/package.json b/developer-extension/package.json index d8ec308c41..bb6e79b3c9 100644 --- a/developer-extension/package.json +++ b/developer-extension/package.json @@ -1,6 +1,6 @@ { "name": "@datadog/browser-sdk-developer-extension", - "version": "4.44.0", + "version": "5.0.0-alpha.0", "private": true, "scripts": { "build": "rm -rf dist && webpack --mode production", diff --git a/lerna.json b/lerna.json index ee50ba2ff8..bbe6794070 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "npmClient": "yarn", - "version": "4.44.0", + "version": "5.0.0-alpha.0", "publishConfig": { "access": "public" } diff --git a/packages/core/package.json b/packages/core/package.json index f88d754ad4..6910861b8c 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@datadog/browser-core", - "version": "4.44.0", + "version": "5.0.0-alpha.0", "license": "Apache-2.0", "main": "cjs/index.js", "module": "esm/index.js", diff --git a/packages/logs/package.json b/packages/logs/package.json index b2dbce37ec..802f25171f 100644 --- a/packages/logs/package.json +++ b/packages/logs/package.json @@ -1,6 +1,6 @@ { "name": "@datadog/browser-logs", - "version": "4.44.0", + "version": "5.0.0-alpha.0", "license": "Apache-2.0", "main": "cjs/entries/main.js", "module": "esm/entries/main.js", @@ -13,10 +13,10 @@ "replace-build-env": "node ../../scripts/build/replace-build-env.js" }, "dependencies": { - "@datadog/browser-core": "4.44.0" + "@datadog/browser-core": "5.0.0-alpha.0" }, "peerDependencies": { - "@datadog/browser-rum": "4.44.0" + "@datadog/browser-rum": "5.0.0-alpha.0" }, "peerDependenciesMeta": { "@datadog/browser-rum": { diff --git a/packages/rum-core/package.json b/packages/rum-core/package.json index 3638ac7549..542c36bfc3 100644 --- a/packages/rum-core/package.json +++ b/packages/rum-core/package.json @@ -1,6 +1,6 @@ { "name": "@datadog/browser-rum-core", - "version": "4.44.0", + "version": "5.0.0-alpha.0", "license": "Apache-2.0", "main": "cjs/index.js", "module": "esm/index.js", @@ -12,7 +12,7 @@ "replace-build-env": "node ../../scripts/build/replace-build-env.js" }, "dependencies": { - "@datadog/browser-core": "4.44.0" + "@datadog/browser-core": "5.0.0-alpha.0" }, "devDependencies": { "ajv": "6.12.6" diff --git a/packages/rum-slim/package.json b/packages/rum-slim/package.json index 6e5d0a3f50..3bbaeb0e92 100644 --- a/packages/rum-slim/package.json +++ b/packages/rum-slim/package.json @@ -1,6 +1,6 @@ { "name": "@datadog/browser-rum-slim", - "version": "4.44.0", + "version": "5.0.0-alpha.0", "license": "Apache-2.0", "main": "cjs/entries/main.js", "module": "esm/entries/main.js", @@ -12,11 +12,11 @@ "build:esm": "rm -rf esm && tsc -p tsconfig.esm.json" }, "dependencies": { - "@datadog/browser-core": "4.44.0", - "@datadog/browser-rum-core": "4.44.0" + "@datadog/browser-core": "5.0.0-alpha.0", + "@datadog/browser-rum-core": "5.0.0-alpha.0" }, "peerDependencies": { - "@datadog/browser-logs": "4.44.0" + "@datadog/browser-logs": "5.0.0-alpha.0" }, "peerDependenciesMeta": { "@datadog/browser-logs": { diff --git a/packages/rum/package.json b/packages/rum/package.json index c70679cc69..c694683375 100644 --- a/packages/rum/package.json +++ b/packages/rum/package.json @@ -1,6 +1,6 @@ { "name": "@datadog/browser-rum", - "version": "4.44.0", + "version": "5.0.0-alpha.0", "license": "Apache-2.0", "main": "cjs/entries/main.js", "module": "esm/entries/main.js", @@ -12,11 +12,11 @@ "build:esm": "rm -rf esm && tsc -p tsconfig.esm.json" }, "dependencies": { - "@datadog/browser-core": "4.44.0", - "@datadog/browser-rum-core": "4.44.0" + "@datadog/browser-core": "5.0.0-alpha.0", + "@datadog/browser-rum-core": "5.0.0-alpha.0" }, "peerDependencies": { - "@datadog/browser-logs": "4.44.0" + "@datadog/browser-logs": "5.0.0-alpha.0" }, "peerDependenciesMeta": { "@datadog/browser-logs": { diff --git a/performances/package.json b/performances/package.json index 44e0923e70..568ec7ca39 100644 --- a/performances/package.json +++ b/performances/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "performances", - "version": "4.44.0", + "version": "5.0.0-alpha.0", "scripts": { "start": "ts-node ./src/main.ts" }, diff --git a/test/app/yarn.lock b/test/app/yarn.lock index cee5603e7b..353cbda7e5 100644 --- a/test/app/yarn.lock +++ b/test/app/yarn.lock @@ -15,9 +15,9 @@ __metadata: version: 0.0.0-use.local resolution: "@datadog/browser-logs@portal:../../packages/logs::locator=app%40workspace%3A." dependencies: - "@datadog/browser-core": 4.44.0 + "@datadog/browser-core": 5.0.0-alpha.0 peerDependencies: - "@datadog/browser-rum": 4.44.0 + "@datadog/browser-rum": 5.0.0-alpha.0 peerDependenciesMeta: "@datadog/browser-rum": optional: true @@ -28,7 +28,7 @@ __metadata: version: 0.0.0-use.local resolution: "@datadog/browser-rum-core@portal:../../packages/rum-core::locator=app%40workspace%3A." dependencies: - "@datadog/browser-core": 4.44.0 + "@datadog/browser-core": 5.0.0-alpha.0 languageName: node linkType: soft @@ -36,10 +36,10 @@ __metadata: version: 0.0.0-use.local resolution: "@datadog/browser-rum@portal:../../packages/rum::locator=app%40workspace%3A." dependencies: - "@datadog/browser-core": 4.44.0 - "@datadog/browser-rum-core": 4.44.0 + "@datadog/browser-core": 5.0.0-alpha.0 + "@datadog/browser-rum-core": 5.0.0-alpha.0 peerDependencies: - "@datadog/browser-logs": 4.44.0 + "@datadog/browser-logs": 5.0.0-alpha.0 peerDependenciesMeta: "@datadog/browser-logs": optional: true diff --git a/yarn.lock b/yarn.lock index a169d6529d..b42fc2b986 100644 --- a/yarn.lock +++ b/yarn.lock @@ -391,7 +391,7 @@ __metadata: languageName: node linkType: hard -"@datadog/browser-core@4.44.0, @datadog/browser-core@workspace:packages/core": +"@datadog/browser-core@5.0.0-alpha.0, @datadog/browser-core@workspace:packages/core": version: 0.0.0-use.local resolution: "@datadog/browser-core@workspace:packages/core" languageName: unknown @@ -401,20 +401,20 @@ __metadata: version: 0.0.0-use.local resolution: "@datadog/browser-logs@workspace:packages/logs" dependencies: - "@datadog/browser-core": 4.44.0 + "@datadog/browser-core": 5.0.0-alpha.0 peerDependencies: - "@datadog/browser-rum": 4.44.0 + "@datadog/browser-rum": 5.0.0-alpha.0 peerDependenciesMeta: "@datadog/browser-rum": optional: true languageName: unknown linkType: soft -"@datadog/browser-rum-core@4.44.0, @datadog/browser-rum-core@workspace:packages/rum-core": +"@datadog/browser-rum-core@5.0.0-alpha.0, @datadog/browser-rum-core@workspace:packages/rum-core": version: 0.0.0-use.local resolution: "@datadog/browser-rum-core@workspace:packages/rum-core" dependencies: - "@datadog/browser-core": 4.44.0 + "@datadog/browser-core": 5.0.0-alpha.0 ajv: 6.12.6 languageName: unknown linkType: soft @@ -423,10 +423,10 @@ __metadata: version: 0.0.0-use.local resolution: "@datadog/browser-rum-slim@workspace:packages/rum-slim" dependencies: - "@datadog/browser-core": 4.44.0 - "@datadog/browser-rum-core": 4.44.0 + "@datadog/browser-core": 5.0.0-alpha.0 + "@datadog/browser-rum-core": 5.0.0-alpha.0 peerDependencies: - "@datadog/browser-logs": 4.44.0 + "@datadog/browser-logs": 5.0.0-alpha.0 peerDependenciesMeta: "@datadog/browser-logs": optional: true @@ -437,12 +437,12 @@ __metadata: version: 0.0.0-use.local resolution: "@datadog/browser-rum@workspace:packages/rum" dependencies: - "@datadog/browser-core": 4.44.0 - "@datadog/browser-rum-core": 4.44.0 + "@datadog/browser-core": 5.0.0-alpha.0 + "@datadog/browser-rum-core": 5.0.0-alpha.0 "@types/pako": 2.0.0 pako: 2.1.0 peerDependencies: - "@datadog/browser-logs": 4.44.0 + "@datadog/browser-logs": 5.0.0-alpha.0 peerDependenciesMeta: "@datadog/browser-logs": optional: true From cd602627d1eadf6fe25f4c899ed2e18fe0188266 Mon Sep 17 00:00:00 2001 From: Bastien Caudan <1331991+bcaudan@users.noreply.github.com> Date: Tue, 25 Jul 2023 14:10:21 +0200 Subject: [PATCH 22/27] =?UTF-8?q?=F0=9F=92=A5=20[RUMF-1597]=20Drop=20`plan?= =?UTF-8?q?`=20and=20send=20`sampled=5Ffor=5Freplay`=20(#2293)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * πŸ”₯remove session plan * ✨send `session.sampled_for_replay` * πŸ‘·update sandbox no need to start replay anymore --- packages/rum-core/src/domain/assembly.spec.ts | 41 ++++++++++++++++--- packages/rum-core/src/domain/assembly.ts | 6 +-- .../src/domain/rumSessionManager.spec.ts | 16 ++++---- .../rum-core/src/domain/rumSessionManager.ts | 17 +------- packages/rum-core/src/index.ts | 2 +- packages/rum-core/src/rawRumEvent.types.ts | 4 -- .../rum-core/test/mockRumSessionManager.ts | 13 ++---- packages/rum-core/test/testSetupBuilder.ts | 4 -- packages/rum/src/boot/recorderApi.spec.ts | 32 +++++++-------- packages/rum/src/boot/startRecording.spec.ts | 2 +- .../src/domain/getSessionReplayLink.spec.ts | 2 +- sandbox/index.html | 1 - 12 files changed, 70 insertions(+), 70 deletions(-) diff --git a/packages/rum-core/src/domain/assembly.spec.ts b/packages/rum-core/src/domain/assembly.spec.ts index 87d8babebb..12d723419b 100644 --- a/packages/rum-core/src/domain/assembly.spec.ts +++ b/packages/rum-core/src/domain/assembly.spec.ts @@ -21,7 +21,6 @@ import type { RumActionEvent, RumErrorEvent, RumEvent } from '../rumEvent.types' import { startRumAssembly } from './assembly' import type { LifeCycle, RawRumEventCollectedData } from './lifeCycle' import { LifeCycleEventType } from './lifeCycle' -import { RumSessionPlan } from './rumSessionManager' import type { RumConfiguration } from './configuration' import type { ViewContext } from './contexts/viewContexts' import type { CommonContext } from './contexts/commonContext' @@ -601,20 +600,18 @@ describe('rum assembly', () => { }) describe('session context', () => { - it('should include the session type, id and plan', () => { + it('should include the session type and id', () => { const { lifeCycle } = setupBuilder.build() notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), }) expect(serverRumEvents[0].session).toEqual({ has_replay: undefined, + sampled_for_replay: jasmine.any(Boolean), is_active: undefined, id: '1234', type: 'user', }) - expect(serverRumEvents[0]._dd.session).toEqual({ - plan: RumSessionPlan.WITH_SESSION_REPLAY, - }) }) it('should detect synthetics sessions based on synthetics worker values', () => { @@ -658,6 +655,40 @@ describe('rum assembly', () => { }) expect(serverRumEvents[0].session.has_replay).toBe(undefined) }) + + it('should set sampled_for_replay on view events when tracked with replay', () => { + const { lifeCycle } = setupBuilder + .withSessionManager(createRumSessionManagerMock().setTrackedWithSessionReplay()) + .build() + + notifyRawRumEvent(lifeCycle, { + rawRumEvent: createRawRumEvent(RumEventType.VIEW), + }) + + expect(serverRumEvents[0].session.sampled_for_replay).toBe(true) + }) + + it('should set sampled_for_replay on view events when tracked without replay', () => { + const { lifeCycle } = setupBuilder + .withSessionManager(createRumSessionManagerMock().setTrackedWithoutSessionReplay()) + .build() + + notifyRawRumEvent(lifeCycle, { + rawRumEvent: createRawRumEvent(RumEventType.VIEW), + }) + + expect(serverRumEvents[0].session.sampled_for_replay).toBe(false) + }) + + it('should not set sampled_for_replay on other events', () => { + const { lifeCycle } = setupBuilder.build() + + notifyRawRumEvent(lifeCycle, { + rawRumEvent: createRawRumEvent(RumEventType.ERROR), + }) + + expect(serverRumEvents[0].session.sampled_for_replay).not.toBeDefined() + }) }) describe('configuration context', () => { diff --git a/packages/rum-core/src/domain/assembly.ts b/packages/rum-core/src/domain/assembly.ts index 35c5bf43b1..3874093008 100644 --- a/packages/rum-core/src/domain/assembly.ts +++ b/packages/rum-core/src/domain/assembly.ts @@ -124,9 +124,6 @@ export function startRumAssembly( _dd: { format_version: 2, drift: currentDrift(), - session: { - plan: session.plan, - }, configuration: { session_sample_rate: round(configuration.sessionSampleRate, 3), session_replay_sample_rate: round(configuration.sessionReplaySampleRate, 3), @@ -162,6 +159,9 @@ export function startRumAssembly( if (!('has_replay' in serverRumEvent.session)) { ;(serverRumEvent.session as Mutable).has_replay = commonContext.hasReplay } + if (serverRumEvent.type === 'view') { + ;(serverRumEvent.session as Mutable).sampled_for_replay = session.sessionReplayAllowed + } if (!isEmptyObject(commonContext.user)) { ;(serverRumEvent.usr as Mutable) = commonContext.user as User & Context diff --git a/packages/rum-core/src/domain/rumSessionManager.spec.ts b/packages/rum-core/src/domain/rumSessionManager.spec.ts index e53baff3ab..560c6e0b36 100644 --- a/packages/rum-core/src/domain/rumSessionManager.spec.ts +++ b/packages/rum-core/src/domain/rumSessionManager.spec.ts @@ -15,7 +15,7 @@ import type { RumConfiguration } from './configuration' import { validateAndBuildRumConfiguration } from './configuration' import { LifeCycle, LifeCycleEventType } from './lifeCycle' -import { RUM_SESSION_KEY, RumTrackingType, startRumSessionManager, RumSessionPlan } from './rumSessionManager' +import { RUM_SESSION_KEY, RumTrackingType, startRumSessionManager } from './rumSessionManager' describe('rum session manager', () => { const DURATION = 123456 @@ -176,30 +176,28 @@ describe('rum session manager', () => { expect(rumSessionManager.findTrackedSession(0 as RelativeTime)!.id).toBe('abcdef') }) - it('should return session with plan WITH_SESSION_REPLAY', () => { + it('should return session TRACKED_WITH_SESSION_REPLAY', () => { setCookie(SESSION_STORE_KEY, 'id=abcdef&rum=1', DURATION) const rumSessionManager = startRumSessionManager(configuration, lifeCycle) - expect(rumSessionManager.findTrackedSession()!.plan).toBe(RumSessionPlan.WITH_SESSION_REPLAY) + expect(rumSessionManager.findTrackedSession()!.sessionReplayAllowed).toBe(true) }) - it('should return session with plan WITHOUT_SESSION_REPLAY', () => { + it('should return session TRACKED_WITHOUT_SESSION_REPLAY', () => { setCookie(SESSION_STORE_KEY, 'id=abcdef&rum=2', DURATION) const rumSessionManager = startRumSessionManager(configuration, lifeCycle) - expect(rumSessionManager.findTrackedSession()!.plan).toBe(RumSessionPlan.WITHOUT_SESSION_REPLAY) + expect(rumSessionManager.findTrackedSession()!.sessionReplayAllowed).toBe(false) }) }) describe('session behaviors', () => { ;[ { - description: - 'WITH_SESSION_REPLAY plan with trackResources/LongTasks=false should have replay, no resources and no long tasks', + description: 'TRACKED_WITH_SESSION_REPLAY should have replay', trackedWithSessionReplay: true, expectSessionReplay: true, }, { - description: - 'WITHOUT_SESSION_REPLAY plan with trackResources/LongTasks=false should have no replay, no resources and no long tasks', + description: 'TRACKED_WITHOUT_SESSION_REPLAY should have no replay', trackedWithSessionReplay: false, expectSessionReplay: false, }, diff --git a/packages/rum-core/src/domain/rumSessionManager.ts b/packages/rum-core/src/domain/rumSessionManager.ts index e3e2009f49..d34444d53b 100644 --- a/packages/rum-core/src/domain/rumSessionManager.ts +++ b/packages/rum-core/src/domain/rumSessionManager.ts @@ -14,20 +14,11 @@ export interface RumSessionManager { export type RumSession = { id: string - plan: RumSessionPlan sessionReplayAllowed: boolean } -export const enum RumSessionPlan { - WITHOUT_SESSION_REPLAY = 1, - WITH_SESSION_REPLAY = 2, -} - export const enum RumTrackingType { NOT_TRACKED = '0', - // Note: the "tracking type" value (stored in the session cookie) does not match the "session - // plan" value (sent in RUM events). This is expected, and was done to keep backward-compatibility - // with active sessions when upgrading the SDK. TRACKED_WITH_SESSION_REPLAY = '1', TRACKED_WITHOUT_SESSION_REPLAY = '2', } @@ -54,14 +45,9 @@ export function startRumSessionManager(configuration: RumConfiguration, lifeCycl if (!session || !isTypeTracked(session.trackingType)) { return } - const plan = - session.trackingType === RumTrackingType.TRACKED_WITH_SESSION_REPLAY - ? RumSessionPlan.WITH_SESSION_REPLAY - : RumSessionPlan.WITHOUT_SESSION_REPLAY return { id: session.id, - plan, - sessionReplayAllowed: plan === RumSessionPlan.WITH_SESSION_REPLAY, + sessionReplayAllowed: session.trackingType === RumTrackingType.TRACKED_WITH_SESSION_REPLAY, } }, expire: sessionManager.expire, @@ -75,7 +61,6 @@ export function startRumSessionManager(configuration: RumConfiguration, lifeCycl export function startRumSessionManagerStub(): RumSessionManager { const session: RumSession = { id: '00000000-aaaa-0000-aaaa-000000000000', - plan: RumSessionPlan.WITHOUT_SESSION_REPLAY, // plan value should not be taken into account for mobile sessionReplayAllowed: false, } return { diff --git a/packages/rum-core/src/index.ts b/packages/rum-core/src/index.ts index 2680370340..a0e2325d69 100644 --- a/packages/rum-core/src/index.ts +++ b/packages/rum-core/src/index.ts @@ -23,7 +23,7 @@ export { startRum } from './boot/startRum' export { LifeCycle, LifeCycleEventType } from './domain/lifeCycle' export { ViewCreatedEvent } from './domain/rumEventsCollection/view/trackViews' export { ViewContexts, ViewContext } from './domain/contexts/viewContexts' -export { RumSessionManager, RumSessionPlan, RumSession } from './domain/rumSessionManager' +export { RumSessionManager, RumSession } from './domain/rumSessionManager' export { getMutationObserverConstructor } from './browser/domMutationObservable' export { initViewportObservable, getViewportDimension } from './browser/viewportObservable' export { getScrollX, getScrollY } from './browser/scroll' diff --git a/packages/rum-core/src/rawRumEvent.types.ts b/packages/rum-core/src/rawRumEvent.types.ts index f59baf51de..ab0398e42c 100644 --- a/packages/rum-core/src/rawRumEvent.types.ts +++ b/packages/rum-core/src/rawRumEvent.types.ts @@ -10,7 +10,6 @@ import type { DefaultPrivacyLevel, } from '@datadog/browser-core' import type { PageState } from './domain/contexts/pageStateHistory' -import type { RumSessionPlan } from './domain/rumSessionManager' export const enum RumEventType { ACTION = 'action', @@ -252,9 +251,6 @@ export interface RumContext { _dd: { format_version: 2 drift: number - session: { - plan: RumSessionPlan - } configuration: { session_sample_rate: number session_replay_sample_rate: number diff --git a/packages/rum-core/test/mockRumSessionManager.ts b/packages/rum-core/test/mockRumSessionManager.ts index 9966b5b17e..40e02b3e02 100644 --- a/packages/rum-core/test/mockRumSessionManager.ts +++ b/packages/rum-core/test/mockRumSessionManager.ts @@ -1,12 +1,11 @@ import { Observable } from '@datadog/browser-core' import type { RumSessionManager } from '../src/domain/rumSessionManager' -import { RumSessionPlan } from '../src/domain/rumSessionManager' export interface RumSessionManagerMock extends RumSessionManager { setId(id: string): RumSessionManagerMock setNotTracked(): RumSessionManagerMock - setPlanWithoutSessionReplay(): RumSessionManagerMock - setPlanWithSessionReplay(): RumSessionManagerMock + setTrackedWithoutSessionReplay(): RumSessionManagerMock + setTrackedWithSessionReplay(): RumSessionManagerMock } const DEFAULT_ID = 'session-id' @@ -30,10 +29,6 @@ export function createRumSessionManagerMock(): RumSessionManagerMock { } return { id, - plan: - sessionStatus === SessionStatus.TRACKED_WITH_SESSION_REPLAY - ? RumSessionPlan.WITH_SESSION_REPLAY - : RumSessionPlan.WITHOUT_SESSION_REPLAY, sessionReplayAllowed: sessionStatus === SessionStatus.TRACKED_WITH_SESSION_REPLAY, } }, @@ -50,11 +45,11 @@ export function createRumSessionManagerMock(): RumSessionManagerMock { sessionStatus = SessionStatus.NOT_TRACKED return this }, - setPlanWithoutSessionReplay() { + setTrackedWithoutSessionReplay() { sessionStatus = SessionStatus.TRACKED_WITHOUT_SESSION_REPLAY return this }, - setPlanWithSessionReplay() { + setTrackedWithSessionReplay() { sessionStatus = SessionStatus.TRACKED_WITH_SESSION_REPLAY return this }, diff --git a/packages/rum-core/test/testSetupBuilder.ts b/packages/rum-core/test/testSetupBuilder.ts index ef5a2518f4..bb9e6d9205 100644 --- a/packages/rum-core/test/testSetupBuilder.ts +++ b/packages/rum-core/test/testSetupBuilder.ts @@ -13,7 +13,6 @@ import type { RawRumEventCollectedData } from '../src/domain/lifeCycle' import { LifeCycle, LifeCycleEventType } from '../src/domain/lifeCycle' import type { ActionContexts } from '../src/domain/rumEventsCollection/action/actionCollection' import type { RumSessionManager } from '../src/domain/rumSessionManager' -import { RumSessionPlan } from '../src/domain/rumSessionManager' import type { RawRumEvent, RumContext } from '../src/rawRumEvent.types' import { validateRumFormat } from './formatValidation' import { createRumSessionManagerMock } from './mockRumSessionManager' @@ -218,9 +217,6 @@ function validateRumEventFormat(rawRumEvent: RawRumEvent) { _dd: { format_version: 2, drift: 0, - session: { - plan: RumSessionPlan.WITH_SESSION_REPLAY, - }, configuration: { session_sample_rate: 40, session_replay_sample_rate: 60, diff --git a/packages/rum/src/boot/recorderApi.spec.ts b/packages/rum/src/boot/recorderApi.spec.ts index 3be3b0a583..6d1ebd4bf5 100644 --- a/packages/rum/src/boot/recorderApi.spec.ts +++ b/packages/rum/src/boot/recorderApi.spec.ts @@ -129,8 +129,8 @@ describe('makeRecorderApi', () => { expect(startRecordingSpy).not.toHaveBeenCalled() }) - it('ignores start calls if the session plan is WITHOUT_REPLAY', () => { - setupBuilder.withSessionManager(createRumSessionManagerMock().setPlanWithoutSessionReplay()).build() + it('ignores start calls if the session is tracked without session replay', () => { + setupBuilder.withSessionManager(createRumSessionManagerMock().setTrackedWithoutSessionReplay()).build() rumInit() recorderApi.start() expect(startRecordingSpy).not.toHaveBeenCalled() @@ -213,7 +213,7 @@ describe('makeRecorderApi', () => { }) }) - describe('when session renewal change the session plan', () => { + describe('when session renewal change the tracking type', () => { let sessionManager: RumSessionManagerMock let lifeCycle: LifeCycle beforeEach(() => { @@ -224,12 +224,12 @@ describe('makeRecorderApi', () => { describe('from WITHOUT_REPLAY to WITH_REPLAY', () => { beforeEach(() => { - sessionManager.setPlanWithoutSessionReplay() + sessionManager.setTrackedWithoutSessionReplay() }) it('starts recording if startSessionReplayRecording was called', () => { rumInit() - sessionManager.setPlanWithSessionReplay() + sessionManager.setTrackedWithSessionReplay() lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) expect(startRecordingSpy).not.toHaveBeenCalled() lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED) @@ -240,7 +240,7 @@ describe('makeRecorderApi', () => { it('does not starts recording if stopSessionReplayRecording was called', () => { rumInit() recorderApi.stop() - sessionManager.setPlanWithSessionReplay() + sessionManager.setTrackedWithSessionReplay() lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED) expect(startRecordingSpy).not.toHaveBeenCalled() @@ -249,7 +249,7 @@ describe('makeRecorderApi', () => { describe('from WITHOUT_REPLAY to untracked', () => { beforeEach(() => { - sessionManager.setPlanWithoutSessionReplay() + sessionManager.setTrackedWithoutSessionReplay() }) it('keeps not recording if startSessionReplayRecording was called', () => { @@ -264,7 +264,7 @@ describe('makeRecorderApi', () => { describe('from WITHOUT_REPLAY to WITHOUT_REPLAY', () => { beforeEach(() => { - sessionManager.setPlanWithoutSessionReplay() + sessionManager.setTrackedWithoutSessionReplay() }) it('keeps not recording if startSessionReplayRecording was called', () => { @@ -278,13 +278,13 @@ describe('makeRecorderApi', () => { describe('from WITH_REPLAY to WITHOUT_REPLAY', () => { beforeEach(() => { - sessionManager.setPlanWithSessionReplay() + sessionManager.setTrackedWithSessionReplay() }) it('stops recording if startSessionReplayRecording was called', () => { rumInit() expect(startRecordingSpy).toHaveBeenCalledTimes(1) - sessionManager.setPlanWithoutSessionReplay() + sessionManager.setTrackedWithoutSessionReplay() lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) expect(stopRecordingSpy).toHaveBeenCalled() lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED) @@ -294,7 +294,7 @@ describe('makeRecorderApi', () => { it('prevents session recording to start if the session is renewed before the DOM is loaded', () => { const { triggerOnDomLoaded } = mockDocumentReadyState() rumInit() - sessionManager.setPlanWithoutSessionReplay() + sessionManager.setTrackedWithoutSessionReplay() lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED) triggerOnDomLoaded() @@ -304,7 +304,7 @@ describe('makeRecorderApi', () => { describe('from WITH_REPLAY to untracked', () => { beforeEach(() => { - sessionManager.setPlanWithSessionReplay() + sessionManager.setTrackedWithSessionReplay() }) it('stops recording if startSessionReplayRecording was called', () => { @@ -320,7 +320,7 @@ describe('makeRecorderApi', () => { describe('from WITH_REPLAY to WITH_REPLAY', () => { beforeEach(() => { - sessionManager.setPlanWithSessionReplay() + sessionManager.setTrackedWithSessionReplay() }) it('keeps recording if startSessionReplayRecording was called', () => { @@ -351,7 +351,7 @@ describe('makeRecorderApi', () => { it('starts recording if startSessionReplayRecording was called', () => { rumInit() - sessionManager.setPlanWithSessionReplay() + sessionManager.setTrackedWithSessionReplay() lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED) expect(startRecordingSpy).toHaveBeenCalled() @@ -361,7 +361,7 @@ describe('makeRecorderApi', () => { it('does not starts recording if stopSessionReplayRecording was called', () => { rumInit() recorderApi.stop() - sessionManager.setPlanWithSessionReplay() + sessionManager.setTrackedWithSessionReplay() lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED) expect(startRecordingSpy).not.toHaveBeenCalled() @@ -376,7 +376,7 @@ describe('makeRecorderApi', () => { it('keeps not recording if startSessionReplayRecording was called', () => { rumInit() - sessionManager.setPlanWithoutSessionReplay() + sessionManager.setTrackedWithoutSessionReplay() lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED) expect(startRecordingSpy).not.toHaveBeenCalled() diff --git a/packages/rum/src/boot/startRecording.spec.ts b/packages/rum/src/boot/startRecording.spec.ts index ba0edfcf8d..8f0fa2a59f 100644 --- a/packages/rum/src/boot/startRecording.spec.ts +++ b/packages/rum/src/boot/startRecording.spec.ts @@ -136,7 +136,7 @@ describe('startRecording', () => { document.body.dispatchEvent(createNewEvent('click', { clientX: 1, clientY: 2 })) - sessionManager.setId('new-session-id').setPlanWithSessionReplay() + sessionManager.setId('new-session-id').setTrackedWithSessionReplay() flushSegment(lifeCycle) document.body.dispatchEvent(createNewEvent('click', { clientX: 1, clientY: 2 })) diff --git a/packages/rum/src/domain/getSessionReplayLink.spec.ts b/packages/rum/src/domain/getSessionReplayLink.spec.ts index 0b161e0d30..f464a6561b 100644 --- a/packages/rum/src/domain/getSessionReplayLink.spec.ts +++ b/packages/rum/src/domain/getSessionReplayLink.spec.ts @@ -52,7 +52,7 @@ describe('getReplayLink', () => { }) it('return a param if replay is sampled out', () => { - const sessionManager = createRumSessionManagerMock().setId('session-id-1').setPlanWithoutSessionReplay() + const sessionManager = createRumSessionManagerMock().setId('session-id-1').setTrackedWithoutSessionReplay() const viewContexts = { findView: () => ({ id: 'view-id-1', diff --git a/sandbox/index.html b/sandbox/index.html index 4a9477b193..6c187bffd7 100644 --- a/sandbox/index.html +++ b/sandbox/index.html @@ -16,7 +16,6 @@ telemetryConfigurationSampleRate: 100, enableExperimentalFeatures: [], }) - DD_RUM.startSessionReplayRecording() DD_LOGS.init({ clientToken: 'xxx', telemetrySampleRate: 100, From be9d18fb301bbfa14990894ff5bde4bd5b806e0e Mon Sep 17 00:00:00 2001 From: Bastien Caudan <1331991+bcaudan@users.noreply.github.com> Date: Tue, 25 Jul 2023 14:47:03 +0200 Subject: [PATCH 23/27] =?UTF-8?q?=E2=9C=A8=20[RUM-255]=20add=20allowUntrus?= =?UTF-8?q?tedEvents=20config=20parameter=20(#2347)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ♻️ remove track(Resources|LongTasks) from core configuration * ✨add allowUntrustedEvents config param * ✨addEventListener: take configuration.allowUntrustedEvents into account * ♻️ propagate signature change * update rum-events-format --- .../core/src/browser/addEventListener.spec.ts | 31 ++++++-- packages/core/src/browser/addEventListener.ts | 7 +- .../src/browser/pageExitObservable.spec.ts | 7 +- .../core/src/browser/pageExitObservable.ts | 6 +- packages/core/src/browser/runOnReadyState.ts | 9 ++- .../core/src/browser/xhrObservable.spec.ts | 7 +- packages/core/src/browser/xhrObservable.ts | 13 ++-- .../configuration/configuration.spec.ts | 21 ++++++ .../src/domain/configuration/configuration.ts | 12 ++- .../domain/report/reportObservable.spec.ts | 9 ++- .../src/domain/report/reportObservable.ts | 9 ++- .../src/domain/session/sessionManager.spec.ts | 73 ++++++++++--------- .../core/src/domain/session/sessionManager.ts | 18 +++-- .../core/src/transport/httpRequest.spec.ts | 15 +++- packages/core/src/transport/httpRequest.ts | 23 ++++-- .../src/transport/startBatchWithReplica.ts | 8 +- packages/logs/src/boot/startLogs.ts | 2 +- .../networkError/networkErrorCollection.ts | 2 +- .../logsCollection/report/reportCollection.ts | 44 +++++------ .../logs/src/domain/logsSessionManager.ts | 7 +- packages/rum-core/src/boot/startRum.ts | 8 +- .../browser/locationChangeObservable.spec.ts | 5 +- .../src/browser/locationChangeObservable.ts | 15 ++-- .../src/browser/performanceCollection.spec.ts | 6 +- .../src/browser/performanceCollection.ts | 30 +++++--- packages/rum-core/src/browser/scroll.spec.ts | 11 ++- .../src/browser/viewportObservable.spec.ts | 5 +- .../src/browser/viewportObservable.ts | 10 ++- packages/rum-core/src/domain/assembly.ts | 2 +- packages/rum-core/src/domain/configuration.ts | 2 + .../domain/contexts/displayContext.spec.ts | 9 ++- .../src/domain/contexts/displayContext.ts | 5 +- .../domain/contexts/pageStateHistory.spec.ts | 10 ++- .../src/domain/contexts/pageStateHistory.ts | 3 + .../rum-core/src/domain/requestCollection.ts | 2 +- .../action/listenActionEvents.spec.ts | 5 +- .../action/listenActionEvents.ts | 10 ++- .../action/trackClickActions.ts | 2 +- .../error/errorCollection.ts | 4 +- .../error/trackReportError.spec.ts | 5 +- .../error/trackReportError.ts | 25 ++++--- .../view/trackFirstHidden.spec.ts | 27 +++++-- .../view/trackFirstHidden.ts | 4 +- .../view/trackInitialViewTimings.spec.ts | 18 ++++- .../view/trackInitialViewTimings.ts | 21 ++++-- .../view/trackViewMetrics.spec.ts | 6 ++ .../view/trackViewMetrics.ts | 6 +- .../rumEventsCollection/view/trackViews.ts | 2 +- .../rum-core/src/domain/rumSessionManager.ts | 7 +- .../rum-core/src/transport/startRumBatch.ts | 2 +- packages/rum/src/boot/recorderApi.spec.ts | 4 +- packages/rum/src/boot/recorderApi.ts | 4 +- packages/rum/src/boot/startRecording.spec.ts | 6 +- packages/rum/src/boot/startRecording.ts | 5 +- .../domain/record/observers/focusObserver.ts | 5 +- .../record/observers/inputObserver.spec.ts | 24 +++--- .../domain/record/observers/inputObserver.ts | 7 +- .../observers/mediaInteractionObserver.ts | 7 +- .../mouseInteractionObserver.spec.ts | 5 +- .../observers/mouseInteractionObserver.ts | 11 ++- .../record/observers/moveObserver.spec.ts | 5 +- .../domain/record/observers/moveObserver.ts | 5 +- .../src/domain/record/observers/observers.ts | 25 ++++--- .../domain/record/observers/scrollObserver.ts | 5 +- .../observers/viewportResizeObserver.ts | 27 +++++-- packages/rum/src/domain/record/record.ts | 10 +-- .../domain/record/shadowRootsController.ts | 2 +- .../domain/segmentCollection/segment.spec.ts | 4 +- .../src/domain/segmentCollection/segment.ts | 3 + .../segmentCollection.spec.ts | 5 +- .../segmentCollection/segmentCollection.ts | 9 ++- .../startDeflateWorker.spec.ts | 41 ++++++----- .../segmentCollection/startDeflateWorker.ts | 10 ++- 73 files changed, 541 insertions(+), 278 deletions(-) diff --git a/packages/core/src/browser/addEventListener.spec.ts b/packages/core/src/browser/addEventListener.spec.ts index bda902127d..9c271af7b7 100644 --- a/packages/core/src/browser/addEventListener.spec.ts +++ b/packages/core/src/browser/addEventListener.spec.ts @@ -1,12 +1,16 @@ +import type { Configuration } from '@datadog/browser-core' import { createNewEvent, stubZoneJs } from '../../test' import { noop } from '../tools/utils/functionUtils' import { addEventListener, DOM_EVENT } from './addEventListener' describe('addEventListener', () => { + let configuration: Configuration + describe('Zone.js support', () => { let zoneJsStub: ReturnType beforeEach(() => { + configuration = { allowUntrustedEvents: false } as Configuration zoneJsStub = stubZoneJs() }) @@ -19,7 +23,7 @@ describe('addEventListener', () => { const eventTarget = document.createElement('div') zoneJsStub.replaceProperty(eventTarget, 'addEventListener', zoneJsPatchedAddEventListener) - addEventListener(eventTarget, DOM_EVENT.CLICK, noop) + addEventListener(configuration, eventTarget, DOM_EVENT.CLICK, noop) expect(zoneJsPatchedAddEventListener).not.toHaveBeenCalled() }) @@ -28,17 +32,21 @@ describe('addEventListener', () => { const eventTarget = document.createElement('div') zoneJsStub.replaceProperty(eventTarget, 'removeEventListener', zoneJsPatchedRemoveEventListener) - const { stop } = addEventListener(eventTarget, DOM_EVENT.CLICK, noop) + const { stop } = addEventListener(configuration, eventTarget, DOM_EVENT.CLICK, noop) stop() expect(zoneJsPatchedRemoveEventListener).not.toHaveBeenCalled() }) }) describe('Untrusted event', () => { + beforeEach(() => { + configuration = { allowUntrustedEvents: false } as Configuration + }) + it('should be ignored if __ddIsTrusted is absent', () => { const listener = jasmine.createSpy() const eventTarget = document.createElement('div') - addEventListener(eventTarget, DOM_EVENT.CLICK, listener) + addEventListener(configuration, eventTarget, DOM_EVENT.CLICK, listener) const event = createNewEvent(DOM_EVENT.CLICK, { __ddIsTrusted: undefined }) eventTarget.dispatchEvent(event) @@ -48,7 +56,7 @@ describe('addEventListener', () => { it('should be ignored if __ddIsTrusted is false', () => { const listener = jasmine.createSpy() const eventTarget = document.createElement('div') - addEventListener(eventTarget, DOM_EVENT.CLICK, listener) + addEventListener(configuration, eventTarget, DOM_EVENT.CLICK, listener) const event = createNewEvent(DOM_EVENT.CLICK, { __ddIsTrusted: false }) eventTarget.dispatchEvent(event) @@ -58,12 +66,25 @@ describe('addEventListener', () => { it('should not be ignored if __ddIsTrusted is true', () => { const listener = jasmine.createSpy() const eventTarget = document.createElement('div') - addEventListener(eventTarget, DOM_EVENT.CLICK, listener) + addEventListener(configuration, eventTarget, DOM_EVENT.CLICK, listener) const event = createNewEvent(DOM_EVENT.CLICK, { __ddIsTrusted: true }) eventTarget.dispatchEvent(event) expect(listener).toHaveBeenCalled() }) + + it('should not be ignored if allowUntrustedEvents is true', () => { + const listener = jasmine.createSpy() + const eventTarget = document.createElement('div') + configuration = { allowUntrustedEvents: true } as Configuration + + addEventListener(configuration, eventTarget, DOM_EVENT.CLICK, listener) + + const event = createNewEvent(DOM_EVENT.CLICK, { __ddIsTrusted: undefined }) + eventTarget.dispatchEvent(event) + + expect(listener).toHaveBeenCalled() + }) }) }) diff --git a/packages/core/src/browser/addEventListener.ts b/packages/core/src/browser/addEventListener.ts index e393e0b945..640e17ce5f 100644 --- a/packages/core/src/browser/addEventListener.ts +++ b/packages/core/src/browser/addEventListener.ts @@ -1,5 +1,6 @@ import { monitor } from '../tools/monitor' import { getZoneJsOriginalValue } from '../tools/getZoneJsOriginalValue' +import type { Configuration } from '../domain/configuration' import type { VisualViewport, VisualViewportEventMap } from './types' export type TrustableEvent = E & { __ddIsTrusted?: boolean } @@ -86,12 +87,13 @@ type EventMapFor = T extends Window * * returns a `stop` function to remove the listener */ export function addEventListener & string>( + configuration: Configuration, eventTarget: Target, eventName: EventName, listener: (event: EventMapFor[EventName]) => void, options?: AddEventListenerOptions ) { - return addEventListeners(eventTarget, [eventName], listener, options) + return addEventListeners(configuration, eventTarget, [eventName], listener, options) } /** @@ -107,13 +109,14 @@ export function addEventListener & string>( + configuration: Configuration, eventTarget: Target, eventNames: EventName[], listener: (event: EventMapFor[EventName]) => void, { once, capture, passive }: AddEventListenerOptions = {} ) { const wrappedListener = monitor((event: TrustableEvent) => { - if (!event.isTrusted && !event.__ddIsTrusted) { + if (!event.isTrusted && !event.__ddIsTrusted && !configuration.allowUntrustedEvents) { return } if (once) { diff --git a/packages/core/src/browser/pageExitObservable.spec.ts b/packages/core/src/browser/pageExitObservable.spec.ts index 271bf80aad..e2382b6ca9 100644 --- a/packages/core/src/browser/pageExitObservable.spec.ts +++ b/packages/core/src/browser/pageExitObservable.spec.ts @@ -1,3 +1,4 @@ +import type { Configuration } from '../domain/configuration' import { createNewEvent, restorePageVisibility, setPageVisibility } from '../../test' import { resetExperimentalFeatures, addExperimentalFeatures, ExperimentalFeature } from '../tools/experimentalFeatures' import type { Subscription } from '../tools/observable' @@ -7,10 +8,12 @@ import { PageExitReason, createPageExitObservable } from './pageExitObservable' describe('createPageExitObservable', () => { let pageExitSubscription: Subscription let onExitSpy: jasmine.Spy<(event: PageExitEvent) => void> + let configuration: Configuration beforeEach(() => { onExitSpy = jasmine.createSpy() - pageExitSubscription = createPageExitObservable().subscribe(onExitSpy) + configuration = {} as Configuration + pageExitSubscription = createPageExitObservable(configuration).subscribe(onExitSpy) }) afterEach(() => { @@ -22,7 +25,7 @@ describe('createPageExitObservable', () => { it('notifies when the page fires pagehide if ff pagehide is enabled', () => { addExperimentalFeatures([ExperimentalFeature.PAGEHIDE]) onExitSpy = jasmine.createSpy() - pageExitSubscription = createPageExitObservable().subscribe(onExitSpy) + pageExitSubscription = createPageExitObservable(configuration).subscribe(onExitSpy) window.dispatchEvent(createNewEvent('pagehide')) window.dispatchEvent(createNewEvent('beforeunload')) diff --git a/packages/core/src/browser/pageExitObservable.ts b/packages/core/src/browser/pageExitObservable.ts index bbc15811f2..465d2c2772 100644 --- a/packages/core/src/browser/pageExitObservable.ts +++ b/packages/core/src/browser/pageExitObservable.ts @@ -2,6 +2,7 @@ import { isExperimentalFeatureEnabled, ExperimentalFeature } from '../tools/expe import { Observable } from '../tools/observable' import { objectValues, includes } from '../tools/utils/polyfills' import { noop } from '../tools/utils/functionUtils' +import type { Configuration } from '../domain/configuration' import { addEventListeners, addEventListener, DOM_EVENT } from './addEventListener' export const PageExitReason = { @@ -17,10 +18,11 @@ export interface PageExitEvent { reason: PageExitReason } -export function createPageExitObservable(): Observable { +export function createPageExitObservable(configuration: Configuration): Observable { const observable = new Observable(() => { const pagehideEnabled = isExperimentalFeatureEnabled(ExperimentalFeature.PAGEHIDE) const { stop: stopListeners } = addEventListeners( + configuration, window, [DOM_EVENT.VISIBILITY_CHANGE, DOM_EVENT.FREEZE, DOM_EVENT.PAGE_HIDE], (event) => { @@ -48,7 +50,7 @@ export function createPageExitObservable(): Observable { let stopBeforeUnloadListener = noop if (!pagehideEnabled) { - stopBeforeUnloadListener = addEventListener(window, DOM_EVENT.BEFORE_UNLOAD, () => { + stopBeforeUnloadListener = addEventListener(configuration, window, DOM_EVENT.BEFORE_UNLOAD, () => { observable.notify({ reason: PageExitReason.UNLOADING }) }).stop } diff --git a/packages/core/src/browser/runOnReadyState.ts b/packages/core/src/browser/runOnReadyState.ts index b33d314583..143d64b3ff 100644 --- a/packages/core/src/browser/runOnReadyState.ts +++ b/packages/core/src/browser/runOnReadyState.ts @@ -1,10 +1,15 @@ +import type { Configuration } from '../domain/configuration' import { DOM_EVENT, addEventListener } from './addEventListener' -export function runOnReadyState(expectedReadyState: 'complete' | 'interactive', callback: () => void) { +export function runOnReadyState( + configuration: Configuration, + expectedReadyState: 'complete' | 'interactive', + callback: () => void +) { if (document.readyState === expectedReadyState || document.readyState === 'complete') { callback() } else { const eventName = expectedReadyState === 'complete' ? DOM_EVENT.LOAD : DOM_EVENT.DOM_CONTENT_LOADED - addEventListener(window, eventName, callback, { once: true }) + addEventListener(configuration, window, eventName, callback, { once: true }) } } diff --git a/packages/core/src/browser/xhrObservable.spec.ts b/packages/core/src/browser/xhrObservable.spec.ts index 9aaf5fd74d..82088dc71e 100644 --- a/packages/core/src/browser/xhrObservable.spec.ts +++ b/packages/core/src/browser/xhrObservable.spec.ts @@ -1,3 +1,4 @@ +import type { Configuration } from '../domain/configuration' import { withXhr, stubXhr } from '../../test' import { isIE } from '../tools/utils/browserDetection' import type { Subscription } from '../tools/observable' @@ -10,9 +11,11 @@ describe('xhr observable', () => { let requests: XhrCompleteContext[] let stubXhrManager: { reset(): void } let originalXhrStubSend: XMLHttpRequest['send'] + let configuration: Configuration beforeEach(() => { stubXhrManager = stubXhr() + configuration = {} as Configuration // eslint-disable-next-line @typescript-eslint/unbound-method originalXhrStubSend = XMLHttpRequest.prototype.send @@ -27,7 +30,7 @@ describe('xhr observable', () => { }) function startTrackingRequests() { - requestsTrackingSubscription = initXhrObservable().subscribe((context) => { + requestsTrackingSubscription = initXhrObservable(configuration).subscribe((context) => { if (context.state === 'complete') { requests.push(context) } @@ -217,7 +220,7 @@ describe('xhr observable', () => { it('should allow to enhance the context', (done) => { type CustomContext = XhrContext & { foo: string } - contextEditionSubscription = initXhrObservable().subscribe((rawContext) => { + contextEditionSubscription = initXhrObservable(configuration).subscribe((rawContext) => { const context = rawContext as CustomContext if (context.state === 'start') { context.foo = 'bar' diff --git a/packages/core/src/browser/xhrObservable.ts b/packages/core/src/browser/xhrObservable.ts index ee6be47548..1bea24b094 100644 --- a/packages/core/src/browser/xhrObservable.ts +++ b/packages/core/src/browser/xhrObservable.ts @@ -4,6 +4,7 @@ import type { Duration, ClocksState } from '../tools/utils/timeUtils' import { elapsed, clocksNow, timeStampNow } from '../tools/utils/timeUtils' import { normalizeUrl } from '../tools/utils/urlPolyfill' import { shallowClone } from '../tools/utils/objectUtils' +import type { Configuration } from '../domain/configuration' import { addEventListener } from './addEventListener' export interface XhrOpenContext { @@ -30,14 +31,14 @@ export type XhrContext = XhrOpenContext | XhrStartContext | XhrCompleteContext let xhrObservable: Observable | undefined const xhrContexts = new WeakMap() -export function initXhrObservable() { +export function initXhrObservable(configuration: Configuration) { if (!xhrObservable) { - xhrObservable = createXhrObservable() + xhrObservable = createXhrObservable(configuration) } return xhrObservable } -function createXhrObservable() { +function createXhrObservable(configuration: Configuration) { const observable = new Observable(() => { const { stop: stopInstrumentingStart } = instrumentMethodAndCallOriginal(XMLHttpRequest.prototype, 'open', { before: openXhr, @@ -45,7 +46,7 @@ function createXhrObservable() { const { stop: stopInstrumentingSend } = instrumentMethodAndCallOriginal(XMLHttpRequest.prototype, 'send', { before() { - sendXhr.call(this, observable) + sendXhr.call(this, configuration, observable) }, }) @@ -70,7 +71,7 @@ function openXhr(this: XMLHttpRequest, method: string, url: string | URL | undef }) } -function sendXhr(this: XMLHttpRequest, observable: Observable) { +function sendXhr(this: XMLHttpRequest, configuration: Configuration, observable: Observable) { const context = xhrContexts.get(this) if (!context) { return @@ -111,7 +112,7 @@ function sendXhr(this: XMLHttpRequest, observable: Observable) { observable.notify(shallowClone(completeContext)) } - const { stop: unsubscribeLoadEndListener } = addEventListener(this, 'loadend', onEnd) + const { stop: unsubscribeLoadEndListener } = addEventListener(configuration, this, 'loadend', onEnd) observable.notify(startContext) } diff --git a/packages/core/src/domain/configuration/configuration.spec.ts b/packages/core/src/domain/configuration/configuration.spec.ts index ce854f3604..39eeeb46a3 100644 --- a/packages/core/src/domain/configuration/configuration.spec.ts +++ b/packages/core/src/domain/configuration/configuration.spec.ts @@ -165,4 +165,25 @@ describe('validateAndBuildConfiguration', () => { expect(displaySpy).toHaveBeenCalledWith('beforeSend threw an error:', myError) }) }) + + describe('allowUntrustedEvents', () => { + it('defaults to false', () => { + expect(validateAndBuildConfiguration({ clientToken: 'yes' })!.allowUntrustedEvents).toBeFalse() + }) + + it('is set to provided value', () => { + expect( + validateAndBuildConfiguration({ clientToken: 'yes', allowUntrustedEvents: true })!.allowUntrustedEvents + ).toBeTrue() + expect( + validateAndBuildConfiguration({ clientToken: 'yes', allowUntrustedEvents: false })!.allowUntrustedEvents + ).toBeFalse() + }) + + it('the provided value is cast to boolean', () => { + expect( + validateAndBuildConfiguration({ clientToken: 'yes', allowUntrustedEvents: 'foo' as any })!.allowUntrustedEvents + ).toBeTrue() + }) + }) }) diff --git a/packages/core/src/domain/configuration/configuration.ts b/packages/core/src/domain/configuration/configuration.ts index 09e4a0db84..1054ca76de 100644 --- a/packages/core/src/domain/configuration/configuration.ts +++ b/packages/core/src/domain/configuration/configuration.ts @@ -27,8 +27,8 @@ export interface InitConfiguration { sessionSampleRate?: number | undefined telemetrySampleRate?: number | undefined silentMultipleInit?: boolean | undefined - trackResources?: boolean | undefined - trackLongTasks?: boolean | undefined + allowFallbackToLocalStorage?: boolean | undefined + allowUntrustedEvents?: boolean | undefined // transport options proxy?: string | undefined @@ -44,9 +44,6 @@ export interface InitConfiguration { useSecureSessionCookie?: boolean | undefined trackSessionAcrossSubdomains?: boolean | undefined - // alternate storage option - allowFallbackToLocalStorage?: boolean | undefined - // internal options enableExperimentalFeatures?: string[] | undefined replica?: ReplicaUserConfiguration | undefined @@ -74,6 +71,7 @@ export interface Configuration extends TransportConfiguration { telemetryConfigurationSampleRate: number service: string | undefined silentMultipleInit: boolean + allowUntrustedEvents: boolean // Event limits eventRateLimiterThreshold: number // Limit the maximum number of actions, errors and logs per minutes @@ -129,6 +127,7 @@ export function validateAndBuildConfiguration(initConfiguration: InitConfigurati telemetryConfigurationSampleRate: initConfiguration.telemetryConfigurationSampleRate ?? 5, service: initConfiguration.service, silentMultipleInit: !!initConfiguration.silentMultipleInit, + allowUntrustedEvents: !!initConfiguration.allowUntrustedEvents, /** * beacon payload max queue size implementation is 64kb @@ -166,8 +165,7 @@ export function serializeConfiguration(initConfiguration: InitConfiguration): Pa use_proxy: !!initConfiguration.proxy, silent_multiple_init: initConfiguration.silentMultipleInit, track_session_across_subdomains: initConfiguration.trackSessionAcrossSubdomains, - track_resources: initConfiguration.trackResources, - track_long_task: initConfiguration.trackLongTasks, allow_fallback_to_local_storage: !!initConfiguration.allowFallbackToLocalStorage, + allow_untrusted_events: !!initConfiguration.allowUntrustedEvents, } } diff --git a/packages/core/src/domain/report/reportObservable.spec.ts b/packages/core/src/domain/report/reportObservable.spec.ts index 41554ed238..27a8c880fc 100644 --- a/packages/core/src/domain/report/reportObservable.spec.ts +++ b/packages/core/src/domain/report/reportObservable.spec.ts @@ -1,5 +1,6 @@ import { stubReportingObserver, stubCspEventListener } from '../../../test' import type { Subscription } from '../../tools/observable' +import type { Configuration } from '../configuration' import { initReportObservable, RawReportType } from './reportObservable' describe('report observable', () => { @@ -7,8 +8,10 @@ describe('report observable', () => { let cspEventListenerStub: ReturnType let consoleSubscription: Subscription let notifyReport: jasmine.Spy + let configuration: Configuration beforeEach(() => { + configuration = {} as Configuration reportingObserverStub = stubReportingObserver() cspEventListenerStub = stubCspEventListener() notifyReport = jasmine.createSpy('notifyReport') @@ -20,7 +23,7 @@ describe('report observable', () => { }) ;[RawReportType.deprecation, RawReportType.intervention].forEach((type) => { it(`should notify ${type} reports`, () => { - consoleSubscription = initReportObservable([type]).subscribe(notifyReport) + consoleSubscription = initReportObservable(configuration, [type]).subscribe(notifyReport) reportingObserverStub.raiseReport(type) const [report] = notifyReport.calls.mostRecent().args @@ -36,7 +39,7 @@ describe('report observable', () => { }) it(`should compute stack for ${RawReportType.intervention}`, () => { - consoleSubscription = initReportObservable([RawReportType.intervention]).subscribe(notifyReport) + consoleSubscription = initReportObservable(configuration, [RawReportType.intervention]).subscribe(notifyReport) reportingObserverStub.raiseReport(RawReportType.intervention) const [report] = notifyReport.calls.mostRecent().args @@ -46,7 +49,7 @@ describe('report observable', () => { }) it(`should notify ${RawReportType.cspViolation}`, () => { - consoleSubscription = initReportObservable([RawReportType.cspViolation]).subscribe(notifyReport) + consoleSubscription = initReportObservable(configuration, [RawReportType.cspViolation]).subscribe(notifyReport) cspEventListenerStub.dispatchEvent() expect(notifyReport).toHaveBeenCalledOnceWith({ diff --git a/packages/core/src/domain/report/reportObservable.ts b/packages/core/src/domain/report/reportObservable.ts index e0cee12700..6866931b09 100644 --- a/packages/core/src/domain/report/reportObservable.ts +++ b/packages/core/src/domain/report/reportObservable.ts @@ -4,6 +4,7 @@ import { mergeObservables, Observable } from '../../tools/observable' import { addEventListener, DOM_EVENT } from '../../browser/addEventListener' import { includes } from '../../tools/utils/polyfills' import { safeTruncate } from '../../tools/utils/stringUtils' +import type { Configuration } from '../configuration' import type { ReportType, InterventionReport, DeprecationReport } from './browser.types' export const RawReportType = { @@ -21,11 +22,11 @@ export interface RawReport { stack?: string } -export function initReportObservable(apis: RawReportType[]) { +export function initReportObservable(configuration: Configuration, apis: RawReportType[]) { const observables: Array> = [] if (includes(apis, RawReportType.cspViolation)) { - observables.push(createCspViolationReportObservable()) + observables.push(createCspViolationReportObservable(configuration)) } const reportTypes = apis.filter((api: RawReportType): api is ReportType => api !== RawReportType.cspViolation) @@ -62,9 +63,9 @@ function createReportObservable(reportTypes: ReportType[]) { return observable } -function createCspViolationReportObservable() { +function createCspViolationReportObservable(configuration: Configuration) { const observable = new Observable(() => { - const { stop } = addEventListener(document, DOM_EVENT.SECURITY_POLICY_VIOLATION, (event) => { + const { stop } = addEventListener(configuration, document, DOM_EVENT.SECURITY_POLICY_VIOLATION, (event) => { observable.notify(buildRawReportFromCspViolation(event)) }) diff --git a/packages/core/src/domain/session/sessionManager.spec.ts b/packages/core/src/domain/session/sessionManager.spec.ts index cee406eb41..6c47cf9527 100644 --- a/packages/core/src/domain/session/sessionManager.spec.ts +++ b/packages/core/src/domain/session/sessionManager.spec.ts @@ -5,6 +5,7 @@ import type { RelativeTime } from '../../tools/utils/timeUtils' import { isIE } from '../../tools/utils/browserDetection' import { DOM_EVENT } from '../../browser/addEventListener' import { ONE_HOUR, ONE_SECOND } from '../../tools/utils/timeUtils' +import type { Configuration } from '../configuration' import type { SessionManager } from './sessionManager' import { startSessionManager, stopSessionManager, VISIBILITY_CHECK_DELAY } from './sessionManager' import { SESSION_EXPIRATION_DELAY, SESSION_TIME_OUT_DELAY } from './sessionConstants' @@ -33,6 +34,7 @@ describe('startSessionManager', () => { const SECOND_PRODUCT_KEY = 'second' const STORE_TYPE: SessionStoreStrategyType = { type: 'Cookie', cookieOptions: {} } let clock: Clock + let configuration: Configuration function expireSessionCookie() { setCookie(SESSION_STORE_KEY, '', DURATION) @@ -72,6 +74,7 @@ describe('startSessionManager', () => { if (isIE()) { pending('no full rum support') } + configuration = { sessionStoreStrategyType: STORE_TYPE } as Configuration clock = mockClock() }) @@ -85,14 +88,14 @@ describe('startSessionManager', () => { describe('cookie management', () => { it('when tracked, should store tracking type and session id', () => { - const sessionManager = startSessionManager(STORE_TYPE, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) + const sessionManager = startSessionManager(configuration, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) expectSessionIdToBeDefined(sessionManager) expectTrackingTypeToBe(sessionManager, FIRST_PRODUCT_KEY, FakeTrackingType.TRACKED) }) it('when not tracked should store tracking type', () => { - const sessionManager = startSessionManager(STORE_TYPE, FIRST_PRODUCT_KEY, () => NOT_TRACKED_SESSION_STATE) + const sessionManager = startSessionManager(configuration, FIRST_PRODUCT_KEY, () => NOT_TRACKED_SESSION_STATE) expectSessionIdToNotBeDefined(sessionManager) expectTrackingTypeToBe(sessionManager, FIRST_PRODUCT_KEY, FakeTrackingType.NOT_TRACKED) @@ -101,7 +104,7 @@ describe('startSessionManager', () => { it('when tracked should keep existing tracking type and session id', () => { setCookie(SESSION_STORE_KEY, 'id=abcdef&first=tracked', DURATION) - const sessionManager = startSessionManager(STORE_TYPE, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) + const sessionManager = startSessionManager(configuration, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) expectSessionIdToBe(sessionManager, 'abcdef') expectTrackingTypeToBe(sessionManager, FIRST_PRODUCT_KEY, FakeTrackingType.TRACKED) @@ -110,7 +113,7 @@ describe('startSessionManager', () => { it('when not tracked should keep existing tracking type', () => { setCookie(SESSION_STORE_KEY, 'first=not-tracked', DURATION) - const sessionManager = startSessionManager(STORE_TYPE, FIRST_PRODUCT_KEY, () => NOT_TRACKED_SESSION_STATE) + const sessionManager = startSessionManager(configuration, FIRST_PRODUCT_KEY, () => NOT_TRACKED_SESSION_STATE) expectSessionIdToNotBeDefined(sessionManager) expectTrackingTypeToBe(sessionManager, FIRST_PRODUCT_KEY, FakeTrackingType.NOT_TRACKED) @@ -125,32 +128,32 @@ describe('startSessionManager', () => { }) it('should be called with an empty value if the cookie is not defined', () => { - startSessionManager(STORE_TYPE, FIRST_PRODUCT_KEY, spy) + startSessionManager(configuration, FIRST_PRODUCT_KEY, spy) expect(spy).toHaveBeenCalledWith(undefined) }) it('should be called with an invalid value if the cookie has an invalid value', () => { setCookie(SESSION_STORE_KEY, 'first=invalid', DURATION) - startSessionManager(STORE_TYPE, FIRST_PRODUCT_KEY, spy) + startSessionManager(configuration, FIRST_PRODUCT_KEY, spy) expect(spy).toHaveBeenCalledWith('invalid') }) it('should be called with TRACKED', () => { setCookie(SESSION_STORE_KEY, 'first=tracked', DURATION) - startSessionManager(STORE_TYPE, FIRST_PRODUCT_KEY, spy) + startSessionManager(configuration, FIRST_PRODUCT_KEY, spy) expect(spy).toHaveBeenCalledWith(FakeTrackingType.TRACKED) }) it('should be called with NOT_TRACKED', () => { setCookie(SESSION_STORE_KEY, 'first=not-tracked', DURATION) - startSessionManager(STORE_TYPE, FIRST_PRODUCT_KEY, spy) + startSessionManager(configuration, FIRST_PRODUCT_KEY, spy) expect(spy).toHaveBeenCalledWith(FakeTrackingType.NOT_TRACKED) }) }) describe('session renewal', () => { it('should renew on activity after expiration', () => { - const sessionManager = startSessionManager(STORE_TYPE, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) + const sessionManager = startSessionManager(configuration, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) const renewSessionSpy = jasmine.createSpy() sessionManager.renewObservable.subscribe(renewSessionSpy) @@ -168,7 +171,7 @@ describe('startSessionManager', () => { }) it('should not renew on visibility after expiration', () => { - const sessionManager = startSessionManager(STORE_TYPE, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) + const sessionManager = startSessionManager(configuration, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) const renewSessionSpy = jasmine.createSpy() sessionManager.renewObservable.subscribe(renewSessionSpy) @@ -183,17 +186,17 @@ describe('startSessionManager', () => { describe('multiple startSessionManager calls', () => { it('should re-use the same session id', () => { - const firstSessionManager = startSessionManager(STORE_TYPE, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) + const firstSessionManager = startSessionManager(configuration, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) const idA = firstSessionManager.findActiveSession()!.id - const secondSessionManager = startSessionManager(STORE_TYPE, SECOND_PRODUCT_KEY, () => TRACKED_SESSION_STATE) + const secondSessionManager = startSessionManager(configuration, SECOND_PRODUCT_KEY, () => TRACKED_SESSION_STATE) const idB = secondSessionManager.findActiveSession()!.id expect(idA).toBe(idB) }) it('should not erase other session type', () => { - startSessionManager(STORE_TYPE, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) + startSessionManager(configuration, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) // schedule an expandOrRenewSession document.dispatchEvent(createNewEvent(DOM_EVENT.CLICK)) @@ -203,7 +206,7 @@ describe('startSessionManager', () => { // expand first session cookie cache document.dispatchEvent(createNewEvent(DOM_EVENT.VISIBILITY_CHANGE)) - startSessionManager(STORE_TYPE, SECOND_PRODUCT_KEY, () => TRACKED_SESSION_STATE) + startSessionManager(configuration, SECOND_PRODUCT_KEY, () => TRACKED_SESSION_STATE) // cookie correctly set expect(getCookie(SESSION_STORE_KEY)).toContain('first') @@ -217,21 +220,25 @@ describe('startSessionManager', () => { }) it('should have independent tracking types', () => { - const firstSessionManager = startSessionManager(STORE_TYPE, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) - const secondSessionManager = startSessionManager(STORE_TYPE, SECOND_PRODUCT_KEY, () => NOT_TRACKED_SESSION_STATE) + const firstSessionManager = startSessionManager(configuration, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) + const secondSessionManager = startSessionManager( + configuration, + SECOND_PRODUCT_KEY, + () => NOT_TRACKED_SESSION_STATE + ) expect(firstSessionManager.findActiveSession()!.trackingType).toEqual(FakeTrackingType.TRACKED) expect(secondSessionManager.findActiveSession()!.trackingType).toEqual(FakeTrackingType.NOT_TRACKED) }) it('should notify each expire and renew observables', () => { - const firstSessionManager = startSessionManager(STORE_TYPE, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) + const firstSessionManager = startSessionManager(configuration, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) const expireSessionASpy = jasmine.createSpy() firstSessionManager.expireObservable.subscribe(expireSessionASpy) const renewSessionASpy = jasmine.createSpy() firstSessionManager.renewObservable.subscribe(renewSessionASpy) - const secondSessionManager = startSessionManager(STORE_TYPE, SECOND_PRODUCT_KEY, () => TRACKED_SESSION_STATE) + const secondSessionManager = startSessionManager(configuration, SECOND_PRODUCT_KEY, () => TRACKED_SESSION_STATE) const expireSessionBSpy = jasmine.createSpy() secondSessionManager.expireObservable.subscribe(expireSessionBSpy) const renewSessionBSpy = jasmine.createSpy() @@ -253,7 +260,7 @@ describe('startSessionManager', () => { describe('session timeout', () => { it('should expire the session when the time out delay is reached', () => { - const sessionManager = startSessionManager(STORE_TYPE, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) + const sessionManager = startSessionManager(configuration, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) const expireSessionSpy = jasmine.createSpy() sessionManager.expireObservable.subscribe(expireSessionSpy) @@ -269,7 +276,7 @@ describe('startSessionManager', () => { it('should renew an existing timed out session', () => { setCookie(SESSION_STORE_KEY, `id=abcde&first=tracked&created=${Date.now() - SESSION_TIME_OUT_DELAY}`, DURATION) - const sessionManager = startSessionManager(STORE_TYPE, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) + const sessionManager = startSessionManager(configuration, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) const expireSessionSpy = jasmine.createSpy() sessionManager.expireObservable.subscribe(expireSessionSpy) @@ -281,7 +288,7 @@ describe('startSessionManager', () => { it('should not add created date to an existing session from an older versions', () => { setCookie(SESSION_STORE_KEY, 'id=abcde&first=tracked', DURATION) - const sessionManager = startSessionManager(STORE_TYPE, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) + const sessionManager = startSessionManager(configuration, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) expect(sessionManager.findActiveSession()!.id).toBe('abcde') expect(getCookie(SESSION_STORE_KEY)).not.toContain('created=') @@ -298,7 +305,7 @@ describe('startSessionManager', () => { }) it('should expire the session after expiration delay', () => { - const sessionManager = startSessionManager(STORE_TYPE, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) + const sessionManager = startSessionManager(configuration, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) const expireSessionSpy = jasmine.createSpy() sessionManager.expireObservable.subscribe(expireSessionSpy) @@ -310,7 +317,7 @@ describe('startSessionManager', () => { }) it('should expand duration on activity', () => { - const sessionManager = startSessionManager(STORE_TYPE, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) + const sessionManager = startSessionManager(configuration, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) const expireSessionSpy = jasmine.createSpy() sessionManager.expireObservable.subscribe(expireSessionSpy) @@ -329,7 +336,7 @@ describe('startSessionManager', () => { }) it('should expand not tracked session duration on activity', () => { - const sessionManager = startSessionManager(STORE_TYPE, FIRST_PRODUCT_KEY, () => NOT_TRACKED_SESSION_STATE) + const sessionManager = startSessionManager(configuration, FIRST_PRODUCT_KEY, () => NOT_TRACKED_SESSION_STATE) const expireSessionSpy = jasmine.createSpy() sessionManager.expireObservable.subscribe(expireSessionSpy) @@ -350,7 +357,7 @@ describe('startSessionManager', () => { it('should expand session on visibility', () => { setPageVisibility('visible') - const sessionManager = startSessionManager(STORE_TYPE, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) + const sessionManager = startSessionManager(configuration, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) const expireSessionSpy = jasmine.createSpy() sessionManager.expireObservable.subscribe(expireSessionSpy) @@ -371,7 +378,7 @@ describe('startSessionManager', () => { it('should expand not tracked session on visibility', () => { setPageVisibility('visible') - const sessionManager = startSessionManager(STORE_TYPE, FIRST_PRODUCT_KEY, () => NOT_TRACKED_SESSION_STATE) + const sessionManager = startSessionManager(configuration, FIRST_PRODUCT_KEY, () => NOT_TRACKED_SESSION_STATE) const expireSessionSpy = jasmine.createSpy() sessionManager.expireObservable.subscribe(expireSessionSpy) @@ -392,7 +399,7 @@ describe('startSessionManager', () => { describe('manual session expiration', () => { it('expires the session when calling expire()', () => { - const sessionManager = startSessionManager(STORE_TYPE, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) + const sessionManager = startSessionManager(configuration, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) const expireSessionSpy = jasmine.createSpy() sessionManager.expireObservable.subscribe(expireSessionSpy) @@ -403,7 +410,7 @@ describe('startSessionManager', () => { }) it('notifies expired session only once when calling expire() multiple times', () => { - const sessionManager = startSessionManager(STORE_TYPE, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) + const sessionManager = startSessionManager(configuration, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) const expireSessionSpy = jasmine.createSpy() sessionManager.expireObservable.subscribe(expireSessionSpy) @@ -415,7 +422,7 @@ describe('startSessionManager', () => { }) it('notifies expired session only once when calling expire() after the session has been expired', () => { - const sessionManager = startSessionManager(STORE_TYPE, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) + const sessionManager = startSessionManager(configuration, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) const expireSessionSpy = jasmine.createSpy() sessionManager.expireObservable.subscribe(expireSessionSpy) @@ -427,7 +434,7 @@ describe('startSessionManager', () => { }) it('renew the session on user activity', () => { - const sessionManager = startSessionManager(STORE_TYPE, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) + const sessionManager = startSessionManager(configuration, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) clock.tick(STORAGE_POLL_DELAY) sessionManager.expire() @@ -440,21 +447,21 @@ describe('startSessionManager', () => { describe('session history', () => { it('should return undefined when there is no current session and no startTime', () => { - const sessionManager = startSessionManager(STORE_TYPE, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) + const sessionManager = startSessionManager(configuration, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) expireSessionCookie() expect(sessionManager.findActiveSession()).toBeUndefined() }) it('should return the current session context when there is no start time', () => { - const sessionManager = startSessionManager(STORE_TYPE, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) + const sessionManager = startSessionManager(configuration, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) expect(sessionManager.findActiveSession()!.id).toBeDefined() expect(sessionManager.findActiveSession()!.trackingType).toBeDefined() }) it('should return the session context corresponding to startTime', () => { - const sessionManager = startSessionManager(STORE_TYPE, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) + const sessionManager = startSessionManager(configuration, FIRST_PRODUCT_KEY, () => TRACKED_SESSION_STATE) // 0s to 10s: first session clock.tick(10 * ONE_SECOND - STORAGE_POLL_DELAY) diff --git a/packages/core/src/domain/session/sessionManager.ts b/packages/core/src/domain/session/sessionManager.ts index c23c8f4ffc..68af45ff1f 100644 --- a/packages/core/src/domain/session/sessionManager.ts +++ b/packages/core/src/domain/session/sessionManager.ts @@ -5,9 +5,9 @@ import type { RelativeTime } from '../../tools/utils/timeUtils' import { relativeNow, clocksOrigin, ONE_MINUTE } from '../../tools/utils/timeUtils' import { DOM_EVENT, addEventListener, addEventListeners } from '../../browser/addEventListener' import { clearInterval, setInterval } from '../../tools/timer' +import type { Configuration } from '../configuration' import { SESSION_TIME_OUT_DELAY } from './sessionConstants' import { startSessionStore } from './sessionStore' -import type { SessionStoreStrategyType } from './storeStrategies/sessionStoreStrategy' export interface SessionManager { findActiveSession: (startTime?: RelativeTime) => SessionContext | undefined @@ -26,11 +26,12 @@ const SESSION_CONTEXT_TIMEOUT_DELAY = SESSION_TIME_OUT_DELAY let stopCallbacks: Array<() => void> = [] export function startSessionManager( - sessionStoreStrategyType: SessionStoreStrategyType, + configuration: Configuration, productKey: string, computeSessionState: (rawTrackingType?: string) => { trackingType: TrackingType; isTracked: boolean } ): SessionManager { - const sessionStore = startSessionStore(sessionStoreStrategyType, productKey, computeSessionState) + // TODO - Improve configuration type and remove assertion + const sessionStore = startSessionStore(configuration.sessionStoreStrategyType!, productKey, computeSessionState) stopCallbacks.push(() => sessionStore.stop()) const sessionContextHistory = new ValueHistory>(SESSION_CONTEXT_TIMEOUT_DELAY) @@ -46,8 +47,8 @@ export function startSessionManager( sessionStore.expandOrRenewSession() sessionContextHistory.add(buildSessionContext(), clocksOrigin().relative) - trackActivity(() => sessionStore.expandOrRenewSession()) - trackVisibility(() => sessionStore.expandSession()) + trackActivity(configuration, () => sessionStore.expandOrRenewSession()) + trackVisibility(configuration, () => sessionStore.expandSession()) function buildSessionContext() { return { @@ -69,8 +70,9 @@ export function stopSessionManager() { stopCallbacks = [] } -function trackActivity(expandOrRenewSession: () => void) { +function trackActivity(configuration: Configuration, expandOrRenewSession: () => void) { const { stop } = addEventListeners( + configuration, window, [DOM_EVENT.CLICK, DOM_EVENT.TOUCH_START, DOM_EVENT.KEY_DOWN, DOM_EVENT.SCROLL], expandOrRenewSession, @@ -79,14 +81,14 @@ function trackActivity(expandOrRenewSession: () => void) { stopCallbacks.push(stop) } -function trackVisibility(expandSession: () => void) { +function trackVisibility(configuration: Configuration, expandSession: () => void) { const expandSessionWhenVisible = () => { if (document.visibilityState === 'visible') { expandSession() } } - const { stop } = addEventListener(document, DOM_EVENT.VISIBILITY_CHANGE, expandSessionWhenVisible) + const { stop } = addEventListener(configuration, document, DOM_EVENT.VISIBILITY_CHANGE, expandSessionWhenVisible) stopCallbacks.push(stop) const visibilityCheckInterval = setInterval(expandSessionWhenVisible, VISIBILITY_CHECK_DELAY) diff --git a/packages/core/src/transport/httpRequest.spec.ts b/packages/core/src/transport/httpRequest.spec.ts index f01f948939..f506109b63 100644 --- a/packages/core/src/transport/httpRequest.spec.ts +++ b/packages/core/src/transport/httpRequest.spec.ts @@ -1,6 +1,6 @@ import { collectAsyncCalls, stubEndpointBuilder, interceptRequests } from '../../test' import type { Request } from '../../test' -import type { EndpointBuilder } from '../domain/configuration' +import type { EndpointBuilder, Configuration } from '../domain/configuration' import { createEndpointBuilder } from '../domain/configuration' import { noop } from '../tools/utils/functionUtils' import { createHttpRequest, fetchKeepAliveStrategy, sendXHR } from './httpRequest' @@ -13,12 +13,14 @@ describe('httpRequest', () => { let requests: Request[] let endpointBuilder: EndpointBuilder let request: HttpRequest + let configuration: Configuration beforeEach(() => { + configuration = {} as Configuration interceptor = interceptRequests() requests = interceptor.requests endpointBuilder = stubEndpointBuilder(ENDPOINT_URL) - request = createHttpRequest(endpointBuilder, BATCH_BYTES_LIMIT, noop) + request = createHttpRequest(configuration, endpointBuilder, BATCH_BYTES_LIMIT, noop) }) afterEach(() => { @@ -107,6 +109,7 @@ describe('httpRequest', () => { interceptor.withFetch(() => Promise.resolve({ status: 429, type: 'cors' })) fetchKeepAliveStrategy( + configuration, endpointBuilder, BATCH_BYTES_LIMIT, { data: '{"foo":"bar1"}\n{"foo":"bar2"}', bytesCount: 10 }, @@ -130,6 +133,7 @@ describe('httpRequest', () => { }) fetchKeepAliveStrategy( + configuration, endpointBuilder, BATCH_BYTES_LIMIT, { data: '{"foo":"bar1"}\n{"foo":"bar2"}', bytesCount: 10 }, @@ -148,6 +152,7 @@ describe('httpRequest', () => { }) fetchKeepAliveStrategy( + configuration, endpointBuilder, BATCH_BYTES_LIMIT, { data: '{"foo":"bar1"}\n{"foo":"bar2"}', bytesCount: BATCH_BYTES_LIMIT }, @@ -176,7 +181,7 @@ describe('httpRequest', () => { }) }) - sendXHR('foo', '', onResponseSpy) + sendXHR(configuration, 'foo', '', onResponseSpy) setTimeout(() => { expect(onResponseSpy).toHaveBeenCalledTimes(1) @@ -256,12 +261,14 @@ describe('httpRequest intake parameters', () => { let requests: Request[] let endpointBuilder: EndpointBuilder let request: HttpRequest + let configuration: Configuration beforeEach(() => { + configuration = {} as Configuration interceptor = interceptRequests() requests = interceptor.requests endpointBuilder = createEndpointBuilder({ clientToken }, 'logs', []) - request = createHttpRequest(endpointBuilder, BATCH_BYTES_LIMIT, noop) + request = createHttpRequest(configuration, endpointBuilder, BATCH_BYTES_LIMIT, noop) }) afterEach(() => { diff --git a/packages/core/src/transport/httpRequest.ts b/packages/core/src/transport/httpRequest.ts index f9693f80f5..7f464cf376 100644 --- a/packages/core/src/transport/httpRequest.ts +++ b/packages/core/src/transport/httpRequest.ts @@ -1,4 +1,4 @@ -import type { EndpointBuilder } from '../domain/configuration' +import type { EndpointBuilder, Configuration } from '../domain/configuration' import { addTelemetryError } from '../domain/telemetry' import type { Context } from '../tools/serialisation/context' import { monitor } from '../tools/monitor' @@ -36,13 +36,14 @@ export interface RetryInfo { } export function createHttpRequest( + configuration: Configuration, endpointBuilder: EndpointBuilder, bytesLimit: number, reportError: (error: RawError) => void ) { const retryState = newRetryState() const sendStrategyForRetry = (payload: Payload, onResponse: (r: HttpResponse) => void) => - fetchKeepAliveStrategy(endpointBuilder, bytesLimit, payload, onResponse) + fetchKeepAliveStrategy(configuration, endpointBuilder, bytesLimit, payload, onResponse) return { send: (payload: Payload) => { @@ -53,12 +54,13 @@ export function createHttpRequest( * keep using sendBeaconStrategy on exit */ sendOnExit: (payload: Payload) => { - sendBeaconStrategy(endpointBuilder, bytesLimit, payload) + sendBeaconStrategy(configuration, endpointBuilder, bytesLimit, payload) }, } } function sendBeaconStrategy( + configuration: Configuration, endpointBuilder: EndpointBuilder, bytesLimit: number, { data, bytesCount, flushReason }: Payload @@ -78,7 +80,7 @@ function sendBeaconStrategy( } const xhrUrl = endpointBuilder.build('xhr', flushReason) - sendXHR(xhrUrl, data) + sendXHR(configuration, xhrUrl, data) } let hasReportedBeaconError = false @@ -91,6 +93,7 @@ function reportBeaconError(e: unknown) { } export function fetchKeepAliveStrategy( + configuration: Configuration, endpointBuilder: EndpointBuilder, bytesLimit: number, { data, bytesCount, flushReason, retry }: Payload, @@ -104,12 +107,12 @@ export function fetchKeepAliveStrategy( monitor(() => { const xhrUrl = endpointBuilder.build('xhr', flushReason, retry) // failed to queue the request - sendXHR(xhrUrl, data, onResponse) + sendXHR(configuration, xhrUrl, data, onResponse) }) ) } else { const xhrUrl = endpointBuilder.build('xhr', flushReason, retry) - sendXHR(xhrUrl, data, onResponse) + sendXHR(configuration, xhrUrl, data, onResponse) } } @@ -122,10 +125,16 @@ function isKeepAliveSupported() { } } -export function sendXHR(url: string, data: Payload['data'], onResponse?: (r: HttpResponse) => void) { +export function sendXHR( + configuration: Configuration, + url: string, + data: Payload['data'], + onResponse?: (r: HttpResponse) => void +) { const request = new XMLHttpRequest() request.open('POST', url, true) addEventListener( + configuration, request, 'loadend', () => { diff --git a/packages/core/src/transport/startBatchWithReplica.ts b/packages/core/src/transport/startBatchWithReplica.ts index 08ea9b61d8..76ba750c78 100644 --- a/packages/core/src/transport/startBatchWithReplica.ts +++ b/packages/core/src/transport/startBatchWithReplica.ts @@ -15,15 +15,15 @@ export function startBatchWithReplica( sessionExpireObservable: Observable, replicaEndpoint?: EndpointBuilder ) { - const primaryBatch = createBatch(endpoint) + const primaryBatch = createBatch(configuration, endpoint) let replicaBatch: Batch | undefined if (replicaEndpoint) { - replicaBatch = createBatch(replicaEndpoint) + replicaBatch = createBatch(configuration, replicaEndpoint) } - function createBatch(endpointBuilder: EndpointBuilder) { + function createBatch(configuration: Configuration, endpointBuilder: EndpointBuilder) { return new Batch( - createHttpRequest(endpointBuilder, configuration.batchBytesLimit, reportError), + createHttpRequest(configuration, endpointBuilder, configuration.batchBytesLimit, reportError), createFlushController({ messagesLimit: configuration.batchMessagesLimit, bytesLimit: configuration.batchBytesLimit, diff --git a/packages/logs/src/boot/startLogs.ts b/packages/logs/src/boot/startLogs.ts index 9946cff850..4b70349ac1 100644 --- a/packages/logs/src/boot/startLogs.ts +++ b/packages/logs/src/boot/startLogs.ts @@ -49,7 +49,7 @@ export function startLogs( }) addTelemetryDebug('Error reported to customer', { 'error.message': error.message }) } - const pageExitObservable = createPageExitObservable() + const pageExitObservable = createPageExitObservable(configuration) const session = configuration.sessionStoreStrategyType && !canUseEventBridge() && !willSyntheticsInjectRum() diff --git a/packages/logs/src/domain/logsCollection/networkError/networkErrorCollection.ts b/packages/logs/src/domain/logsCollection/networkError/networkErrorCollection.ts index 06789fc60e..13b6e96bb2 100644 --- a/packages/logs/src/domain/logsCollection/networkError/networkErrorCollection.ts +++ b/packages/logs/src/domain/logsCollection/networkError/networkErrorCollection.ts @@ -22,7 +22,7 @@ export function startNetworkErrorCollection(configuration: LogsConfiguration, li return { stop: noop } } - const xhrSubscription = initXhrObservable().subscribe((context) => { + const xhrSubscription = initXhrObservable(configuration).subscribe((context) => { if (context.state === 'complete') { handleResponse(RequestType.XHR, context) } diff --git a/packages/logs/src/domain/logsCollection/report/reportCollection.ts b/packages/logs/src/domain/logsCollection/report/reportCollection.ts index 937869a2ce..4a257010bb 100644 --- a/packages/logs/src/domain/logsCollection/report/reportCollection.ts +++ b/packages/logs/src/domain/logsCollection/report/reportCollection.ts @@ -25,29 +25,31 @@ const LogStatusForReport = { } export function startReportCollection(configuration: LogsConfiguration, lifeCycle: LifeCycle) { - const reportSubscription = initReportObservable(configuration.forwardReports).subscribe((report: RawReport) => { - let message = report.message - const status = LogStatusForReport[report.type] - let error - if (status === StatusType.error) { - error = { - kind: report.subtype, - stack: report.stack, + const reportSubscription = initReportObservable(configuration, configuration.forwardReports).subscribe( + (report: RawReport) => { + let message = report.message + const status = LogStatusForReport[report.type] + let error + if (status === StatusType.error) { + error = { + kind: report.subtype, + stack: report.stack, + } + } else if (report.stack) { + message += ` Found in ${getFileFromStackTraceString(report.stack)!}` } - } else if (report.stack) { - message += ` Found in ${getFileFromStackTraceString(report.stack)!}` - } - lifeCycle.notify(LifeCycleEventType.RAW_LOG_COLLECTED, { - rawLogsEvent: { - date: timeStampNow(), - message, - origin: ErrorSource.REPORT, - error, - status, - }, - }) - }) + lifeCycle.notify(LifeCycleEventType.RAW_LOG_COLLECTED, { + rawLogsEvent: { + date: timeStampNow(), + message, + origin: ErrorSource.REPORT, + error, + status, + }, + }) + } + ) return { stop: () => { diff --git a/packages/logs/src/domain/logsSessionManager.ts b/packages/logs/src/domain/logsSessionManager.ts index 26fe80a6b0..acbb7d0abb 100644 --- a/packages/logs/src/domain/logsSessionManager.ts +++ b/packages/logs/src/domain/logsSessionManager.ts @@ -19,11 +19,8 @@ export const enum LoggerTrackingType { } export function startLogsSessionManager(configuration: LogsConfiguration): LogsSessionManager { - const sessionManager = startSessionManager( - // TODO - Improve configuration type and remove assertion - configuration.sessionStoreStrategyType!, - LOGS_SESSION_KEY, - (rawTrackingType) => computeSessionState(configuration, rawTrackingType) + const sessionManager = startSessionManager(configuration, LOGS_SESSION_KEY, (rawTrackingType) => + computeSessionState(configuration, rawTrackingType) ) return { findTrackedSession: (startTime) => { diff --git a/packages/rum-core/src/boot/startRum.ts b/packages/rum-core/src/boot/startRum.ts index 0903aaa60c..9e8581b6c0 100644 --- a/packages/rum-core/src/boot/startRum.ts +++ b/packages/rum-core/src/boot/startRum.ts @@ -73,7 +73,7 @@ export function startRum( } const featureFlagContexts = startFeatureFlagContexts(lifeCycle) - const pageExitObservable = createPageExitObservable() + const pageExitObservable = createPageExitObservable(configuration) pageExitObservable.subscribe((event) => { lifeCycle.notify(LifeCycleEventType.PAGE_EXITED, event) }) @@ -102,7 +102,7 @@ export function startRum( } const domMutationObservable = createDOMMutationObservable() - const locationChangeObservable = createLocationChangeObservable(location) + const locationChangeObservable = createLocationChangeObservable(configuration, location) const { viewContexts, pageStateHistory, urlContexts, actionContexts, addAction } = startRumEventCollection( lifeCycle, @@ -133,7 +133,7 @@ export function startRum( webVitalTelemetryDebug, initialViewOptions ) - const { addError } = startErrorCollection(lifeCycle, pageStateHistory, featureFlagContexts) + const { addError } = startErrorCollection(lifeCycle, configuration, pageStateHistory, featureFlagContexts) startRequestCollection(lifeCycle, configuration, session) startPerformanceCollection(lifeCycle, configuration) @@ -182,7 +182,7 @@ export function startRumEventCollection( const viewContexts = startViewContexts(lifeCycle) const urlContexts = startUrlContexts(lifeCycle, locationChangeObservable, location) - const pageStateHistory = startPageStateHistory() + const pageStateHistory = startPageStateHistory(configuration) const { addAction, actionContexts } = startActionCollection( lifeCycle, diff --git a/packages/rum-core/src/browser/locationChangeObservable.spec.ts b/packages/rum-core/src/browser/locationChangeObservable.spec.ts index 3c3dbf6788..08ffbf5ef2 100644 --- a/packages/rum-core/src/browser/locationChangeObservable.spec.ts +++ b/packages/rum-core/src/browser/locationChangeObservable.spec.ts @@ -1,5 +1,6 @@ import type { Observable, Subscription } from '@datadog/browser-core' import { mockLocation } from '@datadog/browser-core/test' +import type { RumConfiguration } from '@datadog/browser-rum-core' import type { LocationChange } from './locationChangeObservable' import { createLocationChangeObservable } from './locationChangeObservable' @@ -9,10 +10,12 @@ describe('locationChangeObservable', () => { let subscription: Subscription let fakeLocation: Partial let cleanupLocation: () => void + let configuration: RumConfiguration beforeEach(() => { ;({ location: fakeLocation, cleanup: cleanupLocation } = mockLocation('/foo')) - observable = createLocationChangeObservable(fakeLocation as Location) + configuration = {} as RumConfiguration + observable = createLocationChangeObservable(configuration, fakeLocation as Location) observer = jasmine.createSpy('obs') subscription = observable.subscribe(observer) }) diff --git a/packages/rum-core/src/browser/locationChangeObservable.ts b/packages/rum-core/src/browser/locationChangeObservable.ts index a9f616b2b7..8e7e80a9c4 100644 --- a/packages/rum-core/src/browser/locationChangeObservable.ts +++ b/packages/rum-core/src/browser/locationChangeObservable.ts @@ -5,17 +5,18 @@ import { Observable, shallowClone, } from '@datadog/browser-core' +import type { RumConfiguration } from '../domain/configuration' export interface LocationChange { oldLocation: Readonly newLocation: Readonly } -export function createLocationChangeObservable(location: Location) { +export function createLocationChangeObservable(configuration: RumConfiguration, location: Location) { let currentLocation = shallowClone(location) const observable = new Observable(() => { - const { stop: stopHistoryTracking } = trackHistory(onLocationChange) - const { stop: stopHashTracking } = trackHash(onLocationChange) + const { stop: stopHistoryTracking } = trackHistory(configuration, onLocationChange) + const { stop: stopHashTracking } = trackHash(configuration, onLocationChange) return () => { stopHistoryTracking() stopHashTracking() @@ -37,14 +38,14 @@ export function createLocationChangeObservable(location: Location) { return observable } -function trackHistory(onHistoryChange: () => void) { +function trackHistory(configuration: RumConfiguration, onHistoryChange: () => void) { const { stop: stopInstrumentingPushState } = instrumentMethodAndCallOriginal(history, 'pushState', { after: onHistoryChange, }) const { stop: stopInstrumentingReplaceState } = instrumentMethodAndCallOriginal(history, 'replaceState', { after: onHistoryChange, }) - const { stop: removeListener } = addEventListener(window, DOM_EVENT.POP_STATE, onHistoryChange) + const { stop: removeListener } = addEventListener(configuration, window, DOM_EVENT.POP_STATE, onHistoryChange) return { stop: () => { @@ -55,6 +56,6 @@ function trackHistory(onHistoryChange: () => void) { } } -function trackHash(onHashChange: () => void) { - return addEventListener(window, DOM_EVENT.HASH_CHANGE, onHashChange) +function trackHash(configuration: RumConfiguration, onHashChange: () => void) { + return addEventListener(configuration, window, DOM_EVENT.HASH_CHANGE, onHashChange) } diff --git a/packages/rum-core/src/browser/performanceCollection.spec.ts b/packages/rum-core/src/browser/performanceCollection.spec.ts index df34050635..e9f6a141fc 100644 --- a/packages/rum-core/src/browser/performanceCollection.spec.ts +++ b/packages/rum-core/src/browser/performanceCollection.spec.ts @@ -1,10 +1,14 @@ import type { TestSetupBuilder } from '../../test' import { setup } from '../../test' +import type { RumConfiguration } from '../domain/configuration' import { retrieveInitialDocumentResourceTiming, startPerformanceCollection } from './performanceCollection' describe('rum initial document resource', () => { let setupBuilder: TestSetupBuilder + let configuration: RumConfiguration + beforeEach(() => { + configuration = {} as RumConfiguration setupBuilder = setup().beforeBuild(({ lifeCycle, configuration }) => { startPerformanceCollection(lifeCycle, configuration) }) @@ -15,7 +19,7 @@ describe('rum initial document resource', () => { }) it('creates a resource timing for the initial document', (done) => { - retrieveInitialDocumentResourceTiming((timing) => { + retrieveInitialDocumentResourceTiming(configuration, (timing) => { expect(timing.entryType).toBe('resource') expect(timing.duration).toBeGreaterThan(0) diff --git a/packages/rum-core/src/browser/performanceCollection.ts b/packages/rum-core/src/browser/performanceCollection.ts index 95fee5cba1..60ac5b4b17 100644 --- a/packages/rum-core/src/browser/performanceCollection.ts +++ b/packages/rum-core/src/browser/performanceCollection.ts @@ -111,7 +111,7 @@ export function supportPerformanceTimingEvent(entryType: string) { } export function startPerformanceCollection(lifeCycle: LifeCycle, configuration: RumConfiguration) { - retrieveInitialDocumentResourceTiming((timing) => { + retrieveInitialDocumentResourceTiming(configuration, (timing) => { handleRumPerformanceEntries(lifeCycle, configuration, [timing]) }) @@ -148,25 +148,28 @@ export function startPerformanceCollection(lifeCycle: LifeCycle, configuration: if (supportPerformanceObject() && 'addEventListener' in performance) { // https://bugzilla.mozilla.org/show_bug.cgi?id=1559377 - addEventListener(performance, 'resourcetimingbufferfull', () => { + addEventListener(configuration, performance, 'resourcetimingbufferfull', () => { performance.clearResourceTimings() }) } } if (!supportPerformanceTimingEvent('navigation')) { - retrieveNavigationTiming((timing) => { + retrieveNavigationTiming(configuration, (timing) => { handleRumPerformanceEntries(lifeCycle, configuration, [timing]) }) } if (!supportPerformanceTimingEvent('first-input')) { - retrieveFirstInputTiming((timing) => { + retrieveFirstInputTiming(configuration, (timing) => { handleRumPerformanceEntries(lifeCycle, configuration, [timing]) }) } } -export function retrieveInitialDocumentResourceTiming(callback: (timing: RumPerformanceResourceTiming) => void) { - runOnReadyState('interactive', () => { +export function retrieveInitialDocumentResourceTiming( + configuration: RumConfiguration, + callback: (timing: RumPerformanceResourceTiming) => void +) { + runOnReadyState(configuration, 'interactive', () => { let timing: RumPerformanceResourceTiming const forcedAttributes = { @@ -195,7 +198,10 @@ export function retrieveInitialDocumentResourceTiming(callback: (timing: RumPerf }) } -function retrieveNavigationTiming(callback: (timing: RumPerformanceNavigationTiming) => void) { +function retrieveNavigationTiming( + configuration: RumConfiguration, + callback: (timing: RumPerformanceNavigationTiming) => void +) { function sendFakeTiming() { callback( assign(computeRelativePerformanceTiming(), { @@ -204,7 +210,7 @@ function retrieveNavigationTiming(callback: (timing: RumPerformanceNavigationTim ) } - runOnReadyState('complete', () => { + runOnReadyState(configuration, 'complete', () => { // Send it a bit after the actual load event, so the "loadEventEnd" timing is accurate setTimeout(sendFakeTiming) }) @@ -214,11 +220,12 @@ function retrieveNavigationTiming(callback: (timing: RumPerformanceNavigationTim * first-input timing entry polyfill based on * https://github.com/GoogleChrome/web-vitals/blob/master/src/lib/polyfills/firstInputPolyfill.ts */ -function retrieveFirstInputTiming(callback: (timing: RumFirstInputTiming) => void) { +function retrieveFirstInputTiming(configuration: RumConfiguration, callback: (timing: RumFirstInputTiming) => void) { const startTimeStamp = dateNow() let timingSent = false const { stop: removeEventListeners } = addEventListeners( + configuration, window, [DOM_EVENT.CLICK, DOM_EVENT.MOUSE_DOWN, DOM_EVENT.KEY_DOWN, DOM_EVENT.TOUCH_START, DOM_EVENT.POINTER_DOWN], (evt) => { @@ -237,7 +244,7 @@ function retrieveFirstInputTiming(callback: (timing: RumFirstInputTiming) => voi } if (evt.type === DOM_EVENT.POINTER_DOWN) { - sendTimingIfPointerIsNotCancelled(timing) + sendTimingIfPointerIsNotCancelled(configuration, timing) } else { sendTiming(timing) } @@ -251,8 +258,9 @@ function retrieveFirstInputTiming(callback: (timing: RumFirstInputTiming) => voi * fired when we scroll. If we're scrolling we don't need to report input delay since FID excludes * scrolling and pinch/zooming. */ - function sendTimingIfPointerIsNotCancelled(timing: RumFirstInputTiming) { + function sendTimingIfPointerIsNotCancelled(configuration: RumConfiguration, timing: RumFirstInputTiming) { addEventListeners( + configuration, window, [DOM_EVENT.POINTER_UP, DOM_EVENT.POINTER_CANCEL], (event) => { diff --git a/packages/rum-core/src/browser/scroll.spec.ts b/packages/rum-core/src/browser/scroll.spec.ts index 0816384308..916137cd57 100644 --- a/packages/rum-core/src/browser/scroll.spec.ts +++ b/packages/rum-core/src/browser/scroll.spec.ts @@ -1,4 +1,5 @@ import { addEventListener, DOM_EVENT, isIE } from '@datadog/browser-core' +import type { RumConfiguration } from '../domain/configuration' import { getScrollX, getScrollY } from './scroll' function isMobileSafari12() { @@ -7,7 +8,7 @@ function isMobileSafari12() { describe('scroll', () => { let shouldWaitForWindowScrollEvent: boolean - + let configuration: RumConfiguration const addVerticalScrollBar = () => { document.body.style.setProperty('margin-bottom', '5000px') } @@ -16,7 +17,7 @@ describe('scroll', () => { if (isIE()) { pending('IE not supported') } - + configuration = {} as RumConfiguration shouldWaitForWindowScrollEvent = false }) @@ -27,7 +28,11 @@ describe('scroll', () => { // Those tests are triggering asynchronous scroll events that might impact tests run after them. // To avoid that, we wait for the next scroll event before continuing to the next one. if (shouldWaitForWindowScrollEvent) { - addEventListener(window, DOM_EVENT.SCROLL, () => done(), { passive: true, once: true, capture: true }) + addEventListener(configuration, window, DOM_EVENT.SCROLL, () => done(), { + passive: true, + once: true, + capture: true, + }) } else { done() } diff --git a/packages/rum-core/src/browser/viewportObservable.spec.ts b/packages/rum-core/src/browser/viewportObservable.spec.ts index 2ea15be26e..bec1531f49 100644 --- a/packages/rum-core/src/browser/viewportObservable.spec.ts +++ b/packages/rum-core/src/browser/viewportObservable.spec.ts @@ -1,6 +1,7 @@ import type { Subscription } from '@datadog/browser-core/src/tools/observable' import type { Clock } from '@datadog/browser-core/test' import { mockClock, createNewEvent } from '@datadog/browser-core/test' +import type { RumConfiguration } from '../domain/configuration' import type { ViewportDimension } from './viewportObservable' import { getViewportDimension, initViewportObservable } from './viewportObservable' @@ -8,9 +9,11 @@ describe('viewportObservable', () => { let viewportSubscription: Subscription let viewportDimension: ViewportDimension let clock: Clock + let configuration: RumConfiguration beforeEach(() => { - viewportSubscription = initViewportObservable().subscribe((dimension) => { + configuration = {} as RumConfiguration + viewportSubscription = initViewportObservable(configuration).subscribe((dimension) => { viewportDimension = dimension }) clock = mockClock() diff --git a/packages/rum-core/src/browser/viewportObservable.ts b/packages/rum-core/src/browser/viewportObservable.ts index e3eab2524d..888dcee032 100644 --- a/packages/rum-core/src/browser/viewportObservable.ts +++ b/packages/rum-core/src/browser/viewportObservable.ts @@ -1,4 +1,5 @@ import { Observable, throttle, addEventListener, DOM_EVENT } from '@datadog/browser-core' +import type { RumConfiguration } from '../domain/configuration' export interface ViewportDimension { height: number @@ -7,20 +8,21 @@ export interface ViewportDimension { let viewportObservable: Observable | undefined -export function initViewportObservable() { +export function initViewportObservable(configuration: RumConfiguration) { if (!viewportObservable) { - viewportObservable = createViewportObservable() + viewportObservable = createViewportObservable(configuration) } return viewportObservable } -export function createViewportObservable() { +export function createViewportObservable(configuration: RumConfiguration) { const observable = new Observable(() => { const { throttled: updateDimension } = throttle(() => { observable.notify(getViewportDimension()) }, 200) - return addEventListener(window, DOM_EVENT.RESIZE, updateDimension, { capture: true, passive: true }).stop + return addEventListener(configuration, window, DOM_EVENT.RESIZE, updateDimension, { capture: true, passive: true }) + .stop }) return observable diff --git a/packages/rum-core/src/domain/assembly.ts b/packages/rum-core/src/domain/assembly.ts index 3874093008..7feda203eb 100644 --- a/packages/rum-core/src/domain/assembly.ts +++ b/packages/rum-core/src/domain/assembly.ts @@ -150,7 +150,7 @@ export function startRumAssembly( action: needToAssembleWithAction(rawRumEvent) && actionId ? { id: actionId } : undefined, synthetics: syntheticsContext, ci_test: ciTestContext, - display: getDisplayContext(), + display: getDisplayContext(configuration), } const serverRumEvent = combine(rumContext as RumContext & Context, rawRumEvent) as RumEvent & Context diff --git a/packages/rum-core/src/domain/configuration.ts b/packages/rum-core/src/domain/configuration.ts index 413e1b049c..da443b3132 100644 --- a/packages/rum-core/src/domain/configuration.ts +++ b/packages/rum-core/src/domain/configuration.ts @@ -195,6 +195,8 @@ export function serializeRumConfiguration(configuration: RumInitConfiguration): Array.isArray(configuration.excludedActivityUrls) && configuration.excludedActivityUrls.length > 0, track_views_manually: configuration.trackViewsManually, track_user_interactions: configuration.trackUserInteractions, + track_resources: configuration.trackResources, + track_long_task: configuration.trackLongTasks, }, baseSerializedConfiguration ) diff --git a/packages/rum-core/src/domain/contexts/displayContext.spec.ts b/packages/rum-core/src/domain/contexts/displayContext.spec.ts index 106844e04c..c9d170410e 100644 --- a/packages/rum-core/src/domain/contexts/displayContext.spec.ts +++ b/packages/rum-core/src/domain/contexts/displayContext.spec.ts @@ -1,12 +1,19 @@ +import type { RumConfiguration } from '../configuration' import { getDisplayContext, resetDisplayContext } from './displayContext' describe('displayContext', () => { + let configuration: RumConfiguration + + beforeEach(() => { + configuration = {} as RumConfiguration + }) + afterEach(() => { resetDisplayContext() }) it('should return current display context', () => { - expect(getDisplayContext()).toEqual({ + expect(getDisplayContext(configuration)).toEqual({ viewport: { width: jasmine.any(Number), height: jasmine.any(Number), diff --git a/packages/rum-core/src/domain/contexts/displayContext.ts b/packages/rum-core/src/domain/contexts/displayContext.ts index 8ecb43a179..633e49f16f 100644 --- a/packages/rum-core/src/domain/contexts/displayContext.ts +++ b/packages/rum-core/src/domain/contexts/displayContext.ts @@ -1,12 +1,13 @@ +import type { RumConfiguration } from '../configuration' import { getViewportDimension, initViewportObservable } from '../../browser/viewportObservable' let viewport: { width: number; height: number } | undefined let stopListeners: (() => void) | undefined -export function getDisplayContext() { +export function getDisplayContext(configuration: RumConfiguration) { if (!viewport) { viewport = getViewportDimension() - stopListeners = initViewportObservable().subscribe((viewportDimension) => { + stopListeners = initViewportObservable(configuration).subscribe((viewportDimension) => { viewport = viewportDimension }).unsubscribe } diff --git a/packages/rum-core/src/domain/contexts/pageStateHistory.spec.ts b/packages/rum-core/src/domain/contexts/pageStateHistory.spec.ts index 45628361e9..98625ecf5f 100644 --- a/packages/rum-core/src/domain/contexts/pageStateHistory.spec.ts +++ b/packages/rum-core/src/domain/contexts/pageStateHistory.spec.ts @@ -1,15 +1,19 @@ import type { RelativeTime, ServerDuration } from '@datadog/browser-core' import type { Clock } from '../../../../core/test' import { mockClock } from '../../../../core/test' +import type { RumConfiguration } from '../configuration' import type { PageStateHistory } from './pageStateHistory' import { startPageStateHistory, PageState } from './pageStateHistory' describe('pageStateHistory', () => { let pageStateHistory: PageStateHistory let clock: Clock + let configuration: RumConfiguration + beforeEach(() => { + configuration = {} as RumConfiguration clock = mockClock() - pageStateHistory = startPageStateHistory() + pageStateHistory = startPageStateHistory(configuration) }) afterEach(() => { @@ -67,7 +71,7 @@ describe('pageStateHistory', () => { it('should limit the number of selectable entries', () => { const maxPageStateEntriesSelectable = 1 - pageStateHistory = startPageStateHistory(maxPageStateEntriesSelectable) + pageStateHistory = startPageStateHistory(configuration, maxPageStateEntriesSelectable) pageStateHistory.addPageState(PageState.ACTIVE) clock.tick(10) @@ -91,7 +95,7 @@ describe('pageStateHistory', () => { it('should return false if the page was not active at the given time', () => { const maxPageStateEntriesSelectable = 1 - pageStateHistory = startPageStateHistory(maxPageStateEntriesSelectable) + pageStateHistory = startPageStateHistory(configuration, maxPageStateEntriesSelectable) pageStateHistory.addPageState(PageState.ACTIVE) clock.tick(10) diff --git a/packages/rum-core/src/domain/contexts/pageStateHistory.ts b/packages/rum-core/src/domain/contexts/pageStateHistory.ts index bc8ef7e69f..5196821d38 100644 --- a/packages/rum-core/src/domain/contexts/pageStateHistory.ts +++ b/packages/rum-core/src/domain/contexts/pageStateHistory.ts @@ -8,6 +8,7 @@ import { relativeNow, DOM_EVENT, } from '@datadog/browser-core' +import type { RumConfiguration } from '../configuration' import type { PageStateServerEntry } from '../../rawRumEvent.types' // Arbitrary value to cap number of element for memory consumption in the browser @@ -35,6 +36,7 @@ export interface PageStateHistory { } export function startPageStateHistory( + configuration: RumConfiguration, maxPageStateEntriesSelectable = MAX_PAGE_STATE_ENTRIES_SELECTABLE ): PageStateHistory { const pageStateHistory = new ValueHistory(PAGE_STATE_CONTEXT_TIME_OUT_DELAY, MAX_PAGE_STATE_ENTRIES) @@ -43,6 +45,7 @@ export function startPageStateHistory( addPageState(getPageState(), relativeNow()) const { stop: stopEventListeners } = addEventListeners( + configuration, window, [ DOM_EVENT.PAGE_SHOW, diff --git a/packages/rum-core/src/domain/requestCollection.ts b/packages/rum-core/src/domain/requestCollection.ts index 0e895e1df8..dd0830227a 100644 --- a/packages/rum-core/src/domain/requestCollection.ts +++ b/packages/rum-core/src/domain/requestCollection.ts @@ -70,7 +70,7 @@ export function startRequestCollection( } export function trackXhr(lifeCycle: LifeCycle, configuration: RumConfiguration, tracer: Tracer) { - const subscription = initXhrObservable().subscribe((rawContext) => { + const subscription = initXhrObservable(configuration).subscribe((rawContext) => { const context = rawContext as RumXhrStartContext | RumXhrCompleteContext if (!isAllowedRequestUrl(configuration, context.url)) { return diff --git a/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.spec.ts b/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.spec.ts index 232412ab42..77daa4b271 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.spec.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.spec.ts @@ -1,4 +1,5 @@ import { createNewEvent } from '@datadog/browser-core/test' +import type { RumConfiguration } from '../../configuration' import type { ActionEventsHooks } from './listenActionEvents' import { listenActionEvents } from './listenActionEvents' @@ -8,13 +9,15 @@ describe('listenActionEvents', () => { onPointerDown: jasmine.Spy['onPointerDown']> } let stopListenEvents: () => void + let configuration: RumConfiguration beforeEach(() => { + configuration = {} as RumConfiguration actionEventsHooks = { onPointerUp: jasmine.createSpy(), onPointerDown: jasmine.createSpy().and.returnValue({}), } - ;({ stop: stopListenEvents } = listenActionEvents(actionEventsHooks)) + ;({ stop: stopListenEvents } = listenActionEvents(configuration, actionEventsHooks)) }) afterEach(() => { diff --git a/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.ts b/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.ts index bda0fa97d4..42ea5d9772 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.ts @@ -1,4 +1,5 @@ import { addEventListener, DOM_EVENT } from '@datadog/browser-core' +import type { RumConfiguration } from '../../configuration' export type MouseEventOnElement = PointerEvent & { target: Element } @@ -11,7 +12,10 @@ export interface ActionEventsHooks { onPointerUp: (context: ClickContext, event: MouseEventOnElement, getUserActivity: () => UserActivity) => void } -export function listenActionEvents({ onPointerDown, onPointerUp }: ActionEventsHooks) { +export function listenActionEvents( + configuration: RumConfiguration, + { onPointerDown, onPointerUp }: ActionEventsHooks +) { let selectionEmptyAtPointerDown: boolean let userActivity: UserActivity = { selection: false, @@ -21,6 +25,7 @@ export function listenActionEvents({ onPointerDown, onPointerUp }: const listeners = [ addEventListener( + configuration, window, DOM_EVENT.POINTER_DOWN, (event: PointerEvent) => { @@ -37,6 +42,7 @@ export function listenActionEvents({ onPointerDown, onPointerUp }: ), addEventListener( + configuration, window, DOM_EVENT.SELECTION_CHANGE, () => { @@ -48,6 +54,7 @@ export function listenActionEvents({ onPointerDown, onPointerUp }: ), addEventListener( + configuration, window, DOM_EVENT.POINTER_UP, (event: PointerEvent) => { @@ -62,6 +69,7 @@ export function listenActionEvents({ onPointerDown, onPointerUp }: ), addEventListener( + configuration, window, DOM_EVENT.INPUT, () => { diff --git a/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.ts b/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.ts index 4b5a58fd76..c27e9d2b03 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.ts @@ -80,7 +80,7 @@ export function trackClickActions( const { stop: stopActionEventsListener } = listenActionEvents<{ clickActionBase: ClickActionBase hadActivityOnPointerDown: () => boolean - }>({ + }>(configuration, { onPointerDown: (pointerDownEvent) => processPointerDown(configuration, lifeCycle, domMutationObservable, pointerDownEvent), onPointerUp: ({ clickActionBase, hadActivityOnPointerDown }, startEvent, getUserActivity) => diff --git a/packages/rum-core/src/domain/rumEventsCollection/error/errorCollection.ts b/packages/rum-core/src/domain/rumEventsCollection/error/errorCollection.ts index 4fa34d8dc9..564b7da90d 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/error/errorCollection.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/error/errorCollection.ts @@ -11,6 +11,7 @@ import { trackRuntimeError, NonErrorPrefix, } from '@datadog/browser-core' +import type { RumConfiguration } from '../../configuration' import type { RawRumErrorEvent } from '../../../rawRumEvent.types' import { RumEventType } from '../../../rawRumEvent.types' import type { LifeCycle, RawRumEventCollectedData } from '../../lifeCycle' @@ -30,6 +31,7 @@ export interface ProvidedError { export function startErrorCollection( lifeCycle: LifeCycle, + configuration: RumConfiguration, pageStateHistory: PageStateHistory, featureFlagContexts: FeatureFlagContexts ) { @@ -37,7 +39,7 @@ export function startErrorCollection( trackConsoleError(errorObservable) trackRuntimeError(errorObservable) - trackReportError(errorObservable) + trackReportError(configuration, errorObservable) errorObservable.subscribe((error) => lifeCycle.notify(LifeCycleEventType.RAW_ERROR_COLLECTED, { error })) diff --git a/packages/rum-core/src/domain/rumEventsCollection/error/trackReportError.spec.ts b/packages/rum-core/src/domain/rumEventsCollection/error/trackReportError.spec.ts index 8e13ceafb0..12d9aa5296 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/error/trackReportError.spec.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/error/trackReportError.spec.ts @@ -2,6 +2,7 @@ import type { RawError, Subscription } from '@datadog/browser-core' import { ErrorHandling, ErrorSource, Observable, clocksNow } from '@datadog/browser-core' import type { Clock } from '@datadog/browser-core/test' import { mockClock, stubReportingObserver } from '@datadog/browser-core/test' +import type { RumConfiguration } from '../../configuration' import { trackReportError } from './trackReportError' describe('trackReportError', () => { @@ -10,8 +11,10 @@ describe('trackReportError', () => { let notifyLog: jasmine.Spy let clock: Clock let reportingObserverStub: { raiseReport(type: string): void; reset(): void } + let configuration: RumConfiguration beforeEach(() => { + configuration = {} as RumConfiguration errorObservable = new Observable() notifyLog = jasmine.createSpy('notifyLog') reportingObserverStub = stubReportingObserver() @@ -26,7 +29,7 @@ describe('trackReportError', () => { }) it('should track reports', () => { - trackReportError(errorObservable) + trackReportError(configuration, errorObservable) reportingObserverStub.raiseReport('intervention') expect(notifyLog).toHaveBeenCalledWith({ diff --git a/packages/rum-core/src/domain/rumEventsCollection/error/trackReportError.ts b/packages/rum-core/src/domain/rumEventsCollection/error/trackReportError.ts index ba7bda0e67..1e988f3c1f 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/error/trackReportError.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/error/trackReportError.ts @@ -1,17 +1,20 @@ import type { Observable, RawError } from '@datadog/browser-core' import { clocksNow, ErrorHandling, ErrorSource, initReportObservable, RawReportType } from '@datadog/browser-core' +import type { RumConfiguration } from '../../configuration' -export function trackReportError(errorObservable: Observable) { - const subscription = initReportObservable([RawReportType.cspViolation, RawReportType.intervention]).subscribe( - (reportError) => - errorObservable.notify({ - startClocks: clocksNow(), - message: reportError.message, - stack: reportError.stack, - type: reportError.subtype, - source: ErrorSource.REPORT, - handling: ErrorHandling.UNHANDLED, - }) +export function trackReportError(configuration: RumConfiguration, errorObservable: Observable) { + const subscription = initReportObservable(configuration, [ + RawReportType.cspViolation, + RawReportType.intervention, + ]).subscribe((reportError) => + errorObservable.notify({ + startClocks: clocksNow(), + message: reportError.message, + stack: reportError.stack, + type: reportError.subtype, + source: ErrorSource.REPORT, + handling: ErrorHandling.UNHANDLED, + }) ) return { diff --git a/packages/rum-core/src/domain/rumEventsCollection/view/trackFirstHidden.spec.ts b/packages/rum-core/src/domain/rumEventsCollection/view/trackFirstHidden.spec.ts index 158ca0ba6a..ddb8bc46c4 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/view/trackFirstHidden.spec.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/view/trackFirstHidden.spec.ts @@ -1,9 +1,16 @@ import type { RelativeTime } from '@datadog/browser-core' import { DOM_EVENT } from '@datadog/browser-core' import { createNewEvent, restorePageVisibility, setPageVisibility } from '@datadog/browser-core/test' +import type { RumConfiguration } from '../../configuration' import { resetFirstHidden, trackFirstHidden } from './trackFirstHidden' describe('trackFirstHidden', () => { + let configuration: RumConfiguration + + beforeEach(() => { + configuration = {} as RumConfiguration + }) + afterEach(() => { resetFirstHidden() restorePageVisibility() @@ -12,13 +19,13 @@ describe('trackFirstHidden', () => { describe('the page is initially hidden', () => { it('should return 0', () => { setPageVisibility('hidden') - expect(trackFirstHidden().timeStamp).toBe(0 as RelativeTime) + expect(trackFirstHidden(configuration).timeStamp).toBe(0 as RelativeTime) }) it('should ignore events', () => { setPageVisibility('hidden') const eventTarget = createWindowEventTarget() - const firstHidden = trackFirstHidden(eventTarget) + const firstHidden = trackFirstHidden(configuration, eventTarget) eventTarget.dispatchEvent(createNewEvent(DOM_EVENT.PAGE_HIDE, { timeStamp: 100 })) eventTarget.dispatchEvent(createNewEvent(DOM_EVENT.VISIBILITY_CHANGE, { timeStamp: 100 })) @@ -28,13 +35,19 @@ describe('trackFirstHidden', () => { }) describe('the page is initially visible', () => { + let configuration: RumConfiguration + + beforeEach(() => { + configuration = {} as RumConfiguration + }) + it('should return Infinity if the page was not hidden yet', () => { - expect(trackFirstHidden().timeStamp).toBe(Infinity as RelativeTime) + expect(trackFirstHidden(configuration).timeStamp).toBe(Infinity as RelativeTime) }) it('should return the timestamp of the first pagehide event', () => { const eventTarget = createWindowEventTarget() - const firstHidden = trackFirstHidden(eventTarget) + const firstHidden = trackFirstHidden(configuration, eventTarget) eventTarget.dispatchEvent(createNewEvent(DOM_EVENT.PAGE_HIDE, { timeStamp: 100 })) @@ -43,7 +56,7 @@ describe('trackFirstHidden', () => { it('should return the timestamp of the first visibilitychange event if the page is hidden', () => { const eventTarget = createWindowEventTarget() - const firstHidden = trackFirstHidden(eventTarget) + const firstHidden = trackFirstHidden(configuration, eventTarget) setPageVisibility('hidden') eventTarget.dispatchEvent(createNewEvent(DOM_EVENT.VISIBILITY_CHANGE, { timeStamp: 100 })) @@ -53,7 +66,7 @@ describe('trackFirstHidden', () => { it('should ignore visibilitychange event if the page is visible', () => { const eventTarget = createWindowEventTarget() - const firstHidden = trackFirstHidden(eventTarget) + const firstHidden = trackFirstHidden(configuration, eventTarget) eventTarget.dispatchEvent(createNewEvent(DOM_EVENT.VISIBILITY_CHANGE, { timeStamp: 100 })) @@ -62,7 +75,7 @@ describe('trackFirstHidden', () => { it('should ignore subsequent events', () => { const eventTarget = createWindowEventTarget() - const firstHidden = trackFirstHidden(eventTarget) + const firstHidden = trackFirstHidden(configuration, eventTarget) eventTarget.dispatchEvent(createNewEvent(DOM_EVENT.PAGE_HIDE, { timeStamp: 100 })) diff --git a/packages/rum-core/src/domain/rumEventsCollection/view/trackFirstHidden.ts b/packages/rum-core/src/domain/rumEventsCollection/view/trackFirstHidden.ts index 74b9985244..0cdecd25d9 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/view/trackFirstHidden.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/view/trackFirstHidden.ts @@ -1,10 +1,11 @@ import type { RelativeTime } from '@datadog/browser-core' import { addEventListeners, DOM_EVENT } from '@datadog/browser-core' +import type { RumConfiguration } from '../../configuration' let trackFirstHiddenSingleton: { timeStamp: RelativeTime } | undefined let stopListeners: (() => void) | undefined -export function trackFirstHidden(eventTarget: Window = window) { +export function trackFirstHidden(configuration: RumConfiguration, eventTarget: Window = window) { if (!trackFirstHiddenSingleton) { if (document.visibilityState === 'hidden') { trackFirstHiddenSingleton = { @@ -15,6 +16,7 @@ export function trackFirstHidden(eventTarget: Window = window) { timeStamp: Infinity as RelativeTime, } ;({ stop: stopListeners } = addEventListeners( + configuration, eventTarget, [DOM_EVENT.PAGE_HIDE, DOM_EVENT.VISIBILITY_CHANGE], (event) => { diff --git a/packages/rum-core/src/domain/rumEventsCollection/view/trackInitialViewTimings.spec.ts b/packages/rum-core/src/domain/rumEventsCollection/view/trackInitialViewTimings.spec.ts index 2d4e6f3340..f300f68772 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/view/trackInitialViewTimings.spec.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/view/trackInitialViewTimings.spec.ts @@ -10,6 +10,7 @@ import type { RumPerformancePaintTiming, } from '../../../browser/performanceCollection' import { LifeCycleEventType } from '../../lifeCycle' +import type { RumConfiguration } from '../../configuration' import { resetFirstHidden } from './trackFirstHidden' import type { Timings } from './trackInitialViewTimings' import { @@ -56,14 +57,17 @@ describe('trackInitialViewTimings', () => { let scheduleViewUpdateSpy: jasmine.Spy<() => void> let trackInitialViewTimingsResult: ReturnType let setLoadEventSpy: jasmine.Spy<(loadEvent: Duration) => void> + let configuration: RumConfiguration beforeEach(() => { + configuration = {} as RumConfiguration scheduleViewUpdateSpy = jasmine.createSpy() setLoadEventSpy = jasmine.createSpy() setupBuilder = setup().beforeBuild(({ lifeCycle }) => { trackInitialViewTimingsResult = trackInitialViewTimings( lifeCycle, + configuration, noopWebVitalTelemetryDebug, setLoadEventSpy, scheduleViewUpdateSpy @@ -158,10 +162,14 @@ describe('trackNavigationTimings', () => { describe('trackFirstContentfulPaintTiming', () => { let setupBuilder: TestSetupBuilder let fcpCallback: jasmine.Spy<(value: RelativeTime) => void> + let configuration: RumConfiguration beforeEach(() => { + configuration = {} as RumConfiguration fcpCallback = jasmine.createSpy() - setupBuilder = setup().beforeBuild(({ lifeCycle }) => trackFirstContentfulPaintTiming(lifeCycle, fcpCallback)) + setupBuilder = setup().beforeBuild(({ lifeCycle }) => + trackFirstContentfulPaintTiming(lifeCycle, configuration, fcpCallback) + ) resetFirstHidden() }) @@ -204,12 +212,14 @@ describe('largestContentfulPaintTiming', () => { let setupBuilder: TestSetupBuilder let lcpCallback: jasmine.Spy<(value: RelativeTime, lcpElement: Element | undefined) => void> let eventTarget: Window + let configuration: RumConfiguration beforeEach(() => { + configuration = {} as RumConfiguration lcpCallback = jasmine.createSpy() eventTarget = document.createElement('div') as unknown as Window setupBuilder = setup().beforeBuild(({ lifeCycle }) => - trackLargestContentfulPaintTiming(lifeCycle, eventTarget, lcpCallback) + trackLargestContentfulPaintTiming(lifeCycle, configuration, eventTarget, lcpCallback) ) resetFirstHidden() }) @@ -271,10 +281,12 @@ describe('firstInputTimings', () => { firstInputTarget: Node | undefined }) => void > + let configuration: RumConfiguration beforeEach(() => { + configuration = {} as RumConfiguration fitCallback = jasmine.createSpy() - setupBuilder = setup().beforeBuild(({ lifeCycle }) => trackFirstInputTimings(lifeCycle, fitCallback)) + setupBuilder = setup().beforeBuild(({ lifeCycle }) => trackFirstInputTimings(lifeCycle, configuration, fitCallback)) resetFirstHidden() }) diff --git a/packages/rum-core/src/domain/rumEventsCollection/view/trackInitialViewTimings.ts b/packages/rum-core/src/domain/rumEventsCollection/view/trackInitialViewTimings.ts index 8b50b4c1aa..7b2f48b669 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/view/trackInitialViewTimings.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/view/trackInitialViewTimings.ts @@ -11,6 +11,7 @@ import { relativeNow, } from '@datadog/browser-core' +import type { RumConfiguration } from '../../configuration' import type { LifeCycle } from '../../lifeCycle' import { LifeCycleEventType } from '../../lifeCycle' import type { @@ -47,6 +48,7 @@ export interface Timings { export function trackInitialViewTimings( lifeCycle: LifeCycle, + configuration: RumConfiguration, webVitalTelemetryDebug: WebVitalTelemetryDebug, setLoadEvent: (loadEnd: Duration) => void, scheduleViewUpdate: () => void @@ -62,11 +64,12 @@ export function trackInitialViewTimings( setLoadEvent(newTimings.loadEvent) setTimings(newTimings) }) - const { stop: stopFCPTracking } = trackFirstContentfulPaintTiming(lifeCycle, (firstContentfulPaint) => + const { stop: stopFCPTracking } = trackFirstContentfulPaintTiming(lifeCycle, configuration, (firstContentfulPaint) => setTimings({ firstContentfulPaint }) ) const { stop: stopLCPTracking } = trackLargestContentfulPaintTiming( lifeCycle, + configuration, window, (largestContentfulPaint, lcpElement) => { webVitalTelemetryDebug.addWebVitalTelemetryDebug('LCP', lcpElement, largestContentfulPaint) @@ -79,6 +82,7 @@ export function trackInitialViewTimings( const { stop: stopFIDTracking } = trackFirstInputTimings( lifeCycle, + configuration, ({ firstInputDelay, firstInputTime, firstInputTarget }) => { webVitalTelemetryDebug.addWebVitalTelemetryDebug('FID', firstInputTarget, firstInputTime) @@ -135,8 +139,12 @@ export function trackNavigationTimings(lifeCycle: LifeCycle, callback: (timings: return { stop } } -export function trackFirstContentfulPaintTiming(lifeCycle: LifeCycle, callback: (fcpTiming: RelativeTime) => void) { - const firstHidden = trackFirstHidden() +export function trackFirstContentfulPaintTiming( + lifeCycle: LifeCycle, + configuration: RumConfiguration, + callback: (fcpTiming: RelativeTime) => void +) { + const firstHidden = trackFirstHidden(configuration) const { unsubscribe: stop } = lifeCycle.subscribe(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, (entries) => { const fcpEntry = find( entries, @@ -161,16 +169,18 @@ export function trackFirstContentfulPaintTiming(lifeCycle: LifeCycle, callback: */ export function trackLargestContentfulPaintTiming( lifeCycle: LifeCycle, + configuration: RumConfiguration, eventTarget: Window, callback: (lcpTiming: RelativeTime, lcpElement?: Element) => void ) { - const firstHidden = trackFirstHidden() + const firstHidden = trackFirstHidden(configuration) // Ignore entries that come after the first user interaction. According to the documentation, the // browser should not send largest-contentful-paint entries after a user interact with the page, // but the web-vitals reference implementation uses this as a safeguard. let firstInteractionTimestamp = Infinity const { stop: stopEventListener } = addEventListeners( + configuration, eventTarget, [DOM_EVENT.POINTER_DOWN, DOM_EVENT.KEY_DOWN], (event) => { @@ -214,6 +224,7 @@ export function trackLargestContentfulPaintTiming( */ export function trackFirstInputTimings( lifeCycle: LifeCycle, + configuration: RumConfiguration, callback: ({ firstInputDelay, firstInputTime, @@ -224,7 +235,7 @@ export function trackFirstInputTimings( firstInputTarget: Node | undefined }) => void ) { - const firstHidden = trackFirstHidden() + const firstHidden = trackFirstHidden(configuration) const { unsubscribe: stop } = lifeCycle.subscribe(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, (entries) => { const firstInputEntry = find( diff --git a/packages/rum-core/src/domain/rumEventsCollection/view/trackViewMetrics.spec.ts b/packages/rum-core/src/domain/rumEventsCollection/view/trackViewMetrics.spec.ts index 8e3ef56497..7ed6c3fc08 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/view/trackViewMetrics.spec.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/view/trackViewMetrics.spec.ts @@ -17,6 +17,7 @@ import { FrustrationType, RumEventType } from '../../../rawRumEvent.types' import type { LifeCycle } from '../../lifeCycle' import { LifeCycleEventType } from '../../lifeCycle' import { PAGE_ACTIVITY_END_DELAY, PAGE_ACTIVITY_VALIDATION_DELAY } from '../../waitPageActivityEnd' +import type { RumConfiguration } from '../../configuration' import { THROTTLE_VIEW_UPDATE_PERIOD } from './trackViews' import type { ViewTest } from './setupViewTest.specHelper' import { setupViewTest } from './setupViewTest.specHelper' @@ -536,6 +537,8 @@ describe('rum track view metrics', () => { let scrollMetrics: ScrollMetrics | undefined let stopTrackScrollMetrics: () => void let clock: Clock + let configuration: RumConfiguration + const getMetrics = jasmine.createSpy('getMetrics') const newScroll = (scrollParams: { scrollHeight: number; scrollDepth: number; scrollTop: number }) => { @@ -547,9 +550,11 @@ describe('rum track view metrics', () => { } describe('with flag enabled', () => { beforeEach(() => { + configuration = {} as RumConfiguration clock = mockClock() addExperimentalFeatures([ExperimentalFeature.SCROLLMAP]) stopTrackScrollMetrics = trackScrollMetrics( + configuration, { relative: 0 as RelativeTime, timeStamp: 0 as TimeStamp }, (s) => (scrollMetrics = s), getMetrics @@ -605,6 +610,7 @@ describe('rum track view metrics', () => { beforeEach(() => { clock = mockClock() stopTrackScrollMetrics = trackScrollMetrics( + configuration, { relative: 1 as RelativeTime, timeStamp: 2 as TimeStamp }, (s) => (scrollMetrics = s), getMetrics diff --git a/packages/rum-core/src/domain/rumEventsCollection/view/trackViewMetrics.ts b/packages/rum-core/src/domain/rumEventsCollection/view/trackViewMetrics.ts index 40805da1a1..b71febf126 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/view/trackViewMetrics.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/view/trackViewMetrics.ts @@ -78,6 +78,7 @@ export function trackViewMetrics( ) const { stop: stopScrollMetricsTracking } = trackScrollMetrics( + configuration, viewStart, (newScrollMetrics) => { scrollMetrics = newScrollMetrics @@ -118,6 +119,7 @@ export function trackViewMetrics( } export function trackScrollMetrics( + configuration: RumConfiguration, viewStart: ClocksState, callback: (scrollMetrics: ScrollMetrics) => void, getScrollValues = computeScrollValues @@ -146,7 +148,9 @@ export function trackScrollMetrics( { leading: false, trailing: true } ) - const { stop } = addEventListener(window, DOM_EVENT.SCROLL, handleScrollEvent.throttled, { passive: true }) + const { stop } = addEventListener(configuration, window, DOM_EVENT.SCROLL, handleScrollEvent.throttled, { + passive: true, + }) return { stop: () => { diff --git a/packages/rum-core/src/domain/rumEventsCollection/view/trackViews.ts b/packages/rum-core/src/domain/rumEventsCollection/view/trackViews.ts index d6f9dc5948..718e44a7a9 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/view/trackViews.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/view/trackViews.ts @@ -212,7 +212,7 @@ function newView( const { scheduleStop: scheduleStopInitialViewTimingsTracking, timings } = loadingType === ViewLoadingType.INITIAL_LOAD - ? trackInitialViewTimings(lifeCycle, webVitalTelemetryDebug, setLoadEvent, scheduleViewUpdate) + ? trackInitialViewTimings(lifeCycle, configuration, webVitalTelemetryDebug, setLoadEvent, scheduleViewUpdate) : { scheduleStop: noop, timings: {} as Timings } const { scheduleStop: scheduleStopEventCountsTracking, eventCounts } = trackViewEventCounts( diff --git a/packages/rum-core/src/domain/rumSessionManager.ts b/packages/rum-core/src/domain/rumSessionManager.ts index d34444d53b..c717cdf425 100644 --- a/packages/rum-core/src/domain/rumSessionManager.ts +++ b/packages/rum-core/src/domain/rumSessionManager.ts @@ -24,11 +24,8 @@ export const enum RumTrackingType { } export function startRumSessionManager(configuration: RumConfiguration, lifeCycle: LifeCycle): RumSessionManager { - const sessionManager = startSessionManager( - // TODO - Improve configuration type and remove assertion - configuration.sessionStoreStrategyType!, - RUM_SESSION_KEY, - (rawTrackingType) => computeSessionState(configuration, rawTrackingType) + const sessionManager = startSessionManager(configuration, RUM_SESSION_KEY, (rawTrackingType) => + computeSessionState(configuration, rawTrackingType) ) sessionManager.expireObservable.subscribe(() => { diff --git a/packages/rum-core/src/transport/startRumBatch.ts b/packages/rum-core/src/transport/startRumBatch.ts index 8ad4864e47..133072ba4d 100644 --- a/packages/rum-core/src/transport/startRumBatch.ts +++ b/packages/rum-core/src/transport/startRumBatch.ts @@ -74,7 +74,7 @@ function makeRumBatch( }) const batch = new Batch( - createHttpRequest(endpointBuilder, configuration.batchBytesLimit, reportError), + createHttpRequest(configuration, endpointBuilder, configuration.batchBytesLimit, reportError), flushController, configuration.messageBytesLimit ) diff --git a/packages/rum/src/boot/recorderApi.spec.ts b/packages/rum/src/boot/recorderApi.spec.ts index 6d1ebd4bf5..a18b42a893 100644 --- a/packages/rum/src/boot/recorderApi.spec.ts +++ b/packages/rum/src/boot/recorderApi.spec.ts @@ -20,11 +20,11 @@ describe('makeRecorderApi', () => { if (!startDeflateWorkerSpy) { startDeflateWorkerSpy = jasmine.createSpy('startDeflateWorker') } - startDeflateWorkerSpy.and.callFake((callback) => callback(worker)) + startDeflateWorkerSpy.and.callFake((_, callback) => callback(worker)) } function callLastRegisteredInitialisationCallback() { - startDeflateWorkerSpy.calls.mostRecent().args[0](FAKE_WORKER) + startDeflateWorkerSpy.calls.mostRecent().args[1](FAKE_WORKER) } function stopDeflateWorker() { diff --git a/packages/rum/src/boot/recorderApi.ts b/packages/rum/src/boot/recorderApi.ts index 322c6c1752..aeeeedb2e8 100644 --- a/packages/rum/src/boot/recorderApi.ts +++ b/packages/rum/src/boot/recorderApi.ts @@ -116,12 +116,12 @@ export function makeRecorderApi( state = { status: RecorderStatus.Starting } - runOnReadyState('interactive', () => { + runOnReadyState(configuration, 'interactive', () => { if (state.status !== RecorderStatus.Starting) { return } - startDeflateWorkerImpl((worker) => { + startDeflateWorkerImpl(configuration, (worker) => { if (state.status !== RecorderStatus.Starting) { return } diff --git a/packages/rum/src/boot/startRecording.spec.ts b/packages/rum/src/boot/startRecording.spec.ts index 8f0fa2a59f..13fc632480 100644 --- a/packages/rum/src/boot/startRecording.spec.ts +++ b/packages/rum/src/boot/startRecording.spec.ts @@ -1,6 +1,6 @@ import type { TimeStamp, HttpRequest, ClocksState } from '@datadog/browser-core' import { PageExitReason, DefaultPrivacyLevel, noop, isIE, timeStampNow } from '@datadog/browser-core' -import type { LifeCycle, ViewCreatedEvent } from '@datadog/browser-rum-core' +import type { LifeCycle, ViewCreatedEvent, RumConfiguration } from '@datadog/browser-rum-core' import { LifeCycleEventType } from '@datadog/browser-rum-core' import type { Clock } from '@datadog/browser-core/test' import { collectAsyncCalls, createNewEvent, mockClock } from '@datadog/browser-core/test' @@ -25,11 +25,13 @@ describe('startRecording', () => { let requestSendSpy: jasmine.Spy let stopRecording: () => void let clock: Clock | undefined + let configuration: RumConfiguration beforeEach((done) => { if (isIE()) { pending('IE not supported') } + configuration = {} as RumConfiguration resetReplayStats() sessionManager = createRumSessionManagerMock() viewId = 'view-id' @@ -39,7 +41,7 @@ describe('startRecording', () => { textField = document.createElement('input') sandbox.appendChild(textField) - startDeflateWorker((worker) => { + startDeflateWorker(configuration, (worker) => { setupBuilder = setup() .withViewContexts({ findView() { diff --git a/packages/rum/src/boot/startRecording.ts b/packages/rum/src/boot/startRecording.ts index d5459f0026..dd122c2bcf 100644 --- a/packages/rum/src/boot/startRecording.ts +++ b/packages/rum/src/boot/startRecording.ts @@ -28,11 +28,12 @@ export function startRecording( } const replayRequest = - httpRequest || createHttpRequest(configuration.sessionReplayEndpointBuilder, SEGMENT_BYTES_LIMIT, reportError) + httpRequest || + createHttpRequest(configuration, configuration.sessionReplayEndpointBuilder, SEGMENT_BYTES_LIMIT, reportError) const { addRecord, stop: stopSegmentCollection } = startSegmentCollection( lifeCycle, - configuration.applicationId, + configuration, sessionManager, viewContexts, replayRequest, diff --git a/packages/rum/src/domain/record/observers/focusObserver.ts b/packages/rum/src/domain/record/observers/focusObserver.ts index b19988c019..21bdee9807 100644 --- a/packages/rum/src/domain/record/observers/focusObserver.ts +++ b/packages/rum/src/domain/record/observers/focusObserver.ts @@ -1,11 +1,12 @@ import type { ListenerHandler } from '@datadog/browser-core' import { DOM_EVENT, addEventListeners } from '@datadog/browser-core' +import type { RumConfiguration } from '@datadog/browser-rum-core' import type { FocusRecord } from '../../../types' export type FocusCallback = (data: FocusRecord['data']) => void -export function initFocusObserver(focusCb: FocusCallback): ListenerHandler { - return addEventListeners(window, [DOM_EVENT.FOCUS, DOM_EVENT.BLUR], () => { +export function initFocusObserver(configuration: RumConfiguration, focusCb: FocusCallback): ListenerHandler { + return addEventListeners(configuration, window, [DOM_EVENT.FOCUS, DOM_EVENT.BLUR], () => { focusCb({ has_focus: document.hasFocus() }) }).stop } diff --git a/packages/rum/src/domain/record/observers/inputObserver.spec.ts b/packages/rum/src/domain/record/observers/inputObserver.spec.ts index a327cf059a..431e2d8320 100644 --- a/packages/rum/src/domain/record/observers/inputObserver.spec.ts +++ b/packages/rum/src/domain/record/observers/inputObserver.spec.ts @@ -1,6 +1,7 @@ import { DefaultPrivacyLevel, isIE } from '@datadog/browser-core' import type { Clock } from '@datadog/browser-core/test' import { createNewEvent, mockClock } from '@datadog/browser-core/test' +import type { RumConfiguration } from '@datadog/browser-rum-core' import { PRIVACY_ATTR_NAME, PRIVACY_ATTR_VALUE_MASK_USER_INPUT } from '../../../constants' import { serializeDocument, SerializationContextStatus } from '../serialization' import { createElementsScrollPositions } from '../elementsScrollPositions' @@ -14,11 +15,13 @@ describe('initInputObserver', () => { let sandbox: HTMLElement let input: HTMLInputElement let clock: Clock | undefined + let configuration: RumConfiguration beforeEach(() => { if (isIE()) { pending('IE not supported') } + configuration = { defaultPrivacyLevel: DefaultPrivacyLevel.ALLOW } as RumConfiguration inputCallbackSpy = jasmine.createSpy() sandbox = document.createElement('div') @@ -40,7 +43,7 @@ describe('initInputObserver', () => { }) it('collects input values when an "input" event is dispatched', () => { - stopInputObserver = initInputObserver(inputCallbackSpy, DefaultPrivacyLevel.ALLOW) + stopInputObserver = initInputObserver(configuration, inputCallbackSpy) dispatchInputEvent('foo') expect(inputCallbackSpy).toHaveBeenCalledOnceWith({ @@ -51,7 +54,7 @@ describe('initInputObserver', () => { it('collects input values when a property setter is used', () => { clock = mockClock() - stopInputObserver = initInputObserver(inputCallbackSpy, DefaultPrivacyLevel.ALLOW) + stopInputObserver = initInputObserver(configuration, inputCallbackSpy) input.value = 'foo' clock.tick(0) @@ -64,7 +67,7 @@ describe('initInputObserver', () => { it('does not invoke callback when the value does not change', () => { clock = mockClock() - stopInputObserver = initInputObserver(inputCallbackSpy, DefaultPrivacyLevel.ALLOW) + stopInputObserver = initInputObserver(configuration, inputCallbackSpy) input.value = 'foo' clock.tick(0) @@ -79,15 +82,15 @@ describe('initInputObserver', () => { const host = document.createElement('div') host.attachShadow({ mode: 'open' }) - stopInputObserver = initInputObserver(inputCallbackSpy, DefaultPrivacyLevel.ALLOW, host.shadowRoot!) + stopInputObserver = initInputObserver(configuration, inputCallbackSpy, host.shadowRoot!) // eslint-disable-next-line @typescript-eslint/unbound-method expect(Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')!.set).toBe(originalSetter) }) - // cannot trigger a event in a Shadow DOM because event with `isTrusted:false` do not cross the root + // cannot trigger an event in a Shadow DOM because event with `isTrusted:false` do not cross the root it('collects input values when an "input" event is composed', () => { - stopInputObserver = initInputObserver(inputCallbackSpy, DefaultPrivacyLevel.ALLOW) + stopInputObserver = initInputObserver(configuration, inputCallbackSpy) dispatchInputEventWithInShadowDom('foo') expect(inputCallbackSpy).toHaveBeenCalledOnceWith({ @@ -97,7 +100,8 @@ describe('initInputObserver', () => { }) it('masks input values according to the element privacy level', () => { - stopInputObserver = initInputObserver(inputCallbackSpy, DefaultPrivacyLevel.ALLOW) + configuration.defaultPrivacyLevel = DefaultPrivacyLevel.ALLOW + stopInputObserver = initInputObserver(configuration, inputCallbackSpy) sandbox.setAttribute(PRIVACY_ATTR_NAME, PRIVACY_ATTR_VALUE_MASK_USER_INPUT) dispatchInputEvent('foo') @@ -106,7 +110,8 @@ describe('initInputObserver', () => { }) it('masks input values according to a parent element privacy level', () => { - stopInputObserver = initInputObserver(inputCallbackSpy, DefaultPrivacyLevel.ALLOW) + configuration.defaultPrivacyLevel = DefaultPrivacyLevel.ALLOW + stopInputObserver = initInputObserver(configuration, inputCallbackSpy) input.setAttribute(PRIVACY_ATTR_NAME, PRIVACY_ATTR_VALUE_MASK_USER_INPUT) dispatchInputEvent('foo') @@ -115,7 +120,8 @@ describe('initInputObserver', () => { }) it('masks input values according to a the default privacy level', () => { - stopInputObserver = initInputObserver(inputCallbackSpy, DefaultPrivacyLevel.MASK) + configuration.defaultPrivacyLevel = DefaultPrivacyLevel.MASK + stopInputObserver = initInputObserver(configuration, inputCallbackSpy) dispatchInputEvent('foo') diff --git a/packages/rum/src/domain/record/observers/inputObserver.ts b/packages/rum/src/domain/record/observers/inputObserver.ts index ae1e0584a1..23b84ab41f 100644 --- a/packages/rum/src/domain/record/observers/inputObserver.ts +++ b/packages/rum/src/domain/record/observers/inputObserver.ts @@ -1,5 +1,6 @@ -import type { DefaultPrivacyLevel, ListenerHandler } from '@datadog/browser-core' +import type { ListenerHandler } from '@datadog/browser-core' import { instrumentSetter, assign, DOM_EVENT, addEventListeners, forEach, noop } from '@datadog/browser-core' +import type { RumConfiguration } from '@datadog/browser-rum-core' import { NodePrivacyLevel } from '../../../constants' import type { InputState } from '../../../types' import { getEventTarget } from '../eventsUtils' @@ -9,15 +10,17 @@ import { getElementInputValue, getSerializedNodeId, hasSerializedNode } from '.. export type InputCallback = (v: InputState & { id: number }) => void export function initInputObserver( + configuration: RumConfiguration, cb: InputCallback, - defaultPrivacyLevel: DefaultPrivacyLevel, target: Document | ShadowRoot = document ): ListenerHandler { + const defaultPrivacyLevel = configuration.defaultPrivacyLevel const lastInputStateMap: WeakMap = new WeakMap() const isShadowRoot = target !== document const { stop: stopEventListeners } = addEventListeners( + configuration, target, // The 'input' event bubbles across shadow roots, so we don't have to listen for it on shadow // roots since it will be handled by the event listener that we did add to the document. Only diff --git a/packages/rum/src/domain/record/observers/mediaInteractionObserver.ts b/packages/rum/src/domain/record/observers/mediaInteractionObserver.ts index be745c23c8..6387db209f 100644 --- a/packages/rum/src/domain/record/observers/mediaInteractionObserver.ts +++ b/packages/rum/src/domain/record/observers/mediaInteractionObserver.ts @@ -1,5 +1,6 @@ import type { DefaultPrivacyLevel, ListenerHandler } from '@datadog/browser-core' import { DOM_EVENT, addEventListeners } from '@datadog/browser-core' +import type { RumConfiguration } from '@datadog/browser-rum-core' import { NodePrivacyLevel } from '../../../constants' import type { MediaInteraction } from '../../../types' import { MediaInteractionType } from '../../../types' @@ -10,6 +11,7 @@ import { getSerializedNodeId, hasSerializedNode } from '../serialization' export type MediaInteractionCallback = (p: MediaInteraction) => void export function initMediaInteractionObserver( + configuration: RumConfiguration, mediaInteractionCb: MediaInteractionCallback, defaultPrivacyLevel: DefaultPrivacyLevel ): ListenerHandler { @@ -27,5 +29,8 @@ export function initMediaInteractionObserver( type: event.type === DOM_EVENT.PLAY ? MediaInteractionType.Play : MediaInteractionType.Pause, }) } - return addEventListeners(document, [DOM_EVENT.PLAY, DOM_EVENT.PAUSE], handler, { capture: true, passive: true }).stop + return addEventListeners(configuration, document, [DOM_EVENT.PLAY, DOM_EVENT.PAUSE], handler, { + capture: true, + passive: true, + }).stop } diff --git a/packages/rum/src/domain/record/observers/mouseInteractionObserver.spec.ts b/packages/rum/src/domain/record/observers/mouseInteractionObserver.spec.ts index b70b7aefce..e1c6d2f170 100644 --- a/packages/rum/src/domain/record/observers/mouseInteractionObserver.spec.ts +++ b/packages/rum/src/domain/record/observers/mouseInteractionObserver.spec.ts @@ -1,5 +1,6 @@ import { DOM_EVENT, DefaultPrivacyLevel, isIE } from '@datadog/browser-core' import { createNewEvent } from '@datadog/browser-core/test' +import type { RumConfiguration } from '@datadog/browser-rum-core' import { IncrementalSource, MouseInteractionType, RecordType } from '../../../types' import { serializeDocument, SerializationContextStatus } from '../serialization' import { createElementsScrollPositions } from '../elementsScrollPositions' @@ -15,12 +16,14 @@ describe('initMouseInteractionObserver', () => { let recordIds: RecordIds let sandbox: HTMLDivElement let a: HTMLAnchorElement + let configuration: RumConfiguration beforeEach(() => { if (isIE()) { pending('IE not supported') } + configuration = { defaultPrivacyLevel: DefaultPrivacyLevel.ALLOW } as RumConfiguration sandbox = document.createElement('div') a = document.createElement('a') a.setAttribute('tabindex', '0') // make the element focusable @@ -36,7 +39,7 @@ describe('initMouseInteractionObserver', () => { mouseInteractionCallbackSpy = jasmine.createSpy() recordIds = initRecordIds() - stopObserver = initMouseInteractionObserver(mouseInteractionCallbackSpy, DefaultPrivacyLevel.ALLOW, recordIds) + stopObserver = initMouseInteractionObserver(configuration, mouseInteractionCallbackSpy, recordIds) }) afterEach(() => { diff --git a/packages/rum/src/domain/record/observers/mouseInteractionObserver.ts b/packages/rum/src/domain/record/observers/mouseInteractionObserver.ts index bfa0658be0..50741856d6 100644 --- a/packages/rum/src/domain/record/observers/mouseInteractionObserver.ts +++ b/packages/rum/src/domain/record/observers/mouseInteractionObserver.ts @@ -1,5 +1,6 @@ -import type { DefaultPrivacyLevel, ListenerHandler } from '@datadog/browser-core' +import type { ListenerHandler } from '@datadog/browser-core' import { assign, addEventListeners, DOM_EVENT } from '@datadog/browser-core' +import type { RumConfiguration } from '@datadog/browser-rum-core' import { NodePrivacyLevel } from '../../../constants' import type { MouseInteraction, MouseInteractionData, BrowserIncrementalSnapshotRecord } from '../../../types' import { IncrementalSource, MouseInteractionType } from '../../../types' @@ -34,13 +35,16 @@ const eventTypeToMouseInteraction = { export type MouseInteractionCallBack = (record: BrowserIncrementalSnapshotRecord) => void export function initMouseInteractionObserver( + configuration: RumConfiguration, cb: MouseInteractionCallBack, - defaultPrivacyLevel: DefaultPrivacyLevel, recordIds: RecordIds ): ListenerHandler { const handler = (event: MouseEvent | TouchEvent | FocusEvent) => { const target = getEventTarget(event) - if (getNodePrivacyLevel(target, defaultPrivacyLevel) === NodePrivacyLevel.HIDDEN || !hasSerializedNode(target)) { + if ( + getNodePrivacyLevel(target, configuration.defaultPrivacyLevel) === NodePrivacyLevel.HIDDEN || + !hasSerializedNode(target) + ) { return } const id = getSerializedNodeId(target) @@ -64,6 +68,7 @@ export function initMouseInteractionObserver( cb(record) } return addEventListeners( + configuration, document, Object.keys(eventTypeToMouseInteraction) as Array, handler, diff --git a/packages/rum/src/domain/record/observers/moveObserver.spec.ts b/packages/rum/src/domain/record/observers/moveObserver.spec.ts index d1740cb523..f00fe8b237 100644 --- a/packages/rum/src/domain/record/observers/moveObserver.spec.ts +++ b/packages/rum/src/domain/record/observers/moveObserver.spec.ts @@ -1,5 +1,6 @@ import { isIE } from '@datadog/browser-core' import { createNewEvent } from '@datadog/browser-core/test' +import type { RumConfiguration } from '@datadog/browser-rum-core' import { SerializationContextStatus, serializeDocument } from '../serialization' import { createElementsScrollPositions } from '../elementsScrollPositions' import { IncrementalSource } from '../../../types' @@ -10,12 +11,14 @@ import { DEFAULT_CONFIGURATION, DEFAULT_SHADOW_ROOT_CONTROLLER } from './observe describe('initMoveObserver', () => { let mouseMoveCallbackSpy: jasmine.Spy let stopObserver: () => void + let configuration: RumConfiguration beforeEach(() => { if (isIE()) { pending('IE not supported') } + configuration = {} as RumConfiguration serializeDocument(document, DEFAULT_CONFIGURATION, { shadowRootsController: DEFAULT_SHADOW_ROOT_CONTROLLER, status: SerializationContextStatus.INITIAL_FULL_SNAPSHOT, @@ -23,7 +26,7 @@ describe('initMoveObserver', () => { }) mouseMoveCallbackSpy = jasmine.createSpy() - stopObserver = initMoveObserver(mouseMoveCallbackSpy) + stopObserver = initMoveObserver(configuration, mouseMoveCallbackSpy) }) afterEach(() => { diff --git a/packages/rum/src/domain/record/observers/moveObserver.ts b/packages/rum/src/domain/record/observers/moveObserver.ts index 08feadd5c4..9c9f024bdf 100644 --- a/packages/rum/src/domain/record/observers/moveObserver.ts +++ b/packages/rum/src/domain/record/observers/moveObserver.ts @@ -1,5 +1,6 @@ import type { ListenerHandler } from '@datadog/browser-core' import { addEventListeners, addTelemetryDebug, DOM_EVENT, throttle } from '@datadog/browser-core' +import type { RumConfiguration } from '@datadog/browser-rum-core' import { getSerializedNodeId, hasSerializedNode } from '../serialization' import type { MousePosition } from '../../../types' import { IncrementalSource } from '../../../types' @@ -13,7 +14,7 @@ export type MousemoveCallBack = ( source: typeof IncrementalSource.MouseMove | typeof IncrementalSource.TouchMove ) => void -export function initMoveObserver(cb: MousemoveCallBack): ListenerHandler { +export function initMoveObserver(configuration: RumConfiguration, cb: MousemoveCallBack): ListenerHandler { const { throttled: updatePosition } = throttle( (event: MouseEvent | TouchEvent) => { const target = getEventTarget(event) @@ -38,7 +39,7 @@ export function initMoveObserver(cb: MousemoveCallBack): ListenerHandler { } ) - return addEventListeners(document, [DOM_EVENT.MOUSE_MOVE, DOM_EVENT.TOUCH_MOVE], updatePosition, { + return addEventListeners(configuration, document, [DOM_EVENT.MOUSE_MOVE, DOM_EVENT.TOUCH_MOVE], updatePosition, { capture: true, passive: true, }).stop diff --git a/packages/rum/src/domain/record/observers/observers.ts b/packages/rum/src/domain/record/observers/observers.ts index 7dfc9adc97..21a2741aeb 100644 --- a/packages/rum/src/domain/record/observers/observers.ts +++ b/packages/rum/src/domain/record/observers/observers.ts @@ -42,25 +42,30 @@ interface ObserverParam { shadowRootsController: ShadowRootsController } -export function initObservers(o: ObserverParam): { stop: ListenerHandler; flush: ListenerHandler } { +export function initObservers( + configuration: RumConfiguration, + o: ObserverParam +): { stop: ListenerHandler; flush: ListenerHandler } { const recordIds = initRecordIds() const mutationHandler = initMutationObserver(o.mutationCb, o.configuration, o.shadowRootsController, document) - const mousemoveHandler = initMoveObserver(o.mousemoveCb) - const mouseInteractionHandler = initMouseInteractionObserver( - o.mouseInteractionCb, + const mousemoveHandler = initMoveObserver(configuration, o.mousemoveCb) + const mouseInteractionHandler = initMouseInteractionObserver(configuration, o.mouseInteractionCb, recordIds) + const scrollHandler = initScrollObserver( + configuration, + o.scrollCb, o.configuration.defaultPrivacyLevel, - recordIds + o.elementsScrollPositions ) - const scrollHandler = initScrollObserver(o.scrollCb, o.configuration.defaultPrivacyLevel, o.elementsScrollPositions) - const viewportResizeHandler = initViewportResizeObserver(o.viewportResizeCb) - const inputHandler = initInputObserver(o.inputCb, o.configuration.defaultPrivacyLevel) + const viewportResizeHandler = initViewportResizeObserver(configuration, o.viewportResizeCb) + const inputHandler = initInputObserver(configuration, o.inputCb) const mediaInteractionHandler = initMediaInteractionObserver( + configuration, o.mediaInteractionCb, o.configuration.defaultPrivacyLevel ) const styleSheetObserver = initStyleSheetObserver(o.styleSheetCb) - const focusHandler = initFocusObserver(o.focusCb) - const visualViewportResizeHandler = initVisualViewportResizeObserver(o.visualViewportResizeCb) + const focusHandler = initFocusObserver(configuration, o.focusCb) + const visualViewportResizeHandler = initVisualViewportResizeObserver(configuration, o.visualViewportResizeCb) const frustrationHandler = initFrustrationObserver(o.lifeCycle, o.frustrationCb, recordIds) return { diff --git a/packages/rum/src/domain/record/observers/scrollObserver.ts b/packages/rum/src/domain/record/observers/scrollObserver.ts index 7bacbea60d..5c106548ba 100644 --- a/packages/rum/src/domain/record/observers/scrollObserver.ts +++ b/packages/rum/src/domain/record/observers/scrollObserver.ts @@ -1,5 +1,6 @@ import type { DefaultPrivacyLevel, ListenerHandler } from '@datadog/browser-core' import { DOM_EVENT, throttle, addEventListener } from '@datadog/browser-core' +import type { RumConfiguration } from '@datadog/browser-rum-core' import { getScrollX, getScrollY } from '@datadog/browser-rum-core' import type { ElementsScrollPositions } from '../elementsScrollPositions' import { getEventTarget } from '../eventsUtils' @@ -13,6 +14,7 @@ const SCROLL_OBSERVER_THRESHOLD = 100 export type ScrollCallback = (p: ScrollPosition) => void export function initScrollObserver( + configuration: RumConfiguration, cb: ScrollCallback, defaultPrivacyLevel: DefaultPrivacyLevel, elementsScrollPositions: ElementsScrollPositions @@ -44,5 +46,6 @@ export function initScrollObserver( y: scrollPositions.scrollTop, }) }, SCROLL_OBSERVER_THRESHOLD) - return addEventListener(document, DOM_EVENT.SCROLL, updatePosition, { capture: true, passive: true }).stop + return addEventListener(configuration, document, DOM_EVENT.SCROLL, updatePosition, { capture: true, passive: true }) + .stop } diff --git a/packages/rum/src/domain/record/observers/viewportResizeObserver.ts b/packages/rum/src/domain/record/observers/viewportResizeObserver.ts index faedfe0768..b75e4d0e9c 100644 --- a/packages/rum/src/domain/record/observers/viewportResizeObserver.ts +++ b/packages/rum/src/domain/record/observers/viewportResizeObserver.ts @@ -1,5 +1,6 @@ import type { ListenerHandler } from '@datadog/browser-core' import { throttle, DOM_EVENT, addEventListeners, noop } from '@datadog/browser-core' +import type { RumConfiguration } from '@datadog/browser-rum-core' import { initViewportObservable } from '@datadog/browser-rum-core' import type { ViewportResizeDimension, VisualViewportRecord } from '../../../types' import { getVisualViewport } from '../viewports' @@ -10,11 +11,17 @@ export type ViewportResizeCallback = (d: ViewportResizeDimension) => void export type VisualViewportResizeCallback = (data: VisualViewportRecord['data']) => void -export function initViewportResizeObserver(cb: ViewportResizeCallback): ListenerHandler { - return initViewportObservable().subscribe(cb).unsubscribe +export function initViewportResizeObserver( + configuration: RumConfiguration, + cb: ViewportResizeCallback +): ListenerHandler { + return initViewportObservable(configuration).subscribe(cb).unsubscribe } -export function initVisualViewportResizeObserver(cb: VisualViewportResizeCallback): ListenerHandler { +export function initVisualViewportResizeObserver( + configuration: RumConfiguration, + cb: VisualViewportResizeCallback +): ListenerHandler { const visualViewport = window.visualViewport if (!visualViewport) { return noop @@ -28,10 +35,16 @@ export function initVisualViewportResizeObserver(cb: VisualViewportResizeCallbac trailing: false, } ) - const removeListener = addEventListeners(visualViewport, [DOM_EVENT.RESIZE, DOM_EVENT.SCROLL], updateDimension, { - capture: true, - passive: true, - }).stop + const removeListener = addEventListeners( + configuration, + visualViewport, + [DOM_EVENT.RESIZE, DOM_EVENT.SCROLL], + updateDimension, + { + capture: true, + passive: true, + } + ).stop return function stop() { removeListener() diff --git a/packages/rum/src/domain/record/record.ts b/packages/rum/src/domain/record/record.ts index d4fedc27b8..180c226bfd 100644 --- a/packages/rum/src/domain/record/record.ts +++ b/packages/rum/src/domain/record/record.ts @@ -37,7 +37,7 @@ export interface RecordAPI { } export function record(options: RecordOptions): RecordAPI { - const { emit } = options + const { emit, configuration } = options // runtime checks for user options if (!emit) { throw new Error('emit function is required') @@ -50,7 +50,7 @@ export function record(options: RecordOptions): RecordAPI { } const inputCb: InputCallback = (s) => emit(assembleIncrementalSnapshot(IncrementalSource.Input, s)) - const shadowRootsController = initShadowRootsController(options.configuration, { mutationCb, inputCb }) + const shadowRootsController = initShadowRootsController(configuration, { mutationCb, inputCb }) const takeFullSnapshot = ( timestamp = timeStampNow(), @@ -81,7 +81,7 @@ export function record(options: RecordOptions): RecordAPI { emit({ data: { - node: serializeDocument(document, options.configuration, serializationContext), + node: serializeDocument(document, configuration, serializationContext), initialOffset: { left: getScrollX(), top: getScrollY(), @@ -102,9 +102,9 @@ export function record(options: RecordOptions): RecordAPI { takeFullSnapshot() - const { stop: stopObservers, flush: flushMutationsFromObservers } = initObservers({ + const { stop: stopObservers, flush: flushMutationsFromObservers } = initObservers(configuration, { lifeCycle: options.lifeCycle, - configuration: options.configuration, + configuration, elementsScrollPositions, inputCb, mediaInteractionCb: (p) => diff --git a/packages/rum/src/domain/record/shadowRootsController.ts b/packages/rum/src/domain/record/shadowRootsController.ts index 85235f4f40..db2a5c98c3 100644 --- a/packages/rum/src/domain/record/shadowRootsController.ts +++ b/packages/rum/src/domain/record/shadowRootsController.ts @@ -37,7 +37,7 @@ export const initShadowRootsController = ( shadowRoot ) // the change event no do bubble up across the shadow root, we have to listen on the shadow root - const stopInputObserver = initInputObserver(inputCb, configuration.defaultPrivacyLevel, shadowRoot) + const stopInputObserver = initInputObserver(configuration, inputCb, shadowRoot) controllerByShadowRoot.set(shadowRoot, { flush, stop: () => { diff --git a/packages/rum/src/domain/segmentCollection/segment.spec.ts b/packages/rum/src/domain/segmentCollection/segment.spec.ts index 240e5f42b4..a8a629db6b 100644 --- a/packages/rum/src/domain/segmentCollection/segment.spec.ts +++ b/packages/rum/src/domain/segmentCollection/segment.spec.ts @@ -1,5 +1,6 @@ import type { TimeStamp } from '@datadog/browser-core' import { noop, setDebugMode, display, isIE } from '@datadog/browser-core' +import type { RumConfiguration } from '@datadog/browser-rum-core' import { MockWorker } from '../../../test' import type { CreationReason, BrowserRecord, SegmentContext, BrowserSegment } from '../../types' import { RecordType } from '../../types' @@ -237,7 +238,8 @@ describe('Segment', () => { onWrote?: (compressedSegmentBytesCount: number) => void onFlushed?: (data: Uint8Array, rawBytesCount: number) => void } = {}) { - return new Segment(worker, context, creationReason, initialRecord, onWrote, onFlushed) + const configuration = {} as RumConfiguration + return new Segment(configuration, worker, context, creationReason, initialRecord, onWrote, onFlushed) } }) diff --git a/packages/rum/src/domain/segmentCollection/segment.ts b/packages/rum/src/domain/segmentCollection/segment.ts index debdeff26a..7c3a10bb39 100644 --- a/packages/rum/src/domain/segmentCollection/segment.ts +++ b/packages/rum/src/domain/segmentCollection/segment.ts @@ -1,5 +1,6 @@ import { addTelemetryDebug, assign, sendToExtension, addEventListener } from '@datadog/browser-core' import type { DeflateWorkerResponse } from '@datadog/browser-worker' +import type { RumConfiguration } from '@datadog/browser-rum-core' import type { BrowserRecord, BrowserSegmentMetadata, CreationReason, SegmentContext } from '../../types' import { RecordType } from '../../types' import * as replayStats from '../replayStats' @@ -17,6 +18,7 @@ export class Segment { private id = nextId++ constructor( + configuration: RumConfiguration, private worker: DeflateWorker, context: SegmentContext, creationReason: CreationReason, @@ -43,6 +45,7 @@ export class Segment { replayStats.addRecord(viewId) const { stop: removeMessageListener } = addEventListener( + configuration, worker, 'message', ({ data }: MessageEvent) => { diff --git a/packages/rum/src/domain/segmentCollection/segmentCollection.spec.ts b/packages/rum/src/domain/segmentCollection/segmentCollection.spec.ts index ce35c16ea0..bdfdcd882c 100644 --- a/packages/rum/src/domain/segmentCollection/segmentCollection.spec.ts +++ b/packages/rum/src/domain/segmentCollection/segmentCollection.spec.ts @@ -1,6 +1,6 @@ import type { ClocksState, HttpRequest, TimeStamp } from '@datadog/browser-core' import { PageExitReason, isIE } from '@datadog/browser-core' -import type { ViewContexts, ViewContext } from '@datadog/browser-rum-core' +import type { ViewContexts, ViewContext, RumConfiguration } from '@datadog/browser-rum-core' import { LifeCycle, LifeCycleEventType } from '@datadog/browser-rum-core' import type { Clock } from '@datadog/browser-core/test' import { mockClock, restorePageVisibility } from '@datadog/browser-core/test' @@ -38,6 +38,7 @@ describe('startSegmentCollection', () => { } let addRecord: (record: BrowserRecord) => void let context: SegmentContext | undefined + let configuration: RumConfiguration function addRecordAndFlushSegment(flushStrategy: () => void = emulatePageUnload) { // Make sure the segment is not empty @@ -59,6 +60,7 @@ describe('startSegmentCollection', () => { if (isIE()) { pending('IE not supported') } + configuration = {} as RumConfiguration lifeCycle = new LifeCycle() worker = new MockWorker() httpRequestSpy = { @@ -68,6 +70,7 @@ describe('startSegmentCollection', () => { context = CONTEXT ;({ stop: stopSegmentCollection, addRecord } = doStartSegmentCollection( lifeCycle, + configuration, () => context, httpRequestSpy, worker diff --git a/packages/rum/src/domain/segmentCollection/segmentCollection.ts b/packages/rum/src/domain/segmentCollection/segmentCollection.ts index e0c4688006..06dae67754 100644 --- a/packages/rum/src/domain/segmentCollection/segmentCollection.ts +++ b/packages/rum/src/domain/segmentCollection/segmentCollection.ts @@ -1,6 +1,6 @@ import type { HttpRequest, TimeoutId } from '@datadog/browser-core' import { isPageExitReason, ONE_SECOND, clearTimeout, setTimeout } from '@datadog/browser-core' -import type { LifeCycle, ViewContexts, RumSessionManager } from '@datadog/browser-rum-core' +import type { LifeCycle, ViewContexts, RumSessionManager, RumConfiguration } from '@datadog/browser-rum-core' import { LifeCycleEventType } from '@datadog/browser-rum-core' import type { BrowserRecord, CreationReason, SegmentContext } from '../../types' import { buildReplayPayload } from './buildReplayPayload' @@ -42,7 +42,7 @@ export let SEGMENT_BYTES_LIMIT = 60_000 export function startSegmentCollection( lifeCycle: LifeCycle, - applicationId: string, + configuration: RumConfiguration, sessionManager: RumSessionManager, viewContexts: ViewContexts, httpRequest: HttpRequest, @@ -50,7 +50,8 @@ export function startSegmentCollection( ) { return doStartSegmentCollection( lifeCycle, - () => computeSegmentContext(applicationId, sessionManager, viewContexts), + configuration, + () => computeSegmentContext(configuration.applicationId, sessionManager, viewContexts), httpRequest, worker ) @@ -77,6 +78,7 @@ type SegmentCollectionState = export function doStartSegmentCollection( lifeCycle: LifeCycle, + configuration: RumConfiguration, getSegmentContext: () => SegmentContext | undefined, httpRequest: HttpRequest, worker: DeflateWorker @@ -122,6 +124,7 @@ export function doStartSegmentCollection( } const segment = new Segment( + configuration, worker, context, creationReason, diff --git a/packages/rum/src/domain/segmentCollection/startDeflateWorker.spec.ts b/packages/rum/src/domain/segmentCollection/startDeflateWorker.spec.ts index 24c1069718..64ac017886 100644 --- a/packages/rum/src/domain/segmentCollection/startDeflateWorker.spec.ts +++ b/packages/rum/src/domain/segmentCollection/startDeflateWorker.spec.ts @@ -1,6 +1,7 @@ import type { RawTelemetryEvent } from '@datadog/browser-core' import { display, isIE, noop, resetTelemetry, startFakeTelemetry } from '@datadog/browser-core' import type { DeflateWorkerResponse } from '@datadog/browser-worker' +import type { RumConfiguration } from '@datadog/browser-rum-core' import { MockWorker } from '../../../test' import type { DeflateWorker } from './startDeflateWorker' import { startDeflateWorker, resetDeflateWorkerState, createDeflateWorker } from './startDeflateWorker' @@ -9,8 +10,10 @@ describe('startDeflateWorker', () => { let deflateWorker: MockWorker let createDeflateWorkerSpy: jasmine.Spy let callbackSpy: jasmine.Spy + let configuration: RumConfiguration beforeEach(() => { + configuration = {} as RumConfiguration deflateWorker = new MockWorker() callbackSpy = jasmine.createSpy('callbackSpy') createDeflateWorkerSpy = jasmine.createSpy('createDeflateWorkerSpy').and.callFake(() => deflateWorker) @@ -21,17 +24,17 @@ describe('startDeflateWorker', () => { }) it('creates a deflate worker and call callback when initialized', () => { - startDeflateWorker(callbackSpy, createDeflateWorkerSpy) + startDeflateWorker(configuration, callbackSpy, createDeflateWorkerSpy) expect(createDeflateWorkerSpy).toHaveBeenCalledTimes(1) deflateWorker.processAllMessages() expect(callbackSpy).toHaveBeenCalledOnceWith(deflateWorker) }) it('uses the previously created worker', () => { - startDeflateWorker(noop, createDeflateWorkerSpy) + startDeflateWorker(configuration, noop, createDeflateWorkerSpy) deflateWorker.processAllMessages() - startDeflateWorker(callbackSpy, createDeflateWorkerSpy) + startDeflateWorker(configuration, callbackSpy, createDeflateWorkerSpy) expect(createDeflateWorkerSpy).toHaveBeenCalledTimes(1) deflateWorker.processAllMessages() expect(callbackSpy).toHaveBeenCalledOnceWith(deflateWorker) @@ -39,16 +42,16 @@ describe('startDeflateWorker', () => { describe('loading state', () => { it('does not create multiple workers when called multiple times while the worker is loading', () => { - startDeflateWorker(noop, createDeflateWorkerSpy) - startDeflateWorker(noop, createDeflateWorkerSpy) + startDeflateWorker(configuration, noop, createDeflateWorkerSpy) + startDeflateWorker(configuration, noop, createDeflateWorkerSpy) expect(createDeflateWorkerSpy).toHaveBeenCalledTimes(1) }) it('calls all registered callbacks when the worker is initialized', () => { const callbackSpy1 = jasmine.createSpy() const callbackSpy2 = jasmine.createSpy() - startDeflateWorker(callbackSpy1, createDeflateWorkerSpy) - startDeflateWorker(callbackSpy2, createDeflateWorkerSpy) + startDeflateWorker(configuration, callbackSpy1, createDeflateWorkerSpy) + startDeflateWorker(configuration, callbackSpy2, createDeflateWorkerSpy) deflateWorker.processAllMessages() expect(callbackSpy1).toHaveBeenCalledOnceWith(deflateWorker) expect(callbackSpy2).toHaveBeenCalledOnceWith(deflateWorker) @@ -60,11 +63,13 @@ describe('startDeflateWorker', () => { // mimic Chrome behavior let CSP_ERROR: DOMException let displaySpy: jasmine.Spy + let configuration: RumConfiguration beforeEach(() => { if (isIE()) { pending('IE does not support CSP blocking worker creation') } + configuration = {} as RumConfiguration displaySpy = spyOn(display, 'error') telemetryEvents = startFakeTelemetry() CSP_ERROR = new DOMException( @@ -77,7 +82,7 @@ describe('startDeflateWorker', () => { }) it('displays CSP instructions when the worker creation throws a CSP error', () => { - startDeflateWorker(noop, () => { + startDeflateWorker(configuration, noop, () => { throw CSP_ERROR }) expect(displaySpy).toHaveBeenCalledWith( @@ -86,30 +91,30 @@ describe('startDeflateWorker', () => { }) it('does not report CSP errors to telemetry', () => { - startDeflateWorker(noop, () => { + startDeflateWorker(configuration, noop, () => { throw CSP_ERROR }) expect(telemetryEvents).toEqual([]) }) it('displays ErrorEvent as CSP error', () => { - startDeflateWorker(noop, createDeflateWorkerSpy) + startDeflateWorker(configuration, noop, createDeflateWorkerSpy) deflateWorker.dispatchErrorEvent() expect(displaySpy).toHaveBeenCalledWith( 'Please make sure CSP is correctly configured https://docs.datadoghq.com/real_user_monitoring/faq/content_security_policy' ) }) it('calls the callback without argument in case of an error occurs during loading', () => { - startDeflateWorker(callbackSpy, createDeflateWorkerSpy) + startDeflateWorker(configuration, callbackSpy, createDeflateWorkerSpy) deflateWorker.dispatchErrorEvent() expect(callbackSpy).toHaveBeenCalledOnceWith() }) it('calls the callback without argument in case of an error occurred in a previous loading', () => { - startDeflateWorker(noop, createDeflateWorkerSpy) + startDeflateWorker(configuration, noop, createDeflateWorkerSpy) deflateWorker.dispatchErrorEvent() - startDeflateWorker(callbackSpy, createDeflateWorkerSpy) + startDeflateWorker(configuration, callbackSpy, createDeflateWorkerSpy) expect(callbackSpy).toHaveBeenCalledOnceWith() }) }) @@ -118,8 +123,10 @@ describe('startDeflateWorker', () => { let telemetryEvents: RawTelemetryEvent[] const UNKNOWN_ERROR = new Error('boom') let displaySpy: jasmine.Spy + let configuration: RumConfiguration beforeEach(() => { + configuration = {} as RumConfiguration displaySpy = spyOn(display, 'error') telemetryEvents = startFakeTelemetry() }) @@ -129,7 +136,7 @@ describe('startDeflateWorker', () => { }) it('displays an error message when the worker creation throws an unknown error', () => { - startDeflateWorker(noop, () => { + startDeflateWorker(configuration, noop, () => { throw UNKNOWN_ERROR }) expect(displaySpy).toHaveBeenCalledOnceWith( @@ -139,7 +146,7 @@ describe('startDeflateWorker', () => { }) it('reports unknown errors to telemetry', () => { - startDeflateWorker(noop, () => { + startDeflateWorker(configuration, noop, () => { throw UNKNOWN_ERROR }) expect(telemetryEvents).toEqual([ @@ -153,7 +160,7 @@ describe('startDeflateWorker', () => { }) it('does not display error messages as CSP error', () => { - startDeflateWorker(noop, createDeflateWorkerSpy) + startDeflateWorker(configuration, noop, createDeflateWorkerSpy) deflateWorker.dispatchErrorMessage('foo') expect(displaySpy).not.toHaveBeenCalledWith( 'Please make sure CSP is correctly configured https://docs.datadoghq.com/real_user_monitoring/faq/content_security_policy' @@ -161,7 +168,7 @@ describe('startDeflateWorker', () => { }) it('reports errors occurring after loading to telemetry', () => { - startDeflateWorker(noop, createDeflateWorkerSpy) + startDeflateWorker(configuration, noop, createDeflateWorkerSpy) deflateWorker.processAllMessages() deflateWorker.dispatchErrorMessage('boom') diff --git a/packages/rum/src/domain/segmentCollection/startDeflateWorker.ts b/packages/rum/src/domain/segmentCollection/startDeflateWorker.ts index 507f5b6e0f..a310bfe0e7 100644 --- a/packages/rum/src/domain/segmentCollection/startDeflateWorker.ts +++ b/packages/rum/src/domain/segmentCollection/startDeflateWorker.ts @@ -1,6 +1,7 @@ import { addTelemetryError, display, includes, addEventListener } from '@datadog/browser-core' import type { DeflateWorkerAction, DeflateWorkerResponse } from '@datadog/browser-worker' import { workerString } from '@datadog/browser-worker/string' +import type { RumConfiguration } from '@datadog/browser-rum-core' /** * In order to be sure that the worker is correctly working, we need a round trip of @@ -47,13 +48,14 @@ export function createDeflateWorker(): DeflateWorker { let state: DeflateWorkerState = { status: DeflateWorkerStatus.Nil } export function startDeflateWorker( + configuration: RumConfiguration, callback: (worker?: DeflateWorker) => void, createDeflateWorkerImpl = createDeflateWorker ) { switch (state.status) { case DeflateWorkerStatus.Nil: state = { status: DeflateWorkerStatus.Loading, callbacks: [callback] } - doStartDeflateWorker(createDeflateWorkerImpl) + doStartDeflateWorker(configuration, createDeflateWorkerImpl) break case DeflateWorkerStatus.Loading: state.callbacks.push(callback) @@ -80,11 +82,11 @@ export function resetDeflateWorkerState() { * * more details: https://bugzilla.mozilla.org/show_bug.cgi?id=1736865#c2 */ -export function doStartDeflateWorker(createDeflateWorkerImpl = createDeflateWorker) { +export function doStartDeflateWorker(configuration: RumConfiguration, createDeflateWorkerImpl = createDeflateWorker) { try { const worker = createDeflateWorkerImpl() - addEventListener(worker, 'error', onError) - addEventListener(worker, 'message', ({ data }: MessageEvent) => { + addEventListener(configuration, worker, 'error', onError) + addEventListener(configuration, worker, 'message', ({ data }: MessageEvent) => { if (data.type === 'errored') { onError(data.error) } else if (data.type === 'initialized') { From 23bbc715714de5e6499cff2b7b626ed0c816447c Mon Sep 17 00:00:00 2001 From: Bastien Caudan Date: Thu, 10 Aug 2023 10:21:21 +0200 Subject: [PATCH 24/27] =?UTF-8?q?=F0=9F=91=B7=20fix=20merge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/scenario/recorder/recorder.scenario.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/test/e2e/scenario/recorder/recorder.scenario.ts b/test/e2e/scenario/recorder/recorder.scenario.ts index 8094139585..3fc4748f00 100644 --- a/test/e2e/scenario/recorder/recorder.scenario.ts +++ b/test/e2e/scenario/recorder/recorder.scenario.ts @@ -803,7 +803,6 @@ describe('recorder', () => { createTest('workerUrl initialization parameter') .withRum({ workerUrl: '/worker.js' }) - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBasePath('/no-blob-worker-csp') .run(async ({ serverEvents }) => { From 4196b47176ab4153b6dd6c64661272f3f299b9fc Mon Sep 17 00:00:00 2001 From: Yannick Adam Date: Tue, 3 Oct 2023 14:05:17 +0200 Subject: [PATCH 25/27] Update lock file --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index cdbf4ebc08..0bc92d568a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -474,7 +474,7 @@ __metadata: dependencies: "@datadog/browser-core": 5.0.0-alpha.0 "@datadog/browser-rum-core": 5.0.0-alpha.0 - "@types/pako": 2.0.0 + "@types/pako": 2.0.1 pako: 2.1.0 peerDependencies: "@datadog/browser-logs": 5.0.0-alpha.0 From 4c7860f46300a54e35062332026742bd954b3682 Mon Sep 17 00:00:00 2001 From: Yannick Adam Date: Wed, 4 Oct 2023 11:52:10 +0200 Subject: [PATCH 26/27] =?UTF-8?q?=E2=9C=A8=20[RUM-1210]=20Add=20W3C=20trac?= =?UTF-8?q?econtext=20to=20default=20propagator=20types=20(#2443)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Ensure both Datadog and Tracecontext headers are present by default --- packages/rum-core/src/domain/configuration.spec.ts | 14 ++++++++------ packages/rum-core/src/domain/configuration.ts | 6 ++++-- test/e2e/scenario/rum/tracing.scenario.ts | 2 ++ 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/rum-core/src/domain/configuration.spec.ts b/packages/rum-core/src/domain/configuration.spec.ts index 61bf6a520f..1c0453dae1 100644 --- a/packages/rum-core/src/domain/configuration.spec.ts +++ b/packages/rum-core/src/domain/configuration.spec.ts @@ -1,6 +1,6 @@ import { DefaultPrivacyLevel, display } from '@datadog/browser-core' import type { RumInitConfiguration } from './configuration' -import { serializeRumConfiguration, validateAndBuildRumConfiguration } from './configuration' +import { DEFAULT_PROPAGATOR_TYPES, serializeRumConfiguration, validateAndBuildRumConfiguration } from './configuration' const DEFAULT_INIT_CONFIGURATION = { clientToken: 'xxx', applicationId: 'xxx' } @@ -90,7 +90,7 @@ describe('validateAndBuildRumConfiguration', () => { allowedTracingUrls: ['foo'], service: 'bar', })!.allowedTracingUrls - ).toEqual([{ match: 'foo', propagatorTypes: ['datadog'] }]) + ).toEqual([{ match: 'foo', propagatorTypes: DEFAULT_PROPAGATOR_TYPES }]) }) it('accepts functions', () => { @@ -102,7 +102,7 @@ describe('validateAndBuildRumConfiguration', () => { allowedTracingUrls: [customOriginFunction], service: 'bar', })!.allowedTracingUrls - ).toEqual([{ match: customOriginFunction, propagatorTypes: ['datadog'] }]) + ).toEqual([{ match: customOriginFunction, propagatorTypes: DEFAULT_PROPAGATOR_TYPES }]) }) it('accepts RegExp', () => { @@ -112,7 +112,7 @@ describe('validateAndBuildRumConfiguration', () => { allowedTracingUrls: [/az/i], service: 'bar', })!.allowedTracingUrls - ).toEqual([{ match: /az/i, propagatorTypes: ['datadog'] }]) + ).toEqual([{ match: /az/i, propagatorTypes: DEFAULT_PROPAGATOR_TYPES }]) }) it('keeps headers', () => { @@ -341,12 +341,14 @@ describe('validateAndBuildRumConfiguration', () => { expect(serializeRumConfiguration(DEFAULT_INIT_CONFIGURATION).selected_tracing_propagators).toEqual([]) }) - it('should return Datadog propagator type', () => { + it('should return the default propagator types', () => { const simpleTracingConfig: RumInitConfiguration = { ...DEFAULT_INIT_CONFIGURATION, allowedTracingUrls: ['foo'], } - expect(serializeRumConfiguration(simpleTracingConfig).selected_tracing_propagators).toEqual(['datadog']) + expect(serializeRumConfiguration(simpleTracingConfig).selected_tracing_propagators).toEqual( + DEFAULT_PROPAGATOR_TYPES + ) }) it('should return all propagator types', () => { diff --git a/packages/rum-core/src/domain/configuration.ts b/packages/rum-core/src/domain/configuration.ts index a3d123f77c..14c2aaeb95 100644 --- a/packages/rum-core/src/domain/configuration.ts +++ b/packages/rum-core/src/domain/configuration.ts @@ -16,6 +16,8 @@ import type { RumEvent } from '../rumEvent.types' import { isTracingOption } from './tracing/tracer' import type { PropagatorType, TracingOption } from './tracing/tracer.types' +export const DEFAULT_PROPAGATOR_TYPES: PropagatorType[] = ['tracecontext', 'datadog'] + export interface RumInitConfiguration extends InitConfiguration { // global options applicationId: string @@ -144,7 +146,7 @@ function validateAndBuildTracingOptions(initConfiguration: RumInitConfiguration) const tracingOptions: TracingOption[] = [] initConfiguration.allowedTracingUrls.forEach((option) => { if (isMatchOption(option)) { - tracingOptions.push({ match: option, propagatorTypes: ['datadog'] }) + tracingOptions.push({ match: option, propagatorTypes: DEFAULT_PROPAGATOR_TYPES }) } else if (isTracingOption(option)) { tracingOptions.push(option) } else { @@ -170,7 +172,7 @@ function getSelectedTracingPropagators(configuration: RumInitConfiguration): Pro if (Array.isArray(configuration.allowedTracingUrls) && configuration.allowedTracingUrls.length > 0) { configuration.allowedTracingUrls.forEach((option) => { if (isMatchOption(option)) { - usedTracingPropagators.add('datadog') + DEFAULT_PROPAGATOR_TYPES.forEach((propagatorType) => usedTracingPropagators.add(propagatorType)) } else if (getType(option) === 'object' && Array.isArray(option.propagatorTypes)) { // Ensure we have an array, as we cannot rely on types yet (configuration is provided by users) option.propagatorTypes.forEach((propagatorType) => usedTracingPropagators.add(propagatorType)) diff --git a/test/e2e/scenario/rum/tracing.scenario.ts b/test/e2e/scenario/rum/tracing.scenario.ts index 13109e38f1..a40186ed70 100644 --- a/test/e2e/scenario/rum/tracing.scenario.ts +++ b/test/e2e/scenario/rum/tracing.scenario.ts @@ -56,10 +56,12 @@ describe('tracing', () => { checkTraceAssociatedToRumEvent(intakeRegistry) }) + // By default, we send both Datadog and W3C tracecontext headers function checkRequestHeaders(rawHeaders: string) { const headers: { [key: string]: string } = JSON.parse(rawHeaders) expect(headers['x-datadog-trace-id']).toMatch(/\d+/) expect(headers['x-datadog-origin']).toBe('rum') + expect(headers['traceparent']).toMatch(/^[0-9a-f]{2}-[0-9a-f]{32}-[0-9a-f]{16}-01$/) expect(headers['x-foo']).toBe('bar, baz') } From 2ae80ed8f76fca1a43545a9cfb4dc707bb16f628 Mon Sep 17 00:00:00 2001 From: Bastien Caudan Date: Thu, 5 Oct 2023 17:58:23 +0200 Subject: [PATCH 27/27] Update READMEs for v5 --- README.md | 10 +++++----- packages/rum/README.md | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 6511818dd8..a7c7b09e1a 100644 --- a/README.md +++ b/README.md @@ -30,11 +30,11 @@ Datadog provides one CDN bundle per [site][60]: | Site | logs | rum | rum-slim | | ------- | -------------------------------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------ | -| US1 | https://www.datadoghq-browser-agent.com/us1/v4/datadog-logs.js | https://www.datadoghq-browser-agent.com/us1/v4/datadog-rum.js | https://www.datadoghq-browser-agent.com/us1/v4/datadog-rum-slim.js | -| US3 | https://www.datadoghq-browser-agent.com/us3/v4/datadog-logs.js | https://www.datadoghq-browser-agent.com/us3/v4/datadog-rum.js | https://www.datadoghq-browser-agent.com/us3/v4/datadog-rum-slim.js | -| US5 | https://www.datadoghq-browser-agent.com/us5/v4/datadog-logs.js | https://www.datadoghq-browser-agent.com/us5/v4/datadog-rum.js | https://www.datadoghq-browser-agent.com/us5/v4/datadog-rum-slim.js | -| EU1 | https://www.datadoghq-browser-agent.com/eu1/v4/datadog-logs.js | https://www.datadoghq-browser-agent.com/eu1/v4/datadog-rum.js | https://www.datadoghq-browser-agent.com/eu1/v4/datadog-rum-slim.js | -| US1-FED | https://www.datadoghq-browser-agent.com/datadog-logs-v4.js | https://www.datadoghq-browser-agent.com/datadog-rum-v4.js | https://www.datadoghq-browser-agent.com/datadog-rum-slim-v4.js | +| US1 | https://www.datadoghq-browser-agent.com/us1/v5/datadog-logs.js | https://www.datadoghq-browser-agent.com/us1/v5/datadog-rum.js | https://www.datadoghq-browser-agent.com/us1/v5/datadog-rum-slim.js | +| US3 | https://www.datadoghq-browser-agent.com/us3/v5/datadog-logs.js | https://www.datadoghq-browser-agent.com/us3/v5/datadog-rum.js | https://www.datadoghq-browser-agent.com/us3/v5/datadog-rum-slim.js | +| US5 | https://www.datadoghq-browser-agent.com/us5/v5/datadog-logs.js | https://www.datadoghq-browser-agent.com/us5/v5/datadog-rum.js | https://www.datadoghq-browser-agent.com/us5/v5/datadog-rum-slim.js | +| EU1 | https://www.datadoghq-browser-agent.com/eu1/v5/datadog-logs.js | https://www.datadoghq-browser-agent.com/eu1/v5/datadog-rum.js | https://www.datadoghq-browser-agent.com/eu1/v5/datadog-rum-slim.js | +| US1-FED | https://www.datadoghq-browser-agent.com/datadog-logs-v5.js | https://www.datadoghq-browser-agent.com/datadog-rum-v5.js | https://www.datadoghq-browser-agent.com/datadog-rum-slim-v5.js | [1]: https://github.githubassets.com/favicons/favicon.png [2]: https://imgix.datadoghq.com/img/favicons/favicon-32x32.png diff --git a/packages/rum/README.md b/packages/rum/README.md index 83c8a45da5..3c4ffe57e9 100644 --- a/packages/rum/README.md +++ b/packages/rum/README.md @@ -19,7 +19,7 @@ datadogRum.init({ // env: 'production', // version: '1.0.0', sessionSampleRate: 100, - sessionReplaySampleRate: 100, // if not included, the default is 100 + sessionReplaySampleRate: 100, trackResources: true, trackLongTasks: true, trackUserInteractions: true,