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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change view logic to emit LifeCycle events #366

Merged
merged 14 commits into from
Apr 21, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
3 changes: 3 additions & 0 deletions packages/rum/src/lifeCycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export enum LifeCycleEventType {
USER_ACTION_COLLECTED,
REQUEST_STARTED,
REQUEST_COMPLETED,
SESSION_WILL_RENEW,
SESSION_RENEWED,
RESOURCE_ADDED_TO_BATCH,
DOM_MUTATED,
Expand All @@ -26,6 +27,7 @@ export class LifeCycle {
notify(eventType: LifeCycleEventType.USER_ACTION_COLLECTED, data: UserAction): void
notify(
eventType:
| LifeCycleEventType.SESSION_WILL_RENEW
| LifeCycleEventType.SESSION_RENEWED
| LifeCycleEventType.RESOURCE_ADDED_TO_BATCH
| LifeCycleEventType.DOM_MUTATED
Expand All @@ -50,6 +52,7 @@ export class LifeCycle {
subscribe(eventType: LifeCycleEventType.USER_ACTION_COLLECTED, callback: (data: UserAction) => void): Subscription
subscribe(
eventType:
| LifeCycleEventType.SESSION_WILL_RENEW
| LifeCycleEventType.SESSION_RENEWED
| LifeCycleEventType.RESOURCE_ADDED_TO_BATCH
| LifeCycleEventType.DOM_MUTATED,
Expand Down
8 changes: 4 additions & 4 deletions packages/rum/src/rum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ export function startRum(
lodashMerge(
{
application_id: applicationId,
session_id: viewContext.sessionId,
session_id: session.getId(),
view: {
id: viewContext.id,
},
Expand All @@ -175,7 +175,7 @@ export function startRum(
session: {
type: sessionTpe,
},
sessionId: viewContext.sessionId,
sessionId: session.getId(),
view: {
id: viewContext.id,
referrer: document.referrer,
Expand All @@ -185,7 +185,7 @@ export function startRum(
() => globalContext
)

trackView(window.location, lifeCycle, session, batch.upsertRumEvent, batch.beforeFlushOnUnload)
trackView(window.location, lifeCycle, batch.upsertRumEvent, batch.beforeFlushOnUnload)
trackErrors(lifeCycle, batch.addRumEvent)
trackRequests(configuration, lifeCycle, session, batch.addRumEvent)
trackPerformanceTiming(configuration, lifeCycle, batch.addRumEvent)
Expand All @@ -203,7 +203,7 @@ export function startRum(
(): InternalContext => {
return {
application_id: applicationId,
session_id: viewContext.sessionId,
session_id: session.getId(),
user_action: getUserActionReference(),
view: {
id: viewContext.id,
Expand Down
46 changes: 40 additions & 6 deletions packages/rum/src/rumSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,53 @@ export enum RumSessionType {
TRACKED_WITHOUT_RESOURCES = '2',
}

class StoredSession {
BenoitZugmeyer marked this conversation as resolved.
Show resolved Hide resolved
private id: string | undefined
private type: RumSessionType | undefined

constructor(private isAlive: () => boolean) {}

store(id: string | undefined, type: RumSessionType | undefined) {
this.id = id
this.type = type
}

getId() {
this.makeSureSessionIsAlive()
return this.id
}

isTracked() {
this.makeSureSessionIsAlive()
return isTracked(this.type)
}

isTrackedWithResource() {
this.makeSureSessionIsAlive()
return this.type === RumSessionType.TRACKED_WITH_RESOURCES
}

private makeSureSessionIsAlive() {
if (!this.isAlive()) {
this.id = undefined
this.type = undefined
}
}
}

export function startRumSession(configuration: Configuration, lifeCycle: LifeCycle): RumSession {
const session = startSessionManagement(RUM_SESSION_KEY, (rawType) => computeSessionState(configuration, rawType))
const storedSession = new StoredSession(() => session.getId() !== undefined)

storedSession.store(session.getId(), session.getType())

session.renewObservable.subscribe(() => {
lifeCycle.notify(LifeCycleEventType.SESSION_WILL_RENEW)
storedSession.store(session.getId(), session.getType())
lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED)
})
BenoitZugmeyer marked this conversation as resolved.
Show resolved Hide resolved

return {
getId: session.getId,
isTracked: () => session.getId() !== undefined && isTracked(session.getType()),
isTrackedWithResource: () =>
session.getId() !== undefined && session.getType() === RumSessionType.TRACKED_WITH_RESOURCES,
}
return storedSession
}

function computeSessionState(configuration: Configuration, rawSessionType?: string) {
Expand Down
29 changes: 12 additions & 17 deletions packages/rum/src/viewTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { DOM_EVENT, generateUUID, getTimestamp, monitor, msToNs, throttle } from

import { LifeCycle, LifeCycleEventType } from './lifeCycle'
import { PerformancePaintTiming, RumEvent, RumEventCategory } from './rum'
import { RumSession } from './rumSession'
import { trackEventCounts } from './trackEventCounts'

export interface ViewMeasures {
Expand All @@ -20,7 +19,6 @@ export interface ViewMeasures {
interface ViewContext {
id: string
location: Location
sessionId: string | undefined
}

export let viewContext: ViewContext
Expand All @@ -33,7 +31,6 @@ let viewMeasures: ViewMeasures
export function trackView(
location: Location,
lifeCycle: LifeCycle,
session: RumSession,
upsertRumEvent: (event: RumEvent, key: string) => void,
beforeFlushOnUnload: (handler: () => void) => void
) {
Expand All @@ -45,25 +42,23 @@ export function trackView(
viewMeasures = { ...viewMeasures, ...eventCounts }
scheduleViewUpdate()
})
newView(location, session, resetEventCounts, upsertRumEvent)
trackHistory(location, session, resetEventCounts, upsertRumEvent)
newView(location, resetEventCounts, upsertRumEvent)
trackHistory(location, resetEventCounts, upsertRumEvent)
trackTimings(lifeCycle, scheduleViewUpdate)
trackRenewSession(location, lifeCycle, session, resetEventCounts, upsertRumEvent)
trackRenewSession(location, lifeCycle, resetEventCounts, upsertRumEvent)

beforeFlushOnUnload(() => updateView(upsertRumEvent))
}

function newView(
location: Location,
session: RumSession,
resetEventCounts: () => void,
upsertRumEvent: (event: RumEvent, key: string) => void
) {
startOrigin = !viewContext ? 0 : performance.now()
viewContext = {
id: generateUUID(),
location: { ...location },
sessionId: session.getId(),
}
documentVersion = 1
viewMeasures = {
Expand Down Expand Up @@ -102,37 +97,35 @@ function upsertViewEvent(upsertRumEvent: (event: RumEvent, key: string) => void)

function trackHistory(
location: Location,
session: RumSession,
resetEventCounts: () => void,
upsertRumEvent: (event: RumEvent, key: string) => void
) {
const originalPushState = history.pushState
history.pushState = monitor(function(this: History['pushState']) {
originalPushState.apply(this, arguments as any)
onUrlChange(location, session, resetEventCounts, upsertRumEvent)
onUrlChange(location, resetEventCounts, upsertRumEvent)
})
const originalReplaceState = history.replaceState
history.replaceState = monitor(function(this: History['replaceState']) {
originalReplaceState.apply(this, arguments as any)
onUrlChange(location, session, resetEventCounts, upsertRumEvent)
onUrlChange(location, resetEventCounts, upsertRumEvent)
})
window.addEventListener(
DOM_EVENT.POP_STATE,
monitor(() => {
onUrlChange(location, session, resetEventCounts, upsertRumEvent)
onUrlChange(location, resetEventCounts, upsertRumEvent)
})
)
}

function onUrlChange(
location: Location,
session: RumSession,
resetEventCounts: () => void,
upsertRumEvent: (event: RumEvent, key: string) => void
) {
if (areDifferentViews(viewContext.location, location)) {
updateView(upsertRumEvent)
newView(location, session, resetEventCounts, upsertRumEvent)
newView(location, resetEventCounts, upsertRumEvent)
}
}

Expand Down Expand Up @@ -166,12 +159,14 @@ function trackTimings(lifeCycle: LifeCycle, scheduleViewUpdate: () => void) {
function trackRenewSession(
location: Location,
lifeCycle: LifeCycle,
session: RumSession,
resetEventCounts: () => void,
upsertRumEvent: (event: RumEvent, key: string) => void
) {
lifeCycle.subscribe(LifeCycleEventType.SESSION_RENEWED, () => {
lifeCycle.subscribe(LifeCycleEventType.SESSION_WILL_RENEW, () => {
updateView(upsertRumEvent)
newView(location, session, resetEventCounts, upsertRumEvent)
})

lifeCycle.subscribe(LifeCycleEventType.SESSION_RENEWED, () => {
newView(location, resetEventCounts, upsertRumEvent)
})
}
1 change: 1 addition & 0 deletions packages/rum/test/rum.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,7 @@ describe('rum session', () => {
expect(initialRequests[0].session_id).toEqual('42')

server.requests = []
lifeCycle.notify(LifeCycleEventType.SESSION_WILL_RENEW)
sessionId = '43'
lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED)

Expand Down
51 changes: 47 additions & 4 deletions packages/rum/test/rumSession.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,19 +95,62 @@ describe('rum session', () => {
expect(getCookie(SESSION_COOKIE_NAME)).toContain(`${RUM_SESSION_KEY}=${RumSessionType.NOT_TRACKED}`)
})

it('should renew on activity after expiration', () => {
startRumSession(configuration as Configuration, lifeCycle)

function renewSession() {
setCookie(SESSION_COOKIE_NAME, '', DURATION)
expect(getCookie(SESSION_COOKIE_NAME)).toBeUndefined()
expect(renewSessionSpy).not.toHaveBeenCalled()
jasmine.clock().tick(COOKIE_ACCESS_DELAY)

setupDraws({ tracked: true, trackedWithResources: true })
document.dispatchEvent(new CustomEvent('click'))
}

it('should renew on activity after expiration', () => {
startRumSession(configuration as Configuration, lifeCycle)

expect(renewSessionSpy).not.toHaveBeenCalled()

renewSession()

expect(renewSessionSpy).toHaveBeenCalled()
expect(getCookie(SESSION_COOKIE_NAME)).toContain(`${RUM_SESSION_KEY}=${RumSessionType.TRACKED_WITH_RESOURCES}`)
expect(getCookie(SESSION_COOKIE_NAME)).toMatch(/id=[a-f0-9-]/)
})

it('should return undefined values when the session is expired', () => {
setCookie(SESSION_COOKIE_NAME, 'id=abcdef&rum=1', DURATION)

const session = startRumSession(configuration as Configuration, lifeCycle)

setCookie(SESSION_COOKIE_NAME, '', DURATION)
jasmine.clock().tick(COOKIE_ACCESS_DELAY)

expect(session.getId()).toBe(undefined)
expect(session.isTracked()).toBe(false)
expect(session.isTrackedWithResource()).toBe(false)
})

it('should return the ending session id when notifying the WILL_RENEW life-cycle', () => {
setCookie(SESSION_COOKIE_NAME, 'id=abcdef&rum=1', DURATION)

const session = startRumSession(configuration as Configuration, lifeCycle)
const idBeforeRenew = session.getId()
let idDuringWillRenewEvent
let idDuringRenewedEvent

lifeCycle.subscribe(LifeCycleEventType.SESSION_WILL_RENEW, () => {
idDuringWillRenewEvent = session.getId()
})

lifeCycle.subscribe(LifeCycleEventType.SESSION_RENEWED, () => {
idDuringRenewedEvent = session.getId()
})

renewSession()

expect(idBeforeRenew).toEqual(jasmine.any(String))
expect(idDuringWillRenewEvent).toEqual(jasmine.any(String))
expect(idDuringRenewedEvent).toEqual(jasmine.any(String))
expect(idBeforeRenew).toBe(idDuringWillRenewEvent)
expect(idBeforeRenew).not.toBe(idDuringRenewedEvent)
})
})
14 changes: 1 addition & 13 deletions packages/rum/test/viewTracker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { getHash, getPathName, getSearch } from '@datadog/browser-core'

import { LifeCycle, LifeCycleEventType } from '../src/lifeCycle'
import { PerformanceLongTaskTiming, PerformancePaintTiming, RumViewEvent } from '../src/rum'
import { RumSession } from '../src/rumSession'
import { UserAction, UserActionType } from '../src/userActionCollection'
import { trackView, viewContext } from '../src/viewTracker'

Expand All @@ -20,18 +19,7 @@ function setup({
fakeLocation.hash = getHash(url)
})
const fakeLocation: Partial<Location> = { pathname: '/foo' }
const fakeSession = {
getId() {
return '42'
},
}
trackView(
fakeLocation as Location,
lifeCycle || new LifeCycle(),
fakeSession as RumSession,
addRumEvent || (() => undefined),
() => undefined
)
trackView(fakeLocation as Location, lifeCycle || new LifeCycle(), addRumEvent || (() => undefined), () => undefined)
}

describe('rum track url change', () => {
Expand Down