@datadog/browser-rum-core
Advanced tools
Comparing version 4.11.3 to 4.11.4
@@ -33,3 +33,3 @@ import type { Context, InitConfiguration } from '@datadog/browser-core'; | ||
removeUser: () => void; | ||
startView: (name: string) => void; | ||
startView: (name?: string | undefined) => void; | ||
startSessionReplayRecording: () => void; | ||
@@ -36,0 +36,0 @@ stopSessionReplayRecording: () => void; |
@@ -49,3 +49,3 @@ "use strict"; | ||
}, | ||
browser_sdk_version: (0, browser_core_1.canUseEventBridge)() ? "4.11.3" : undefined, | ||
browser_sdk_version: (0, browser_core_1.canUseEventBridge)() ? "4.11.4" : undefined, | ||
}, | ||
@@ -52,0 +52,0 @@ application: { |
@@ -29,2 +29,4 @@ "use strict"; | ||
id: action.id, | ||
target: action.target, | ||
position: action.position, | ||
loading_time: (0, browser_core_1.toServerDuration)(action.duration), | ||
@@ -31,0 +33,0 @@ frustration: { |
@@ -15,6 +15,17 @@ import type { Duration, ClocksState, RelativeTime, TimeStamp } from '@datadog/browser-core'; | ||
name: string; | ||
target?: { | ||
selector: string; | ||
width: number; | ||
height: number; | ||
}; | ||
position?: { | ||
x: number; | ||
y: number; | ||
}; | ||
startClocks: ClocksState; | ||
duration?: Duration; | ||
counts: ActionCounts; | ||
event: MouseEvent; | ||
event: MouseEvent & { | ||
target: Element; | ||
}; | ||
frustrationTypes: FrustrationType[]; | ||
@@ -34,3 +45,5 @@ } | ||
declare function newClick(lifeCycle: LifeCycle, history: ClickActionIdHistory, trackFrustrations: boolean, base: Pick<ClickAction, 'startClocks' | 'event' | 'name'>): { | ||
event: MouseEvent; | ||
event: MouseEvent & { | ||
target: Element; | ||
}; | ||
addFrustration: (frustration: FrustrationType) => void; | ||
@@ -37,0 +50,0 @@ stop: (endTime?: TimeStamp | undefined) => void; |
@@ -9,2 +9,3 @@ "use strict"; | ||
var getActionNameFromElement_1 = require("./getActionNameFromElement"); | ||
var getSelectorFromElement_1 = require("./getSelectorFromElement"); | ||
// Maximum duration for click actions | ||
@@ -112,2 +113,17 @@ exports.CLICK_ACTION_MAX_DURATION = 10 * browser_core_1.ONE_SECOND; | ||
var id = (0, browser_core_1.generateUUID)(); | ||
var target; | ||
var position; | ||
if ((0, browser_core_1.isExperimentalFeatureEnabled)('clickmap')) { | ||
var rect = base.event.target.getBoundingClientRect(); | ||
target = { | ||
selector: (0, getSelectorFromElement_1.getSelectorFromElement)(base.event.target), | ||
width: Math.round(rect.width), | ||
height: Math.round(rect.height), | ||
}; | ||
position = { | ||
// Use clientX and Y because for SVG element offsetX and Y are relatives to the <svg> element | ||
x: Math.round(base.event.clientX - rect.left), | ||
y: Math.round(base.event.clientY - rect.top), | ||
}; | ||
} | ||
var historyEntry = history.add(id, base.startClocks.relative); | ||
@@ -159,2 +175,4 @@ var eventCountsSubscription = (0, trackEventCounts_1.trackEventCounts)(lifeCycle); | ||
frustrationTypes: (0, browser_core_1.setToArray)(frustrations), | ||
target: target, | ||
position: position, | ||
counts: { | ||
@@ -161,0 +179,0 @@ resourceCount: resourceCount, |
@@ -132,3 +132,10 @@ import type { Context, Duration, ErrorSource, ErrorHandling, ResourceType, ServerDuration, TimeStamp } from '@datadog/browser-core'; | ||
name: string; | ||
selector?: string; | ||
width?: number; | ||
height?: number; | ||
}; | ||
position?: { | ||
x: number; | ||
y: number; | ||
}; | ||
}; | ||
@@ -135,0 +142,0 @@ view?: { |
@@ -40,2 +40,14 @@ /** | ||
name: string; | ||
/** | ||
* CSS selector path of the target element | ||
*/ | ||
readonly selector?: string; | ||
/** | ||
* Width of the target element (in pixels) | ||
*/ | ||
readonly width?: number; | ||
/** | ||
* Height of the target element (in pixels) | ||
*/ | ||
readonly height?: number; | ||
[k: string]: unknown; | ||
@@ -54,2 +66,16 @@ }; | ||
/** | ||
* Action position properties | ||
*/ | ||
readonly position?: { | ||
/** | ||
* X coordinate of the action (in pixels) | ||
*/ | ||
readonly x: number; | ||
/** | ||
* Y coordinate of the action (in pixels) | ||
*/ | ||
readonly y: number; | ||
[k: string]: unknown; | ||
}; | ||
/** | ||
* Properties of the errors of the action | ||
@@ -56,0 +82,0 @@ */ |
@@ -33,3 +33,3 @@ import type { Context, InitConfiguration } from '@datadog/browser-core'; | ||
removeUser: () => void; | ||
startView: (name: string) => void; | ||
startView: (name?: string | undefined) => void; | ||
startSessionReplayRecording: () => void; | ||
@@ -36,0 +36,0 @@ stopSessionReplayRecording: () => void; |
@@ -46,3 +46,3 @@ import { combine, isEmptyObject, limitModification, timeStampNow, currentDrift, display, createEventRateLimiter, canUseEventBridge, isExperimentalFeatureEnabled, } from '@datadog/browser-core'; | ||
}, | ||
browser_sdk_version: canUseEventBridge() ? "4.11.3" : undefined, | ||
browser_sdk_version: canUseEventBridge() ? "4.11.4" : undefined, | ||
}, | ||
@@ -49,0 +49,0 @@ application: { |
@@ -25,2 +25,4 @@ import { noop, assign, combine, toServerDuration, generateUUID } from '@datadog/browser-core'; | ||
id: action.id, | ||
target: action.target, | ||
position: action.position, | ||
loading_time: toServerDuration(action.duration), | ||
@@ -27,0 +29,0 @@ frustration: { |
@@ -15,6 +15,17 @@ import type { Duration, ClocksState, RelativeTime, TimeStamp } from '@datadog/browser-core'; | ||
name: string; | ||
target?: { | ||
selector: string; | ||
width: number; | ||
height: number; | ||
}; | ||
position?: { | ||
x: number; | ||
y: number; | ||
}; | ||
startClocks: ClocksState; | ||
duration?: Duration; | ||
counts: ActionCounts; | ||
event: MouseEvent; | ||
event: MouseEvent & { | ||
target: Element; | ||
}; | ||
frustrationTypes: FrustrationType[]; | ||
@@ -34,3 +45,5 @@ } | ||
declare function newClick(lifeCycle: LifeCycle, history: ClickActionIdHistory, trackFrustrations: boolean, base: Pick<ClickAction, 'startClocks' | 'event' | 'name'>): { | ||
event: MouseEvent; | ||
event: MouseEvent & { | ||
target: Element; | ||
}; | ||
addFrustration: (frustration: FrustrationType) => void; | ||
@@ -37,0 +50,0 @@ stop: (endTime?: TimeStamp | undefined) => void; |
@@ -1,2 +0,2 @@ | ||
import { setToArray, Observable, assign, getRelativeTime, ONE_MINUTE, ContextHistory, addEventListener, generateUUID, clocksNow, ONE_SECOND, elapsed, } from '@datadog/browser-core'; | ||
import { isExperimentalFeatureEnabled, setToArray, Observable, assign, getRelativeTime, ONE_MINUTE, ContextHistory, addEventListener, generateUUID, clocksNow, ONE_SECOND, elapsed, } from '@datadog/browser-core'; | ||
import { trackEventCounts } from '../../trackEventCounts'; | ||
@@ -6,2 +6,3 @@ import { waitPageActivityEnd } from '../../waitPageActivityEnd'; | ||
import { getActionNameFromElement } from './getActionNameFromElement'; | ||
import { getSelectorFromElement } from './getSelectorFromElement'; | ||
// Maximum duration for click actions | ||
@@ -108,2 +109,17 @@ export var CLICK_ACTION_MAX_DURATION = 10 * ONE_SECOND; | ||
var id = generateUUID(); | ||
var target; | ||
var position; | ||
if (isExperimentalFeatureEnabled('clickmap')) { | ||
var rect = base.event.target.getBoundingClientRect(); | ||
target = { | ||
selector: getSelectorFromElement(base.event.target), | ||
width: Math.round(rect.width), | ||
height: Math.round(rect.height), | ||
}; | ||
position = { | ||
// Use clientX and Y because for SVG element offsetX and Y are relatives to the <svg> element | ||
x: Math.round(base.event.clientX - rect.left), | ||
y: Math.round(base.event.clientY - rect.top), | ||
}; | ||
} | ||
var historyEntry = history.add(id, base.startClocks.relative); | ||
@@ -155,2 +171,4 @@ var eventCountsSubscription = trackEventCounts(lifeCycle); | ||
frustrationTypes: setToArray(frustrations), | ||
target: target, | ||
position: position, | ||
counts: { | ||
@@ -157,0 +175,0 @@ resourceCount: resourceCount, |
@@ -132,3 +132,10 @@ import type { Context, Duration, ErrorSource, ErrorHandling, ResourceType, ServerDuration, TimeStamp } from '@datadog/browser-core'; | ||
name: string; | ||
selector?: string; | ||
width?: number; | ||
height?: number; | ||
}; | ||
position?: { | ||
x: number; | ||
y: number; | ||
}; | ||
}; | ||
@@ -135,0 +142,0 @@ view?: { |
@@ -40,2 +40,14 @@ /** | ||
name: string; | ||
/** | ||
* CSS selector path of the target element | ||
*/ | ||
readonly selector?: string; | ||
/** | ||
* Width of the target element (in pixels) | ||
*/ | ||
readonly width?: number; | ||
/** | ||
* Height of the target element (in pixels) | ||
*/ | ||
readonly height?: number; | ||
[k: string]: unknown; | ||
@@ -54,2 +66,16 @@ }; | ||
/** | ||
* Action position properties | ||
*/ | ||
readonly position?: { | ||
/** | ||
* X coordinate of the action (in pixels) | ||
*/ | ||
readonly x: number; | ||
/** | ||
* Y coordinate of the action (in pixels) | ||
*/ | ||
readonly y: number; | ||
[k: string]: unknown; | ||
}; | ||
/** | ||
* Properties of the errors of the action | ||
@@ -56,0 +82,0 @@ */ |
{ | ||
"name": "@datadog/browser-rum-core", | ||
"version": "4.11.3", | ||
"version": "4.11.4", | ||
"license": "Apache-2.0", | ||
@@ -15,3 +15,3 @@ "main": "cjs/index.js", | ||
"dependencies": { | ||
"@datadog/browser-core": "4.11.3" | ||
"@datadog/browser-core": "4.11.4" | ||
}, | ||
@@ -26,3 +26,3 @@ "devDependencies": { | ||
}, | ||
"gitHead": "2833c9db3336b373ae24804293321f3c6cf2151c" | ||
"gitHead": "c059f7a31fc28a270511780f3cc9a2dd4f260a37" | ||
} |
@@ -158,3 +158,3 @@ import type { Context, InitConfiguration, TimeStamp, RelativeTime } from '@datadog/browser-core' | ||
const startView: { | ||
(name: string): void | ||
(name?: string): void | ||
// (options: ViewOptions): void // uncomment when removing the feature flag | ||
@@ -161,0 +161,0 @@ } = monitor((options?: string) => { |
@@ -28,3 +28,4 @@ import type { Duration, RelativeTime, ServerDuration, TimeStamp } from '@datadog/browser-core' | ||
const { lifeCycle, rawRumEvents } = setupBuilder.build() | ||
const event = createNewEvent('click') | ||
const event = createNewEvent('click', { target: document.createElement('button') }) | ||
lifeCycle.notify(LifeCycleEventType.AUTO_ACTION_COMPLETED, { | ||
@@ -43,2 +44,8 @@ counts: { | ||
event, | ||
target: { | ||
selector: '#foo', | ||
width: 1, | ||
height: 2, | ||
}, | ||
position: { x: 1, y: 2 }, | ||
}) | ||
@@ -65,3 +72,10 @@ | ||
name: 'foo', | ||
selector: '#foo', | ||
width: 1, | ||
height: 2, | ||
}, | ||
position: { | ||
x: 1, | ||
y: 2, | ||
}, | ||
type: ActionType.CLICK, | ||
@@ -68,0 +82,0 @@ }, |
@@ -63,2 +63,4 @@ import type { ClocksState, Context, Observable } from '@datadog/browser-core' | ||
id: action.id, | ||
target: action.target, | ||
position: action.position, | ||
loading_time: toServerDuration(action.duration), | ||
@@ -65,0 +67,0 @@ frustration: { |
@@ -0,56 +1,45 @@ | ||
import { createIsolatedDOM } from '../../../../test/createIsolatedDom' | ||
import { getActionNameFromElement } from './getActionNameFromElement' | ||
describe('getActionNameFromElement', () => { | ||
const iframes: HTMLIFrameElement[] = [] | ||
let isolatedDOM: ReturnType<typeof createIsolatedDOM> | ||
function element(s: TemplateStringsArray) { | ||
// Simply using a DOMParser does not fit here, because script tags created this way are | ||
// considered as normal markup, so they are not ignored when getting the textual content of the | ||
// target via innerText | ||
beforeEach(() => { | ||
isolatedDOM = createIsolatedDOM() | ||
}) | ||
const iframe = document.createElement('iframe') | ||
iframes.push(iframe) | ||
document.body.appendChild(iframe) | ||
const doc = iframe.contentDocument! | ||
doc.open() | ||
doc.write(`<html><body>${s[0]}</body></html>`) | ||
doc.close() | ||
return doc.querySelector('[target]') || doc.body.children[0] | ||
} | ||
afterEach(() => { | ||
iframes.forEach((iframe) => iframe.parentNode!.removeChild(iframe)) | ||
iframes.length = 0 | ||
isolatedDOM.clear() | ||
}) | ||
it('extracts the textual content of an element', () => { | ||
expect(getActionNameFromElement(element`<div>Foo <div>bar</div></div>`)).toBe('Foo bar') | ||
expect(getActionNameFromElement(isolatedDOM.element`<div>Foo <div>bar</div></div>`)).toBe('Foo bar') | ||
}) | ||
it('extracts the text of an input button', () => { | ||
expect(getActionNameFromElement(element`<input type="button" value="Click" />`)).toBe('Click') | ||
expect(getActionNameFromElement(isolatedDOM.element`<input type="button" value="Click" />`)).toBe('Click') | ||
}) | ||
it('extracts the alt text of an image', () => { | ||
expect(getActionNameFromElement(element`<img title="foo" alt="bar" />`)).toBe('bar') | ||
expect(getActionNameFromElement(isolatedDOM.element`<img title="foo" alt="bar" />`)).toBe('bar') | ||
}) | ||
it('extracts the title text of an image', () => { | ||
expect(getActionNameFromElement(element`<img title="foo" />`)).toBe('foo') | ||
expect(getActionNameFromElement(isolatedDOM.element`<img title="foo" />`)).toBe('foo') | ||
}) | ||
it('extracts the text of an aria-label attribute', () => { | ||
expect(getActionNameFromElement(element`<span aria-label="Foo" />`)).toBe('Foo') | ||
expect(getActionNameFromElement(isolatedDOM.element`<span aria-label="Foo" />`)).toBe('Foo') | ||
}) | ||
it('gets the parent element textual content if everything else fails', () => { | ||
expect(getActionNameFromElement(element`<div>Foo <img target /></div>`)).toBe('Foo') | ||
expect(getActionNameFromElement(isolatedDOM.element`<div>Foo <img target /></div>`)).toBe('Foo') | ||
}) | ||
it("doesn't get the value of a text input", () => { | ||
expect(getActionNameFromElement(element`<input type="text" value="foo" />`)).toBe('') | ||
expect(getActionNameFromElement(isolatedDOM.element`<input type="text" value="foo" />`)).toBe('') | ||
}) | ||
it("doesn't get the value of a password input", () => { | ||
expect(getActionNameFromElement(element`<input type="password" value="foo" />`)).toBe('') | ||
expect(getActionNameFromElement(isolatedDOM.element`<input type="password" value="foo" />`)).toBe('') | ||
}) | ||
@@ -62,3 +51,3 @@ | ||
// eslint-disable-next-line max-len | ||
element`<div>Foooooooooooooooooo baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaar baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaz</div>` | ||
isolatedDOM.element`<div>Foooooooooooooooooo baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaar baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaz</div>` | ||
) | ||
@@ -72,11 +61,11 @@ ).toBe( | ||
it('normalize white spaces', () => { | ||
expect(getActionNameFromElement(element`<div>foo\tbar\n\n baz</div>`)).toBe('foo bar baz') | ||
expect(getActionNameFromElement(isolatedDOM.element`<div>foo\tbar\n\n baz</div>`)).toBe('foo bar baz') | ||
}) | ||
it('ignores the inline script textual content', () => { | ||
expect(getActionNameFromElement(element`<div><script>console.log('toto')</script>b</div>`)).toBe('b') | ||
expect(getActionNameFromElement(isolatedDOM.element`<div><script>console.log('toto')</script>b</div>`)).toBe('b') | ||
}) | ||
it('extracts text from SVG elements', () => { | ||
expect(getActionNameFromElement(element`<svg><text>foo bar</text></svg>`)).toBe('foo bar') | ||
expect(getActionNameFromElement(isolatedDOM.element`<svg><text>foo bar</text></svg>`)).toBe('foo bar') | ||
}) | ||
@@ -86,3 +75,3 @@ | ||
expect( | ||
getActionNameFromElement(element` | ||
getActionNameFromElement(isolatedDOM.element` | ||
<div> | ||
@@ -99,3 +88,3 @@ <label for="toto">label text</label> | ||
expect( | ||
getActionNameFromElement(element` | ||
getActionNameFromElement(isolatedDOM.element` | ||
<label> | ||
@@ -114,3 +103,3 @@ foo | ||
expect( | ||
getActionNameFromElement(element` | ||
getActionNameFromElement(isolatedDOM.element` | ||
<select> | ||
@@ -126,3 +115,3 @@ <option>foo</option> | ||
expect( | ||
getActionNameFromElement(element` | ||
getActionNameFromElement(isolatedDOM.element` | ||
<div> | ||
@@ -139,3 +128,3 @@ <label id="toto">label text</label> | ||
expect( | ||
getActionNameFromElement(element` | ||
getActionNameFromElement(isolatedDOM.element` | ||
<div> | ||
@@ -154,3 +143,3 @@ <label id="toto1">label</label> | ||
expect( | ||
getActionNameFromElement(element` | ||
getActionNameFromElement(isolatedDOM.element` | ||
<div> | ||
@@ -166,3 +155,3 @@ <div>ignored</div> | ||
expect( | ||
getActionNameFromElement(element` | ||
getActionNameFromElement(isolatedDOM.element` | ||
<div> | ||
@@ -178,3 +167,3 @@ <div>ignored</div> | ||
expect( | ||
getActionNameFromElement(element` | ||
getActionNameFromElement(isolatedDOM.element` | ||
<div> | ||
@@ -192,3 +181,3 @@ <div>ignored</div> | ||
expect( | ||
getActionNameFromElement(element` | ||
getActionNameFromElement(isolatedDOM.element` | ||
<div>ignored</div> | ||
@@ -202,3 +191,3 @@ <i target></i> | ||
expect( | ||
getActionNameFromElement(element` | ||
getActionNameFromElement(isolatedDOM.element` | ||
<div> | ||
@@ -216,3 +205,3 @@ <div>ignored</div> | ||
expect( | ||
getActionNameFromElement(element` | ||
getActionNameFromElement(isolatedDOM.element` | ||
<div> | ||
@@ -230,3 +219,3 @@ <div>ignored</div> | ||
expect( | ||
getActionNameFromElement(element` | ||
getActionNameFromElement(isolatedDOM.element` | ||
<button> | ||
@@ -242,3 +231,3 @@ foo | ||
expect( | ||
getActionNameFromElement(element` | ||
getActionNameFromElement(isolatedDOM.element` | ||
<div contenteditable> | ||
@@ -254,3 +243,3 @@ <i target>ignored</i> | ||
expect( | ||
getActionNameFromElement(element` | ||
getActionNameFromElement(isolatedDOM.element` | ||
<div contenteditable> | ||
@@ -267,3 +256,3 @@ <i aria-label="foo" target>ignored</i> | ||
expect( | ||
getActionNameFromElement(element` | ||
getActionNameFromElement(isolatedDOM.element` | ||
<div data-dd-action-name="foo">ignored</div> | ||
@@ -275,3 +264,3 @@ `) | ||
it('considers any parent', () => { | ||
const target = element` | ||
const target = isolatedDOM.element` | ||
<form> | ||
@@ -290,3 +279,3 @@ <i><i><i><i><i><i><i><i><i><i><i><i> | ||
expect( | ||
getActionNameFromElement(element` | ||
getActionNameFromElement(isolatedDOM.element` | ||
<div data-dd-action-name=" foo \t bar ">ignored</div> | ||
@@ -299,3 +288,3 @@ `) | ||
expect( | ||
getActionNameFromElement(element` | ||
getActionNameFromElement(isolatedDOM.element` | ||
<div data-dd-action-name="ignored"> | ||
@@ -313,3 +302,3 @@ <div data-dd-action-name=""> | ||
getActionNameFromElement( | ||
element` | ||
isolatedDOM.element` | ||
<div data-test-id="foo">ignored</div> | ||
@@ -325,3 +314,3 @@ `, | ||
getActionNameFromElement( | ||
element` | ||
isolatedDOM.element` | ||
<div data-test-id="foo" data-dd-action-name="bar">ignored</div> | ||
@@ -335,5 +324,5 @@ `, | ||
it('remove children with programmatic action name in textual content', () => { | ||
expect(getActionNameFromElement(element`<div>Foo <div data-dd-action-name="custom action">bar<div></div>`)).toBe( | ||
'Foo' | ||
) | ||
expect( | ||
getActionNameFromElement(isolatedDOM.element`<div>Foo <div data-dd-action-name="custom action">bar<div></div>`) | ||
).toBe('Foo') | ||
}) | ||
@@ -344,3 +333,6 @@ | ||
expect( | ||
getActionNameFromElement(element`<div>Foo <div data-test-id="custom action">bar<div></div>`, 'data-test-id') | ||
getActionNameFromElement( | ||
isolatedDOM.element`<div>Foo <div data-test-id="custom action">bar<div></div>`, | ||
'data-test-id' | ||
) | ||
).toBe('Foo') | ||
@@ -347,0 +339,0 @@ }) |
@@ -187,3 +187,3 @@ import { Observable, ONE_SECOND, timeStampNow } from '@datadog/browser-core' | ||
function createFakeClick(eventPartial?: Partial<MouseEvent>): Click & { clonedClick?: Click } { | ||
function createFakeClick(eventPartial?: Partial<MouseEvent & { target: Element }>): Click & { clonedClick?: Click } { | ||
const stopObservable = new Observable<void>() | ||
@@ -199,2 +199,3 @@ let isStopped = false | ||
timeStamp: timeStampNow(), | ||
target: document.body, | ||
...eventPartial, | ||
@@ -201,0 +202,0 @@ }), |
import type { Context, Observable, Duration } from '@datadog/browser-core' | ||
import { clocksNow, timeStampNow, relativeNow } from '@datadog/browser-core' | ||
import { | ||
updateExperimentalFeatures, | ||
resetExperimentalFeatures, | ||
clocksNow, | ||
timeStampNow, | ||
relativeNow, | ||
} from '@datadog/browser-core' | ||
import type { Clock } from '../../../../../core/test/specHelper' | ||
@@ -47,2 +53,5 @@ import { createNewEvent } from '../../../../../core/test/specHelper' | ||
button.type = 'button' | ||
button.id = 'button' | ||
button.style.width = '100px' | ||
button.style.height = '100px' | ||
button.appendChild(document.createTextNode('Click me')) | ||
@@ -92,4 +101,6 @@ document.body.appendChild(button) | ||
type: ActionType.CLICK, | ||
event: createNewEvent('click'), | ||
event: createNewEvent('click', { target: document.createElement('button') }), | ||
frustrationTypes: [], | ||
target: undefined, | ||
position: undefined, | ||
}, | ||
@@ -99,2 +110,24 @@ ]) | ||
describe('when clickmap ff is enabled', () => { | ||
beforeEach(() => { | ||
updateExperimentalFeatures(['clickmap']) | ||
}) | ||
afterEach(() => { | ||
resetExperimentalFeatures() | ||
}) | ||
it('should set click position and target', () => { | ||
const { domMutationObservable, clock } = setupBuilder.build() | ||
emulateClickWithActivity(domMutationObservable, clock) | ||
clock.tick(EXPIRE_DELAY) | ||
expect(events[0]).toEqual( | ||
jasmine.objectContaining({ | ||
target: { selector: '#button', width: 100, height: 100 }, | ||
position: { x: 50, y: 50 }, | ||
}) | ||
) | ||
}) | ||
}) | ||
it('should keep track of previously validated click actions', () => { | ||
@@ -380,7 +413,11 @@ const { domMutationObservable, clock } = setupBuilder.build() | ||
const targetPosition = target.getBoundingClientRect() | ||
const offsetX = targetPosition.width / 2 | ||
const offsetY = targetPosition.height / 2 | ||
target.dispatchEvent( | ||
createNewEvent('click', { | ||
target, | ||
clientX: targetPosition.left + targetPosition.width / 2, | ||
clientY: targetPosition.top + targetPosition.height / 2, | ||
clientX: targetPosition.left + offsetX, | ||
clientY: targetPosition.top + offsetY, | ||
offsetX, | ||
offsetY, | ||
timeStamp: timeStampNow(), | ||
@@ -387,0 +424,0 @@ }) |
import type { Duration, ClocksState, RelativeTime, TimeStamp } from '@datadog/browser-core' | ||
import { | ||
isExperimentalFeatureEnabled, | ||
setToArray, | ||
@@ -25,2 +26,3 @@ Observable, | ||
import { getActionNameFromElement } from './getActionNameFromElement' | ||
import { getSelectorFromElement } from './getSelectorFromElement' | ||
@@ -37,6 +39,12 @@ interface ActionCounts { | ||
name: string | ||
target?: { | ||
selector: string | ||
width: number | ||
height: number | ||
} | ||
position?: { x: number; y: number } | ||
startClocks: ClocksState | ||
duration?: Duration | ||
counts: ActionCounts | ||
event: MouseEvent | ||
event: MouseEvent & { target: Element } | ||
frustrationTypes: FrustrationType[] | ||
@@ -200,2 +208,18 @@ } | ||
const id = generateUUID() | ||
let target: ClickAction['target'] | ||
let position: ClickAction['position'] | ||
if (isExperimentalFeatureEnabled('clickmap')) { | ||
const rect = base.event.target.getBoundingClientRect() | ||
target = { | ||
selector: getSelectorFromElement(base.event.target), | ||
width: Math.round(rect.width), | ||
height: Math.round(rect.height), | ||
} | ||
position = { | ||
// Use clientX and Y because for SVG element offsetX and Y are relatives to the <svg> element | ||
x: Math.round(base.event.clientX - rect.left), | ||
y: Math.round(base.event.clientY - rect.top), | ||
} | ||
} | ||
const historyEntry = history.add(id, base.startClocks.relative) | ||
@@ -256,2 +280,4 @@ const eventCountsSubscription = trackEventCounts(lifeCycle) | ||
frustrationTypes: setToArray(frustrations), | ||
target, | ||
position, | ||
counts: { | ||
@@ -258,0 +284,0 @@ resourceCount, |
@@ -152,3 +152,10 @@ import type { | ||
name: string | ||
selector?: string | ||
width?: number | ||
height?: number | ||
} | ||
position?: { | ||
x: number | ||
y: number | ||
} | ||
} | ||
@@ -155,0 +162,0 @@ view?: { |
@@ -42,2 +42,14 @@ /* eslint-disable */ | ||
name: string | ||
/** | ||
* CSS selector path of the target element | ||
*/ | ||
readonly selector?: string | ||
/** | ||
* Width of the target element (in pixels) | ||
*/ | ||
readonly width?: number | ||
/** | ||
* Height of the target element (in pixels) | ||
*/ | ||
readonly height?: number | ||
[k: string]: unknown | ||
@@ -56,2 +68,16 @@ } | ||
/** | ||
* Action position properties | ||
*/ | ||
readonly position?: { | ||
/** | ||
* X coordinate of the action (in pixels) | ||
*/ | ||
readonly x: number | ||
/** | ||
* Y coordinate of the action (in pixels) | ||
*/ | ||
readonly y: number | ||
[k: string]: unknown | ||
} | ||
/** | ||
* Properties of the errors of the action | ||
@@ -58,0 +84,0 @@ */ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1109403
340
22639
+ Added@datadog/browser-core@4.11.4(transitive)
- Removed@datadog/browser-core@4.11.3(transitive)
Updated@datadog/browser-core@4.11.4