Skip to content

Commit

Permalink
feat: Add person processing mode preview (#1109)
Browse files Browse the repository at this point in the history
* Add process_person init config option

* Add $process_person property

* Default true

* Add init test

* Use new common method _isIdentified

* Ensure that $process_person is not denylisted

* Test fixes

* Add tests for $process_person

* Send initial referrer and campaign params with personful events

* Tidy up tests

* Fix mocks

* Fix tests

* Fix tests

* Rename to __preview_process_person

* Ensure default behaviour is identical to current behaviour

* Help the minifier

* Simplify test setup

* Simplify test setup

* Combine initial props functions

* Simplify $set and $set_once handling

* Fix functional tests

* Fix overzealous test pruning
  • Loading branch information
robbie-c authored Apr 8, 2024
1 parent 7bcb8f7 commit 5f117a6
Show file tree
Hide file tree
Showing 11 changed files with 313 additions and 42 deletions.
8 changes: 6 additions & 2 deletions functional_tests/identify.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ describe('FunctionalTests / Identify', () => {
expect.objectContaining({
event: '$identify',
$set: { email: 'first@email.com' },
$set_once: { location: 'first' },
$set_once: expect.objectContaining({
location: 'first',
}),
properties: expect.objectContaining({
distinct_id: 'test-id',
$anon_distinct_id: anonymousId,
Expand Down Expand Up @@ -93,7 +95,9 @@ describe('FunctionalTests / Identify', () => {
expect.objectContaining({
event: '$identify',
$set: { email: 'first@email.com' },
$set_once: { location: 'first' },
$set_once: expect.objectContaining({
location: 'first',
}),
properties: expect.objectContaining({
distinct_id: 'test-id',
$anon_distinct_id: anonymousId,
Expand Down
3 changes: 1 addition & 2 deletions playground/nextjs/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import '@/styles/globals.css'

import React, { useEffect } from 'react'
import { useEffect } from 'react'
import type { AppProps } from 'next/app'
import { useRouter } from 'next/router'

Expand All @@ -15,7 +15,6 @@ if (typeof window !== 'undefined') {
recordCrossOriginIframes: true,
},
debug: true,
__preview_send_client_session_params: true,
scroll_root_selector: ['#scroll_element', 'html'],
persistence: cookieConsentGiven() ? 'localStorage+cookie' : 'memory',
})
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/identify.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe('identify', () => {
expect(jest.mocked(logger).warn).toBeCalledTimes(1)
})

it('it should send $is_identified = true with the identify event and following events', async () => {
it('should send $is_identified = true with the identify event and following events', async () => {
// arrange
const token = uuidv7()
const onCapture = jest.fn()
Expand Down
170 changes: 170 additions & 0 deletions src/__tests__/personProcessing.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { createPosthogInstance } from './helpers/posthog-instance'
import { uuidv7 } from '../uuidv7'
import { logger } from '../utils/logger'
jest.mock('../utils/logger')

describe('person processing', () => {
const distinctId = '123'

beforeEach(() => {
console.error = jest.fn()
})

const setup = async (processPerson: 'always' | 'identified_only' | 'never' | undefined) => {
const token = uuidv7()
const onCapture = jest.fn()
const posthog = await createPosthogInstance(token, {
_onCapture: onCapture,
__preview_process_person: processPerson,
})
return { token, onCapture, posthog }
}

describe('init', () => {
it("should default to 'always' process_person", async () => {
// arrange
const token = uuidv7()

// act
const posthog = await createPosthogInstance(token, {
__preview_process_person: undefined,
})

// assert
expect(posthog.config.__preview_process_person).toEqual('always')
})
it('should read process_person from init config', async () => {
// arrange
const token = uuidv7()

// act
const posthog = await createPosthogInstance(token, {
__preview_process_person: 'never',
})

// assert
expect(posthog.config.__preview_process_person).toEqual('never')
})
})

describe('identify', () => {
it('should fail if process_person is set to never', async () => {
// arrange
const { posthog, onCapture } = await setup('never')

// act
posthog.identify(distinctId)

// assert
expect(jest.mocked(logger).error).toBeCalledTimes(1)
expect(jest.mocked(logger).error).toHaveBeenCalledWith(
'posthog.identify was called, but the process_person configuration is set to "never". This call will be ignored.'
)
expect(onCapture).toBeCalledTimes(0)
})

it('should switch events to $person_process=true if process_person is identified_only', async () => {
// arrange
const { posthog, onCapture } = await setup('identified_only')

// act
posthog.capture('custom event before identify')
posthog.identify(distinctId)
posthog.capture('custom event after identify')
// assert
expect(jest.mocked(logger).error).toBeCalledTimes(0)
const eventBeforeIdentify = onCapture.mock.calls[0]
expect(eventBeforeIdentify[1].properties.$process_person).toEqual(false)
const identifyCall = onCapture.mock.calls[1]
expect(identifyCall[0]).toEqual('$identify')
expect(identifyCall[1].properties.$process_person).toEqual(true)
const eventAfterIdentify = onCapture.mock.calls[2]
expect(eventAfterIdentify[1].properties.$process_person).toEqual(true)
})

it('should not change $person_process if process_person is always', async () => {
// arrange
const { posthog, onCapture } = await setup('always')

// act
posthog.capture('custom event before identify')
posthog.identify(distinctId)
posthog.capture('custom event after identify')
// assert
expect(jest.mocked(logger).error).toBeCalledTimes(0)
const eventBeforeIdentify = onCapture.mock.calls[0]
expect(eventBeforeIdentify[1].properties.$process_person).toEqual(true)
const identifyCall = onCapture.mock.calls[1]
expect(identifyCall[0]).toEqual('$identify')
expect(identifyCall[1].properties.$process_person).toEqual(true)
const eventAfterIdentify = onCapture.mock.calls[2]
expect(eventAfterIdentify[1].properties.$process_person).toEqual(true)
})

it('should include initial referrer info in identify event if identified_only', async () => {
// arrange
const { posthog, onCapture } = await setup('identified_only')

// act
posthog.identify(distinctId)

// assert
const identifyCall = onCapture.mock.calls[0]
expect(identifyCall[0]).toEqual('$identify')
expect(identifyCall[1].$set_once).toEqual({
$initial_referrer: '$direct',
$initial_referring_domain: '$direct',
})
})

it('should not include initial referrer info in identify event if always', async () => {
// arrange
const { posthog, onCapture } = await setup('always')

// act
posthog.identify(distinctId)

// assert
const identifyCall = onCapture.mock.calls[0]
expect(identifyCall[0]).toEqual('$identify')
expect(identifyCall[1].$set_once).toEqual({})
})
})

describe('capture', () => {
it('should include initial referrer info iff the event has person processing when in identified_only mode', async () => {
// arrange
const { posthog, onCapture } = await setup('identified_only')

// act
posthog.capture('custom event before identify')
posthog.identify(distinctId)
posthog.capture('custom event after identify')

// assert
const eventBeforeIdentify = onCapture.mock.calls[0]
expect(eventBeforeIdentify[1].$set_once).toBeUndefined()
const eventAfterIdentify = onCapture.mock.calls[2]
expect(eventAfterIdentify[1].$set_once).toEqual({
$initial_referrer: '$direct',
$initial_referring_domain: '$direct',
})
})

it('should not add initial referrer to set_once when in always mode', async () => {
// arrange
const { posthog, onCapture } = await setup('always')

// act
posthog.capture('custom event before identify')
posthog.identify(distinctId)
posthog.capture('custom event after identify')

// assert
const eventBeforeIdentify = onCapture.mock.calls[0]
expect(eventBeforeIdentify[1].$set_once).toEqual(undefined)
const eventAfterIdentify = onCapture.mock.calls[2]
expect(eventAfterIdentify[1].$set_once).toEqual(undefined)
})
})
})
15 changes: 15 additions & 0 deletions src/__tests__/posthog-core.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ describe('posthog core', () => {
},
props: {},
get_user_state: () => 'anonymous',
get_initial_campaign_params: () => undefined,
get_initial_referrer_info: () => undefined,
},
sessionPersistence: {
update_search_keyword: jest.fn(),
Expand All @@ -69,6 +71,8 @@ describe('posthog core', () => {
update_config: jest.fn(),
properties: jest.fn(),
get_user_state: () => 'anonymous',
get_initial_campaign_params: () => undefined,
get_initial_referrer_info: () => undefined,
},
_send_request: jest.fn(),
compression: {},
Expand Down Expand Up @@ -411,6 +415,7 @@ describe('posthog core', () => {
$window_id: 'windowId',
$session_id: 'sessionId',
$is_identified: false,
$process_person: true,
})
})

Expand All @@ -432,6 +437,7 @@ describe('posthog core', () => {
$session_id: 'sessionId',
$lib_custom_api_host: 'https://custom.posthog.com',
$is_identified: false,
$process_person: true,
})
})

Expand All @@ -444,9 +450,17 @@ describe('posthog core', () => {
distinct_id: 'abc',
$window_id: 'windowId',
$session_id: 'sessionId',
$process_person: true,
})
})

it("can't deny or blacklist $process_person", () => {
given('property_denylist', () => ['$process_person'])
given('property_blacklist', () => ['$process_person'])

expect(given.subject['$process_person']).toEqual(true)
})

it('only adds token and distinct_id if event_name is $snapshot', () => {
given('event_name', () => '$snapshot')
expect(given.subject).toEqual({
Expand Down Expand Up @@ -475,6 +489,7 @@ describe('posthog core', () => {
expect(given.subject).toEqual({
event_name: given.event_name,
token: 'testtoken',
$process_person: true,
})
})

Expand Down
4 changes: 4 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export const FLAG_CALL_REPORTED = '$flag_call_reported'
export const USER_STATE = '$user_state'
export const POSTHOG_QUOTA_LIMITED = '$posthog_quota_limited'
export const CLIENT_SESSION_PROPS = '$client_session_props'
export const INITIAL_CAMPAIGN_PARAMS = '$initial_campaign_params'
export const INITIAL_REFERRER_INFO = '$initial_referrer_info'

// These are properties that are reserved and will not be automatically included in events
export const PERSISTENCE_RESERVED_PROPERTIES = [
Expand All @@ -46,4 +48,6 @@ export const PERSISTENCE_RESERVED_PROPERTIES = [
SURVEYS,
FLAG_CALL_REPORTED,
CLIENT_SESSION_PROPS,
INITIAL_CAMPAIGN_PARAMS,
INITIAL_REFERRER_INFO,
]
Loading

0 comments on commit 5f117a6

Please sign in to comment.