Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

♻️[RUMF-1517] split domain utils #2105

Merged
merged 9 commits into from
Mar 28, 2023
9 changes: 9 additions & 0 deletions packages/core/src/tools/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ export function round(num: number, decimals: 0 | 1 | 2 | 3 | 4) {
// eslint-disable-next-line @typescript-eslint/no-empty-function
export function noop() {}

export type ListenerHandler = () => void

/**
* Custom implementation of JSON.stringify that ignores some toJSON methods. We need to do that
* because some sites badly override toJSON on certain objects. Removing all toJSON methods from
Expand Down Expand Up @@ -223,6 +225,13 @@ export function findLast<T, S extends T>(
return undefined
}

export function forEach<List extends { [index: number]: any }>(
list: List,
callback: (value: List[number], index: number, parent: List) => void
) {
Array.prototype.forEach.call(list, callback as any)
}

export function isPercentage(value: unknown) {
return isNumber(value) && value >= 0 && value <= 100
}
Expand Down
19 changes: 19 additions & 0 deletions packages/rum/src/domain/record/assembly.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { assign, timeStampNow } from '@datadog/browser-core'
import type { BrowserIncrementalData, BrowserIncrementalSnapshotRecord } from '../../types'
import { RecordType } from '../../types'

export function assembleIncrementalSnapshot<Data extends BrowserIncrementalData>(
source: Data['source'],
data: Omit<Data, 'source'>
): BrowserIncrementalSnapshotRecord {
return {
data: assign(
{
source,
},
data
) as Data,
type: RecordType.IncrementalSnapshot,
timestamp: timeStampNow(),
}
}
12 changes: 12 additions & 0 deletions packages/rum/src/domain/record/eventsUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { isNodeShadowHost } from '@datadog/browser-rum-core'

export function isTouchEvent(event: MouseEvent | TouchEvent): event is TouchEvent {
return Boolean((event as TouchEvent).changedTouches)
}

export function getEventTarget(event: Event): Node {
if (event.composed === true && isNodeShadowHost(event.target as Node)) {
return event.composedPath()[0] as Node
}
return event.target as Node
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ListenerHandler } from '@datadog/browser-core'
import { DOM_EVENT, addEventListeners } from '@datadog/browser-core'
import type { FocusRecord } from '../../../types'
import type { ListenerHandler } from './utils'

export type FocusCallback = (data: FocusRecord['data']) => void

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,24 @@ import type { RawRumEventCollectedData } from 'packages/rum-core/src/domain/life
import { RecordType } from '../../../types'
import type { FrustrationCallback } from './frustrationObserver'
import { initFrustrationObserver } from './frustrationObserver'
import { getRecordIdForEvent } from './utils'
import type { RecordIds } from './recordIds'
import { initRecordIds } from './recordIds'

describe('initFrustrationObserver', () => {
const lifeCycle = new LifeCycle()
let stopFrustrationObserver: () => void
let frustrationsCallbackSpy: jasmine.Spy<FrustrationCallback>
let mouseEvent: MouseEvent
let rumData: RawRumEventCollectedData<RawRumActionEvent>
let recordIds: RecordIds

beforeEach(() => {
if (isIE()) {
pending('IE not supported')
}
mouseEvent = new MouseEvent('pointerup')
frustrationsCallbackSpy = jasmine.createSpy()
recordIds = initRecordIds()

rumData = {
startTime: relativeNow(),
Expand All @@ -46,19 +49,19 @@ describe('initFrustrationObserver', () => {
})

it('calls callback if the raw data inserted is a click action', () => {
stopFrustrationObserver = initFrustrationObserver(lifeCycle, frustrationsCallbackSpy)
stopFrustrationObserver = initFrustrationObserver(lifeCycle, frustrationsCallbackSpy, recordIds)
lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, rumData)

const frustrationRecord = frustrationsCallbackSpy.calls.first().args[0]
expect(frustrationRecord.type).toEqual(RecordType.FrustrationRecord)
expect(frustrationRecord.timestamp).toEqual(rumData.rawRumEvent.date)
expect(frustrationRecord.data.frustrationTypes).toEqual(rumData.rawRumEvent.action.frustration!.type)
expect(frustrationRecord.data.recordIds).toEqual([getRecordIdForEvent(mouseEvent)])
expect(frustrationRecord.data.recordIds).toEqual([recordIds.getIdForEvent(mouseEvent)])
})

it('ignores events other than click actions', () => {
rumData.rawRumEvent.action.type = ActionType.CUSTOM
stopFrustrationObserver = initFrustrationObserver(lifeCycle, frustrationsCallbackSpy)
stopFrustrationObserver = initFrustrationObserver(lifeCycle, frustrationsCallbackSpy, recordIds)
lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, rumData)

expect(frustrationsCallbackSpy).not.toHaveBeenCalled()
Expand All @@ -67,7 +70,7 @@ describe('initFrustrationObserver', () => {
it('ignores click actions without frustrations', () => {
rumData.rawRumEvent.action.frustration = { type: [] }

stopFrustrationObserver = initFrustrationObserver(lifeCycle, frustrationsCallbackSpy)
stopFrustrationObserver = initFrustrationObserver(lifeCycle, frustrationsCallbackSpy, recordIds)
lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, rumData)

expect(frustrationsCallbackSpy).not.toHaveBeenCalled()
Expand All @@ -76,7 +79,7 @@ describe('initFrustrationObserver', () => {
it('ignores click actions which are missing the original mouse events', () => {
rumData.domainContext = {}

stopFrustrationObserver = initFrustrationObserver(lifeCycle, frustrationsCallbackSpy)
stopFrustrationObserver = initFrustrationObserver(lifeCycle, frustrationsCallbackSpy, recordIds)
lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, rumData)

expect(frustrationsCallbackSpy).not.toHaveBeenCalled()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import type { ListenerHandler } from '@datadog/browser-core'
import type { LifeCycle } from '@datadog/browser-rum-core'
import { ActionType, RumEventType, LifeCycleEventType } from '@datadog/browser-rum-core'
import type { FrustrationRecord } from '../../../types'
import { RecordType } from '../../../types'
import type { ListenerHandler } from './utils'
import { getRecordIdForEvent } from './utils'
import type { RecordIds } from './recordIds'

export type FrustrationCallback = (record: FrustrationRecord) => void

export function initFrustrationObserver(lifeCycle: LifeCycle, frustrationCb: FrustrationCallback): ListenerHandler {
export function initFrustrationObserver(
lifeCycle: LifeCycle,
frustrationCb: FrustrationCallback,
recordIds: RecordIds
): ListenerHandler {
return lifeCycle.subscribe(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, (data) => {
if (
data.rawRumEvent.type === RumEventType.ACTION &&
Expand All @@ -21,7 +25,7 @@ export function initFrustrationObserver(lifeCycle: LifeCycle, frustrationCb: Fru
type: RecordType.FrustrationRecord,
data: {
frustrationTypes: data.rawRumEvent.action.frustration.type,
recordIds: data.domainContext.events.map((e) => getRecordIdForEvent(e)),
recordIds: data.domainContext.events.map((e) => recordIds.getIdForEvent(e)),
},
})
}
Expand Down
7 changes: 3 additions & 4 deletions packages/rum/src/domain/record/observers/inputObserver.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import type { DefaultPrivacyLevel } from '@datadog/browser-core'
import { instrumentSetter, assign, DOM_EVENT, addEventListeners } from '@datadog/browser-core'
import type { DefaultPrivacyLevel, ListenerHandler } from '@datadog/browser-core'
import { instrumentSetter, assign, DOM_EVENT, addEventListeners, forEach } from '@datadog/browser-core'
import { NodePrivacyLevel } from '../../../constants'
import type { InputState } from '../../../types'
import { getEventTarget } from '../eventsUtils'
import { getNodePrivacyLevel, shouldMaskNode } from '../privacy'
import { getElementInputValue, getSerializedNodeId, hasSerializedNode } from '../serialization'
import type { ListenerHandler } from './utils'
import { getEventTarget, forEach } from './utils'

type InputObserverOptions = {
domEvents?: Array<DOM_EVENT.INPUT | DOM_EVENT.CHANGE>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import type { DefaultPrivacyLevel } from '@datadog/browser-core'
import type { DefaultPrivacyLevel, ListenerHandler } from '@datadog/browser-core'
import { DOM_EVENT, addEventListeners } from '@datadog/browser-core'
import { NodePrivacyLevel } from '../../../constants'
import type { MediaInteraction } from '../../../types'
import { MediaInteractionType } from '../../../types'
import { getEventTarget } from '../eventsUtils'
import { getNodePrivacyLevel } from '../privacy'
import { getSerializedNodeId, hasSerializedNode } from '../serialization'
import type { ListenerHandler } from './utils'
import { getEventTarget } from './utils'

export type MediaInteractionCallback = (p: MediaInteraction) => void

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import { createElementsScrollPositions } from '../elementsScrollPositions'
import { DEFAULT_CONFIGURATION, DEFAULT_SHADOW_ROOT_CONTROLLER } from '../../../../test/utils'
import type { MouseInteractionCallBack } from './mouseInteractionObserver'
import { initMouseInteractionObserver } from './mouseInteractionObserver'
import { getRecordIdForEvent } from './utils'
import type { RecordIds } from './recordIds'
import { initRecordIds } from './recordIds'

describe('initMouseInteractionObserver', () => {
let mouseInteractionCallbackSpy: jasmine.Spy<MouseInteractionCallBack>
let stopObserver: () => void
let recordIds: RecordIds
let sandbox: HTMLDivElement
let a: HTMLAnchorElement

Expand All @@ -33,7 +35,8 @@ describe('initMouseInteractionObserver', () => {
})

mouseInteractionCallbackSpy = jasmine.createSpy()
stopObserver = initMouseInteractionObserver(mouseInteractionCallbackSpy, DefaultPrivacyLevel.ALLOW)
recordIds = initRecordIds()
stopObserver = initMouseInteractionObserver(mouseInteractionCallbackSpy, DefaultPrivacyLevel.ALLOW, recordIds)
})

afterEach(() => {
Expand Down Expand Up @@ -63,7 +66,7 @@ describe('initMouseInteractionObserver', () => {
a.dispatchEvent(pointerupEvent)

expect(mouseInteractionCallbackSpy).toHaveBeenCalledWith({
id: getRecordIdForEvent(pointerupEvent),
id: recordIds.getIdForEvent(pointerupEvent),
type: RecordType.IncrementalSnapshot,
timestamp: jasmine.any(Number),
data: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import type { DefaultPrivacyLevel } from '@datadog/browser-core'
import type { DefaultPrivacyLevel, ListenerHandler } from '@datadog/browser-core'
import { assign, addEventListeners, DOM_EVENT } from '@datadog/browser-core'
import { NodePrivacyLevel } from '../../../constants'
import type { MouseInteraction, MouseInteractionData, BrowserIncrementalSnapshotRecord } from '../../../types'
import { IncrementalSource, MouseInteractionType } from '../../../types'
import { assembleIncrementalSnapshot } from '../assembly'
import { getEventTarget } from '../eventsUtils'
import { getNodePrivacyLevel } from '../privacy'
import { getSerializedNodeId, hasSerializedNode } from '../serialization'
import { assembleIncrementalSnapshot } from '../utils'
import { tryToComputeCoordinates } from './moveObserver'
import type { ListenerHandler } from './utils'
import { getRecordIdForEvent, getEventTarget } from './utils'
import type { RecordIds } from './recordIds'

const eventTypeToMouseInteraction = {
// Listen for pointerup DOM events instead of mouseup for MouseInteraction/MouseUp records. This
Expand All @@ -35,7 +35,8 @@ export type MouseInteractionCallBack = (record: BrowserIncrementalSnapshotRecord

export function initMouseInteractionObserver(
cb: MouseInteractionCallBack,
defaultPrivacyLevel: DefaultPrivacyLevel
defaultPrivacyLevel: DefaultPrivacyLevel,
recordIds: RecordIds
): ListenerHandler {
const handler = (event: MouseEvent | TouchEvent) => {
const target = getEventTarget(event)
Expand All @@ -57,7 +58,7 @@ export function initMouseInteractionObserver(
}

const record = assign(
{ id: getRecordIdForEvent(event) },
{ id: recordIds.getIdForEvent(event) },
assembleIncrementalSnapshot<MouseInteractionData>(IncrementalSource.MouseInteraction, interaction)
)
cb(record)
Expand Down
5 changes: 2 additions & 3 deletions packages/rum/src/domain/record/observers/moveObserver.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import type { ListenerHandler } from '@datadog/browser-core'
import { addEventListeners, addTelemetryDebug, DOM_EVENT, throttle } from '@datadog/browser-core'
import { getSerializedNodeId, hasSerializedNode } from '../serialization'
import { isTouchEvent } from '../utils'
import type { MousePosition } from '../../../types'
import { IncrementalSource } from '../../../types'
import { getEventTarget, isTouchEvent } from '../eventsUtils'
import { convertMouseEventToLayoutCoordinates } from '../viewports'
import type { ListenerHandler } from './utils'
import { getEventTarget } from './utils'

const MOUSE_MOVE_OBSERVER_THRESHOLD = 50

Expand Down
9 changes: 6 additions & 3 deletions packages/rum/src/domain/record/observers/observers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { ListenerHandler } from '@datadog/browser-core'
import type { LifeCycle, RumConfiguration } from '@datadog/browser-rum-core'
import type { ElementsScrollPositions } from '../elementsScrollPositions'
import type { ShadowRootsController } from '../shadowRootsController'
Expand All @@ -21,7 +22,7 @@ import type { MutationCallBack } from './mutationObserver'
import { initMutationObserver } from './mutationObserver'
import type { FocusCallback } from './focusObserver'
import { initFocusObserver } from './focusObserver'
import type { ListenerHandler } from './utils'
import { initRecordIds } from './recordIds'

interface ObserverParam {
lifeCycle: LifeCycle
Expand All @@ -42,11 +43,13 @@ interface ObserverParam {
}

export function initObservers(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,
o.configuration.defaultPrivacyLevel
o.configuration.defaultPrivacyLevel,
recordIds
)
const scrollHandler = initScrollObserver(o.scrollCb, o.configuration.defaultPrivacyLevel, o.elementsScrollPositions)
const viewportResizeHandler = initViewportResizeObserver(o.viewportResizeCb)
Expand All @@ -58,7 +61,7 @@ export function initObservers(o: ObserverParam): { stop: ListenerHandler; flush:
const styleSheetObserver = initStyleSheetObserver(o.styleSheetCb)
const focusHandler = initFocusObserver(o.focusCb)
const visualViewportResizeHandler = initVisualViewportResizeObserver(o.visualViewportResizeCb)
const frustrationHandler = initFrustrationObserver(o.lifeCycle, o.frustrationCb)
const frustrationHandler = initFrustrationObserver(o.lifeCycle, o.frustrationCb, recordIds)

return {
flush: () => {
Expand Down
15 changes: 15 additions & 0 deletions packages/rum/src/domain/record/observers/recordIds.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export type RecordIds = ReturnType<typeof initRecordIds>

export function initRecordIds() {
const recordIds = new WeakMap<Event, number>()
let nextId = 1

return {
getIdForEvent(event: Event): number {
if (!recordIds.has(event)) {
recordIds.set(event, nextId++)
}
return recordIds.get(event)!
},
}
}
6 changes: 2 additions & 4 deletions packages/rum/src/domain/record/observers/scrollObserver.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import type { DefaultPrivacyLevel } from '@datadog/browser-core'
import type { DefaultPrivacyLevel, ListenerHandler } from '@datadog/browser-core'
import { DOM_EVENT, throttle, addEventListener } from '@datadog/browser-core'
import type { ElementsScrollPositions } from '../elementsScrollPositions'
import { getEventTarget } from '../eventsUtils'
import { getNodePrivacyLevel } from '../privacy'
import { getSerializedNodeId, hasSerializedNode } from '../serialization'

import { getScrollX, getScrollY } from '../viewports'
import type { ScrollPosition } from '../../../types'
import { NodePrivacyLevel } from '../../../constants'
import type { ListenerHandler } from './utils'
import { getEventTarget } from './utils'

const SCROLL_OBSERVER_THRESHOLD = 100

Expand Down
Loading