Skip to content

Commit d7aa56c

Browse files
committed
[PANA-6072] add composedPathSelector to click actions target behind FF
1 parent 81bb997 commit d7aa56c

File tree

11 files changed

+586
-8
lines changed

11 files changed

+586
-8
lines changed

packages/core/src/tools/experimentalFeatures.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export enum ExperimentalFeature {
2323
USE_CHANGE_RECORDS = 'use_change_records',
2424
SOURCE_CODE_CONTEXT = 'source_code_context',
2525
LCP_SUBPARTS = 'lcp_subparts',
26+
COMPOSED_PATH_SELECTOR = 'composed_path_selector',
2627
}
2728

2829
const enabledExperimentalFeatures: Set<ExperimentalFeature> = new Set()

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ describe('trackClickActions', () => {
134134
selector: '#button',
135135
width: 100,
136136
height: 100,
137+
composed_path_selector: undefined,
137138
},
138139
position: { x: 50, y: 50 },
139140
events: [domEvent],
@@ -661,6 +662,24 @@ describe('trackClickActions', () => {
661662
expect(events[0].name).toBe('Shadow Button')
662663
})
663664
})
665+
666+
describe('when composed path selector is enabled', () => {
667+
it('should return a composed_path_selector', () => {
668+
addExperimentalFeatures([ExperimentalFeature.COMPOSED_PATH_SELECTOR])
669+
startClickActionsTracking()
670+
emulateClick({
671+
target: button,
672+
activity: {},
673+
eventProperty: {
674+
composed: true,
675+
composedPath: () => [button, document.body, document],
676+
},
677+
})
678+
679+
clock.tick(EXPIRE_DELAY)
680+
expect(events[0].target?.composed_path_selector).toBeDefined()
681+
})
682+
})
664683
})
665684

