Skip to content

Commit

Permalink
✨ [RUM-4819] Add an experimental 'updateViewName' API (#2808)
Browse files Browse the repository at this point in the history
* new api change view name manually

* update chore

* update and add unit test

* add experimental feature

* update e2e test

* update unit test after feature flag commit

* update e2e test

* update e2e test

* update test and add getter

* don't expose api if feature flag not enabled

* update test for feature flag

* update init scenario

* update e2e test

* update e2e test

* add second argument
  • Loading branch information
RomanGaignault committed Jul 3, 2024
1 parent 73e3016 commit b3ef206
Show file tree
Hide file tree
Showing 10 changed files with 142 additions and 7 deletions.
1 change: 1 addition & 0 deletions packages/core/src/tools/experimentalFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export enum ExperimentalFeature {
CUSTOM_VITALS = 'custom_vitals',
TOLERANT_RESOURCE_TIMINGS = 'tolerant_resource_timings',
REMOTE_CONFIGURATION = 'remote_configuration',
UPDATE_VIEW_NAME = 'update_view_name',
PLUGINS = 'plugins',
}

Expand Down
13 changes: 13 additions & 0 deletions packages/rum-core/src/boot/preStartRum.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,13 +288,16 @@ describe('preStartRum', () => {
let strategy: Strategy
let startViewSpy: jasmine.Spy<StartRumResult['startView']>
let addTimingSpy: jasmine.Spy<StartRumResult['addTiming']>
let updateViewNameSpy: jasmine.Spy<StartRumResult['updateViewName']>

beforeEach(() => {
startViewSpy = jasmine.createSpy('startView')
addTimingSpy = jasmine.createSpy('addTiming')
updateViewNameSpy = jasmine.createSpy('updateViewName')
doStartRumSpy.and.returnValue({
startView: startViewSpy,
addTiming: addTimingSpy,
updateViewName: updateViewNameSpy,
} as unknown as StartRumResult)
strategy = createPreStartStrategy({}, getCommonContextSpy, createTrackingConsentState(), doStartRumSpy)
})
Expand Down Expand Up @@ -646,6 +649,16 @@ describe('preStartRum', () => {
expect(addTimingSpy).toHaveBeenCalledOnceWith(name, time)
})

it('updateViewName', () => {
const updateViewNameSpy = jasmine.createSpy()
doStartRumSpy.and.returnValue({ updateViewName: updateViewNameSpy } as unknown as StartRumResult)

const name = 'foo'
strategy.updateViewName(name)
strategy.init(DEFAULT_INIT_CONFIGURATION, PUBLIC_API)
expect(updateViewNameSpy).toHaveBeenCalledOnceWith(name)
})

it('addFeatureFlagEvaluation', () => {
const addFeatureFlagEvaluationSpy = jasmine.createSpy()
doStartRumSpy.and.returnValue({
Expand Down
4 changes: 4 additions & 0 deletions packages/rum-core/src/boot/preStartRum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,10 @@ export function createPreStartStrategy(
}
},

updateViewName(name) {
bufferApiCalls.add((startRumResult) => startRumResult.updateViewName(name))
},

addAction(action, commonContext = getCommonContext()) {
bufferApiCalls.add((startRumResult) => startRumResult.addAction(action, commonContext))
},
Expand Down
34 changes: 33 additions & 1 deletion packages/rum-core/src/boot/rumPublicApi.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
CustomerDataCompressionStatus,
timeStampToClocks,
} from '@datadog/browser-core'
import { cleanupSyntheticsWorkerValues } from '@datadog/browser-core/test'
import { cleanupSyntheticsWorkerValues, mockExperimentalFeatures } from '@datadog/browser-core/test'
import type { TestSetupBuilder } from '../../test'
import { setup, noopRecorderApi } from '../../test'
import { ActionType } from '../rawRumEvent.types'
Expand All @@ -25,6 +25,7 @@ const noopStartRum = (): ReturnType<StartRum> => ({
addTiming: () => undefined,
addFeatureFlagEvaluation: () => undefined,
startView: () => undefined,
updateViewName: () => undefined,
getInternalContext: () => undefined,
lifeCycle: {} as any,
viewContexts: {} as any,
Expand Down Expand Up @@ -856,4 +857,35 @@ describe('rum public api', () => {
const rumPublicApi = makeRumPublicApi(noopStartRum, noopRecorderApi)
expect(rumPublicApi.version).toBe('test')
})

describe('updateViewName', () => {
let updateViewNameSpy: jasmine.Spy<ReturnType<StartRum>['updateViewName']>
let rumPublicApi: RumPublicApi

beforeEach(() => {
updateViewNameSpy = jasmine.createSpy()
rumPublicApi = makeRumPublicApi(
() => ({
...noopStartRum(),
updateViewName: updateViewNameSpy,
}),
noopRecorderApi
)
})

it('should not expose update view name api when ff is disabled', () => {
const rumPublicApi = makeRumPublicApi(noopStartRum, noopRecorderApi)
rumPublicApi.init(DEFAULT_INIT_CONFIGURATION)
expect((rumPublicApi as any).updateViewName).toBeUndefined()
})

it('should update the view name', () => {
mockExperimentalFeatures([ExperimentalFeature.UPDATE_VIEW_NAME])
rumPublicApi.init(DEFAULT_INIT_CONFIGURATION)
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
;(rumPublicApi as any).updateViewName('foo')

expect(updateViewNameSpy).toHaveBeenCalledWith('foo')
})
})
})
14 changes: 14 additions & 0 deletions packages/rum-core/src/boot/rumPublicApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ export interface Strategy {
stopSession: StartRumResult['stopSession']
addTiming: StartRumResult['addTiming']
startView: StartRumResult['startView']
updateViewName: StartRumResult['updateViewName']
addAction: StartRumResult['addAction']
addError: StartRumResult['addError']
addFeatureFlagEvaluation: StartRumResult['addFeatureFlagEvaluation']
Expand Down Expand Up @@ -353,6 +354,19 @@ export function makeRumPublicApi(
)
}

if (isExperimentalFeatureEnabled(ExperimentalFeature.UPDATE_VIEW_NAME)) {
/**
* Update View Name.
*
* Enable to manually change the name of the current view.
* @param name name of the view
* See [Override default RUM view names](https://docs.datadoghq.com/real_user_monitoring/browser/advanced_configuration/#override-default-rum-view-names) for further information.
*/
;(rumPublicApi as any).updateViewName = monitor((name: string) => {
strategy.updateViewName(name)
})
}

if (configuration.storeContextsAcrossPages) {
storeContextManager(configuration, globalContextManager, RUM_STORAGE_KEY, CustomerDataType.GlobalContext)
storeContextManager(configuration, userContextManager, RUM_STORAGE_KEY, CustomerDataType.User)
Expand Down
2 changes: 2 additions & 0 deletions packages/rum-core/src/boot/startRum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ export function startRum(
const {
addTiming,
startView,
updateViewName,
stop: stopViewCollection,
} = startViewCollection(
lifeCycle,
Expand Down Expand Up @@ -184,6 +185,7 @@ export function startRum(
addTiming,
addFeatureFlagEvaluation: featureFlagContexts.addFeatureFlagEvaluation,
startView,
updateViewName,
lifeCycle,
viewContexts,
session,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function setupViewTest(
const { handler: viewEndHandler, getViewEvent: getViewEnd, getHandledCount: getViewEndCount } = spyOnViews('view end')
lifeCycle.subscribe(LifeCycleEventType.VIEW_ENDED, viewEndHandler)

const { stop, startView, addTiming } = trackViews(
const { stop, startView, updateViewName, addTiming } = trackViews(
location,
lifeCycle,
domMutationObservable,
Expand All @@ -38,6 +38,7 @@ export function setupViewTest(
return {
stop,
startView,
updateViewName,
addTiming,
getViewUpdate,
getViewUpdateCount,
Expand Down
52 changes: 51 additions & 1 deletion packages/rum-core/src/domain/view/trackViews.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import type { Context, Duration, RelativeTime } from '@datadog/browser-core'
import { PageExitReason, timeStampNow, display, relativeToClocks, relativeNow } from '@datadog/browser-core'
import {
PageExitReason,
timeStampNow,
display,
relativeToClocks,
relativeNow,
ExperimentalFeature,
} from '@datadog/browser-core'

import { mockExperimentalFeatures } from '@datadog/browser-core/test'
import type { TestSetupBuilder } from '../../../test'
import { createPerformanceEntry, setup } from '../../../test'
import { RumEventType, ViewLoadingType } from '../../rawRumEvent.types'
Expand Down Expand Up @@ -910,4 +919,45 @@ describe('view event count', () => {
view: viewTest.getLatestViewContext(),
} as RumEvent & Context
}

describe('update view name', () => {
let setupBuilder: TestSetupBuilder
let viewTest: ViewTest

beforeEach(() => {
setupBuilder = setup().beforeBuild((buildContext) => {
viewTest = setupViewTest(buildContext)
return viewTest
})
})

it('should update an undefined view name if the experimental feature is enabled', () => {
mockExperimentalFeatures([ExperimentalFeature.UPDATE_VIEW_NAME])
setupBuilder.build()
const { getViewUpdate, startView, updateViewName } = viewTest

startView()
updateViewName('foo')
expect(getViewUpdate(3).name).toEqual('foo')
})

it('should update a defined view name if the experimental feature is enabled', () => {
mockExperimentalFeatures([ExperimentalFeature.UPDATE_VIEW_NAME])
setupBuilder.build()
const { getViewUpdate, startView, updateViewName } = viewTest

startView({ name: 'initial view name' })
updateViewName('foo')
expect(getViewUpdate(3).name).toEqual('foo')
})

it('should not update a defined view name if the experimental feature is not enabled', () => {
setupBuilder.build()
const { getViewUpdate, startView, updateViewName } = viewTest

startView({ name: 'initial view name' })
updateViewName('foo')
expect(getViewUpdate(2).name).toEqual('initial view name')
})
})
})
17 changes: 16 additions & 1 deletion packages/rum-core/src/domain/view/trackViews.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
clearInterval,
setTimeout,
Observable,
isExperimentalFeatureEnabled,
ExperimentalFeature,
} from '@datadog/browser-core'

import type { ViewCustomTimings } from '../../rawRumEvent.types'
Expand Down Expand Up @@ -154,6 +156,10 @@ export function trackViews(
currentView.end({ endClocks: startClocks })
currentView = startNewView(ViewLoadingType.ROUTE_CHANGE, startClocks, options)
},
updateViewName: (name: string) => {
currentView.updateViewName(name)
},

stop: () => {
locationChangeSubscription?.unsubscribe()
currentView.end()
Expand Down Expand Up @@ -261,7 +267,9 @@ function newView(
}

return {
name,
get name() {
return name
},
service,
version,
stopObservable,
Expand Down Expand Up @@ -297,6 +305,13 @@ function newView(
customTimings[sanitizeTiming(name)] = relativeTime
scheduleViewUpdate()
},
updateViewName(updatedName: string) {
if (!isExperimentalFeatureEnabled(ExperimentalFeature.UPDATE_VIEW_NAME)) {
return
}
name = updatedName
triggerViewUpdate()
},
}
}

Expand Down
9 changes: 6 additions & 3 deletions test/e2e/scenario/rum/init.scenario.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,19 +66,20 @@ describe('API calls and events around init', () => {
})

createTest('should be associated to corresponding views when views are manually tracked')
.withRum({ trackViewsManually: true })
.withRum({ trackViewsManually: true, enableExperimentalFeatures: ['update_view_name'] })
.withRumSlim()
.withRumInit((configuration) => {
window.DD_RUM!.addError('before init')
window.DD_RUM!.addAction('before init')
window.DD_RUM!.addTiming('before init')
// global.updateViewName('before init') TODO uncomment when the api is not behind a ff anymore

setTimeout(() => window.DD_RUM!.init(configuration), 10)

setTimeout(() => {
window.DD_RUM!.addError('before manual view')
window.DD_RUM!.addAction('before manual view')
window.DD_RUM!.addTiming('before manual view')
// global.updateViewName('before manual view') TODO uncomment when the api is not behind a ff anymore
}, 20)

setTimeout(() => window.DD_RUM!.startView('manual view'), 30)
Expand All @@ -87,13 +88,15 @@ describe('API calls and events around init', () => {
window.DD_RUM!.addError('after manual view')
window.DD_RUM!.addAction('after manual view')
window.DD_RUM!.addTiming('after manual view')
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
;(window.DD_RUM as any).updateViewName('after manual view')
}, 40)
})
.run(async ({ intakeRegistry }) => {
await flushEvents()

const initialView = intakeRegistry.rumViewEvents[0]
expect(initialView.view.name).toBe('manual view')
expect(initialView.view.name).toBe('after manual view')
expect(initialView.view.custom_timings).toEqual({
before_init: jasmine.any(Number),
before_manual_view: jasmine.any(Number),
Expand Down

0 comments on commit b3ef206

Please sign in to comment.