Skip to content

Commit c6f3d12

Browse files
committed
👌 Address code review feedback
1 parent 9c48257 commit c6f3d12

6 files changed

Lines changed: 42 additions & 68 deletions

File tree

‎packages/rum-core/src/domain/assembly.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,10 @@ export function startRumAssembly(
5050
...ROOT_MODIFIABLE_FIELD_PATHS,
5151
},
5252
// view_update events are created post-assembly in startRumBatch.ts and never go through
53-
// this pipeline — they intentionally bypass beforeSend. This entry is required by the
54-
// exhaustive type but is never reached in practice.
53+
// this pipeline. The full view event already went through assembly (as RumEventType.VIEW),
54+
// so any beforeSend modifications (e.g. PII scrubbing on view.performance.lcp.resource_url)
55+
// are already reflected in the view_update diff. This entry is required by the exhaustive
56+
// type but is never reached in practice.
5557
[RumEventType.VIEW_UPDATE]: {},
5658
[RumEventType.ERROR]: {
5759
'error.message': 'string',

‎packages/rum-core/src/domain/view/viewDiff.spec.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ describe('isEqual', () => {
2828
expect(isEqual({ a: 1 }, { b: 1 })).toBe(false)
2929
})
3030

31+
it('should return true for objects with same keys in different order', () => {
32+
expect(isEqual({ a: 1, b: 2 }, { b: 2, a: 1 })).toBe(true)
33+
})
34+
3135
it('should return true for equal arrays', () => {
3236
expect(isEqual([1, 2, 3], [1, 2, 3])).toBe(true)
3337
})

‎packages/rum-core/src/transport/startRumBatch.spec.ts‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { resetExperimentalFeatures } from '@datadog/browser-core/src/tools/exper
33
import { registerCleanupTask } from '@datadog/browser-core/test'
44
import type { AssembledRumEvent } from '../rawRumEvent.types'
55
import { RumEventType } from '../rawRumEvent.types'
6-
import { computeAssembledViewDiff, PARTIAL_VIEW_UPDATE_CHECKPOINT_INTERVAL } from './startRumBatch'
6+
import { assembleViewUpdateEvent, PARTIAL_VIEW_UPDATE_CHECKPOINT_INTERVAL } from './startRumBatch'
77

88
function makeAssembledView(overrides: Record<string, unknown> = {}): AssembledRumEvent {
99
return {
@@ -38,7 +38,7 @@ function makeAssembledView(overrides: Record<string, unknown> = {}): AssembledRu
3838
} as unknown as AssembledRumEvent
3939
}
4040

41-
describe('computeAssembledViewDiff', () => {
41+
describe('assembleViewUpdateEvent', () => {
4242
it('should return undefined when nothing has changed', () => {
4343
const last = makeAssembledView()
4444
const current = makeAssembledView({
@@ -49,7 +49,7 @@ describe('computeAssembledViewDiff', () => {
4949
configuration: { start_session_replay_recording_manually: false },
5050
},
5151
})
52-
const result = computeAssembledViewDiff(current, last)
52+
const result = assembleViewUpdateEvent(current, last)
5353

5454
// Only document_version changed (always required, not a "meaningful change")
5555
// view.* unchanged → should return undefined
@@ -78,7 +78,7 @@ describe('computeAssembledViewDiff', () => {
7878
time_spent: 100,
7979
},
8080
})
81-
const result = computeAssembledViewDiff(current, last)!
81+
const result = assembleViewUpdateEvent(current, last)!
8282

8383
expect(result.type).toBe(RumEventType.VIEW_UPDATE)
8484
expect((result as any).application).toEqual({ id: 'app-1' })
@@ -111,7 +111,7 @@ describe('computeAssembledViewDiff', () => {
111111
time_spent: 5000,
112112
},
113113
})
114-
const result = computeAssembledViewDiff(current, last)!
114+
const result = assembleViewUpdateEvent(current, last)!
115115

116116
expect((result.view as any).action).toEqual({ count: 3 }) // changed
117117
expect((result.view as any).time_spent).toBe(5000) // changed
@@ -144,7 +144,7 @@ describe('computeAssembledViewDiff', () => {
144144
service: 'svc',
145145
version: '1.0.0',
146146
})
147-
const result = computeAssembledViewDiff(current, last)!
147+
const result = assembleViewUpdateEvent(current, last)!
148148

149149
expect(result.service).toBeUndefined() // unchanged, stripped
150150
expect((result as any).version).toBeUndefined() // unchanged, stripped
@@ -173,7 +173,7 @@ describe('computeAssembledViewDiff', () => {
173173
},
174174
service: 'new-service',
175175
})
176-
const result = computeAssembledViewDiff(current, last)!
176+
const result = assembleViewUpdateEvent(current, last)!
177177

178178
expect(result.service).toBe('new-service')
179179
})
@@ -201,7 +201,7 @@ describe('computeAssembledViewDiff', () => {
201201
},
202202
})
203203
const currentService = current.service
204-
computeAssembledViewDiff(current, last)
204+
assembleViewUpdateEvent(current, last)
205205

206206
expect(current.service).toBe(currentService)
207207
})

