-
Notifications
You must be signed in to change notification settings - Fork 42
feat: v2 analytics and split testing #247
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
Changes from all commits
84229ac
c825c8d
08ba379
e898725
951dd13
dd6bd15
1aa3e0c
9cef36f
e035b4d
a4371e3
5d254be
9c377ce
3a23dd7
3c13bca
ded79ff
db37d65
54d3889
1936779
ebfbf9d
62ce8c3
fc71031
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
// Sample test | ||
import { getFlagsmith, getMockFetchWithValue, testIdentity } from './test-constants'; | ||
|
||
describe('Analytics', () => { | ||
|
||
beforeEach(() => { | ||
jest.useFakeTimers(); // Mocked to allow time to pass for analytics flush | ||
}); | ||
afterEach(() => { | ||
jest.useRealTimers(); | ||
}); | ||
test('should not attempt to track events when split testing is disabled', async () => { | ||
const { flagsmith } = getFlagsmith({ | ||
cacheFlags: true, | ||
identity: testIdentity, | ||
enableAnalytics: true, | ||
splitTestingAnalytics: false, // Disable split testing | ||
}); | ||
|
||
await expect(flagsmith.trackEvent("checkout")) | ||
.rejects.toThrow('This feature is only enabled for self-hosted customers using split testing.'); | ||
}); | ||
test('should track v1 analytics', async () => { | ||
const onChange = jest.fn(); | ||
const fetchFn = getMockFetchWithValue({ | ||
flags:[{feature:{name:"font_size"}, enabled: true}, {feature:{name:"off_value"}, enabled: false}], | ||
}); | ||
const { flagsmith, initConfig, mockFetch } = getFlagsmith({ | ||
cacheFlags: true, | ||
identity: testIdentity, | ||
enableAnalytics: true, | ||
onChange, | ||
}, fetchFn); | ||
await flagsmith.init(initConfig); | ||
flagsmith.getValue("font_size") | ||
flagsmith.hasFeature("off_value") | ||
flagsmith.hasFeature("off_value") | ||
jest.advanceTimersByTime(10000); | ||
expect(mockFetch).toHaveBeenCalledWith( | ||
`${flagsmith.api}analytics/flags/`, | ||
{ | ||
method: 'POST', | ||
body: JSON.stringify({ | ||
font_size: 1, | ||
off_value: 2, | ||
}), | ||
cache: 'no-cache', | ||
headers: { | ||
'X-Environment-Key': flagsmith.getContext().environment?.apiKey, | ||
'Content-Type': 'application/json; charset=utf-8', | ||
}, | ||
}, | ||
); | ||
}); | ||
test('should track conversion events when trackEvent is called', async () => { | ||
const onChange = jest.fn(); | ||
const fetchFn = getMockFetchWithValue({ | ||
flags:[{feature:{name:"font_size"}, enabled: true}, {feature:{name:"off_value"}, enabled: false}], | ||
}); | ||
const { flagsmith, initConfig, mockFetch } = getFlagsmith({ | ||
cacheFlags: true, | ||
identity: testIdentity, | ||
enableAnalytics: true, | ||
splitTestingAnalytics: true, | ||
onChange, | ||
}, fetchFn); | ||
await flagsmith.init(initConfig); | ||
flagsmith.getValue("font_size") | ||
flagsmith.hasFeature("off_value") | ||
flagsmith.hasFeature("off_value") | ||
await flagsmith.trackEvent('checkout'); | ||
|
||
expect(mockFetch).toHaveBeenCalledWith( | ||
`${flagsmith.api}split-testing/conversion-events/`, | ||
{ | ||
method: 'POST', | ||
body: JSON.stringify({ | ||
identity_identifier: testIdentity, | ||
type: 'checkout', | ||
}), | ||
cache: 'no-cache', | ||
headers: { | ||
'X-Environment-Key': flagsmith.getContext().environment?.apiKey, | ||
'Content-Type': 'application/json; charset=utf-8', | ||
}, | ||
}, | ||
); | ||
expect(mockFetch).toHaveBeenCalledWith( | ||
`${flagsmith.api.replace('/v1/', '/v2/')}analytics/flags/`, | ||
{ | ||
method: 'POST', | ||
body: JSON.stringify({ | ||
"evaluations": [ | ||
{ | ||
"feature_name": "font_size", | ||
"identity_identifier": "test_identity", | ||
"count": 1, | ||
"enabled_when_evaluated": true | ||
}, | ||
{ | ||
"feature_name": "off_value", | ||
"identity_identifier": "test_identity", | ||
"count": 2, | ||
"enabled_when_evaluated": false | ||
} | ||
] | ||
}), | ||
cache: 'no-cache', | ||
headers: { | ||
'X-Environment-Key': flagsmith.getContext().environment?.apiKey, | ||
'Content-Type': 'application/json; charset=utf-8', | ||
}, | ||
}, | ||
); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -90,16 +90,15 @@ export declare type LoadingState = { | |
|
||
export type OnChange<F extends string = string> = (previousFlags: IFlags<F> | null, params: IRetrieveInfo, loadingState:LoadingState) => void | ||
export interface IInitConfig<F extends string = string, T extends string = string> { | ||
environmentID: string; | ||
AsyncStorage?: any; | ||
angularHttpClient?: any; | ||
api?: string; | ||
evaluationContext?: ClientEvaluationContext; | ||
cacheFlags?: boolean; | ||
cacheOptions?: ICacheOptions; | ||
datadogRum?: IDatadogRum; | ||
defaultFlags?: IFlags<F>; | ||
fetch?: any; | ||
realtime?: boolean; | ||
eventSourceUrl?: string; | ||
enableAnalytics?: boolean; | ||
enableDynatrace?: boolean; | ||
enableLogs?: boolean; | ||
|
@@ -108,6 +107,8 @@ export interface IInitConfig<F extends string = string, T extends string = strin | |
* * @deprecated Please consider using evaluationContext.identity: {@link IInitConfig.evaluationContext}. | ||
* */ | ||
environmentID?: string; | ||
eventSourceUrl?: string; | ||
fetch?: any; | ||
headers?: object; | ||
/** | ||
* * @deprecated Please consider using evaluationContext.identity: {@link IInitConfig.evaluationContext}. | ||
|
@@ -120,7 +121,10 @@ export interface IInitConfig<F extends string = string, T extends string = strin | |
onChange?: OnChange<F>; | ||
onError?: (err: Error) => void; | ||
preventFetch?: boolean; | ||
realtime?: boolean; | ||
splitTestingAnalytics?: boolean; | ||
state?: IState; | ||
traits?: ITraits<T>; | ||
_trigger?: () => void; | ||
_triggerLoadingState?: () => void; | ||
} | ||
|
@@ -240,6 +244,11 @@ export interface IFlagsmith<F extends string = string, T extends string = string | |
* @deprecated in favour of {@link IFlagsmith.setContext}. | ||
*/ | ||
setTraits: (traits: ITraits) => Promise<void>; | ||
/** | ||
* Only available for self hosted split testing analytics. | ||
* Track a conversion event within your application, used for split testing analytics. | ||
*/ | ||
trackEvent: (event: string) => Promise<void>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's safe to assume that events could be recorded at a much higher rate than flags are evaluated, so they should definitely be batched and flushed in the background. The current implementation is probably good enough to validate the idea in a PoC but I would exclude this method from this SDK's public/versioned API until we can implement event batching. This method being async is also not ergonomic to use on event handlers for DOM events like clicking, hovering or navigating. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can't think of a case where we'd track events often, and even if they were it'd happen way less than flag evaluations since they'd occur potentially every render. The only usecase I can imagine is conversions e.g. checkout. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another use case for batching (or decoupling tracking from pushing) is for mobile/low-powered devices, where you might want to either not send events or reduce the rate at which they're sent when running on battery or on metered connections. This is a tradeoff between metrics accuracy and device impact that customers need to make, so I'd like to have some API that lets them make this decision. I suppose nothing prevents customers from implementing the batching themselves if they really want, so maybe we could rename this to |
||
/** | ||
* * @deprecated Please consider using evaluationContext.identity: {@link IFlagsmith.getContext}. | ||
* */ | ||
|
Uh oh!
There was an error while loading. Please reload this page.