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

feat: Heatmaps instrumentation #1131

Merged
merged 43 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
f898d51
Refactoring of autocapture things
benjackwhite Apr 10, 2024
a7f44a5
Fixes
benjackwhite Apr 10, 2024
fcc41fd
Fixes
benjackwhite Apr 10, 2024
0ac0175
Fixed up imports
benjackwhite Apr 10, 2024
ec77ae1
Fixes
benjackwhite Apr 10, 2024
5637e27
Fixe
benjackwhite Apr 10, 2024
bdc80ec
Merge branch 'main' into feat/heatmaps
benjackwhite Apr 10, 2024
65f5344
Fixes
benjackwhite Apr 10, 2024
c31de30
Remove "enable_collect_everything"
benjackwhite Apr 10, 2024
d601ad8
Fixes
benjackwhite Apr 10, 2024
ee50931
fix
benjackwhite Apr 10, 2024
315026c
Fix tests
benjackwhite Apr 10, 2024
88e5875
Fixes
benjackwhite Apr 10, 2024
82f7ffc
Fixed up
benjackwhite Apr 10, 2024
e52eca5
Added try catch
benjackwhite Apr 10, 2024
4c3ae34
Fixes
benjackwhite Apr 10, 2024
4ffac3c
fix
benjackwhite Apr 10, 2024
71ab606
Fix
benjackwhite Apr 10, 2024
1752b65
Fix
benjackwhite Apr 10, 2024
74e31a2
Fix?
benjackwhite Apr 10, 2024
2f2ba57
Fix?
benjackwhite Apr 10, 2024
7b9dbec
fix
benjackwhite Apr 10, 2024
140010c
Added heatmaps tracking preview code
benjackwhite Apr 10, 2024
3f76d9c
Fixes
benjackwhite Apr 10, 2024
817074f
Fix test
benjackwhite Apr 10, 2024
6af76f4
Change name
benjackwhite Apr 11, 2024
97ad2b5
chore: Pageview refactor (#1132)
benjackwhite Apr 11, 2024
ae795c2
Fixes
benjackwhite Apr 11, 2024
db050c4
Fix
benjackwhite Apr 11, 2024
08d0503
Fix
benjackwhite Apr 11, 2024
d9c7ab2
Fixes
benjackwhite Apr 12, 2024
1412ffd
Fix
benjackwhite Apr 12, 2024
004ba76
Fix
benjackwhite Apr 12, 2024
f7c9e78
Fixes
benjackwhite Apr 12, 2024
a9c51fb
Fix
benjackwhite Apr 12, 2024
4372aa1
it can be undefined maybe
pauldambra Apr 12, 2024
a6f548b
Added skipping for heatmaps on snapshots
benjackwhite Apr 18, 2024
dc260f8
Merge branch 'main' into feat/heatmaps-part2
benjackwhite Apr 18, 2024
d46b885
fix
benjackwhite Apr 18, 2024
0cbf653
Fixes
benjackwhite Apr 18, 2024
c79d24c
Fixes
benjackwhite Apr 18, 2024
b18767a
fix
benjackwhite Apr 18, 2024
c2f13a2
fix
benjackwhite Apr 18, 2024
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
6 changes: 5 additions & 1 deletion playground/nextjs/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ export default function Home() {
<meta name="viewport" content="width=device-width, initial-scale=1" />
</Head>
<main>
<h1>PostHog React</h1>
<div className="sticky top-0 bg-white border-b mb-4">
<h1 className="m-0">
<b>PostHog</b> React
</h1>
</div>

<p>The current time is {time}</p>

Expand Down
3 changes: 2 additions & 1 deletion playground/nextjs/src/posthog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ if (typeof window !== 'undefined') {
debug: true,
scroll_root_selector: ['#scroll_element', 'html'],
// persistence: cookieConsentGiven() ? 'localStorage+cookie' : 'memory',
__preview_process_person: 'identified_only',
person_profiles: 'identified_only',
__preview_heatmaps: true,
...configForConsent(),
})
;(window as any).posthog = posthog
Expand Down
4 changes: 4 additions & 0 deletions src/__tests__/extensions/replay/sessionrecording.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,7 @@ describe('SessionRecording', () => {
{
_url: 'https://test.com/s/',
_noTruncate: true,
_noHeatmaps: true,
_batchKey: 'recordings',
}
)
Expand Down Expand Up @@ -606,6 +607,7 @@ describe('SessionRecording', () => {
{
_url: 'https://test.com/s/',
_noTruncate: true,
_noHeatmaps: true,
_batchKey: 'recordings',
}
)
Expand Down Expand Up @@ -688,6 +690,7 @@ describe('SessionRecording', () => {
{
_url: 'https://test.com/s/',
_noTruncate: true,
_noHeatmaps: true,
_batchKey: 'recordings',
}
)
Expand Down Expand Up @@ -1322,6 +1325,7 @@ describe('SessionRecording', () => {
{
_batchKey: 'recordings',
_noTruncate: true,
_noHeatmaps: true,
_url: 'https://test.com/s/',
}
)
Expand Down
91 changes: 91 additions & 0 deletions src/__tests__/heatmaps.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { createPosthogInstance } from './helpers/posthog-instance'
import { uuidv7 } from '../uuidv7'
import { PostHog } from '../posthog-core'
jest.mock('../utils/logger')

describe('heatmaps', () => {
let posthog: PostHog
let onCapture = jest.fn()

const mockClickEvent = {
target: document.body,
clientX: 10,
clientY: 20,
} as unknown as MouseEvent

const createMockMouseEvent = (props: Partial<MouseEvent> = {}) =>
({
target: document.body,
clientX: 10,
clientY: 10,
...props,
} as unknown as MouseEvent)

beforeEach(async () => {
onCapture = jest.fn()
posthog = await createPosthogInstance(uuidv7(), { _onCapture: onCapture })
})

it('should include generated heatmap data', async () => {
posthog.heatmaps?.['_onClick']?.(mockClickEvent as MouseEvent)
posthog.capture('test event')

expect(onCapture).toBeCalledTimes(1)
expect(onCapture.mock.lastCall).toMatchObject([
'test event',
{
event: 'test event',
properties: {
$heatmap_data: {
'http://localhost/': [
{
target_fixed: false,
type: 'click',
x: 10,
y: 20,
},
],
},
},
},
])
})

it('should add rageclick events in the same area', async () => {
posthog.heatmaps?.['_onClick']?.(createMockMouseEvent())
posthog.heatmaps?.['_onClick']?.(createMockMouseEvent())
posthog.heatmaps?.['_onClick']?.(createMockMouseEvent())

posthog.capture('test event')

expect(onCapture).toBeCalledTimes(1)
expect(onCapture.mock.lastCall[1].properties.$heatmap_data['http://localhost/']).toHaveLength(4)
expect(onCapture.mock.lastCall[1].properties.$heatmap_data['http://localhost/'].map((x) => x.type)).toEqual([
'click',
'click',
'rageclick',
'click',
])
})

it('should clear the buffer after each call', async () => {
posthog.heatmaps?.['_onClick']?.(createMockMouseEvent())
posthog.heatmaps?.['_onClick']?.(createMockMouseEvent())
posthog.capture('test event')
expect(onCapture).toBeCalledTimes(1)
expect(onCapture.mock.lastCall[1].properties.$heatmap_data['http://localhost/']).toHaveLength(2)

posthog.capture('test event 2')
expect(onCapture).toBeCalledTimes(2)
expect(onCapture.mock.lastCall[1].properties.$heatmap_data).toBeUndefined()
})

it('should not include generated heatmap data for $snapshot events with _noHeatmaps', async () => {
posthog.heatmaps?.['_onClick']?.(createMockMouseEvent())
posthog.capture('$snapshot', undefined, { _noHeatmaps: true })

expect(onCapture).toBeCalledTimes(1)
expect(onCapture.mock.lastCall).toMatchObject(['$snapshot', {}])
expect(onCapture.mock.lastCall[1].properties).not.toHaveProperty('$heatmap_data')
})
})
25 changes: 13 additions & 12 deletions src/__tests__/page-view.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { PageViewManager } from '../page-view'
import { PostHog } from '../posthog-core'
import { ScrollManager } from '../scroll-manager'

const mockWindowGetter = jest.fn()
jest.mock('../utils/globals', () => ({
Expand All @@ -11,10 +12,15 @@ jest.mock('../utils/globals', () => ({

describe('PageView ID manager', () => {
describe('doPageView', () => {
const instance: PostHog = {
config: {},
} as any
let instance: PostHog
let pageViewIdManager: PageViewManager

beforeEach(() => {
instance = {
config: {},
} as any
instance.scrollManager = new ScrollManager(instance)
pageViewIdManager = new PageViewManager(instance)
mockWindowGetter.mockReturnValue({
location: {
pathname: '/pathname',
Expand Down Expand Up @@ -45,11 +51,10 @@ describe('PageView ID manager', () => {
},
})

const pageViewIdManager = new PageViewManager(instance)
pageViewIdManager.doPageView()

// force the manager to update the scroll data by calling an internal method
pageViewIdManager._updateScrollData()
instance.scrollManager['_updateScrollData']()

const secondPageView = pageViewIdManager.doPageView()
expect(secondPageView.$prev_pageview_last_scroll).toEqual(2000)
Expand All @@ -76,11 +81,10 @@ describe('PageView ID manager', () => {
},
})

const pageViewIdManager = new PageViewManager(instance)
pageViewIdManager.doPageView()

// force the manager to update the scroll data by calling an internal method
pageViewIdManager._updateScrollData()
instance.scrollManager['_updateScrollData']()

const secondPageView = pageViewIdManager.doPageView()
expect(secondPageView.$prev_pageview_last_scroll).toEqual(0)
Expand All @@ -94,9 +98,7 @@ describe('PageView ID manager', () => {
})

it('can handle scroll updates before doPageView is called', () => {
const pageViewIdManager = new PageViewManager(instance)

pageViewIdManager._updateScrollData()
instance.scrollManager['_updateScrollData']()
const firstPageView = pageViewIdManager.doPageView()
expect(firstPageView.$prev_pageview_last_scroll).toBeUndefined()

Expand All @@ -105,8 +107,7 @@ describe('PageView ID manager', () => {
})

it('should include the pathname', () => {
const pageViewIdManager = new PageViewManager(instance)

instance.scrollManager['_updateScrollData']()
const firstPageView = pageViewIdManager.doPageView()
expect(firstPageView.$prev_pageview_pathname).toBeUndefined()
const secondPageView = pageViewIdManager.doPageView()
Expand Down
12 changes: 0 additions & 12 deletions src/__tests__/posthog-core.js
Original file line number Diff line number Diff line change
Expand Up @@ -449,18 +449,6 @@ describe('posthog core', () => {
expect(given.overrides.sessionManager.checkAndGetSessionAndWindowId).not.toHaveBeenCalled()
})

it('only adds a few propertes if event is $performance_event', () => {
given('event_name', () => '$performance_event')
expect(given.subject).toEqual({
distinct_id: 'abc',
event: 'prop', // from actual mock event properties
$current_url: undefined,
$session_id: 'sessionId',
$window_id: 'windowId',
token: 'testtoken',
})
})

it('calls sanitize_properties', () => {
given('sanitize_properties', () => (props, event_name) => ({ token: props.token, event_name }))

Expand Down
2 changes: 1 addition & 1 deletion src/autocapture-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ function checkIfElementTreePassesCSSSelectorAllowList(
return false
}

function getParentElement(curEl: Element): Element | false {
export function getParentElement(curEl: Element): Element | false {
const parentNode = curEl.parentNode
if (!parentNode || !isElementNode(parentNode)) return false
return parentNode
Expand Down
1 change: 0 additions & 1 deletion src/autocapture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ export class Autocapture {
_isDisabledServerSide: boolean | null = null
rageclicks = new RageClick()
_elementsChainAsString = false
_decideResponse?: DecideResponse

constructor(instance: PostHog) {
this.instance = instance
Expand Down
1 change: 1 addition & 0 deletions src/extensions/exception-autocapture/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ export class ExceptionObserver {
this.instance.capture('$exception', properties, {
_noTruncate: true,
_batchKey: 'exceptionEvent',
_noHeatmaps: true,
})
}
}
1 change: 1 addition & 0 deletions src/extensions/replay/sessionrecording.ts
Original file line number Diff line number Diff line change
Expand Up @@ -927,6 +927,7 @@ export class SessionRecording {
_url: this.instance.requestRouter.endpointFor('api', this._endpoint),
_noTruncate: true,
_batchKey: SESSION_RECORDING_BATCH_KEY,
_noHeatmaps: true, // Session Replay ingestion can't handle heatamap data
})
}
}
Loading
Loading