Skip to content

Commit

Permalink
Merge branch 'main' into feat/save-posthog-config-too
Browse files Browse the repository at this point in the history
  • Loading branch information
pauldambra committed Feb 8, 2024
2 parents b946d6c + 33c65fb commit 52262ea
Show file tree
Hide file tree
Showing 28 changed files with 363 additions and 125 deletions.
1 change: 1 addition & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
## Checklist
- [ ] Tests for new code (see [advice on the tests we use](https://github.com/PostHog/posthog-js#tiers-of-testing))
- [ ] Accounted for the impact of any changes across different browsers
- [ ] Accounted for backwards compatibility of any changes (no breaking changes in posthog-js!)
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
## 1.105.5 - 2024-02-08

- feat: account for persistence for canvas recording (#1006)
- chore: improve template to account for backwards compatibility (#1007)

## 1.105.4 - 2024-02-07

- feat: Add dynamic routing of ingestion endpoints (#986)
- Update CHANGELOG.md (#1004)

## 1.105.3 - 2024-02-07

identical to 1.105.1 - bug in CI scripts
Expand Down
83 changes: 64 additions & 19 deletions cypress/e2e/session-recording.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,39 @@
import { _isNull } from '../../src/utils/type-utils'
import { start } from '../support/setup'

function ensureRecordingIsStopped() {
cy.get('[data-cy-input]')
.type('hello posthog!')
.wait(250)
.then(() => {
cy.phCaptures({ full: true }).then((captures) => {
// should be no captured data
expect(captures.map((c) => c.event)).to.deep.equal([])
})
})
}

function ensureActivitySendsSnapshots() {
cy.get('[data-cy-input]')
.type('hello posthog!')
.wait('@session-recording')
.then(() => {
cy.phCaptures({ full: true }).then((captures) => {
expect(captures.map((c) => c.event)).to.deep.equal(['$snapshot'])
expect(captures[0]['properties']['$snapshot_data']).to.have.length.above(14).and.below(39)
// a meta and then a full snapshot
expect(captures[0]['properties']['$snapshot_data'][0].type).to.equal(4) // meta
expect(captures[0]['properties']['$snapshot_data'][1].type).to.equal(2) // full_snapshot
expect(captures[0]['properties']['$snapshot_data'][2].type).to.equal(5) // custom event with options
expect(captures[0]['properties']['$snapshot_data'][3].type).to.equal(5) // custom event with posthog config
// Making a set from the rest should all be 3 - incremental snapshots
expect(new Set(captures[0]['properties']['$snapshot_data'].slice(4).map((s) => s.type))).to.deep.equal(
new Set([3])
)
})
})
}

describe('Session recording', () => {
describe('array.full.js', () => {
it('captures session events', () => {
Expand Down Expand Up @@ -58,27 +91,39 @@ describe('Session recording', () => {
})

it('captures session events', () => {
cy.phCaptures({ full: true }).then((captures) => {
// should be a pageview at the beginning
expect(captures.map((c) => c.event)).to.deep.equal(['$pageview'])
})
cy.resetPhCaptures()

let startingSessionId: string | null = null
cy.posthog().then((ph) => {
startingSessionId = ph.get_session_id()
})

cy.get('[data-cy-input]').type('hello world! ')
cy.wait(500)
cy.get('[data-cy-input]')
.type('hello posthog!')
.wait('@session-recording')
.then(() => {
cy.phCaptures({ full: true }).then((captures) => {
// should be a pageview and a $snapshot
expect(captures.map((c) => c.event)).to.deep.equal(['$pageview', '$snapshot'])
expect(captures[1]['properties']['$snapshot_data']).to.have.length.above(33).and.below(39)
// a meta and then a full snapshot
expect(captures[1]['properties']['$snapshot_data'][0].type).to.equal(4) // meta
expect(captures[1]['properties']['$snapshot_data'][1].type).to.equal(2) // full_snapshot
expect(captures[1]['properties']['$snapshot_data'][2].type).to.equal(5) // custom event with options
expect(captures[1]['properties']['$snapshot_data'][3].type).to.equal(5) // custom event with posthog config
// Making a set from the rest should all be 3 - incremental snapshots
expect(
new Set(captures[1]['properties']['$snapshot_data'].slice(4).map((s) => s.type))
).to.deep.equal(new Set([3]))
})
})
ensureActivitySendsSnapshots()
cy.posthog().then((ph) => {
ph.stopSessionRecording()
})
cy.resetPhCaptures()
ensureRecordingIsStopped()

// restarting recording
cy.posthog().then((ph) => {
ph.startSessionRecording()
})
ensureActivitySendsSnapshots()

// the session id is not rotated by stopping and starting the recording
cy.posthog().then((ph) => {
const secondSessionId = ph.get_session_id()
expect(startingSessionId).not.to.be.null
expect(secondSessionId).not.to.be.null
expect(secondSessionId).to.equal(startingSessionId)
})
})

it('captures snapshots when the mouse moves', () => {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "posthog-js",
"version": "1.105.3",
"version": "1.105.5",
"description": "Posthog-js allows you to automatically capture usage and send events to PostHog.",
"repository": "https://github.com/PostHog/posthog-js",
"author": "[email protected]",
Expand Down
2 changes: 1 addition & 1 deletion playground/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"eslint": "8.34.0",
"eslint-config-next": "13.1.6",
"next": "13.5.6",
"posthog-js": "^1.88.1",
"posthog-js": "^1.103.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"typescript": "4.9.5"
Expand Down
2 changes: 1 addition & 1 deletion playground/nextjs/pages/canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react'
import Head from 'next/head'
import { useEffect, useRef } from 'react'

export default function Home() {
export default function Canvas() {
const ref = useRef<HTMLCanvasElement>(null)

useEffect(() => {
Expand Down
13 changes: 9 additions & 4 deletions playground/nextjs/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/__tests__/decide.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { autocapture } from '../autocapture'
import { Decide } from '../decide'
import { _base64Encode } from '../utils'
import { PostHogPersistence } from '../posthog-persistence'
import { RequestRouter } from '../utils/request-router'

const expectDecodedSendRequest = (send_request, data) => {
const lastCall = send_request.mock.calls[send_request.mock.calls.length - 1]
Expand Down Expand Up @@ -49,6 +50,7 @@ describe('Decide', () => {
setReloadingPaused: jest.fn(),
_startReloadTimer: jest.fn(),
},
requestRouter: new RequestRouter({ config: given.config }),
_hasBootstrappedFeatureFlags: jest.fn(),
getGroups: () => ({ organization: '5' }),
}))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { PostHog } from '../../../posthog-core'
import { DecideResponse, PostHogConfig } from '../../../types'
import { ExceptionObserver } from '../../../extensions/exception-autocapture'
import { window } from '../../../utils/globals'
import { RequestRouter } from '../../../utils/request-router'

describe('Exception Observer', () => {
let exceptionObserver: ExceptionObserver
Expand All @@ -17,6 +18,7 @@ describe('Exception Observer', () => {
config: mockConfig,
get_distinct_id: jest.fn(() => 'mock-distinct-id'),
capture: mockCapture,
requestRouter: new RequestRouter({ config: mockConfig } as any),
}
exceptionObserver = new ExceptionObserver(mockPostHogInstance as PostHog)
})
Expand Down
44 changes: 31 additions & 13 deletions src/__tests__/extensions/replay/sessionrecording.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
SESSION_RECORDING_ENABLED_SERVER_SIDE,
SESSION_RECORDING_IS_SAMPLED,
SESSION_RECORDING_RECORDER_VERSION_SERVER_SIDE,
SESSION_RECORDING_CANVAS_RECORDING,
} from '../../../constants'
import { SessionIdManager } from '../../../sessionid'
import {
Expand All @@ -24,6 +25,7 @@ import {
SessionRecording,
} from '../../../extensions/replay/sessionrecording'
import { assignableWindow } from '../../../utils/globals'
import { RequestRouter } from '../../../utils/request-router'

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

Expand Down Expand Up @@ -117,6 +119,7 @@ describe('SessionRecording', () => {
onFeatureFlagsCallback = cb
},
sessionManager: sessionManager,
requestRouter: new RequestRouter({ config } as any),
_addCaptureHook: jest.fn(),
} as unknown as PostHog

Expand Down Expand Up @@ -292,6 +295,22 @@ describe('SessionRecording', () => {
expect(posthog.get_property(SESSION_RECORDING_ENABLED_SERVER_SIDE)).toBe(true)
})

it('stores true in persistence if canvas is enabled from the server', () => {
posthog.persistence?.register({ [SESSION_RECORDING_CANVAS_RECORDING]: undefined })

sessionRecording.afterDecideResponse(
makeDecideResponse({
sessionRecording: { endpoint: '/s/', recordCanvas: true, canvasFps: 6, canvasQuality: '0.2' },
})
)

expect(posthog.get_property(SESSION_RECORDING_CANVAS_RECORDING)).toEqual({
enabled: true,
fps: 6,
quality: '0.2',
})
})

it('stores false in persistence if recording is not enabled from the server', () => {
posthog.persistence?.register({ [SESSION_RECORDING_ENABLED_SERVER_SIDE]: undefined })

Expand Down Expand Up @@ -422,16 +441,15 @@ describe('SessionRecording', () => {

describe('canvas', () => {
it('passes the remote config to rrweb', () => {
sessionRecording.startRecordingIfEnabled()
posthog.persistence?.register({
[SESSION_RECORDING_CANVAS_RECORDING]: {
enabled: true,
fps: 6,
quality: 0.2,
},
})

sessionRecording.afterDecideResponse(
makeDecideResponse({
sessionRecording: { endpoint: '/s/', recordCanvas: true, canvasFps: 6, canvasQuality: '0.2' },
})
)
expect(sessionRecording['_recordCanvas']).toStrictEqual(true)
expect(sessionRecording['_canvasFps']).toStrictEqual(6)
expect(sessionRecording['_canvasQuality']).toStrictEqual(0.2)
sessionRecording.startRecordingIfEnabled()

sessionRecording['_onScriptLoaded']()
expect(assignableWindow.rrwebRecord).toHaveBeenCalledWith(
Expand Down Expand Up @@ -537,7 +555,7 @@ describe('SessionRecording', () => {
},
{
method: 'POST',
endpoint: '/s/',
_url: 'https://test.com/s/',
_noTruncate: true,
_batchKey: 'recordings',
_metrics: expect.anything(),
Expand Down Expand Up @@ -574,7 +592,7 @@ describe('SessionRecording', () => {
},
{
method: 'POST',
endpoint: '/s/',
_url: 'https://test.com/s/',
_noTruncate: true,
_batchKey: 'recordings',
_metrics: expect.anything(),
Expand Down Expand Up @@ -658,7 +676,7 @@ describe('SessionRecording', () => {
},
{
method: 'POST',
endpoint: '/s/',
_url: 'https://test.com/s/',
_noTruncate: true,
_batchKey: 'recordings',
_metrics: expect.anything(),
Expand Down Expand Up @@ -1283,7 +1301,7 @@ describe('SessionRecording', () => {
_batchKey: 'recordings',
_metrics: { rrweb_full_snapshot: false },
_noTruncate: true,
endpoint: '/s/',
_url: 'https://test.com/s/',
method: 'POST',
}
)
Expand Down
3 changes: 3 additions & 0 deletions src/__tests__/extensions/toolbar.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { _isString, _isUndefined } from '../../utils/type-utils'
import { PostHog } from '../../posthog-core'
import { PostHogConfig, ToolbarParams } from '../../types'
import { assignableWindow, window } from '../../utils/globals'
import { RequestRouter } from '../../utils/request-router'

jest.mock('../../utils', () => ({
...jest.requireActual('../../utils'),
Expand All @@ -25,6 +26,8 @@ describe('Toolbar', () => {
api_host: 'http://api.example.com',
token: 'test_token',
} as unknown as PostHogConfig,
requestRouter: new RequestRouter(instance),

set_config: jest.fn(),
} as unknown as PostHog
toolbar = new Toolbar(instance)
Expand Down
14 changes: 5 additions & 9 deletions src/__tests__/featureflags.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { PostHogFeatureFlags, parseFeatureFlagDecideResponse, filterActiveFeatureFlags } from '../posthog-featureflags'
import { PostHogPersistence } from '../posthog-persistence'
import { RequestRouter } from '../utils/request-router'

jest.useFakeTimers()
jest.spyOn(global, 'setTimeout')
Expand All @@ -12,13 +13,15 @@ describe('featureflags', () => {
const config = {
token: 'random fake token',
persistence: 'memory',
api_host: 'https://app.posthog.com',
}
given('instance', () => ({
config,
get_distinct_id: () => 'blah id',
getGroups: () => {},
_prepare_callback: (callback) => callback,
persistence: new PostHogPersistence(config),
requestRouter: new RequestRouter({ config }),
register: (props) => given.instance.persistence.register(props),
unregister: (key) => given.instance.persistence.unregister(key),
get_property: (key) => given.instance.persistence.props[key],
Expand Down Expand Up @@ -320,20 +323,13 @@ describe('featureflags', () => {
earlyAccessFeatures: [EARLY_ACCESS_FEATURE_FIRST],
}))

beforeEach(() => {
given.instance.config = {
...given.instance.config,
api_host: 'https://decide.com',
}
})

it('getEarlyAccessFeatures requests early access features if not present', () => {
given.featureFlags.getEarlyAccessFeatures((data) => {
expect(data).toEqual([EARLY_ACCESS_FEATURE_FIRST])
})

expect(given.instance._send_request).toHaveBeenCalledWith(
'https://decide.com/api/early_access_features/?token=random fake token',
'https://app.posthog.com/api/early_access_features/?token=random fake token',
{},
{ method: 'GET' },
expect.any(Function)
Expand All @@ -359,7 +355,7 @@ describe('featureflags', () => {
})

expect(given.instance._send_request).toHaveBeenCalledWith(
'https://decide.com/api/early_access_features/?token=random fake token',
'https://app.posthog.com/api/early_access_features/?token=random fake token',
{},
{ method: 'GET' },
expect.any(Function)
Expand Down
Loading

0 comments on commit 52262ea

Please sign in to comment.