‎packages/rum-core/src/transport/startRumBatch.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { diffMerge } from '../domain/view/viewDiff'
1919

2020
export const PARTIAL_VIEW_UPDATE_CHECKPOINT_INTERVAL = 100
2121

22-
export function computeAssembledViewDiff(
22+
export function assembleViewUpdateEvent(
2323
current: AssembledRumEvent,
2424
last: AssembledRumEvent
2525
): AssembledRumEvent | undefined {
@@ -50,7 +50,7 @@ export function computeAssembledViewDiff(
5050
const currentView = currentObj.view as Record<string, unknown>
5151
const currentDd = currentObj._dd as Record<string, unknown>
5252

53-
// Merge always-required fields on top of the diff for backend routing
53+
// Restore the ignoreKeys — backend needs them on every event
5454
return combine(diff, {
5555
type: RumEventType.VIEW_UPDATE,
5656
date: currentObj.date,
@@ -137,7 +137,7 @@ export function startRumBatch(
137137
// They intentionally bypass RAW_RUM_EVENT_COLLECTED → assembly → RUM_EVENT_COLLECTED, which
138138
// means they skip beforeSend entirely. view_update is an internal bandwidth optimization —
139139
// not a customer-visible event type, and not modifiable via beforeSend.
140-
const diff = computeAssembledViewDiff(serverRumEvent, lastSentView)
140+
const diff = assembleViewUpdateEvent(serverRumEvent, lastSentView)
141141
lastSentView = serverRumEvent
142142
if (diff) {
143143
sendToExtension('rum', diff)

‎test/e2e/lib/framework/intakeRegistry.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ export class IntakeRegistry {
8888
return this.rumEvents.filter(isRumViewEvent)
8989
}
9090

91+
get rumViewUpdateEvents() {
92+
return this.rumEvents.filter(isRumViewUpdateEvent)
93+
}
94+
9195
get rumVitalEvents() {
9296
return this.rumEvents.filter(isRumVitalEvent)
9397
}
@@ -193,6 +197,10 @@ function isRumViewEvent(event: RumEvent): event is RumViewEvent {
193197
return event.type === 'view'
194198
}
195199

200+
function isRumViewUpdateEvent(event: RumEvent): boolean {
201+
return (event.type as string) === 'view_update'
202+
}
203+
196204
function isRumErrorEvent(event: RumEvent): event is RumErrorEvent {
197205
return event.type === 'error'
198206
}

‎test/e2e/scenario/rum/partialViewUpdates.scenario.ts‎

Lines changed: 15 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,5 @@
11
import { test, expect } from '@playwright/test'
2-
import { createTest, html, waitForRequests } from '../../lib/framework'
3-
import type { IntakeRegistry } from '../../lib/framework'
4-
5-
// Loose type for view_update events received at the intake (no generated schema type yet)
6-
interface ViewUpdateEvent {
7-
type: string
8-
date: number
9-
application: { id: string }
10-
session: { id: string }
11-
view: { id: string; is_active?: boolean; [key: string]: unknown }
12-
_dd: { document_version: number; [key: string]: unknown }
13-
[key: string]: unknown
14-
}
15-
16-
// Helper: extract view_update events from all RUM events
17-
// (intakeRegistry.rumViewEvents only returns type==='view')
18-
function getViewUpdateEvents(intakeRegistry: IntakeRegistry): ViewUpdateEvent[] {
19-
return intakeRegistry.rumEvents.filter((e) => (e.type as string) === 'view_update') as unknown as ViewUpdateEvent[]
20-
}
2+
import { createTest, waitForRequests } from '../../lib/framework'
213

224
test.describe('partial view updates', () => {
235
createTest('should send view_update events after the initial view event')
@@ -38,7 +20,7 @@ test.describe('partial view updates', () => {
3820
expect(viewEvents[0].type).toBe('view')
3921

4022
// Should have at least one view_update
41-
const viewUpdateEvents = getViewUpdateEvents(intakeRegistry)
23+
const viewUpdateEvents = intakeRegistry.rumViewUpdateEvents
4224
expect(viewUpdateEvents.length).toBeGreaterThanOrEqual(1)
4325

4426
// All events share the same view.id
@@ -59,20 +41,16 @@ test.describe('partial view updates', () => {
5941

6042
await flushEvents()
6143

62-
// Collect all view-related events (view + view_update) sorted by document_version
63-
const allViewRelatedEvents = [
64-
...intakeRegistry.rumViewEvents.map((e) => ({ _dd: e._dd })),
65-
...getViewUpdateEvents(intakeRegistry).map((e) => ({ _dd: e._dd })),
66-
].sort((a, b) => a._dd.document_version - b._dd.document_version)
44+
// Collect document_versions from all view-related events (view + view_update)
45+
const allDocVersions = [
46+
...intakeRegistry.rumViewEvents.map((e) => e._dd.document_version),
47+
...intakeRegistry.rumViewUpdateEvents.map((e) => (e._dd as { document_version: number }).document_version),
48+
]
6749

68-
expect(allViewRelatedEvents.length).toBeGreaterThanOrEqual(2)
50+
expect(allDocVersions.length).toBeGreaterThanOrEqual(2)
6951

70-
// Verify monotonic increase
71-
for (let i = 1; i < allViewRelatedEvents.length; i++) {
72-
expect(allViewRelatedEvents[i]._dd.document_version).toBeGreaterThan(
73-
allViewRelatedEvents[i - 1]._dd.document_version
74-
)
75-
}
52+
// Verify all document_versions are unique (no duplicates)
53+
expect(new Set(allDocVersions).size).toBe(allDocVersions.length)
7654
})
7755

7856
createTest('should only send view events when feature flag is not enabled')
@@ -88,25 +66,16 @@ test.describe('partial view updates', () => {
8866
expect(intakeRegistry.rumViewEvents.length).toBeGreaterThanOrEqual(1)
8967

9068
// Should NOT have any view_update events
91-
const viewUpdateEvents = getViewUpdateEvents(intakeRegistry)
69+
const viewUpdateEvents = intakeRegistry.rumViewUpdateEvents
9270
expect(viewUpdateEvents).toHaveLength(0)
9371
})
9472

9573
createTest('should emit a new full view event after navigation')
9674
.withRum({
9775
enableExperimentalFeatures: ['partial_view_updates'],
9876
})
99-
.withBody(html`
100-
<a id="nav-link">Navigate</a>
101-
<script>
102-
document.getElementById('nav-link').addEventListener('click', () => {
103-
history.pushState(null, '', '/new-page')
104-
})
105-
</script>
106-
`)
10777
.run(async ({ intakeRegistry, flushEvents, page }) => {
108-
// Trigger a route change to create a new view
109-
await page.click('#nav-link')
78+
await page.evaluate(() => history.pushState(null, '', '/new-page'))
11079

11180
await flushEvents()
11281

@@ -130,7 +99,7 @@ test.describe('partial view updates', () => {
13099

131100
await flushEvents()
132101

133-
const viewUpdateEvents = getViewUpdateEvents(intakeRegistry)
102+
const viewUpdateEvents = intakeRegistry.rumViewUpdateEvents
134103
expect(viewUpdateEvents.length).toBeGreaterThanOrEqual(1)
135104

136105
for (const event of viewUpdateEvents) {
@@ -148,17 +117,8 @@ test.describe('partial view updates', () => {
148117
.withRum({
149118
enableExperimentalFeatures: ['partial_view_updates'],
150119
})
151-
.withBody(html`
152-
<a id="nav-link">Navigate</a>
153-
<script>
154-
document.getElementById('nav-link').addEventListener('click', () => {
155-
history.pushState(null, '', '/other-page')
156-
})
157-
</script>
158-
`)
159120
.run(async ({ intakeRegistry, flushEvents, page }) => {
160-
// Navigate to trigger view end on the first view
161-
await page.click('#nav-link')
121+
await page.evaluate(() => history.pushState(null, '', '/other-page'))
162122

163123
await flushEvents()
164124

@@ -170,7 +130,7 @@ test.describe('partial view updates', () => {
170130
expect(endEvent?.type).toBe('view')
171131

172132
// No view_update should have is_active: false
173-
const viewUpdateEvents = getViewUpdateEvents(intakeRegistry)
133+
const viewUpdateEvents = intakeRegistry.rumViewUpdateEvents
174134
const endUpdateEvent = viewUpdateEvents.find((e) => e.view.id === firstViewId && e.view.is_active === false)
175135
expect(endUpdateEvent).toBeUndefined()
176136
})

0 commit comments

Comments
 (0)