Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

馃悰 [RUM-3039] Fix missing pending mutations at view end #2598

Merged
merged 14 commits into from
Feb 19, 2024
11 changes: 8 additions & 3 deletions packages/core/src/domain/session/sessionManager.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Observable } from '../../tools/observable'
import { Observable } from '../../tools/observable'
import type { Context } from '../../tools/serialisation/context'
import { ValueHistory } from '../../tools/valueHistory'
import type { RelativeTime } from '../../tools/utils/timeUtils'
Expand Down Expand Up @@ -32,6 +32,9 @@ export function startSessionManager<TrackingType extends string>(
computeSessionState: (rawTrackingType?: string) => { trackingType: TrackingType; isTracked: boolean },
trackingConsentState: TrackingConsentState
): SessionManager<TrackingType> {
const renewObservable = new Observable<void>()
const expireObservable = new Observable<void>()

// TODO - Improve configuration type and remove assertion
const sessionStore = startSessionStore(configuration.sessionStoreStrategyType!, productKey, computeSessionState)
stopCallbacks.push(() => sessionStore.stop())
Expand All @@ -41,8 +44,10 @@ export function startSessionManager<TrackingType extends string>(

sessionStore.renewObservable.subscribe(() => {
sessionContextHistory.add(buildSessionContext(), relativeNow())
renewObservable.notify()
bcaudan marked this conversation as resolved.
Show resolved Hide resolved
})
sessionStore.expireObservable.subscribe(() => {
expireObservable.notify()
sessionContextHistory.closeActive(relativeNow())
})

Expand Down Expand Up @@ -75,8 +80,8 @@ export function startSessionManager<TrackingType extends string>(

return {
findActiveSession: (startTime) => sessionContextHistory.find(startTime),
renewObservable: sessionStore.renewObservable,
expireObservable: sessionStore.expireObservable,
renewObservable,
expireObservable,
expire: sessionStore.expire,
}
}
Expand Down
12 changes: 10 additions & 2 deletions packages/core/src/tools/instrumentMethod.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { setTimeout } from './timer'
import type { TimeoutId } from './timer'
import { setTimeout, clearTimeout } from './timer'
import { callMonitored } from './monitor'
import { noop } from './utils/functionUtils'

Expand Down Expand Up @@ -125,11 +126,14 @@ export function instrumentSetter<TARGET extends { [key: string]: any }, PROPERTY
return { stop: noop }
}

const timeoutIds = new Set<TimeoutId>()
let instrumentation = (target: TARGET, value: TARGET[PROPERTY]) => {
// put hooked setter into event loop to avoid of set latency
setTimeout(() => {
const timeoutId = setTimeout(() => {
timeoutIds.delete(timeoutId)
after(target, value)
}, 0)
timeoutIds.add(timeoutId)
}
amortemousque marked this conversation as resolved.
Show resolved Hide resolved

const instrumentationWrapper = function (this: TARGET, value: TARGET[PROPERTY]) {
Expand All @@ -148,6 +152,10 @@ export function instrumentSetter<TARGET extends { [key: string]: any }, PROPERTY
} else {
instrumentation = noop
}
if (timeoutIds) {
timeoutIds.forEach(clearTimeout)
timeoutIds.clear()
}
},
}
}
20 changes: 10 additions & 10 deletions packages/rum-core/src/domain/contexts/featureFlagContext.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ describe('featureFlagContexts', () => {

const { lifeCycle } = setupBuilder.build()

lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, {
lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, {
startClocks: relativeToClocks(0 as RelativeTime),
} as ViewCreatedEvent)

Expand All @@ -67,7 +67,7 @@ describe('featureFlagContexts', () => {

const { lifeCycle } = setupBuilder.build()

lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, {
lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, {
startClocks: relativeToClocks(0 as RelativeTime),
} as ViewCreatedEvent)

Expand All @@ -83,7 +83,7 @@ describe('featureFlagContexts', () => {
it('should not add feature flag evaluation when the ff feature_flags is disabled', () => {
const { lifeCycle } = setupBuilder.build()

lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, {
lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, {
startClocks: relativeToClocks(0 as RelativeTime),
} as ViewCreatedEvent)

Expand All @@ -101,7 +101,7 @@ describe('featureFlagContexts', () => {

const updateCustomerDataSpy = spyOn(customerDataTracker, 'updateCustomerData')

lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, {
lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, {
startClocks: relativeToClocks(0 as RelativeTime),
} as ViewCreatedEvent)

Expand Down Expand Up @@ -129,14 +129,14 @@ describe('featureFlagContexts', () => {

const { lifeCycle } = setupBuilder.build()

lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, {
lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, {
startClocks: relativeToClocks(0 as RelativeTime),
} as ViewCreatedEvent)
featureFlagContexts.addFeatureFlagEvaluation('feature', 'foo')
lifeCycle.notify(LifeCycleEventType.VIEW_ENDED, {
lifeCycle.notify(LifeCycleEventType.AFTER_VIEW_ENDED, {
endClocks: relativeToClocks(10 as RelativeTime),
} as ViewEndedEvent)
lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, {
lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, {
startClocks: relativeToClocks(10 as RelativeTime),
} as ViewCreatedEvent)

Expand All @@ -149,16 +149,16 @@ describe('featureFlagContexts', () => {

const { lifeCycle, clock } = setupBuilder.withFakeClock().build()

lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, {
lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, {
startClocks: relativeToClocks(0 as RelativeTime),
} as ViewCreatedEvent)

clock.tick(10)
featureFlagContexts.addFeatureFlagEvaluation('feature', 'one')
lifeCycle.notify(LifeCycleEventType.VIEW_ENDED, {
lifeCycle.notify(LifeCycleEventType.AFTER_VIEW_ENDED, {
endClocks: relativeToClocks(10 as RelativeTime),
} as ViewEndedEvent)
lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, {
lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, {
startClocks: relativeToClocks(10 as RelativeTime),
} as ViewCreatedEvent)

Expand Down
10 changes: 5 additions & 5 deletions packages/rum-core/src/domain/contexts/featureFlagContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@ export function startFeatureFlagContexts(

const featureFlagContexts = new ValueHistory<FeatureFlagContext>(FEATURE_FLAG_CONTEXT_TIME_OUT_DELAY)

lifeCycle.subscribe(LifeCycleEventType.VIEW_ENDED, ({ endClocks }) => {
featureFlagContexts.closeActive(endClocks.relative)
})

lifeCycle.subscribe(LifeCycleEventType.VIEW_CREATED, ({ startClocks }) => {
lifeCycle.subscribe(LifeCycleEventType.BEFORE_VIEW_CREATED, ({ startClocks }) => {
featureFlagContexts.add({}, startClocks.relative)
customerDataTracker.resetCustomerData()
})

lifeCycle.subscribe(LifeCycleEventType.AFTER_VIEW_ENDED, ({ endClocks }) => {
featureFlagContexts.closeActive(endClocks.relative)
})

return {
findFeatureFlagEvaluations: (startTime?: RelativeTime) => featureFlagContexts.find(startTime),
addFeatureFlagEvaluation: (key: string, value: ContextValue) => {
Expand Down
24 changes: 12 additions & 12 deletions packages/rum-core/src/domain/contexts/urlContexts.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe('urlContexts', () => {
it('should return current url and document referrer for initial view', () => {
const { lifeCycle } = setupBuilder.build()

lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, {
lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, {
startClocks: relativeToClocks(0 as RelativeTime),
} as ViewCreatedEvent)

Expand All @@ -50,7 +50,7 @@ describe('urlContexts', () => {
it('should update url context on location change', () => {
const { lifeCycle, changeLocation } = setupBuilder.build()

lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, {
lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, {
startClocks: relativeToClocks(0 as RelativeTime),
} as ViewCreatedEvent)
changeLocation('/foo')
Expand All @@ -63,14 +63,14 @@ describe('urlContexts', () => {
it('should update url context on new view', () => {
const { lifeCycle, changeLocation } = setupBuilder.build()

lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, {
lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, {
startClocks: relativeToClocks(0 as RelativeTime),
} as ViewCreatedEvent)
changeLocation('/foo')
lifeCycle.notify(LifeCycleEventType.VIEW_ENDED, {
lifeCycle.notify(LifeCycleEventType.AFTER_VIEW_ENDED, {
endClocks: relativeToClocks(10 as RelativeTime),
} as ViewEndedEvent)
lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, {
lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, {
startClocks: relativeToClocks(10 as RelativeTime),
} as ViewCreatedEvent)

Expand All @@ -82,16 +82,16 @@ describe('urlContexts', () => {
it('should return the url context corresponding to the start time', () => {
const { lifeCycle, changeLocation, clock } = setupBuilder.build()

lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, {
lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, {
startClocks: relativeToClocks(0 as RelativeTime),
} as ViewCreatedEvent)

clock.tick(10)
changeLocation('/foo')
lifeCycle.notify(LifeCycleEventType.VIEW_ENDED, {
lifeCycle.notify(LifeCycleEventType.AFTER_VIEW_ENDED, {
endClocks: relativeToClocks(10 as RelativeTime),
} as ViewEndedEvent)
lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, {
lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, {
startClocks: relativeToClocks(10 as RelativeTime),
} as ViewCreatedEvent)

Expand All @@ -100,10 +100,10 @@ describe('urlContexts', () => {

clock.tick(10)
changeLocation('/qux')
lifeCycle.notify(LifeCycleEventType.VIEW_ENDED, {
lifeCycle.notify(LifeCycleEventType.AFTER_VIEW_ENDED, {
endClocks: relativeToClocks(30 as RelativeTime),
} as ViewEndedEvent)
lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, {
lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, {
startClocks: relativeToClocks(30 as RelativeTime),
} as ViewCreatedEvent)

Expand Down Expand Up @@ -132,10 +132,10 @@ describe('urlContexts', () => {
it('should return undefined when no current view', () => {
const { lifeCycle } = setupBuilder.build()

lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, {
lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, {
startClocks: relativeToClocks(0 as RelativeTime),
} as ViewCreatedEvent)
lifeCycle.notify(LifeCycleEventType.VIEW_ENDED, {
lifeCycle.notify(LifeCycleEventType.AFTER_VIEW_ENDED, {
endClocks: relativeToClocks(10 as RelativeTime),
} as ViewEndedEvent)

Expand Down
10 changes: 5 additions & 5 deletions packages/rum-core/src/domain/contexts/urlContexts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,7 @@ export function startUrlContexts(

let previousViewUrl: string | undefined

lifeCycle.subscribe(LifeCycleEventType.VIEW_ENDED, ({ endClocks }) => {
urlContextHistory.closeActive(endClocks.relative)
})

lifeCycle.subscribe(LifeCycleEventType.VIEW_CREATED, ({ startClocks }) => {
lifeCycle.subscribe(LifeCycleEventType.BEFORE_VIEW_CREATED, ({ startClocks }) => {
const viewUrl = location.href
urlContextHistory.add(
buildUrlContext({
Expand All @@ -47,6 +43,10 @@ export function startUrlContexts(
previousViewUrl = viewUrl
})

lifeCycle.subscribe(LifeCycleEventType.AFTER_VIEW_ENDED, ({ endClocks }) => {
urlContextHistory.closeActive(endClocks.relative)
})

const locationChangeSubscription = locationChangeObservable.subscribe(({ newLocation }) => {
const current = urlContextHistory.find()
if (current) {
Expand Down
40 changes: 20 additions & 20 deletions packages/rum-core/src/domain/contexts/viewContexts.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ describe('viewContexts', () => {
it('should return the current view context when there is no start time', () => {
const { lifeCycle } = setupBuilder.build()

lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, buildViewCreatedEvent())
lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, buildViewCreatedEvent())

expect(viewContexts.findView()).toBeDefined()
expect(viewContexts.findView()!.id).toEqual(FAKE_ID)
Expand All @@ -51,19 +51,19 @@ describe('viewContexts', () => {
const { lifeCycle } = setupBuilder.build()

lifeCycle.notify(
LifeCycleEventType.VIEW_CREATED,
LifeCycleEventType.BEFORE_VIEW_CREATED,
buildViewCreatedEvent({ startClocks: relativeToClocks(10 as RelativeTime), id: 'view 1' })
)
lifeCycle.notify(LifeCycleEventType.VIEW_ENDED, { endClocks: relativeToClocks(20 as RelativeTime) })
lifeCycle.notify(LifeCycleEventType.AFTER_VIEW_ENDED, { endClocks: relativeToClocks(20 as RelativeTime) })

lifeCycle.notify(
LifeCycleEventType.VIEW_CREATED,
LifeCycleEventType.BEFORE_VIEW_CREATED,
buildViewCreatedEvent({ startClocks: relativeToClocks(20 as RelativeTime), id: 'view 2' })
)
lifeCycle.notify(LifeCycleEventType.VIEW_ENDED, { endClocks: relativeToClocks(30 as RelativeTime) })
lifeCycle.notify(LifeCycleEventType.AFTER_VIEW_ENDED, { endClocks: relativeToClocks(30 as RelativeTime) })

lifeCycle.notify(
LifeCycleEventType.VIEW_CREATED,
LifeCycleEventType.BEFORE_VIEW_CREATED,
buildViewCreatedEvent({ startClocks: relativeToClocks(30 as RelativeTime), id: 'view 3' })
)

Expand All @@ -76,33 +76,33 @@ describe('viewContexts', () => {
const { lifeCycle } = setupBuilder.build()

lifeCycle.notify(
LifeCycleEventType.VIEW_CREATED,
LifeCycleEventType.BEFORE_VIEW_CREATED,
buildViewCreatedEvent({ startClocks: relativeToClocks(10 as RelativeTime), id: 'view 1' })
)
lifeCycle.notify(LifeCycleEventType.VIEW_ENDED, { endClocks: relativeToClocks(20 as RelativeTime) })
lifeCycle.notify(LifeCycleEventType.AFTER_VIEW_ENDED, { endClocks: relativeToClocks(20 as RelativeTime) })
lifeCycle.notify(
LifeCycleEventType.VIEW_CREATED,
LifeCycleEventType.BEFORE_VIEW_CREATED,
buildViewCreatedEvent({ startClocks: relativeToClocks(20 as RelativeTime), id: 'view 2' })
)
lifeCycle.notify(LifeCycleEventType.VIEW_ENDED, { endClocks: relativeToClocks(20 as RelativeTime) })
lifeCycle.notify(LifeCycleEventType.AFTER_VIEW_ENDED, { endClocks: relativeToClocks(20 as RelativeTime) })

expect(viewContexts.findView(5 as RelativeTime)).not.toBeDefined()
})

it('should set the current view context on VIEW_CREATED', () => {
it('should set the current view context on BEFORE_VIEW_CREATED', () => {
const { lifeCycle } = setupBuilder.build()

lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, buildViewCreatedEvent())
lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, buildViewCreatedEvent())
const newViewId = 'fake 2'
lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, buildViewCreatedEvent({ id: newViewId }))
lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, buildViewCreatedEvent({ id: newViewId }))

expect(viewContexts.findView()!.id).toEqual(newViewId)
})

it('should return the view name with the view', () => {
const { lifeCycle } = setupBuilder.build()

lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, buildViewCreatedEvent({ name: 'Fake name' }))
lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, buildViewCreatedEvent({ name: 'Fake name' }))
expect(viewContexts.findView()!.name).toBe('Fake name')
})
})
Expand All @@ -112,15 +112,15 @@ describe('viewContexts', () => {
const { lifeCycle } = setupBuilder.build()

lifeCycle.notify(
LifeCycleEventType.VIEW_CREATED,
LifeCycleEventType.BEFORE_VIEW_CREATED,
buildViewCreatedEvent({
id: 'view 1',
startClocks: relativeToClocks(10 as RelativeTime),
})
)
lifeCycle.notify(LifeCycleEventType.VIEW_ENDED, { endClocks: relativeToClocks(20 as RelativeTime) })
lifeCycle.notify(LifeCycleEventType.AFTER_VIEW_ENDED, { endClocks: relativeToClocks(20 as RelativeTime) })
lifeCycle.notify(
LifeCycleEventType.VIEW_CREATED,
LifeCycleEventType.BEFORE_VIEW_CREATED,
buildViewCreatedEvent({
id: 'view 2',
startClocks: relativeToClocks(20 as RelativeTime),
Expand All @@ -144,17 +144,17 @@ describe('viewContexts', () => {
const targetTime = (originalTime + 5) as RelativeTime

lifeCycle.notify(
LifeCycleEventType.VIEW_CREATED,
LifeCycleEventType.BEFORE_VIEW_CREATED,
buildViewCreatedEvent({
id: 'view 1',
startClocks: originalClocks,
})
)
lifeCycle.notify(LifeCycleEventType.VIEW_ENDED, {
lifeCycle.notify(LifeCycleEventType.AFTER_VIEW_ENDED, {
endClocks: relativeToClocks((originalTime + 10) as RelativeTime),
})
lifeCycle.notify(
LifeCycleEventType.VIEW_CREATED,
LifeCycleEventType.BEFORE_VIEW_CREATED,
buildViewCreatedEvent({ startClocks: relativeToClocks((originalTime + 10) as RelativeTime), id: 'view 2' })
)

Expand Down
Loading