Skip to content

Commit

Permalink
feat(feature-flags): Enable experience continuity (#404)
Browse files Browse the repository at this point in the history
  • Loading branch information
neilkakkar authored Jun 28, 2022
1 parent bee89e3 commit 3f7526b
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 14 deletions.
118 changes: 105 additions & 13 deletions src/__tests__/featureflags.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,32 @@
import { PostHogFeatureFlags, parseFeatureFlagDecideResponse } from '../posthog-featureflags'
jest.useFakeTimers()
jest.spyOn(global, 'setTimeout')

describe('featureflags', () => {
given('decideEndpointWasHit', () => false)
given('instance', () => ({
get_config: jest.fn().mockImplementation((key) => given.config[key]),
get_property: (key) => given.properties[key],
get_distinct_id: () => 'blah id',
getGroups: () => {},
_prepare_callback: (callback) => callback,
persistence: {
props: {
$active_feature_flags: ['beta-feature', 'alpha-feature-2', 'multivariate-flag'],
$enabled_feature_flags: {
'beta-feature': true,
'alpha-feature-2': true,
'multivariate-flag': 'variant-1',
},
$override_feature_flags: false,
},
register: (dict) => {
given.instance.persistence.props = { ...given.instance.persistence.props, ...dict }
},
},
get_property: (key) => given.instance.persistence.props[key],
capture: () => {},
decideEndpointWasHit: given.decideEndpointWasHit,
_send_request: jest.fn().mockImplementation((url, data, headers, callback) => callback(given.decideResponse)),
}))

given('featureFlags', () => new PostHogFeatureFlags(given.instance))
Expand All @@ -16,16 +36,6 @@ describe('featureflags', () => {
jest.spyOn(window.console, 'warn').mockImplementation()
})

given('properties', () => ({
$active_feature_flags: ['beta-feature', 'alpha-feature-2', 'multivariate-flag'],
$enabled_feature_flags: {
'beta-feature': true,
'alpha-feature-2': true,
'multivariate-flag': 'variant-1',
},
$override_feature_flags: false,
}))

it('should return the right feature flag and call capture', () => {
expect(given.featureFlags.getFlags()).toEqual(['beta-feature', 'alpha-feature-2', 'multivariate-flag'])
expect(given.featureFlags.getFlagVariants()).toEqual({
Expand All @@ -50,7 +60,7 @@ describe('featureflags', () => {
})

it('supports overrides', () => {
given('properties', () => ({
given.instance.persistence.props = {
$active_feature_flags: ['beta-feature', 'alpha-feature-2', 'multivariate-flag'],
$enabled_feature_flags: {
'beta-feature': true,
Expand All @@ -61,7 +71,8 @@ describe('featureflags', () => {
'beta-feature': false,
'alpha-feature-2': 'as-a-variant',
},
}))
}

expect(given.featureFlags.getFlags()).toEqual(['alpha-feature-2', 'multivariate-flag'])
expect(given.featureFlags.getFlagVariants()).toEqual({
'alpha-feature-2': 'as-a-variant',
Expand All @@ -84,6 +95,87 @@ describe('featureflags', () => {

called = false
})

describe('reloadFeatureFlags', () => {
given('decideResponse', () => ({
featureFlags: {
first: 'variant-1',
second: true,
},
}))

given('config', () => ({
token: 'random fake token',
}))

it('on providing anonDistinctId', () => {
given.featureFlags.setAnonymousDistinctId('rando_id')
given.featureFlags.reloadFeatureFlags()

jest.runAllTimers()

expect(given.featureFlags.getFlagVariants()).toEqual({
first: 'variant-1',
second: true,
})

// check the request sent $anon_distinct_id
expect(
JSON.parse(Buffer.from(given.instance._send_request.mock.calls[0][1].data, 'base64').toString())
).toEqual({
token: 'random fake token',
distinct_id: 'blah id',
$anon_distinct_id: 'rando_id',
})
})

it('on providing anonDistinctId and calling reload multiple times', () => {
given.featureFlags.setAnonymousDistinctId('rando_id')
given.featureFlags.reloadFeatureFlags()
given.featureFlags.reloadFeatureFlags()

jest.runAllTimers()

expect(given.featureFlags.getFlagVariants()).toEqual({
first: 'variant-1',
second: true,
})

// check the request sent $anon_distinct_id
expect(
JSON.parse(Buffer.from(given.instance._send_request.mock.calls[0][1].data, 'base64').toString())
).toEqual({
token: 'random fake token',
distinct_id: 'blah id',
$anon_distinct_id: 'rando_id',
})

given.featureFlags.reloadFeatureFlags()
given.featureFlags.reloadFeatureFlags()
jest.runAllTimers()

// check the request didn't send $anon_distinct_id the second time around
expect(
JSON.parse(Buffer.from(given.instance._send_request.mock.calls[1][1].data, 'base64').toString())
).toEqual({
token: 'random fake token',
distinct_id: 'blah id',
// $anon_distinct_id: "rando_id"
})

given.featureFlags.reloadFeatureFlags()
jest.runAllTimers()

// check the request didn't send $anon_distinct_id the second time around
expect(
JSON.parse(Buffer.from(given.instance._send_request.mock.calls[2][1].data, 'base64').toString())
).toEqual({
token: 'random fake token',
distinct_id: 'blah id',
// $anon_distinct_id: "rando_id"
})
})
})
})

describe('parseFeatureFlagDecideResponse', () => {
Expand Down
13 changes: 12 additions & 1 deletion src/__tests__/posthog-core.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ describe('identify()', () => {
_captureMetrics: {
incr: jest.fn(),
},
featureFlags: {
setAnonymousDistinctId: jest.fn(),
},
reloadFeatureFlags: jest.fn(),
}))

Expand Down Expand Up @@ -63,6 +66,7 @@ describe('identify()', () => {
{ $set_once: {} }
)
expect(given.overrides.people.set).not.toHaveBeenCalled()
expect(given.overrides.featureFlags.setAnonymousDistinctId).toHaveBeenCalledWith('oldIdentity')
})

it('calls capture when identity changes and old ID is anonymous', () => {
Expand All @@ -80,6 +84,7 @@ describe('identify()', () => {
{ $set_once: {} }
)
expect(given.overrides.people.set).not.toHaveBeenCalled()
expect(given.overrides.featureFlags.setAnonymousDistinctId).toHaveBeenCalledWith('oldIdentity')
})

it("don't identify if the old id isn't anonymous", () => {
Expand All @@ -89,6 +94,7 @@ describe('identify()', () => {

expect(given.overrides.capture).not.toHaveBeenCalled()
expect(given.overrides.people.set).not.toHaveBeenCalled()
expect(given.overrides.featureFlags.setAnonymousDistinctId).not.toHaveBeenCalled()
})

it('calls capture with user properties if passed', () => {
Expand All @@ -106,6 +112,7 @@ describe('identify()', () => {
{ $set: { email: 'john@example.com' } },
{ $set_once: { howOftenAmISet: 'once!' } }
)
expect(given.overrides.featureFlags.setAnonymousDistinctId).toHaveBeenCalledWith('oldIdentity')
})

describe('identity did not change', () => {
Expand All @@ -116,6 +123,7 @@ describe('identify()', () => {

expect(given.overrides.capture).not.toHaveBeenCalled()
expect(given.overrides.people.set).not.toHaveBeenCalled()
expect(given.overrides.featureFlags.setAnonymousDistinctId).not.toHaveBeenCalled()
})

it('calls people.set when user properties passed', () => {
Expand All @@ -125,6 +133,7 @@ describe('identify()', () => {
given.subject()

expect(given.overrides.capture).not.toHaveBeenCalled()
expect(given.overrides.featureFlags.setAnonymousDistinctId).not.toHaveBeenCalled()
expect(given.overrides.people.set).toHaveBeenCalledWith({ email: 'john@example.com' })
expect(given.overrides.people.set_once).toHaveBeenCalledWith({ howOftenAmISet: 'once!' })
})
Expand All @@ -148,6 +157,7 @@ describe('identify()', () => {
it('reloads when identity changes', () => {
given.subject()

expect(given.overrides.featureFlags.setAnonymousDistinctId).toHaveBeenCalledWith('oldIdentity')
expect(given.overrides.reloadFeatureFlags).toHaveBeenCalled()
})

Expand All @@ -156,6 +166,7 @@ describe('identify()', () => {

given.subject()

expect(given.overrides.featureFlags.setAnonymousDistinctId).not.toHaveBeenCalled()
expect(given.overrides.reloadFeatureFlags).not.toHaveBeenCalled()
})

Expand All @@ -165,7 +176,7 @@ describe('identify()', () => {
given('userPropertiesToSetOnce', () => ({ howOftenAmISet: 'once!' }))

given.subject()

expect(given.overrides.featureFlags.setAnonymousDistinctId).not.toHaveBeenCalled()
expect(given.overrides.reloadFeatureFlags).not.toHaveBeenCalled()
})
})
Expand Down
3 changes: 3 additions & 0 deletions src/posthog-core.js
Original file line number Diff line number Diff line change
Expand Up @@ -880,6 +880,9 @@ PostHogLib.prototype.identify = function (new_distinct_id, userPropertiesToSet,
{ $set: userPropertiesToSet || {} },
{ $set_once: userPropertiesToSetOnce || {} }
)
// let the reload feature flag request know to send this previous distinct id
// for flag consistency
this.featureFlags.setAnonymousDistinctId(previous_distinct_id)
} else {
if (userPropertiesToSet) {
this['people'].set(userPropertiesToSet)
Expand Down
10 changes: 10 additions & 0 deletions src/posthog-featureflags.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ export class PostHogFeatureFlags {
}
}

setAnonymousDistinctId(anon_distinct_id) {
this.$anon_distinct_id = anon_distinct_id
}

setReloadingPaused(isPaused) {
this.reloadFeatureFlagsInAction = isPaused
}
Expand All @@ -116,13 +120,19 @@ export class PostHogFeatureFlags {
token: token,
distinct_id: this.instance.get_distinct_id(),
groups: this.instance.getGroups(),
$anon_distinct_id: this.$anon_distinct_id,
})

const encoded_data = _.base64Encode(json_data)
this.instance._send_request(
this.instance.get_config('api_host') + '/decide/?v=2',
{ data: encoded_data },
{ method: 'POST' },
this.instance._prepare_callback((response) => {
// reset anon_distinct_id after at least a single request with it
// makes it through
this.$anon_distinct_id = undefined

this.receivedFeatureFlags(response)

// :TRICKY: Reload - start another request if queued!
Expand Down

0 comments on commit 3f7526b

Please sign in to comment.