🚀 Big News: Socket Acquires Coana to Bring Reachability Analysis to Every Appsec Team.Learn more
Socket
DemoInstallSign in
Socket

decentraland-experiments

Package Overview
Dependencies
Maintainers
1
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

decentraland-experiments - npm Package Compare versions

Comparing version

to
1.0.1

2

dist/Experiment.js

@@ -132,3 +132,3 @@ "use strict";

Experiment.prototype.setState = function (patchState) {
if (this.state !== undefined) {
if (patchState && this.isActive() && !this.isCompleted()) {
this.state = Object.assign({}, this.state, patchState);

@@ -135,0 +135,0 @@ }

@@ -12,3 +12,3 @@ /// <reference types="segment-analytics" />

private storage;
private analytics;
private _analytics;
/**

@@ -31,3 +31,4 @@ * Semaphore to handle localStorage or sessionStorage changes

*/
constructor(experiments: ExperimentMap, storage?: Storage, analytics?: SegmentAnalytics.AnalyticsJS | undefined);
constructor(experiments: ExperimentMap, storage: Storage, _analytics: SegmentAnalytics.AnalyticsJS | null | undefined);
readonly analytics: SegmentAnalytics.AnalyticsJS | null;
/**

@@ -34,0 +35,0 @@ * Persist methods

@@ -53,9 +53,8 @@ "use strict";

*/
function Experiments(experiments, storage, analytics) {
function Experiments(experiments, storage, _analytics) {
var _this = this;
if (storage === void 0) { storage = window_1.default.localStorage; }
if (analytics === void 0) { analytics = window_1.default.analytics; }
this.experiments = experiments;
this.storage = storage;
this.analytics = analytics;
this._analytics = _analytics;
/**

@@ -88,2 +87,5 @@ * Semaphore to handle localStorage or sessionStorage changes

}
else {
console.warn("Analytics is not present in the project, experiments framework will not generate any report. Follow this guide to include it: https://segment.com/docs/sources/website/analytics.js/quickstart/");
}
this.loadPersisted();

@@ -94,2 +96,9 @@ if (this.isBrowserStorage()) {

}
Object.defineProperty(Experiments.prototype, "analytics", {
get: function () {
return this._analytics || window_1.default.analytics || null;
},
enumerable: true,
configurable: true
});
/**

@@ -211,3 +220,3 @@ * Persist methods

if (this.analytics) {
var experimentState = experiment.state || {};
var experimentState = experiment.state;
this.analytics.track('experiment_conversion', __assign({ experiment: experiment.name, variation: experiment.variant.name }, experimentState));

@@ -214,0 +223,0 @@ }

@@ -22,7 +22,9 @@ "use strict";

var SIGN_UP_EVENT = 'sign_up_event';
function createExperiments(storage) {
function createExperiments(storage, segment) {
if (storage === void 0) { storage = window_1.default.localStorage; }
if (segment === void 0) { segment = analytics_1.default; }
return new index_1.Experiments({
avatar_sign_up_test: new index_1.Experiment({
name: 'sign_up_vs_send',
initialState: function () { return ({ calls: 0 }); },
variants: [

@@ -34,2 +36,5 @@ new index_1.Variant('sign_up', 0.5, 'Sign up'),

if (event.type === 'track' && event.name === SIGN_UP_EVENT) {
currentExperiment.setState({
calls: currentExperiment.state.calls + 1
});
currentExperiment.complete();

@@ -54,3 +59,3 @@ }

})
}, storage, analytics_1.default);
}, storage, segment);
}

@@ -79,7 +84,2 @@ describe("src/Experiments", function () {

});
test("must listen for analytics event", function () {
var experiments = createExperiments();
expect(analytics_1.on.mock.calls.length).toEqual(1);
expect(analytics_1.on.mock.calls[0]).toEqual(['track', experiments.handleTrackEvent]);
});
test("must listen for storage event", function () {

@@ -93,6 +93,20 @@ var experiments = createExperiments();

});
test("must listen for analytics event", function () {
var experiments = createExperiments();
expect(analytics_1.on.mock.calls.length).toEqual(1);
expect(analytics_1.on.mock.calls[0]).toEqual(['track', experiments.handleTrackEvent]);
});
test("must listen for window.analytics event", function () {
window_1.default.analytics = analytics_1.default;
var experiments = createExperiments(window_1.default.localStorage, null);
expect(analytics_1.on.mock.calls.length).toEqual(1);
expect(analytics_1.on.mock.calls[0]).toEqual(['track', experiments.handleTrackEvent]);
delete window_1.default.analytics;
});
test("must not fail if analytics isn't present", function () {
createExperiments(window_1.default.localStorage, null);
expect(analytics_1.on.mock.calls.length).toEqual(0);
expect(console_1.warn.mock.calls.length).toEqual(1);
});
});
describe(".emit()", function () {
test("", function () { });
});
describe(".detach()", function () {

@@ -215,3 +229,3 @@ test("must remove all analytics event", function () {

'experiment_conversion',
{ experiment: 'sign_up_vs_send', variation: 'sign_up' }
{ experiment: 'sign_up_vs_send', variation: 'sign_up', calls: 1 }
]);

@@ -218,0 +232,0 @@ });

{
"name": "decentraland-experiments",
"version": "1.0.0",
"version": "1.0.1",
"description": "Experiment Tracking Tool (A/B Testing)",

@@ -8,3 +8,2 @@ "main": "dist",

"devDependencies": {
"@semantic-release/changelog": "^3.0.4",
"@types/jest": "^24.0.18",

@@ -15,6 +14,5 @@ "@types/node": "^12.7.5",

"jest": "^24.9.0",
"jest-date-mock": "^1.0.7",
"jest-mock-random": "^1.0.2",
"semantic-release": "^15.13.24",
"ts-jest": "^24.0.2",
"tslint-language-service": "^0.9.9",
"typescript": "^3.6.3"

@@ -21,0 +19,0 @@ },

@@ -7,1 +7,167 @@ # decentraland-experiments

🛠 Experiment Tracking Tool (A/B Testing)
> Implemented from [RFC](RCF.md) ([#54](https://github.com/decentraland/decentraland-dapps/issues/54))
## Index
- [Installation](#installation)
- [Usage](#usage)
- [Vanilla JS](#vanilla-js)
- [React](#react)
- [React+Context](#react--context)
- [Testing](#testing-with-jest)
## Installation
```bash
npm install -s decentraland-experiments
```
## Usage
### Vanilla JS
Instantiate all experiments
```typescript
import { Experiments, Experiment, Variant } from 'decentraland-experiments';
const experiments = new Experiments({ avatar_signup_test: ... }, localStorage, analytics)
```
Retrieve and use the test value
```typescript
// if there are any test for `avatar_signup_test` it will be activate
const value = experiments.getCurrentValueFor('avatar_signup_test', 'Sing Up')
```
Track segments events
```typescript
analytics.track(SIGNUP_EVENT)
```
### React
Instantiate all experiments
```typescript
import { Experiments, Experiment, Variant } from 'decentraland-experiments';
const experiments = new Experiments({ avatar_signup_test: ... }, localStorage, analytics)
```
Retrieve and use the test value
```jsx
import { experiments } from 'path/to/experiments'
export default class SignUpButton extends React.PureComponent<Props, State> {
render() {
const text = experiments.getCurrentValueFor('avatar_signup_test', 'avatars.form.signup')
<Button>{t(text)}</Button>
}
}
```
Track segments events
```jsx
import { experiments } from 'path/to/experiments'
export default class SignUpButton extends React.PureComponent<Props, State> {
handleClick = (event: React.MouseEvent<HTMLElement>) => {
// ...
analytics.track(SIGNUP_EVENT)
}
render() {
const text = experiments.getCurrentValueFor('avatar_signup_test', 'avatars.form.signup')
<Button onClick={this.handleClick}>{t(text)}</Button>
}
}
```
### React + Context
Create the new Context without experiments
```typescript
import { Experiments } from 'decentraland-experiments'
const ExperimentsContext = React.createContext(new Experiments({}))
```
Instantiate all experiments
```typescript
import { Experiments, Experiment, Variant } from 'decentraland-experiments';
const experiments = new Experiments({ avatar_signup_test: ... }, localStorage, analytics)
```
Add `Context.Provider` to initial render and set the experiments instance as value property
```jsx
import ExperimentsContext from 'path/to/context'
ReactDOM.render(
<ExperimentsContext.Provider value={experiments}>
{/* ... */}
</ExperimentsContext.Provider>,
document.getElementById('root')
)
```
Add `Context` to the testing element and retrieve the test value
```jsx
import ExperimentsContext from 'path/to/context';
export default class SignUpButton extends React.PureComponent<Props, State> {
static contextType = ExperimentsContext
render() {
const text = this.context.getCurrentValueFor('avatar_signup_test', 'avatars.form.signup')
<Button>{t(text)}</Button>
}
}
```
Track segments events
```jsx
import ExperimentsContext from 'path/to/context';
export default class SignUpButton extends React.PureComponent<Props, State> {
handleClick = (event: React.MouseEvent<HTMLElement>) => {
// ...
analytics.track(SIGNUP_EVENT)
}
render() {
const text = this.context.getCurrentValueFor('avatar_signup_test', 'avatars.form.signup')
<Button>{t(text)}</Button>
}
}
```
## Testing (with Jest)
Retrieve all values and ensure its types
```typescript
import { experiments } from 'path/to/experiments'
test(`all experiment value for avatar_signup_test are not empty strings`, () => {
for (const value of experiments.getAllValuesFor('avatar_signup_test')) {
expect(typeof value).toBe('string')
expect(value.length).toBeGreaterThanOrEqual(5)
}
})
```

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet