Skip to content

Commit

Permalink
Merge branch 'main' into fix/decide-loading
Browse files Browse the repository at this point in the history
# Conflicts:
#	src/__tests__/featureflags.test.ts
#	src/decide.ts
#	src/posthog-core.ts
#	src/posthog-featureflags.ts
  • Loading branch information
benjackwhite committed Dec 13, 2024
2 parents d350876 + 2c12b4f commit ba33272
Show file tree
Hide file tree
Showing 11 changed files with 146 additions and 9 deletions.
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
## 1.199.0 - 2024-12-12

- chore(flags): Add new debugging property `$used_bootstrap_value`, `$feature_flag_bootstrapped_response` and `$feature_flag_bootstrapped_payload` to `$feature_flag_called` event (#1567)

## 1.198.0 - 2024-12-12

- feat: Allow users to customize `fetch` behavior (#1599)

## 1.197.0 - 2024-12-12

- feat: send snapshot library (#1587)

## 1.196.1 - 2024-12-12

- feat: Don't create person profile when setting properties for flags (#1586)

## 1.196.0 - 2024-12-12

- feat: include attribution with all web vitals (#1594)

## 1.195.0 - 2024-12-10

- Reduce type (#1590)
Expand Down
45 changes: 44 additions & 1 deletion cypress/e2e/capture.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,50 @@ describe('Event capture', () => {

cy.get('[data-cy-feature-flag-button]').click()

cy.phCaptures().should('include', '$feature_flag_called')
cy.phCaptures({ full: true }).then((captures) => {
const flagCallEvents = captures.filter((capture) => capture.event === '$feature_flag_called')
expect(flagCallEvents.length).to.eq(1)
const flagCallEvent = flagCallEvents[0]
expect(flagCallEvent.properties.$feature_flag_bootstrapped_response).to.not.exist
expect(flagCallEvent.properties.$feature_flag_bootstrapped_payload).to.not.exist
expect(flagCallEvent.properties.$used_bootstrap_value).to.equal(false)
})
})

it('captures $feature_flag_called with bootstrapped value properties', () => {
start({
options: {
bootstrap: {
featureFlags: {
'some-feature': 'some-value',
},
featureFlagPayloads: {
'some-feature': 'some-payload',
},
},
advanced_disable_feature_flags: true,
},
waitForDecide: false,
})

cy.intercept(
'/decide/*',
{ times: 1 },
{
forceNetworkError: true,
}
)

cy.get('[data-cy-feature-flag-button]').click()

cy.phCaptures({ full: true }).then((captures) => {
const flagCallEvents = captures.filter((capture) => capture.event === '$feature_flag_called')
expect(flagCallEvents.length).to.eq(1)
const flagCallEvent = flagCallEvents[0]
expect(flagCallEvent.properties.$feature_flag_bootstrapped_response).to.equal('some-value')
expect(flagCallEvent.properties.$feature_flag_bootstrapped_payload).to.equal('some-payload')
expect(flagCallEvent.properties.$used_bootstrap_value).to.equal(true)
})
})

it('captures rage clicks', () => {
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.195.0",
"version": "1.199.0",
"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
30 changes: 29 additions & 1 deletion src/__tests__/extensions/replay/sessionrecording.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ import { SimpleEventEmitter } from '../../../utils/simple-event-emitter'

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

jest.mock('../../../config', () => ({ LIB_VERSION: 'v0.0.1' }))
jest.mock('../../../config', () => ({ LIB_VERSION: '0.0.1' }))

const EMPTY_BUFFER = {
data: [],
Expand Down Expand Up @@ -858,6 +858,8 @@ describe('SessionRecording', () => {
],
$session_id: sessionId,
$window_id: 'windowId',
$lib: 'web',
$lib_version: '0.0.1',
},
{
_url: 'https://test.com/s/',
Expand Down Expand Up @@ -893,6 +895,8 @@ describe('SessionRecording', () => {
{ type: 3, data: { source: 1 } },
{ type: 3, data: { source: 2 } },
],
$lib: 'web',
$lib_version: '0.0.1',
},
{
_url: 'https://test.com/s/',
Expand Down Expand Up @@ -973,6 +977,8 @@ describe('SessionRecording', () => {
$window_id: 'windowId',
$snapshot_data: [{ data: { source: 1 }, emit: 1, type: 3 }],
$snapshot_bytes: 39,
$lib: 'web',
$lib_version: '0.0.1',
},
{
_url: 'https://test.com/s/',
Expand Down Expand Up @@ -1580,6 +1586,8 @@ describe('SessionRecording', () => {
$session_id: firstSessionId,
$snapshot_bytes: 186,
$window_id: expect.any(String),
$lib: 'web',
$lib_version: '0.0.1',
},
{
_batchKey: 'recordings',
Expand Down Expand Up @@ -1670,6 +1678,8 @@ describe('SessionRecording', () => {
$session_id: firstSessionId,
$snapshot_bytes: 186,
$window_id: expect.any(String),
$lib: 'web',
$lib_version: '0.0.1',
},
{
_batchKey: 'recordings',
Expand All @@ -1696,6 +1706,8 @@ describe('SessionRecording', () => {
$session_id: firstSessionId,
$snapshot_bytes: 186,
$window_id: expect.any(String),
$lib: 'web',
$lib_version: '0.0.1',
},
{
_batchKey: 'recordings',
Expand Down Expand Up @@ -2116,6 +2128,8 @@ describe('SessionRecording', () => {
$session_id: sessionId,
$snapshot_bytes: expect.any(Number),
$window_id: 'windowId',
$lib: 'web',
$lib_version: '0.0.1',
},
captureOptions
)
Expand All @@ -2137,6 +2151,8 @@ describe('SessionRecording', () => {
$session_id: sessionId,
$snapshot_bytes: expect.any(Number),
$window_id: 'windowId',
$lib: 'web',
$lib_version: '0.0.1',
},
captureOptions
)
Expand Down Expand Up @@ -2166,6 +2182,8 @@ describe('SessionRecording', () => {
$session_id: sessionId,
$snapshot_bytes: expect.any(Number),
$window_id: 'windowId',
$lib: 'web',
$lib_version: '0.0.1',
},
captureOptions
)
Expand Down Expand Up @@ -2196,6 +2214,8 @@ describe('SessionRecording', () => {
$session_id: sessionId,
$snapshot_bytes: expect.any(Number),
$window_id: 'windowId',
$lib: 'web',
$lib_version: '0.0.1',
},
captureOptions
)
Expand All @@ -2215,6 +2235,8 @@ describe('SessionRecording', () => {
$session_id: sessionId,
$snapshot_bytes: 86,
$window_id: 'windowId',
$lib: 'web',
$lib_version: '0.0.1',
},
captureOptions
)
Expand All @@ -2239,6 +2261,8 @@ describe('SessionRecording', () => {
$session_id: sessionId,
$snapshot_bytes: 58,
$window_id: 'windowId',
$lib: 'web',
$lib_version: '0.0.1',
},
captureOptions
)
Expand All @@ -2262,6 +2286,8 @@ describe('SessionRecording', () => {
$session_id: sessionId,
$snapshot_bytes: 69,
$window_id: 'windowId',
$lib: 'web',
$lib_version: '0.0.1',
},
captureOptions
)
Expand Down Expand Up @@ -2308,6 +2334,8 @@ describe('SessionRecording', () => {
{ type: 3, data: { source: 1 } },
{ type: 3, data: { source: 2 } },
],
$lib: 'web',
$lib_version: '0.0.1',
},
expect.any(Object)
)
Expand Down
17 changes: 17 additions & 0 deletions src/__tests__/request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,23 @@ describe('request', () => {
})
})

it('supports nextOptions parameter', async () => {
request(
createRequest({
fetchOptions: { cache: 'force-cache', next: { revalidate: 0, tags: ['test'] } },
})
)
await flushPromises()

expect(mockedFetch).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
cache: 'force-cache',
next: { revalidate: 0, tags: ['test'] },
})
)
})

describe('keepalive with fetch and large bodies can cause some browsers to reject network calls', () => {
it.each([
['always keepalive with small json POST', 'POST', 'small', undefined, true, ''],
Expand Down
3 changes: 1 addition & 2 deletions src/entrypoints/web-vitals.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { onLCP, onCLS, onFCP } from 'web-vitals'
import { onINP } from 'web-vitals/attribution'
import { onINP, onLCP, onCLS, onFCP } from 'web-vitals/attribution'
import { assignableWindow } from '../utils/globals'

const postHogWebVitalsCallbacks = {
Expand Down
3 changes: 3 additions & 0 deletions src/extensions/replay/sessionrecording.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import { MutationRateLimiter } from './mutation-rate-limiter'
import { gzipSync, strFromU8, strToU8 } from 'fflate'
import { clampToRange } from '../../utils/number-utils'
import { includes } from '../../utils'
import Config from '../../config'

const LOGGER_PREFIX = '[SessionRecording]'
const logger = createLogger(LOGGER_PREFIX)
Expand Down Expand Up @@ -1185,6 +1186,8 @@ export class SessionRecording {
$snapshot_data: snapshotBuffer.data,
$session_id: snapshotBuffer.sessionId,
$window_id: snapshotBuffer.windowId,
$lib: 'web',
$lib_version: Config.LIB_VERSION,
})
})
}
Expand Down
7 changes: 4 additions & 3 deletions src/posthog-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,10 @@ export class PostHog {
}
options.compression = options.compression === 'best-available' ? this.compression : options.compression

// Specially useful if you're doing SSR with NextJS
// Users must be careful when tweaking `cache` because they might get out-of-date feature flags
options.fetchOptions = options.fetchOptions || this.config.fetch_options

request({
...options,
callback: (response) => {
Expand Down Expand Up @@ -1496,9 +1500,6 @@ export class PostHog {
* to update user properties.
*/
setPersonPropertiesForFlags(properties: Properties, reloadFeatureFlags = true): void {
if (!this._requirePersonProcessing('posthog.setPersonPropertiesForFlags')) {
return
}
this.featureFlags.setPersonPropertiesForFlags(properties, reloadFeatureFlags)
}

Expand Down
11 changes: 10 additions & 1 deletion src/posthog-featureflags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,16 @@ export class PostHogFeatureFlags {
}
this.instance.persistence?.register({ [FLAG_CALL_REPORTED]: flagCallReported })

this.instance.capture('$feature_flag_called', { $feature_flag: key, $feature_flag_response: flagValue })
this.instance.capture('$feature_flag_called', {
$feature_flag: key,
$feature_flag_response: flagValue,
$feature_flag_payload: this.getFeatureFlagPayload(key) || null,
$feature_flag_bootstrapped_response: this.instance.config.bootstrap?.featureFlags?.[key] || null,
$feature_flag_bootstrapped_payload:
this.instance.config.bootstrap?.featureFlagPayloads?.[key] || null,
// If we haven't yet received a response from the /decide endpoint, we must have used the bootstrapped value
$used_bootstrap_value: !this.instance.decideEndpointWasHit,
})
}
}
return flagValue
Expand Down
1 change: 1 addition & 0 deletions src/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ const _fetch = (options: RequestOptions) => {
keepalive: options.method === 'POST' && (estimatedSize || 0) < KEEP_ALIVE_THRESHOLD,
body,
signal: aborter?.signal,
...options.fetchOptions,
})
.then((response) => {
return response.text().then((responseText) => {
Expand Down
16 changes: 16 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,15 @@ export interface PostHogConfig {
events_burst_limit?: number
}

/** Used when sending data via `fetch`, use with care, this is intentionally meant to be used with NextJS `fetch`
* Incorrect usage may cause out-of-date data for feature flags, actions tracking, etc.
* See https://nextjs.org/docs/app/api-reference/functions/fetch#fetchurl-options
*/
fetch_options?: {
cache?: RequestInit['cache']
next_options?: NextOptions
}

/**
* PREVIEW - MAY CHANGE WITHOUT WARNING - DO NOT USE IN PRODUCTION
* whether to wrap fetch and add tracing headers to the request
Expand Down Expand Up @@ -440,6 +449,9 @@ export interface RequestResponse {

export type RequestCallback = (response: RequestResponse) => void

// See https://nextjs.org/docs/app/api-reference/functions/fetch#fetchurl-options
type NextOptions = { revalidate: false | 0 | number; tags: string[] }

export interface RequestOptions {
url: string
// Data can be a single object or an array of objects when batched
Expand All @@ -452,6 +464,10 @@ export interface RequestOptions {
timeout?: number
noRetries?: boolean
compression?: Compression | 'best-available'
fetchOptions?: {
cache?: RequestInit['cache']
next?: NextOptions
}
}

// Queued request types - the same as a request but with additional queueing information
Expand Down

0 comments on commit ba33272

Please sign in to comment.