Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
2f11566
add s/s action
BeltranBulbarellaDD Dec 17, 2025
065a6fe
Add unit tests for startAction and stopAction
BeltranBulbarellaDD Dec 17, 2025
aec81c6
Linting fixes
BeltranBulbarellaDD Dec 17, 2025
270c0cb
cleanup
BeltranBulbarellaDD Dec 17, 2025
b0bc642
fix leaked listener
BeltranBulbarellaDD Dec 17, 2025
a06410a
Integrated trackEventCounts and integrate with startActionCollection.
BeltranBulbarellaDD Dec 30, 2025
2080477
Merge branch 'main' into beltran.bulbarella/start-stopAction
BeltranBulbarellaDD Dec 30, 2025
b4af4f6
merged rum-events-format
BeltranBulbarellaDD Dec 30, 2025
3e88bd7
Fix format
BeltranBulbarellaDD Dec 30, 2025
f129855
sync schemas
BeltranBulbarellaDD Dec 30, 2025
2259509
fix comma
BeltranBulbarellaDD Dec 30, 2025
446390e
Add telemetry type (remove after merge in R-E-F)
BeltranBulbarellaDD Dec 30, 2025
efe5ffc
Add loading_time to custom actions
BeltranBulbarellaDD Dec 31, 2025
2c95977
wait for fetch to complete in e2e test so to avoid flaky test
BeltranBulbarellaDD Dec 31, 2025
546fdde
revert test
BeltranBulbarellaDD Dec 31, 2025
d7a7e14
wait for requests to complete in e2e test so to avoid flaky test
BeltranBulbarellaDD Dec 31, 2025
e8725ba
Update activeCustomActions to track existing actions
BeltranBulbarellaDD Jan 5, 2026
9864c12
Clean up activeCustomActions on session renewal
BeltranBulbarellaDD Jan 5, 2026
a19c4d0
Stop active actions on session renewal, and reset the action context.
BeltranBulbarellaDD Jan 5, 2026
5b40d1e
Clean up active custom actions on stop()
BeltranBulbarellaDD Jan 5, 2026
b513153
Create trackAction to reuse in actionCollection and trackClickActions.
BeltranBulbarellaDD Jan 7, 2026
f13b695
Fix linter
BeltranBulbarellaDD Jan 7, 2026
969a859
Fix eslint config and linter
BeltranBulbarellaDD Jan 7, 2026
f8c506e
Prevent collision in getActionLookupKey
BeltranBulbarellaDD Jan 7, 2026
c3ddeeb
Remove unneded test, update getActionLookupKey test
BeltranBulbarellaDD Jan 7, 2026
e1f4512
Remove telemetry events
BeltranBulbarellaDD Jan 7, 2026
fda20bb
remove space
BeltranBulbarellaDD Jan 7, 2026
80850cf
Return an array of action IDs in findActionId
BeltranBulbarellaDD Jan 12, 2026
573cb0a
Merge branch 'main' into beltran.bulbarella/start-stopAction
BeltranBulbarellaDD Jan 13, 2026
ee43b93
run format
BeltranBulbarellaDD Jan 13, 2026
7946916
support start time for pre init action tracking
BeltranBulbarellaDD Jan 13, 2026
4227e82
Merge branch 'main' into beltran.bulbarella/start-stopAction
BeltranBulbarellaDD Jan 13, 2026
1b690e5
modify to test that the clocks are captured at call time
BeltranBulbarellaDD Jan 13, 2026
3e0bbd9
Remove actionKey, make experimental docs, pass clocks to actionCollec…
BeltranBulbarellaDD Jan 14, 2026
40e8331
Created trackCustomActions, revamp processAction, edit clickAction.
BeltranBulbarellaDD Jan 14, 2026
8e6763f
remove actionKey, run formatter.
BeltranBulbarellaDD Jan 14, 2026
feead72
Linting fixes
BeltranBulbarellaDD Jan 14, 2026
d6a11c0
Linting fixes.
BeltranBulbarellaDD Jan 14, 2026
02fa207
Re add actionKey, created TrackedActionMetadata.
BeltranBulbarellaDD Jan 14, 2026
c28599f
Fix typecheck
BeltranBulbarellaDD Jan 14, 2026
20019e3
Change getActionLookupKey
BeltranBulbarellaDD Jan 14, 2026
0dc0e6a
TEST REDUCE BUNDLE SIZE
BeltranBulbarellaDD Jan 14, 2026
53b70d9
SECOND TEST REDUCE BUNDLE SIZE
BeltranBulbarellaDD Jan 14, 2026
66ce586
Add back action types
BeltranBulbarellaDD Jan 14, 2026
c0a3fd5
Fix const names, removed InstantCustomAction, remove TrackedActionMet…
BeltranBulbarellaDD Jan 15, 2026
06d7565
Linter
BeltranBulbarellaDD Jan 15, 2026
d299a36
Merge branch 'main' into beltran.bulbarella/start-stopAction
BeltranBulbarellaDD Jan 16, 2026
7754bcc
Renamed trackCustomActions to trackManualActions, moved addAction to …
BeltranBulbarellaDD Jan 20, 2026
33289a4
Merge branch 'main' into beltran.bulbarella/start-stopAction
BeltranBulbarellaDD Jan 20, 2026
17846f0
Renamed ActiveManualAction to ManualActionStart, simplified tests
BeltranBulbarellaDD Jan 21, 2026
2404882
Add ERROR_CLICK frustration to manual actions
BeltranBulbarellaDD Jan 21, 2026
27d07cf
Simplify tests
BeltranBulbarellaDD Jan 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/rum-core/src/boot/preStartRum.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,8 @@ describe('preStartRum', () => {
errorCount: 0,
longTaskCount: 0,
resourceCount: 0,
actionCount: 0,
frustrationCount: 0,
},
}
strategy.addAction(customAction)
Expand Down
75 changes: 32 additions & 43 deletions packages/rum-core/src/domain/action/actionCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { RumEventType } from '../../rawRumEvent.types'
import type { LifeCycle, RawRumEventCollectedData } from '../lifeCycle'
import { LifeCycleEventType } from '../lifeCycle'
import type { RumConfiguration } from '../configuration'
import type { RumActionEventDomainContext } from '../../domainContext.types'
import type { DefaultRumEventAttributes, DefaultTelemetryEventAttributes, Hooks } from '../hooks'
import type { RumMutationRecord } from '../../browser/domMutationObservable'
import { trackClickActions } from './trackClickActions'
Expand Down Expand Up @@ -134,59 +133,49 @@ export function startActionCollection(
function processAction(action: AutoAction | CustomAction): RawRumEventCollectedData<RawRumActionEvent> {
const isAuto = isAutoAction(action)

const actionEvent: RawRumActionEvent = {
type: RumEventType.ACTION,
date: action.startClocks.timeStamp,
action: {
id: action.id,
loading_time: discardNegativeDuration(toServerDuration(action.duration)),
target: { name: action.name },
type: action.type,
...(action.counts && {
return {
rawRumEvent: {
type: RumEventType.ACTION,
date: action.startClocks.timeStamp,
action: {
id: action.id,
loading_time: discardNegativeDuration(toServerDuration(action.duration)),
target: { name: action.name },
type: action.type,
error: { count: action.counts.errorCount },
long_task: { count: action.counts.longTaskCount },
resource: { count: action.counts.resourceCount },
}),
frustration: isAuto ? { type: action.frustrationTypes } : undefined,
frustration: isAuto ? { type: action.frustrationTypes } : undefined,
},
context: isAuto ? undefined : action.context,
_dd: isAuto
? {
action: {
target: action.target,
position: action.position,
name_source: action.nameSource,
},
}
: undefined,
},
context: isAuto ? undefined : action.context,
_dd: isAuto
? {
action: {
target: action.target,
position: action.position,
name_source: action.nameSource,
},
}
: undefined,
}

const domainContext: RumActionEventDomainContext = isAuto
? { events: action.events }
: { handlingStack: action.handlingStack }

return {
rawRumEvent: actionEvent,
duration: action.duration,
startTime: action.startClocks.relative,
domainContext,
domainContext: isAuto ? { events: action.events } : { handlingStack: action.handlingStack },
}
}

function processInstantAction(action: InstantCustomAction): RawRumEventCollectedData<RawRumActionEvent> {
const actionEvent: RawRumActionEvent = {
type: RumEventType.ACTION,
date: action.startClocks.timeStamp,
action: {
id: generateUUID(),
target: { name: action.name },
type: action.type,
},
context: action.context,
}

return {
rawRumEvent: actionEvent,
rawRumEvent: {
type: RumEventType.ACTION,
date: action.startClocks.timeStamp,
action: {
id: generateUUID(),
target: { name: action.name },
type: action.type,
},
context: action.context,
},
duration: action.duration,
startTime: action.startClocks.relative,
domainContext: { handlingStack: action.handlingStack },
Expand Down
57 changes: 22 additions & 35 deletions packages/rum-core/src/domain/action/trackAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,43 +38,30 @@ export interface ActionTracker {

export function startActionTracker(lifeCycle: LifeCycle): ActionTracker {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭 thought: ‏With this implementation, custom actions can have a duration and be linked to other events. I think it’s a good direction, but we should check with Product to get their view. We should also align with Frontend to make sure they can handle these new action types coming from the browser.

const history = createValueHistory<string>({ expireDelay: ACTION_CONTEXT_TIME_OUT_DELAY })
const activeEventCountSubscriptions = new Set<ReturnType<typeof trackEventCounts>>()
const activeSubs = new Set<ReturnType<typeof trackEventCounts>>()

const sessionRenewalSubscription = lifeCycle.subscribe(LifeCycleEventType.SESSION_RENEWED, () => {
history.reset()
activeEventCountSubscriptions.forEach((s) => s.stop())
activeEventCountSubscriptions.clear()
activeSubs.forEach((s) => s.stop())
activeSubs.clear()
})

function createTrackedAction(startClocks: ClocksState, metadata?: TrackedActionMetadata): TrackedAction {
const id = generateUUID()
const historyEntry: ValueHistoryEntry<string> = history.add(id, startClocks.relative)
let stopped = false
let duration: Duration | undefined

const eventCountsSubscription = trackEventCounts({
const sub = trackEventCounts({
lifeCycle,
isChildEvent: (event) =>
event.action !== undefined &&
(Array.isArray(event.action.id) ? event.action.id.includes(id) : event.action.id === id),
})
activeEventCountSubscriptions.add(eventCountsSubscription)
activeSubs.add(sub)

function stopOrDiscard(endTime?: RelativeTime) {
if (stopped) {
return
}
stopped = true

if (endTime !== undefined) {
historyEntry.close(endTime)
duration = elapsed(startClocks.relative, endTime)
} else {
historyEntry.remove()
}

eventCountsSubscription.stop()
activeEventCountSubscriptions.delete(eventCountsSubscription)
function cleanup() {
sub.stop()
activeSubs.delete(sub)
}

return {
Expand All @@ -85,32 +72,32 @@ export function startActionTracker(lifeCycle: LifeCycle): ActionTracker {
return duration
},
get counts() {
return eventCountsSubscription.eventCounts
return sub.eventCounts
},
stop(endTime: RelativeTime) {
historyEntry.close(endTime)
duration = elapsed(startClocks.relative, endTime)
cleanup()
},
discard() {
historyEntry.remove()
cleanup()
},
stop: stopOrDiscard,
discard: stopOrDiscard,
}
}

function findActionId(startTime?: RelativeTime): string | string[] | undefined {
const ids = history.findAll(startTime)
if (ids.length === 0) {
return undefined
}
return ids
return ids.length ? ids : undefined
}

function stop() {
sessionRenewalSubscription.unsubscribe()
activeEventCountSubscriptions.forEach((s) => s.stop())
activeEventCountSubscriptions.clear()
activeSubs.forEach((s) => s.stop())
activeSubs.clear()
history.reset()
history.stop()
}

return {
createTrackedAction,
findActionId,
stop,
}
return { createTrackedAction, findActionId, stop }
}
14 changes: 0 additions & 14 deletions test/e2e/scenario/rum/actions.scenario.ts
Original file line number Diff line number Diff line change
Expand Up @@ -612,20 +612,6 @@ test.describe('custom actions with startAction/stopAction', () => {
)
})

createTest('support custom action types')
.withRum({ enableExperimentalFeatures: ['start_stop_action'] })
.run(async ({ intakeRegistry, flushEvents, page }) => {
await page.evaluate(() => {
window.DD_RUM!.startAction('swipe_left', { type: 'swipe' })
window.DD_RUM!.stopAction('swipe_left')
})
await flushEvents()

const actionEvents = intakeRegistry.rumActionEvents
expect(actionEvents).toHaveLength(1)
expect(actionEvents[0].action.type).toBe('swipe')
})

createTest('preserve timing when startAction is called before init')
.withRum({ enableExperimentalFeatures: ['start_stop_action'] })
.withRumInit((configuration) => {
Expand Down