From 435654667b04ac88ee6c999d6eb5b68e9d8af29f Mon Sep 17 00:00:00 2001 From: Brett Burley Date: Tue, 17 Feb 2026 14:52:18 -0800 Subject: [PATCH 1/4] =?UTF-8?q?=E2=9C=A8=20Add=20url=20option=20to=20start?= =?UTF-8?q?View=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allow passing an explicit URL when calling startView() so frameworks like React Router v7 (framework mode) that update the URL after loaders complete can provide the correct destination URL upfront. Falls back to location.href when no url is provided. --- .../src/domain/contexts/urlContexts.spec.ts | 20 ++++++++++++++++ .../src/domain/contexts/urlContexts.ts | 4 ++-- .../rum-core/src/domain/view/trackViews.ts | 3 +++ test/e2e/scenario/rum/init.scenario.ts | 23 +++++++++++++++++++ 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/packages/rum-core/src/domain/contexts/urlContexts.spec.ts b/packages/rum-core/src/domain/contexts/urlContexts.spec.ts index 8b9725044e..7e6a84e4af 100644 --- a/packages/rum-core/src/domain/contexts/urlContexts.spec.ts +++ b/packages/rum-core/src/domain/contexts/urlContexts.spec.ts @@ -49,6 +49,26 @@ describe('urlContexts', () => { expect(urlContext.referrer).toBe(document.referrer) }) + it('should use the provided url override instead of location', () => { + lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, { + startClocks: relativeToClocks(0 as RelativeTime), + url: 'https://example.com/overridden-path', + } as ViewCreatedEvent) + + const urlContext = urlContexts.findUrl()! + expect(urlContext.url).toBe('https://example.com/overridden-path') + expect(urlContext.referrer).toBe(document.referrer) + }) + + it('should fall back to location.href when no url override is provided', () => { + lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, { + startClocks: relativeToClocks(0 as RelativeTime), + } as ViewCreatedEvent) + + const urlContext = urlContexts.findUrl()! + expect(urlContext.url).toBe('http://fake-url.com/') + }) + it('should update url context on location change', () => { lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, { startClocks: relativeToClocks(0 as RelativeTime), diff --git a/packages/rum-core/src/domain/contexts/urlContexts.ts b/packages/rum-core/src/domain/contexts/urlContexts.ts index dc5cd84c7e..20d9965e10 100644 --- a/packages/rum-core/src/domain/contexts/urlContexts.ts +++ b/packages/rum-core/src/domain/contexts/urlContexts.ts @@ -39,8 +39,8 @@ export function startUrlContexts( let previousViewUrl: string | undefined - lifeCycle.subscribe(LifeCycleEventType.BEFORE_VIEW_CREATED, ({ startClocks }) => { - const viewUrl = mockable(location).href + lifeCycle.subscribe(LifeCycleEventType.BEFORE_VIEW_CREATED, ({ startClocks, url }) => { + const viewUrl = url || mockable(location).href urlContextHistory.add( buildUrlContext({ url: viewUrl, diff --git a/packages/rum-core/src/domain/view/trackViews.ts b/packages/rum-core/src/domain/view/trackViews.ts index 36bb7afaf8..f2d10f6e01 100644 --- a/packages/rum-core/src/domain/view/trackViews.ts +++ b/packages/rum-core/src/domain/view/trackViews.ts @@ -71,6 +71,7 @@ export interface ViewCreatedEvent { version?: string context?: Context startClocks: ClocksState + url?: string } export interface BeforeViewUpdateEvent { @@ -102,6 +103,7 @@ export interface ViewOptions { version?: RumInitConfiguration['version'] context?: Context handlingStack?: string + url?: string } export function trackViews( @@ -241,6 +243,7 @@ function newView( service, version, context, + url: viewOptions?.url, } lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, viewCreatedEvent) lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, viewCreatedEvent) diff --git a/test/e2e/scenario/rum/init.scenario.ts b/test/e2e/scenario/rum/init.scenario.ts index 95316c0f92..24cecf94ba 100644 --- a/test/e2e/scenario/rum/init.scenario.ts +++ b/test/e2e/scenario/rum/init.scenario.ts @@ -119,6 +119,29 @@ test.describe('API calls and events around init', () => { ) }) + createTest('should use the provided url option instead of location') + .withRum() + .withRumSlim() + .withRumInit((configuration) => { + window.DD_RUM!.init(configuration) + + setTimeout( + () => + window.DD_RUM!.startView({ + name: 'manual view', + url: 'https://example.com/overridden-path', + }), + 10 + ) + }) + .run(async ({ intakeRegistry, flushEvents }) => { + await flushEvents() + + const manualView = intakeRegistry.rumViewEvents.find((event) => event.view.name === 'manual view')! + expect(manualView).toBeTruthy() + expect(manualView.view.url).toBe('https://example.com/overridden-path') + }) + createTest('should be able to set view context') .withRum() .withRumSlim() From 7f6ff27902f2c64fec4658a4d147348c63438e49 Mon Sep 17 00:00:00 2001 From: "beltran.bulbarella" Date: Thu, 5 Mar 2026 15:23:58 +0100 Subject: [PATCH 2/4] test: add coverage for startView url option and nullish check for viewUrl add undefined url test add test resource also gets the manual url --- .../src/domain/contexts/urlContexts.spec.ts | 28 +++++++++++++++++++ .../src/domain/contexts/urlContexts.ts | 2 +- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/rum-core/src/domain/contexts/urlContexts.spec.ts b/packages/rum-core/src/domain/contexts/urlContexts.spec.ts index 7e6a84e4af..6c6801c7d0 100644 --- a/packages/rum-core/src/domain/contexts/urlContexts.spec.ts +++ b/packages/rum-core/src/domain/contexts/urlContexts.spec.ts @@ -69,6 +69,34 @@ describe('urlContexts', () => { expect(urlContext.url).toBe('http://fake-url.com/') }) + it('should fall back to location.href when url override is explicitly undefined', () => { + lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, { + startClocks: relativeToClocks(0 as RelativeTime), + url: undefined, + } as ViewCreatedEvent) + + const urlContext = urlContexts.findUrl()! + expect(urlContext.url).toBe('http://fake-url.com/') + }) + + it('should use the provided url override for events starting before a location change', () => { + lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, { + startClocks: clocksNow(), + url: 'https://example.com/manual-url', + } as ViewCreatedEvent) + + clock.tick(10) + const resourceStartTime = clock.relative(10) + + clock.tick(10) + changeLocation('/new-path') + + expect(urlContexts.findUrl(resourceStartTime)).toEqual({ + url: 'https://example.com/manual-url', + referrer: document.referrer, + }) + }) + it('should update url context on location change', () => { lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, { startClocks: relativeToClocks(0 as RelativeTime), diff --git a/packages/rum-core/src/domain/contexts/urlContexts.ts b/packages/rum-core/src/domain/contexts/urlContexts.ts index 20d9965e10..b830429d4d 100644 --- a/packages/rum-core/src/domain/contexts/urlContexts.ts +++ b/packages/rum-core/src/domain/contexts/urlContexts.ts @@ -40,7 +40,7 @@ export function startUrlContexts( let previousViewUrl: string | undefined lifeCycle.subscribe(LifeCycleEventType.BEFORE_VIEW_CREATED, ({ startClocks, url }) => { - const viewUrl = url || mockable(location).href + const viewUrl = url ?? mockable(location).href urlContextHistory.add( buildUrlContext({ url: viewUrl, From 4e5fb0fbdd6c1883fb3ed8a159941d1d15ba325c Mon Sep 17 00:00:00 2001 From: "beltran.bulbarella" Date: Thu, 5 Mar 2026 17:11:15 +0100 Subject: [PATCH 3/4] clarify startView options and add url --- packages/rum-core/src/boot/rumPublicApi.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rum-core/src/boot/rumPublicApi.ts b/packages/rum-core/src/boot/rumPublicApi.ts index 622046c9a9..592d40415f 100644 --- a/packages/rum-core/src/boot/rumPublicApi.ts +++ b/packages/rum-core/src/boot/rumPublicApi.ts @@ -388,7 +388,7 @@ export interface RumPublicApi extends PublicApi { * * Context - @category Data Collection * - * @param nameOrOptions - Name or options (name, service, version) for the view + * @param nameOrOptions - The view name, or a {@link ViewOptions} object to configure the view */ startView(nameOrOptions?: string | ViewOptions): void From 91329459abd0aa243d87acb00b4aaa023113f0c9 Mon Sep 17 00:00:00 2001 From: "beltran.bulbarella" Date: Thu, 5 Mar 2026 18:19:38 +0100 Subject: [PATCH 4/4] Add url validation --- .../rum-core/src/domain/contexts/urlContexts.spec.ts | 10 ++++++++++ packages/rum-core/src/domain/contexts/urlContexts.ts | 4 +++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/rum-core/src/domain/contexts/urlContexts.spec.ts b/packages/rum-core/src/domain/contexts/urlContexts.spec.ts index 6c6801c7d0..084e9dcf18 100644 --- a/packages/rum-core/src/domain/contexts/urlContexts.spec.ts +++ b/packages/rum-core/src/domain/contexts/urlContexts.spec.ts @@ -60,6 +60,16 @@ describe('urlContexts', () => { expect(urlContext.referrer).toBe(document.referrer) }) + it('should resolve a relative url override against the current location', () => { + lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, { + startClocks: relativeToClocks(0 as RelativeTime), + url: '/dashboard', + } as ViewCreatedEvent) + + const urlContext = urlContexts.findUrl()! + expect(urlContext.url).toBe('http://fake-url.com/dashboard') + }) + it('should fall back to location.href when no url override is provided', () => { lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, { startClocks: relativeToClocks(0 as RelativeTime), diff --git a/packages/rum-core/src/domain/contexts/urlContexts.ts b/packages/rum-core/src/domain/contexts/urlContexts.ts index b830429d4d..d2aef3d0c4 100644 --- a/packages/rum-core/src/domain/contexts/urlContexts.ts +++ b/packages/rum-core/src/domain/contexts/urlContexts.ts @@ -6,6 +6,7 @@ import { HookNames, DISCARDED, mockable, + buildUrl, } from '@datadog/browser-core' import type { LocationChange } from '../../browser/locationChangeObservable' import type { LifeCycle } from '../lifeCycle' @@ -40,7 +41,8 @@ export function startUrlContexts( let previousViewUrl: string | undefined lifeCycle.subscribe(LifeCycleEventType.BEFORE_VIEW_CREATED, ({ startClocks, url }) => { - const viewUrl = url ?? mockable(location).href + const mockedLocationHref = mockable(location).href + const viewUrl = url !== undefined ? buildUrl(url, mockedLocationHref).href : mockedLocationHref urlContextHistory.add( buildUrlContext({ url: viewUrl,