@amplitude/experiment-js-client
Advanced tools
Comparing version 1.2.0 to 1.3.0
@@ -6,2 +6,13 @@ # Change Log | ||
# [1.3.0](https://github.com/amplitude/experiment-js-client/compare/v1.2.0...v1.3.0) (2021-10-18) | ||
### Features | ||
* unset user properties when variant evaluates to none or is a fallback ([#13](https://github.com/amplitude/experiment-js-client/issues/13)) ([dbab7e8](https://github.com/amplitude/experiment-js-client/commit/dbab7e83659628edcd4fca71e001fc38cae6b27b)) | ||
# [1.2.0](https://github.com/amplitude/experiment-js-client/compare/v1.1.1...v1.2.0) (2021-08-12) | ||
@@ -8,0 +19,0 @@ |
@@ -7,53 +7,4 @@ (function (global, factory) { | ||
/** | ||
* Determines the primary source of variants before falling back. | ||
* | ||
* @category Configuration | ||
*/ | ||
(function (Source) { | ||
/** | ||
* The default way to source variants within your application. Before the | ||
* assignments are fetched, `getVariant(s)` will fallback to local storage | ||
* first, then `initialVariants` if local storage is empty. This option | ||
* effectively falls back to an assignment fetched previously. | ||
*/ | ||
Source["LocalStorage"] = "localStorage"; | ||
/** | ||
* This bootstrap option is used primarily for servers-side rendering using an | ||
* Experiment server SDK. This bootstrap option always prefers the config | ||
* `initialVariants` over data in local storage, even if variants are fetched | ||
* successfully and stored locally. | ||
*/ | ||
Source["InitialVariants"] = "initialVariants"; | ||
})(exports.Source || (exports.Source = {})); | ||
/** | ||
Defaults for Experiment Config options | ||
var safeGlobal = typeof globalThis !== 'undefined' ? globalThis : global || self; | ||
| **Option** | **Default** | | ||
|------------------|-----------------------------------| | ||
| **debug** | `false` | | ||
| **fallbackVariant** | `null` | | ||
| **initialVariants** | `null` | | ||
| **source** | `Source.LocalStorage` | | ||
| **serverUrl** | `"https://api.lab.amplitude.com"` | | ||
| **assignmentTimeoutMillis** | `10000` | | ||
| **retryFailedAssignment** | `true` | | ||
| **userProvider** | `null` | | ||
| **analyticsProvider** | `null` | | ||
* | ||
* @category Configuration | ||
*/ | ||
var Defaults = { | ||
debug: false, | ||
fallbackVariant: {}, | ||
initialVariants: {}, | ||
source: exports.Source.LocalStorage, | ||
serverUrl: 'https://api.lab.amplitude.com', | ||
fetchTimeoutMillis: 10000, | ||
retryFetchOnFailure: true, | ||
userProvider: null, | ||
analyticsProvider: null, | ||
}; | ||
/** | ||
@@ -104,7 +55,16 @@ * An AmplitudeUserProvider injects information from the Amplitude SDK into | ||
AmplitudeAnalyticsProvider.prototype.track = function (event) { | ||
if (event.userProperties) { | ||
this.amplitudeInstance.setUserProperties(event.userProperties); | ||
} | ||
this.amplitudeInstance.logEvent(event.name, event.properties); | ||
}; | ||
AmplitudeAnalyticsProvider.prototype.setUserProperty = function (event) { | ||
var _a; | ||
var _b; | ||
// if the variant has a value, set the user property and log an event | ||
this.amplitudeInstance.setUserProperties((_a = {}, | ||
_a[event.userProperty] = (_b = event.variant) === null || _b === void 0 ? void 0 : _b.value, | ||
_a)); | ||
}; | ||
AmplitudeAnalyticsProvider.prototype.unsetUserProperty = function (event) { | ||
// if the variant does not have a value, unset the user property | ||
this.amplitudeInstance.identify(new safeGlobal.amplitude.Identify().unset(event.userProperty)); | ||
}; | ||
return AmplitudeAnalyticsProvider; | ||
@@ -287,5 +247,81 @@ }()); | ||
var version = "1.2.0"; | ||
var version = "1.3.0"; | ||
/** | ||
* Determines the primary source of variants before falling back. | ||
* | ||
* @category Source | ||
*/ | ||
(function (Source) { | ||
/** | ||
* The default way to source variants within your application. Before the | ||
* assignments are fetched, `getVariant(s)` will fallback to local storage | ||
* first, then `initialVariants` if local storage is empty. This option | ||
* effectively falls back to an assignment fetched previously. | ||
*/ | ||
Source["LocalStorage"] = "localStorage"; | ||
/** | ||
* This bootstrap option is used primarily for servers-side rendering using an | ||
* Experiment server SDK. This bootstrap option always prefers the config | ||
* `initialVariants` over data in local storage, even if variants are fetched | ||
* successfully and stored locally. | ||
*/ | ||
Source["InitialVariants"] = "initialVariants"; | ||
})(exports.Source || (exports.Source = {})); | ||
/** | ||
* Indicates from which source the variant() function determines the variant | ||
* | ||
* @category Source | ||
*/ | ||
var VariantSource; | ||
(function (VariantSource) { | ||
VariantSource["LocalStorage"] = "storage"; | ||
VariantSource["InitialVariants"] = "initial"; | ||
VariantSource["SecondaryLocalStoraage"] = "secondary-storage"; | ||
VariantSource["SecondaryInitialVariants"] = "secondary-initial"; | ||
VariantSource["FallbackInline"] = "fallback-inline"; | ||
VariantSource["FallbackConfig"] = "fallback-config"; | ||
})(VariantSource || (VariantSource = {})); | ||
/** | ||
* Returns true if the VariantSource is one of the fallbacks (inline or config) | ||
* | ||
* @param source a {@link VariantSource} | ||
* @returns true if source is {@link VariantSource.FallbackInline} or {@link VariantSource.FallbackConfig} | ||
*/ | ||
var isFallback = function (source) { | ||
return (source === VariantSource.FallbackInline || | ||
source === VariantSource.FallbackConfig); | ||
}; | ||
/** | ||
Defaults for Experiment Config options | ||
| **Option** | **Default** | | ||
|------------------|-----------------------------------| | ||
| **debug** | `false` | | ||
| **fallbackVariant** | `null` | | ||
| **initialVariants** | `null` | | ||
| **source** | `Source.LocalStorage` | | ||
| **serverUrl** | `"https://api.lab.amplitude.com"` | | ||
| **assignmentTimeoutMillis** | `10000` | | ||
| **retryFailedAssignment** | `true` | | ||
| **userProvider** | `null` | | ||
| **analyticsProvider** | `null` | | ||
* | ||
* @category Configuration | ||
*/ | ||
var Defaults = { | ||
debug: false, | ||
fallbackVariant: {}, | ||
initialVariants: {}, | ||
source: exports.Source.LocalStorage, | ||
serverUrl: 'https://api.lab.amplitude.com', | ||
fetchTimeoutMillis: 10000, | ||
retryFetchOnFailure: true, | ||
userProvider: null, | ||
analyticsProvider: null, | ||
}; | ||
/** | ||
* @packageDocumentation | ||
@@ -400,4 +436,2 @@ * @internal | ||
var safeGlobal = typeof globalThis !== 'undefined' ? globalThis : global || self; | ||
/** | ||
@@ -437,19 +471,28 @@ * @packageDocumentation | ||
*/ | ||
var ExposureEvent = /** @class */ (function () { | ||
function ExposureEvent(user, key, variant) { | ||
var _a; | ||
this.name = '[Experiment] Exposure'; | ||
this.key = key; | ||
this.variant = variant; | ||
this.properties = { | ||
var exposureEvent = function (user, key, variant, source) { | ||
var _a; | ||
var name = '[Experiment] Exposure'; | ||
var value = variant === null || variant === void 0 ? void 0 : variant.value; | ||
var userProperty = "[Experiment] " + key; | ||
return { | ||
name: name, | ||
user: user, | ||
key: key, | ||
variant: variant, | ||
userProperty: userProperty, | ||
properties: { | ||
key: key, | ||
variant: variant.value, | ||
}; | ||
this.userProperties = (_a = {}, | ||
_a["[Experiment] " + key] = variant.value, | ||
_a); | ||
} | ||
return ExposureEvent; | ||
}()); | ||
variant: value, | ||
source: source, | ||
}, | ||
userProperties: (_a = {}, | ||
_a[userProperty] = value, | ||
_a), | ||
}; | ||
}; | ||
var isNullOrUndefined = function (value) { | ||
return value === null || value === undefined; | ||
}; | ||
var Backoff = /** @class */ (function () { | ||
@@ -701,5 +744,5 @@ function Backoff(attempts, min, max, scalar) { | ||
* | ||
* If an {@link ExperimentAnalyticsProvider} is configured, this function will | ||
* call the provider with an {@link ExposureEvent}. The exposure event does | ||
* not count towards your event volume within Amplitude. | ||
* If an {@link ExperimentAnalyticsProvider} is configured and trackExposure is | ||
* true, this function will call the provider with an {@link ExposureEvent}. | ||
* The exposure event does not count towards your event volume within Amplitude. | ||
* | ||
@@ -712,15 +755,85 @@ * @param key The key to get the variant for. | ||
ExperimentClient.prototype.variant = function (key, fallback) { | ||
var _a, _b, _c; | ||
var _a, _b, _c, _d, _e; | ||
if (!this.apiKey) { | ||
return { value: undefined }; | ||
} | ||
var sourceVariant = this.sourceVariants()[key]; | ||
if (sourceVariant === null || sourceVariant === void 0 ? void 0 : sourceVariant.value) { | ||
(_a = this.config.analyticsProvider) === null || _a === void 0 ? void 0 : _a.track(new ExposureEvent(this.addContext(this.getUser()), key, this.convertVariant(sourceVariant))); | ||
var _f = this.variantAndSource(key, fallback), source = _f.source, variant = _f.variant; | ||
if (isFallback(source) || !(variant === null || variant === void 0 ? void 0 : variant.value)) { | ||
// fallbacks indicate not being allocated into an experiment, so | ||
// we can unset the property | ||
(_b = (_a = this.config.analyticsProvider) === null || _a === void 0 ? void 0 : _a.unsetUserProperty) === null || _b === void 0 ? void 0 : _b.call(_a, exposureEvent(this.addContext(this.getUser()), key, variant, source)); | ||
} | ||
var variant = (_c = (_b = sourceVariant !== null && sourceVariant !== void 0 ? sourceVariant : fallback) !== null && _b !== void 0 ? _b : this.secondaryVariants()[key]) !== null && _c !== void 0 ? _c : this.config.fallbackVariant; | ||
var converted = this.convertVariant(variant); | ||
this.debug("[Experiment] variant for " + key + " is " + converted.value); | ||
return converted; | ||
else if (variant === null || variant === void 0 ? void 0 : variant.value) { | ||
// only track when there's a value for a non fallback variant | ||
var event_1 = exposureEvent(this.addContext(this.getUser()), key, variant, source); | ||
(_d = (_c = this.config.analyticsProvider) === null || _c === void 0 ? void 0 : _c.setUserProperty) === null || _d === void 0 ? void 0 : _d.call(_c, event_1); | ||
(_e = this.config.analyticsProvider) === null || _e === void 0 ? void 0 : _e.track(event_1); | ||
} | ||
this.debug("[Experiment] variant for " + key + " is " + variant.value); | ||
return variant; | ||
}; | ||
ExperimentClient.prototype.variantAndSource = function (key, fallback) { | ||
if (this.config.source === exports.Source.InitialVariants) { | ||
// for source = InitialVariants, fallback order goes: | ||
// 1. InitialFlags | ||
// 2. Local Storage | ||
// 3. Function fallback | ||
// 4. Config fallback | ||
var sourceVariant = this.sourceVariants()[key]; | ||
if (!isNullOrUndefined(sourceVariant)) { | ||
return { | ||
variant: this.convertVariant(sourceVariant), | ||
source: VariantSource.InitialVariants, | ||
}; | ||
} | ||
var secondaryVariant = this.secondaryVariants()[key]; | ||
if (!isNullOrUndefined(secondaryVariant)) { | ||
return { | ||
variant: this.convertVariant(secondaryVariant), | ||
source: VariantSource.SecondaryLocalStoraage, | ||
}; | ||
} | ||
if (!isNullOrUndefined(fallback)) { | ||
return { | ||
variant: this.convertVariant(fallback), | ||
source: VariantSource.FallbackInline, | ||
}; | ||
} | ||
return { | ||
variant: this.convertVariant(this.config.fallbackVariant), | ||
source: VariantSource.FallbackConfig, | ||
}; | ||
} | ||
else { | ||
// for source = LocalStorage, fallback order goes: | ||
// 1. Local Storage | ||
// 2. Function fallback | ||
// 3. InitialFlags | ||
// 4. Config fallback | ||
var sourceVariant = this.sourceVariants()[key]; | ||
if (!isNullOrUndefined(sourceVariant)) { | ||
return { | ||
variant: this.convertVariant(sourceVariant), | ||
source: VariantSource.LocalStorage, | ||
}; | ||
} | ||
if (!isNullOrUndefined(fallback)) { | ||
return { | ||
variant: this.convertVariant(fallback), | ||
source: VariantSource.FallbackInline, | ||
}; | ||
} | ||
var secondaryVariant = this.secondaryVariants()[key]; | ||
if (!isNullOrUndefined(secondaryVariant)) { | ||
return { | ||
variant: this.convertVariant(secondaryVariant), | ||
source: VariantSource.SecondaryInitialVariants, | ||
}; | ||
} | ||
return { | ||
variant: this.convertVariant(this.config.fallbackVariant), | ||
source: VariantSource.FallbackConfig, | ||
}; | ||
} | ||
}; | ||
/** | ||
@@ -727,0 +840,0 @@ * Returns all variants for the user. |
import { ExperimentAnalyticsProvider, ExperimentUserProvider } from './types/provider'; | ||
import { Source } from './types/source'; | ||
import { Variant, Variants } from './types/variant'; | ||
/** | ||
* Determines the primary source of variants before falling back. | ||
* | ||
* @category Configuration | ||
*/ | ||
export declare enum Source { | ||
/** | ||
* The default way to source variants within your application. Before the | ||
* assignments are fetched, `getVariant(s)` will fallback to local storage | ||
* first, then `initialVariants` if local storage is empty. This option | ||
* effectively falls back to an assignment fetched previously. | ||
*/ | ||
LocalStorage = "localStorage", | ||
/** | ||
* This bootstrap option is used primarily for servers-side rendering using an | ||
* Experiment server SDK. This bootstrap option always prefers the config | ||
* `initialVariants` over data in local storage, even if variants are fetched | ||
* successfully and stored locally. | ||
*/ | ||
InitialVariants = "initialVariants" | ||
} | ||
/** | ||
* @category Configuration | ||
*/ | ||
export interface ExperimentConfig { | ||
@@ -28,0 +8,0 @@ /** |
@@ -66,5 +66,5 @@ /** | ||
* | ||
* If an {@link ExperimentAnalyticsProvider} is configured, this function will | ||
* call the provider with an {@link ExposureEvent}. The exposure event does | ||
* not count towards your event volume within Amplitude. | ||
* If an {@link ExperimentAnalyticsProvider} is configured and trackExposure is | ||
* true, this function will call the provider with an {@link ExposureEvent}. | ||
* The exposure event does not count towards your event volume within Amplitude. | ||
* | ||
@@ -77,2 +77,3 @@ * @param key The key to get the variant for. | ||
variant(key: string, fallback?: string | Variant): Variant; | ||
private variantAndSource; | ||
/** | ||
@@ -79,0 +80,0 @@ * Returns all variants for the user. |
@@ -7,3 +7,3 @@ /** | ||
*/ | ||
export { ExperimentConfig, Source } from './config'; | ||
export { ExperimentConfig } from './config'; | ||
export { AmplitudeUserProvider, AmplitudeAnalyticsProvider, } from './integration/amplitude'; | ||
@@ -15,3 +15,4 @@ export { Experiment } from './factory'; | ||
export { ExperimentUserProvider, ExperimentAnalyticsProvider, } from './types/provider'; | ||
export { Source } from './types/source'; | ||
export { ExperimentUser } from './types/user'; | ||
export { Variant, Variants } from './types/variant'; |
import { ExperimentAnalyticsEvent } from '../types/analytics'; | ||
import { ExperimentUserProvider, ExperimentAnalyticsProvider } from '../types/provider'; | ||
import { ExperimentUser } from '../types/user'; | ||
declare global { | ||
var amplitude: any; | ||
} | ||
declare type AmplitudeIdentify = { | ||
set(property: string, value: unknown): void; | ||
unset(property: string): void; | ||
}; | ||
declare type AmplitudeInstance = { | ||
@@ -9,2 +16,3 @@ options?: AmplitudeOptions; | ||
setUserProperties(userProperties: Record<string, unknown>): void; | ||
identify(identify: AmplitudeIdentify): void; | ||
}; | ||
@@ -47,3 +55,5 @@ declare type AmplitudeOptions = { | ||
track(event: ExperimentAnalyticsEvent): void; | ||
setUserProperty(event: ExperimentAnalyticsEvent): void; | ||
unsetUserProperty(event: ExperimentAnalyticsEvent): void; | ||
} | ||
export {}; |
@@ -0,1 +1,2 @@ | ||
import { VariantSource } from './source'; | ||
import { ExperimentUser } from './user'; | ||
@@ -20,2 +21,9 @@ import { Variant } from './variant'; | ||
* {@link ExperimentAnalyticsProvider}. | ||
* This is equivalent to | ||
* ``` | ||
* { | ||
* "key": key, | ||
* "variant": variant, | ||
* } | ||
* ``` | ||
*/ | ||
@@ -25,13 +33,10 @@ properties: Record<string, string>; | ||
* User properties to identify with the user prior to sending the event. | ||
* This is equivalent to | ||
* ``` | ||
* { | ||
* [userProperty]: variant | ||
* } | ||
* ``` | ||
*/ | ||
userProperties?: Record<string, unknown>; | ||
} | ||
/** | ||
* Event for tracking a user's exposure to a variant. This event will not count | ||
* towards your analytics event volume. | ||
*/ | ||
export declare class ExposureEvent implements ExperimentAnalyticsEvent { | ||
name: string; | ||
properties: Record<string, string>; | ||
userProperties?: Record<string, unknown>; | ||
/** | ||
@@ -49,3 +54,11 @@ * The user exposed to the flag/experiment variant. | ||
variant: Variant; | ||
constructor(user: ExperimentUser, key: string, variant: Variant); | ||
/** | ||
* The user property for the flag/experiment (auto-generated from the key) | ||
*/ | ||
userProperty: string; | ||
} | ||
/** | ||
* Event for tracking a user's exposure to a variant. This event will not count | ||
* towards your analytics event volume. | ||
*/ | ||
export declare const exposureEvent: (user: ExperimentUser, key: string, variant: Variant, source: VariantSource) => ExperimentAnalyticsEvent; |
@@ -18,3 +18,23 @@ import { ExperimentAnalyticsEvent } from './analytics'; | ||
export interface ExperimentAnalyticsProvider { | ||
/** | ||
* Wraps an analytics event track call. This is typically called by the | ||
* experiment client after setting user properties to track an | ||
* "[Experiment] Exposure" event | ||
* @param event see {@link ExperimentAnalyticsEvent} | ||
*/ | ||
track(event: ExperimentAnalyticsEvent): void; | ||
/** | ||
* Wraps an analytics identify or set user property call. This is typically | ||
* called by the experiment client before sending an | ||
* "[Experiment] Exposure" event. | ||
* @param event see {@link ExperimentAnalyticsEvent} | ||
*/ | ||
setUserProperty?(event: ExperimentAnalyticsEvent): void; | ||
/** | ||
* Wraps an analytics unset user property call. This is typically | ||
* called by the experiment client when a user has been evaluated to use | ||
* a fallback variant. | ||
* @param event see {@link ExperimentAnalyticsEvent} | ||
*/ | ||
unsetUserProperty?(event: ExperimentAnalyticsEvent): void; | ||
} |
{ | ||
"name": "@amplitude/experiment-js-client", | ||
"version": "1.2.0", | ||
"version": "1.3.0", | ||
"description": "Javascript Client SDK for Amplitude Experiment", | ||
@@ -34,3 +34,3 @@ "main": "dist/experiment.umd.js", | ||
}, | ||
"gitHead": "5c67aba98fd8e61cba4897328d75ef955bdc5923" | ||
"gitHead": "1e45f1b8314b0398d6a7d1ae9e7a5851e0f96ec6" | ||
} |
71450
25
1682