666685
describe('finalizeClicks', () => {

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
import type { Duration, ClocksState, TimeStamp } from '@datadog/browser-core'
2-
import { timeStampNow, Observable, timeStampToClocks, relativeToClocks, generateUUID } from '@datadog/browser-core'
2+
import {
3+
timeStampNow,
4+
Observable,
5+
timeStampToClocks,
6+
relativeToClocks,
7+
generateUUID,
8+
isExperimentalFeatureEnabled,
9+
ExperimentalFeature,
10+
} from '@datadog/browser-core'
311
import { isNodeShadowHost } from '../../browser/htmlDomUtils'
412
import type { FrustrationType } from '../../rawRumEvent.types'
513
import { ActionType } from '../../rawRumEvent.types'
@@ -13,6 +21,7 @@ import type { RumConfiguration } from '../configuration'
1321
import type { RumMutationRecord } from '../../browser/domMutationObservable'
1422
import { startEventTracker } from '../eventTracker'
1523
import type { StoppedEvent, DiscardedEvent, EventTracker } from '../eventTracker'
24+
import { getComposedPathSelector } from '../getComposedPathSelector'
1625
import type { ClickChain } from './clickChain'
1726
import { createClickChain } from './clickChain'
1827
import { getActionNameFromElement } from './getActionNameFromElement'
@@ -36,6 +45,7 @@ export interface ClickAction {
3645
nameSource: ActionNameSource
3746
target?: {
3847
selector: string | undefined
48+
composed_path_selector?: string
3949
width: number
4050
height: number
4151
}
@@ -231,9 +241,14 @@ function computeClickActionBase(
231241
nodePrivacyLevel: NodePrivacyLevel,
232242
configuration: RumConfiguration
233243
): ClickActionBase {
244+
const eventPath = event.composedPath()
245+
234246
const selectorTarget = event.target
235247
const rect = selectorTarget.getBoundingClientRect()
236248
const selector = getSelectorFromElement(selectorTarget, configuration.actionNameAttribute)
249+
const composedPathSelector = isExperimentalFeatureEnabled(ExperimentalFeature.COMPOSED_PATH_SELECTOR)
250+
? getComposedPathSelector(eventPath, configuration.actionNameAttribute, configuration.allowedHtmlAttributes || [])
251+
: undefined
237252

238253
if (selector) {
239254
updateInteractionSelector(event.timeStamp, selector)
@@ -248,6 +263,7 @@ function computeClickActionBase(
248263
width: Math.round(rect.width),
249264
height: Math.round(rect.height),
250265
selector,
266+
composed_path_selector: composedPathSelector ?? undefined,
251267
},
252268
position: {
253269
// Use clientX and Y because for SVG element offsetX and Y are relatives to the <svg> element

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,7 @@ describe('serializeRumConfiguration', () => {
631631
profilingSampleRate: 42,
632632
propagateTraceBaggage: true,
633633
betaTrackActionsInShadowDom: true,
634+
allowedHtmlAttributes: ['data-testid'],
634635
}
635636

636637
type MapRumInitConfigurationKey<Key extends string> = Key extends keyof InitConfiguration
@@ -641,6 +642,7 @@ describe('serializeRumConfiguration', () => {
641642
| 'excludedActivityUrls'
642643
| 'remoteConfigurationProxy'
643644
| 'allowedGraphQlUrls'
645+
| 'allowedHtmlAttributes'
644646
? `use_${CamelToSnakeCase<Key>}`
645647
: Key extends 'trackLongTasks'
646648
? 'track_long_task' // We forgot the s, keeping this for backward compatibility
@@ -687,6 +689,7 @@ describe('serializeRumConfiguration', () => {
687689
remote_configuration_id: '123',
688690
use_remote_configuration_proxy: true,
689691
profiling_sample_rate: 42,
692+
use_allowed_html_attributes: true,
690693
})
691694
})
692695
})

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,15 @@ export interface RumInitConfiguration extends InitConfiguration {
281281
* @category Data Collection
282282
*/
283283
allowedGraphQlUrls?: Array<MatchOption | GraphQlUrlOption> | undefined
284+
285+
/**
286+
* A list of HTML attributes allowed to be used in the action selector collection.
287+
* Matches attributes against the event target and its ancestors.
288+
* If not provided, the SDK will use a default list of HTML attributes.
289+
*
290+
* @category Data Collection
291+
*/
292+
allowedHtmlAttributes?: MatchOption[] | undefined
284293
}
285294

286295
export type HybridInitConfiguration = Omit<RumInitConfiguration, 'applicationId' | 'clientToken'>
@@ -321,6 +330,7 @@ export interface RumConfiguration extends Configuration {
321330
profilingSampleRate: number
322331
propagateTraceBaggage: boolean
323332
allowedGraphQlUrls: GraphQlUrlOption[]
333+
allowedHtmlAttributes?: MatchOption[]
324334
}
325335

326336
export function validateAndBuildRumConfiguration(
@@ -400,6 +410,9 @@ export function validateAndBuildRumConfiguration(
400410
profilingSampleRate: initConfiguration.profilingSampleRate ?? 0,
401411
propagateTraceBaggage: !!initConfiguration.propagateTraceBaggage,
402412
allowedGraphQlUrls,
413+
allowedHtmlAttributes: Array.isArray(initConfiguration.allowedHtmlAttributes)
414+
? initConfiguration.allowedHtmlAttributes.filter(isMatchOption)
415+
: [],
403416
...baseConfiguration,
404417
}
405418
}
@@ -548,6 +561,7 @@ export function serializeRumConfiguration(configuration: RumInitConfiguration) {
548561
remote_configuration_id: configuration.remoteConfigurationId,
549562
profiling_sample_rate: configuration.profilingSampleRate,
550563
use_remote_configuration_proxy: !!configuration.remoteConfigurationProxy,
564+
use_allowed_html_attributes: isNonEmptyArray(configuration.allowedHtmlAttributes),
551565
...baseSerializedConfiguration,
552566
} satisfies RawTelemetryConfiguration
553567
}

0 commit comments

Comments
 (0)