Skip to content

Commit

Permalink
feat: allow backend to specify a custom analytics endpoint (#831)
Browse files Browse the repository at this point in the history
  • Loading branch information
xvello authored Oct 26, 2023
1 parent e8af23a commit 716dd2a
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 104 deletions.
69 changes: 0 additions & 69 deletions src/__tests__/compression.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import sinon from 'sinon'
import { autocapture } from '../autocapture'
import { decideCompression, compressData } from '../compression'
import { Decide } from '../decide'
import { AUTOCAPTURE_DISABLED_SERVER_SIDE } from '../constants'

describe('decideCompression()', () => {
given('subject', () => decideCompression(given.compressionSupport))
Expand Down Expand Up @@ -46,68 +42,3 @@ describe('compressData()', () => {
expect(given.subject).toMatchSnapshot()
})
})

describe('Payload Compression', () => {
afterEach(() => {
document.getElementsByTagName('html')[0].innerHTML = ''
})

describe('compression', () => {
let lib, sandbox

beforeEach(() => {
document.title = 'test page'
sandbox = sinon.createSandbox()
autocapture._initializedTokens = []
lib = {
debug: true,
_prepare_callback: sandbox.spy((callback) => callback),
_send_request: sandbox.spy((url, params, options, callback) => {
if (url === 'https://test.com/decide/?v=3') {
callback({ config: { enable_collect_everything: true }, supportedCompression: ['gzip-js'] })
} else {
throw new Error('Should not get here')
}
}),
config: {
api_host: 'https://test.com',
token: 'testtoken',
},
token: 'testtoken',
get_distinct_id() {
return 'distinctid'
},
getGroups: () => ({}),

toolbar: {
maybeLoadToolbar: jest.fn(),
afterDecideResponse: jest.fn(),
},
sessionRecording: {
afterDecideResponse: jest.fn(),
},
featureFlags: {
receivedFeatureFlags: jest.fn(),
setReloadingPaused: jest.fn(),
_startReloadTimer: jest.fn(),
},
_hasBootstrappedFeatureFlags: jest.fn(),
get_property: (property_key) =>
property_key === AUTOCAPTURE_DISABLED_SERVER_SIDE
? given.$autocapture_disabled_server_side
: undefined,
}
})
given('$autocapture_disabled_server_side', () => false)

afterEach(() => {
sandbox.restore()
})

it('should save supported compression in instance', () => {
new Decide(lib).call()
autocapture.init(lib)
expect(lib.compression).toEqual({ 'gzip-js': true })
})
})
})
26 changes: 2 additions & 24 deletions src/__tests__/decide.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ describe('Decide', () => {
get_property: (key) => given.posthog.persistence.props[key],
capture: jest.fn(),
_addCaptureHook: jest.fn(),
_afterDecideResponse: jest.fn(),
_prepare_callback: jest.fn().mockImplementation((callback) => callback),
get_distinct_id: jest.fn().mockImplementation(() => 'distinctid'),
_send_request: jest
Expand Down Expand Up @@ -154,33 +155,10 @@ describe('Decide', () => {
expect(given.posthog.sessionRecording.afterDecideResponse).toHaveBeenCalledWith(given.decideResponse)
expect(given.posthog.toolbar.afterDecideResponse).toHaveBeenCalledWith(given.decideResponse)
expect(given.posthog.featureFlags.receivedFeatureFlags).toHaveBeenCalledWith(given.decideResponse)
expect(given.posthog._afterDecideResponse).toHaveBeenCalledWith(given.decideResponse)
expect(autocapture.afterDecideResponse).toHaveBeenCalledWith(given.decideResponse, given.posthog)
})

it('enables compression from decide response', () => {
given('decideResponse', () => ({ supportedCompression: ['gzip', 'lz64'] }))
given.subject()

expect(given.posthog.compression['gzip']).toBe(true)
expect(given.posthog.compression['lz64']).toBe(true)
})

it('enables compression from decide response when only one received', () => {
given('decideResponse', () => ({ supportedCompression: ['lz64'] }))
given.subject()

expect(given.posthog.compression).not.toHaveProperty('gzip')
expect(given.posthog.compression['lz64']).toBe(true)
})

it('does not enable compression from decide response if compression is disabled', () => {
given('config', () => ({ disable_compression: true, persistence: 'memory' }))
given('decideResponse', () => ({ supportedCompression: ['gzip', 'lz64'] }))
given.subject()

expect(given.posthog.compression).toEqual({})
})

it('Make sure receivedFeatureFlags is not called if the decide response fails', () => {
given('decideResponse', () => ({ status: 0 }))
window.POSTHOG_DEBUG = true
Expand Down
87 changes: 87 additions & 0 deletions src/__tests__/posthog-core.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ describe('capture()', () => {
)

given('config', () => ({
api_host: 'https://app.posthog.com',
property_blacklist: [],
_onCapture: jest.fn(),
get_device_id: jest.fn().mockReturnValue('device-id'),
Expand All @@ -57,6 +58,7 @@ describe('capture()', () => {
update_config: jest.fn(),
properties: jest.fn(),
},
_send_request: jest.fn(),
compression: {},
__captureHooks: [],
rateLimiter: {
Expand Down Expand Up @@ -191,6 +193,91 @@ describe('capture()', () => {
const captureResult = given.lib.capture('event-name', { foo: 'bar', length: 0 })
expect(captureResult.properties).toEqual(expect.objectContaining({ foo: 'bar', length: 0 }))
})

it('sends payloads to /e/ by default', () => {
given.lib.capture('event-name', { foo: 'bar', length: 0 })
expect(given.lib._send_request).toHaveBeenCalledWith(
'https://app.posthog.com/e/',
expect.any(Object),
expect.any(Object),
undefined
)
})

it('sends payloads to alternative endpoint if given', () => {
given.lib._afterDecideResponse({ analytics: { endpoint: '/i/v0/e/' } })
given.lib.capture('event-name', { foo: 'bar', length: 0 })

expect(given.lib._send_request).toHaveBeenCalledWith(
'https://app.posthog.com/i/v0/e/',
expect.any(Object),
expect.any(Object),
undefined
)
})

it('sends payloads to overriden endpoint if given', () => {
given.lib.capture('event-name', { foo: 'bar', length: 0 }, { endpoint: '/s/' })
expect(given.lib._send_request).toHaveBeenCalledWith(
'https://app.posthog.com/s/',
expect.any(Object),
expect.any(Object),
undefined
)
})

it('sends payloads to overriden endpoint, even if alternative endpoint is set', () => {
given.lib._afterDecideResponse({ analytics: { endpoint: '/i/v0/e/' } })
given.lib.capture('event-name', { foo: 'bar', length: 0 }, { endpoint: '/s/' })
expect(given.lib._send_request).toHaveBeenCalledWith(
'https://app.posthog.com/s/',
expect.any(Object),
expect.any(Object),
undefined
)
})
})

describe('_afterDecideResponse', () => {
given('subject', () => () => given.lib._afterDecideResponse(given.decideResponse))

it('enables compression from decide response', () => {
given('decideResponse', () => ({ supportedCompression: ['gzip', 'lz64'] }))
given.subject()

expect(given.lib.compression['gzip']).toBe(true)
expect(given.lib.compression['lz64']).toBe(true)
})

it('enables compression from decide response when only one received', () => {
given('decideResponse', () => ({ supportedCompression: ['lz64'] }))
given.subject()

expect(given.lib.compression).not.toHaveProperty('gzip')
expect(given.lib.compression['lz64']).toBe(true)
})

it('does not enable compression from decide response if compression is disabled', () => {
given('config', () => ({ disable_compression: true, persistence: 'memory' }))
given('decideResponse', () => ({ supportedCompression: ['gzip', 'lz64'] }))
given.subject()

expect(given.lib.compression).toEqual({})
})

it('defaults to /e if no endpoint is given', () => {
given('decideResponse', () => ({}))
given.subject()

expect(given.lib.analyticsDefaultEndpoint).toEqual('/e/')
})

it('uses the specified analytics endpoint if given', () => {
given('decideResponse', () => ({ analytics: { endpoint: '/i/v0/e/' } }))
given.subject()

expect(given.lib.analyticsDefaultEndpoint).toEqual('/i/v0/e/')
})
})

describe('_calculate_event_properties()', () => {
Expand Down
12 changes: 2 additions & 10 deletions src/decide.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { autocapture } from './autocapture'
import { _base64Encode, _isUndefined, loadScript, logger } from './utils'
import { PostHog } from './posthog-core'
import { Compression, DecideResponse } from './types'
import { DecideResponse } from './types'
import { STORED_GROUP_PROPERTIES_KEY, STORED_PERSON_PROPERTIES_KEY } from './constants'

export class Decide {
Expand Down Expand Up @@ -59,20 +59,12 @@ export class Decide {
this.instance.sessionRecording?.afterDecideResponse(response)
autocapture.afterDecideResponse(response, this.instance)
this.instance.webPerformance?.afterDecideResponse(response)
this.instance._afterDecideResponse(response)

if (!this.instance.config.advanced_disable_feature_flags_on_first_load) {
this.instance.featureFlags.receivedFeatureFlags(response)
}

this.instance['compression'] = {}
if (response['supportedCompression'] && !this.instance.config.disable_compression) {
const compression: Partial<Record<Compression, boolean>> = {}
for (const method of response['supportedCompression']) {
compression[method] = true
}
this.instance['compression'] = compression
}

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const surveysGenerator = window?.extendPostHogWithSurveys
Expand Down
20 changes: 19 additions & 1 deletion src/posthog-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
CaptureOptions,
CaptureResult,
Compression,
DecideResponse,
EarlyAccessFeatureCallback,
GDPROptions,
isFeatureEnabledOptions,
Expand Down Expand Up @@ -289,6 +290,7 @@ export class PostHog {
__request_queue: [url: string, data: Record<string, any>, options: XHROptions, callback?: RequestCallback][]
__autocapture: boolean | AutocaptureConfig | undefined
decideEndpointWasHit: boolean
analyticsDefaultEndpoint: string

SentryIntegration: typeof SentryIntegration
segmentIntegration: () => any
Expand All @@ -311,6 +313,7 @@ export class PostHog {
this.__loaded_recorder_version = undefined
this.__autocapture = undefined
this._jsc = function () {} as JSC
this.analyticsDefaultEndpoint = '/e/'

this.featureFlags = new PostHogFeatureFlags(this)
this.toolbar = new Toolbar(this)
Expand Down Expand Up @@ -524,6 +527,21 @@ export class PostHog {

// Private methods

_afterDecideResponse(response: DecideResponse) {
this.compression = {}
if (response.supportedCompression && !this.config.disable_compression) {
const compression: Partial<Record<Compression, boolean>> = {}
for (const method of response['supportedCompression']) {
compression[method] = true
}
this.compression = compression
}

if (response.analytics?.endpoint) {
this.analyticsDefaultEndpoint = response.analytics.endpoint
}
}

_loaded(): void {
// Pause `reloadFeatureFlags` calls in config.loaded callback.
// These feature flags are loaded in the decide call made right
Expand Down Expand Up @@ -874,7 +892,7 @@ export class PostHog {
logger.info('send', data)
const jsonData = JSON.stringify(data)

const url = this.config.api_host + (options.endpoint || '/e/')
const url = this.config.api_host + (options.endpoint || this.analyticsDefaultEndpoint)

const has_unique_traits = options !== __NOOPTIONS

Expand Down
3 changes: 3 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,9 @@ export interface DecideResponse {
errorsWhileComputingFlags: boolean
autocapture_opt_out?: boolean
capturePerformance?: boolean
analytics?: {
endpoint?: string
}
// this is currently in development and may have breaking changes without a major version bump
autocaptureExceptions?:
| boolean
Expand Down

0 comments on commit 716dd2a

Please sign in to comment.