Skip to content

Commit

Permalink
feat: allow triggering sessions when events occur (#1523)
Browse files Browse the repository at this point in the history
Co-authored-by: Richard Borcsik <[email protected]>
Co-authored-by: Paul D'Ambra <[email protected]>
  • Loading branch information
3 people authored Nov 15, 2024
1 parent 441bd76 commit 8db1006
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 67 deletions.
2 changes: 1 addition & 1 deletion cypress/e2e/session-recording.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -654,7 +654,7 @@ describe('Session recording', () => {
cy.posthog().invoke('capture', 'test_registered_property')
cy.phCaptures({ full: true }).then((captures) => {
expect((captures || []).map((c) => c.event)).to.deep.equal(['$pageview', 'test_registered_property'])
expect(captures[1]['properties']['$session_recording_start_reason']).to.equal('sampling_override')
expect(captures[1]['properties']['$session_recording_start_reason']).to.equal('sampling_overridden')
})

cy.resetPhCaptures()
Expand Down
72 changes: 72 additions & 0 deletions src/__tests__/extensions/replay/sessionrecording.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import {
import Mock = jest.Mock
import { ConsentManager } from '../../../consent'
import { waitFor } from '@testing-library/preact'
import { SimpleEventEmitter } from '../../../utils/simple-event-emitter'

// Type and source defined here designate a non-user-generated recording event

Expand Down Expand Up @@ -185,6 +186,7 @@ describe('SessionRecording', () => {
let onFeatureFlagsCallback: ((flags: string[], variants: Record<string, string | boolean>) => void) | null
let removeCaptureHookMock: Mock
let addCaptureHookMock: Mock
let simpleEventEmitter: SimpleEventEmitter

const addRRwebToWindow = () => {
assignableWindow.__PosthogExtensions__.rrweb = {
Expand Down Expand Up @@ -239,6 +241,8 @@ describe('SessionRecording', () => {
removeCaptureHookMock = jest.fn()
addCaptureHookMock = jest.fn().mockImplementation(() => removeCaptureHookMock)

simpleEventEmitter = new SimpleEventEmitter()
// TODO we really need to make this a real posthog instance :cry:
posthog = {
get_property: (property_key: string): Property | undefined => {
return postHogPersistence?.['props'][property_key]
Expand All @@ -261,6 +265,10 @@ describe('SessionRecording', () => {
},
} as unknown as ConsentManager,
register_for_session() {},
_internalEventEmitter: simpleEventEmitter,
on: (event, cb) => {
return simpleEventEmitter.on(event, cb)
},
} as Partial<PostHog> as PostHog

loadScriptMock.mockImplementation((_ph, _path, callback) => {
Expand Down Expand Up @@ -1883,6 +1891,7 @@ describe('SessionRecording', () => {
loadScriptMock.mockImplementation((_ph, _path, callback) => {
callback()
})
sessionRecording = new SessionRecording(posthog)

sessionRecording.afterDecideResponse(makeDecideResponse({ sessionRecording: { endpoint: '/s/' } }))
sessionRecording.startIfEnabledOrStop()
Expand Down Expand Up @@ -2250,4 +2259,67 @@ describe('SessionRecording', () => {
])
})
})

describe('Event triggering', () => {
beforeEach(() => {
sessionRecording.startIfEnabledOrStop()
})

it('flushes buffer and starts when sees event', async () => {
sessionRecording.afterDecideResponse(
makeDecideResponse({
sessionRecording: {
endpoint: '/s/',
eventTriggers: ['$exception'],
},
})
)

expect(sessionRecording['status']).toBe('buffering')

// Emit some events before hitting blocked URL
_emit(createIncrementalSnapshot({ data: { source: 1 } }))
_emit(createIncrementalSnapshot({ data: { source: 2 } }))

expect(sessionRecording['buffer'].data).toHaveLength(2)

simpleEventEmitter.emit('eventCaptured', { event: 'not-$exception' })

expect(sessionRecording['status']).toBe('buffering')

simpleEventEmitter.emit('eventCaptured', { event: '$exception' })

expect(sessionRecording['status']).toBe('active')
expect(sessionRecording['buffer'].data).toHaveLength(0)
})

it('starts if sees an event but still waiting for a URL', async () => {
sessionRecording.afterDecideResponse(
makeDecideResponse({
sessionRecording: {
endpoint: '/s/',
eventTriggers: ['$exception'],
urlTriggers: [{ url: 'start-on-me', matching: 'regex' }],
},
})
)

expect(sessionRecording['status']).toBe('buffering')

// Emit some events before hitting blocked URL
_emit(createIncrementalSnapshot({ data: { source: 1 } }))
_emit(createIncrementalSnapshot({ data: { source: 2 } }))

expect(sessionRecording['buffer'].data).toHaveLength(2)

simpleEventEmitter.emit('eventCaptured', { event: 'not-$exception' })

expect(sessionRecording['status']).toBe('buffering')

simpleEventEmitter.emit('eventCaptured', { event: '$exception' })

// even though still waiting for URL to trigger
expect(sessionRecording['status']).toBe('active')
})
})
})
2 changes: 2 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export const SESSION_ID = '$sesid'
export const SESSION_RECORDING_IS_SAMPLED = '$session_is_sampled'
export const SESSION_RECORDING_URL_TRIGGER_ACTIVATED_SESSION = '$session_recording_url_trigger_activated_session'
export const SESSION_RECORDING_URL_TRIGGER_STATUS = '$session_recording_url_trigger_status'
export const SESSION_RECORDING_EVENT_TRIGGER_ACTIVATED_SESSION = '$session_recording_event_trigger_activated_session'
export const SESSION_RECORDING_EVENT_TRIGGER_STATUS = '$session_recording_event_trigger_status'
export const ENABLED_FEATURE_FLAGS = '$enabled_feature_flags'
export const PERSISTENCE_EARLY_ACCESS_FEATURES = '$early_access_features'
export const STORED_PERSON_PROPERTIES_KEY = '$stored_person_properties'
Expand Down
Loading

0 comments on commit 8db1006

Please sign in to comment.