New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

analytics-client

Package Overview
Dependencies
Maintainers
1
Versions
145
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

analytics-client - npm Package Compare versions

Comparing version 0.4.0-roman-experiments-fd969ee053e500586da4050c94ba48bf6ab03da1 to 0.4.0

6

CHANGELOG.md

@@ -7,6 +7,10 @@ # Change Log

## 0.4.0 - 2020-03-17
## 0.4.0 - 2020-03-18
* Add experiments implementation [Roman Mazur]
## 0.3.1 - 2020-03-18
* Bump balena linter [Roman Mazur]
## 0.3.0 - 2020-03-17

@@ -13,0 +17,0 @@

export { AnalyticsUrlParams } from './src/url-params';
export { Client, Config, createClient } from './src/client';
export { Experiment, LocalExperiment } from './src/experiment';

@@ -7,1 +7,3 @@ "use strict";

exports.createClient = client_1.createClient;
var experiment_1 = require("./src/experiment");
exports.LocalExperiment = experiment_1.LocalExperiment;

8

dist/src/experiment.d.ts

@@ -10,10 +10,8 @@ import { Client } from './client';

private readonly data;
private defaultVariation?;
private coveredPercent;
constructor(name: string, analytics?: Client | undefined);
private static checkPercent;
private checkDuplicates;
private static dataString;
define(variation: Variation, fraction: number): LocalExperiment<Variation>;
defineDefault(variation: Variation): LocalExperiment<Variation>;
engage(userId: string): Variation;
define(variation: Variation, targetPercent: number): LocalExperiment<Variation>;
engage(deviceId: string): Variation;
}

@@ -5,2 +5,7 @@ "use strict";

var LOCAL_STORAGE_EXPERIMENTS_PREFIX = '__analytics_experiments_';
var checkPercent = function (f) {
if (isNaN(f) || f < 0 || f > 100) {
throw new Error('Variation target percent must be defined as a percent value between 0 and 100');
}
};
var LocalExperiment = (function () {

@@ -11,29 +16,23 @@ function LocalExperiment(name, analytics) {

this.data = [];
this.coveredPercent = 0;
}
LocalExperiment.checkPercent = function (f) {
if (isNaN(f) || f < 0 || f > 100) {
throw new Error('Variation fraction must be defines as a percent value between 0 and 100');
}
};
LocalExperiment.prototype.checkDuplicates = function (variation) {
var present = this.data.find(function (d) { return d.variation === variation; });
if (present != null) {
throw new Error("Variation [" + present.variation + " " + present.fraction + "%] already exists in experiment " + this.name + ".");
throw new Error("Variation [" + present.variation + " " + present.targetPercent + "%] already exists in experiment " + this.name + ".");
}
};
LocalExperiment.dataString = function (data) {
return data.map(function (d) { return "variation " + d.variation + ": " + d.fraction + "%"; }).join(', ');
return data
.map(function (d) { return "variation " + d.variation + ": " + d.targetPercent + "%"; })
.join(', ');
};
LocalExperiment.prototype.define = function (variation, fraction) {
LocalExperiment.checkPercent(fraction);
LocalExperiment.prototype.define = function (variation, targetPercent) {
checkPercent(targetPercent);
this.checkDuplicates(variation);
var previousMargin = 0;
if (this.data.length > 0) {
previousMargin = this.data[this.data.length - 1].margin;
}
var margin = previousMargin + fraction;
var varData = { variation: variation, margin: margin, fraction: fraction };
if (margin > 100) {
var varData = { variation: variation, targetPercent: targetPercent };
this.coveredPercent += targetPercent;
if (this.coveredPercent > 100) {
var allData = LocalExperiment.dataString(this.data.concat(varData));
throw new Error("Incorrect fraction in experiment " + this.name + ". Sum of fractions is greater than 100%: " + allData);
throw new Error("Incorrect target percent in experiment " + this.name + ". Sum of fractions is greater than 100%: " + allData);
}

@@ -43,19 +42,13 @@ this.data.push(varData);

};
LocalExperiment.prototype.defineDefault = function (variation) {
this.defaultVariation = variation;
return this;
};
LocalExperiment.prototype.engage = function (userId) {
LocalExperiment.prototype.engage = function (deviceId) {
if (this.data.length === 0) {
throw new Error("Variations are not defined for experiment " + this.name);
}
if (this.data[this.data.length - 1].margin < 100 &&
this.defaultVariation == null) {
throw new Error("Experiments is not fully defined. Current data: " + LocalExperiment.dataString(this.data));
if (this.coveredPercent < 100) {
throw new Error("Experiment " + this.name + " is not fully defined. Current data: " + LocalExperiment.dataString(this.data));
}
if (window.localStorage == null) {
console.log(userId);
return this.defaultVariation || this.data[0].variation;
if ((window === null || window === void 0 ? void 0 : window.localStorage) == null) {
return this.data[0].variation;
}
var key = "" + LOCAL_STORAGE_EXPERIMENTS_PREFIX + this.name + "_" + userId;
var key = "" + LOCAL_STORAGE_EXPERIMENTS_PREFIX + this.name + "_" + deviceId;
var value = window.localStorage.getItem(key);

@@ -67,5 +60,7 @@ if (value != null) {

var result = null;
var margin = 0;
for (var _i = 0, _a = this.data; _i < _a.length; _i++) {
var varData = _a[_i];
if (dieRoll < varData.margin) {
margin += varData.targetPercent;
if (dieRoll < margin) {
result = varData.variation;

@@ -76,3 +71,3 @@ break;

if (result == null) {
result = this.defaultVariation;
throw new Error("Variations implementation problem: " + LocalExperiment.dataString(this.data));
}

@@ -79,0 +74,0 @@ window.localStorage.setItem(key, result);

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

try {
exp.engage('test-user');
exp.engage('test-device');
}

@@ -40,8 +40,8 @@ catch (e) {

var twoVariants = new experiment_1.LocalExperiment('test');
twoVariants.define('var1', 50).defineDefault('var2');
twoVariants.define('var1', 50).define('var2', 50);
test('engage user idempotence', function () {
var variation = twoVariants.engage('test-user-id');
var variation = twoVariants.engage('test-device-id');
expect(variation).toBeTruthy();
expect(twoVariants.engage('test-user-id')).toStrictEqual(variation);
expect(twoVariants.engage('test-user-id')).toStrictEqual(variation);
expect(twoVariants.engage('test-device-id')).toStrictEqual(variation);
expect(twoVariants.engage('test-device-id')).toStrictEqual(variation);
});

@@ -52,3 +52,3 @@ test('engage user variance', function () {

for (var i = 0; i < 100; i++) {
if (twoVariants.engage("test-user-id-" + i) === 'var1') {
if (twoVariants.engage("test-device-id-" + i) === 'var1') {
var1Counter++;

@@ -76,3 +76,3 @@ }

it('sets user property', function () {
exp.engage('test-user-1');
exp.engage('test-device-1');
expect(identifyCallsCount).toStrictEqual(1);

@@ -79,0 +79,0 @@ });

@@ -21,1 +21,6 @@ const client = analyticsClient.createClient({

client.linkDevices('test-user-1', urlHandler.allDeviceIds());
const exp = new analyticsClient.LocalExperiment('test-exp')
.define('v1', 50)
.define('v2', 50);
console.log('Variation:', exp.engage(client.deviceId()));
export { AnalyticsUrlParams } from './src/url-params';
export { Client, Config, createClient } from './src/client';
export { Experiment, LocalExperiment } from './src/experiment';
{
"name": "analytics-client",
"version": "0.4.0-roman-experiments-fd969ee053e500586da4050c94ba48bf6ab03da1",
"version": "0.4.0",
"description": "Convenient builders to compose analytics tools",

@@ -14,4 +14,4 @@ "repository": {

"test": "jest",
"prettify": "resin-lint --typescript --fix src/ test/ index.ts",
"lint": "resin-lint --typescript src/ test/ index.ts && tsc --noEmit",
"prettify": "balena-lint --typescript --fix src/ test/ index.ts",
"lint": "balena-lint --typescript src/ test/ index.ts && tsc --noEmit",
"build": "npm run lint && npm run test && tsc && webpack",

@@ -26,2 +26,3 @@ "prepublish": "npm run build"

"devDependencies": {
"@balena/lint": "^4.0.1",
"@types/jest": "^24.0.18",

@@ -35,3 +36,2 @@ "@types/js-cookie": "^2.2.5",

"mixpanel-browser": "^2.29.1",
"resin-lint": "^3.3.1",
"ts-jest": "^24.1.0",

@@ -45,6 +45,6 @@ "ts-loader": "^6.2.0",

"*.ts": [
"resin-lint --typescript --fix"
"balena-lint --typescript --fix"
],
"test/**/*.ts": [
"resin-lint --typescript --no-prettier --tests"
"balena-lint --typescript --no-prettier --tests"
]

@@ -51,0 +51,0 @@ },

@@ -26,2 +26,23 @@ Analytics client

UI experiments definition.
```typescript
import { createClient, LocalExperiment } from 'analytics-client';
const client = createClient({projectName: 'my-project'});
type Variation = 'modal' | 'sidebar-left' | 'sidebar-right';
const experiment = new LocalExperiment<Variation>('WelcomeUI', client)
.define('modal', 50)
.define('sidebar-left', 25)
.define('sidebar-right', 25);
switch (experiment.engage(client.deviceId())) {
case 'modal':
showModal();
break;
// ...
}
```
## Using without npm packages

@@ -28,0 +49,0 @@

@@ -9,3 +9,2 @@ import { Identify } from 'amplitude-js';

name: string;
engage(userId: string): Variation;

@@ -16,6 +15,13 @@ }

variation: Variation;
fraction: number;
margin: number;
targetPercent: number;
}
const checkPercent = (f: number) => {
if (isNaN(f) || f < 0 || f > 100) {
throw new Error(
'Variation target percent must be defined as a percent value between 0 and 100',
);
}
};
/** LocalExperiment allows describing an Experiment implemented with local storage. */

@@ -25,3 +31,3 @@ export class LocalExperiment<Variation extends string>

private readonly data: Array<VariationData<Variation>> = [];
private defaultVariation?: Variation;
private coveredPercent: number = 0;

@@ -33,10 +39,2 @@ constructor(

private static checkPercent(f: number) {
if (isNaN(f) || f < 0 || f > 100) {
throw new Error(
'Variation fraction must be defines as a percent value between 0 and 100',
);
}
}
private checkDuplicates(variation: Variation) {

@@ -46,3 +44,3 @@ const present = this.data.find(d => d.variation === variation);

throw new Error(
`Variation [${present.variation} ${present.fraction}%] already exists in experiment ${this.name}.`,
`Variation [${present.variation} ${present.targetPercent}%] already exists in experiment ${this.name}.`,
);

@@ -53,20 +51,20 @@ }

private static dataString(data: Array<VariationData<any>>): string {
return data.map(d => `variation ${d.variation}: ${d.fraction}%`).join(', ');
return data
.map(d => `variation ${d.variation}: ${d.targetPercent}%`)
.join(', ');
}
define(variation: Variation, fraction: number): LocalExperiment<Variation> {
LocalExperiment.checkPercent(fraction);
define(
variation: Variation,
targetPercent: number,
): LocalExperiment<Variation> {
checkPercent(targetPercent);
this.checkDuplicates(variation);
const varData = { variation, targetPercent };
let previousMargin = 0;
if (this.data.length > 0) {
previousMargin = this.data[this.data.length - 1].margin;
}
const margin = previousMargin + fraction;
const varData = { variation, margin, fraction };
if (margin > 100) {
this.coveredPercent += targetPercent;
if (this.coveredPercent > 100) {
const allData = LocalExperiment.dataString(this.data.concat(varData));
throw new Error(
`Incorrect fraction in experiment ${this.name}. Sum of fractions is greater than 100%: ${allData}`,
`Incorrect target percent in experiment ${this.name}. Sum of fractions is greater than 100%: ${allData}`,
);

@@ -79,17 +77,11 @@ }

defineDefault(variation: Variation): LocalExperiment<Variation> {
this.defaultVariation = variation;
return this;
}
engage(userId: string): Variation {
engage(deviceId: string): Variation {
if (this.data.length === 0) {
throw new Error(`Variations are not defined for experiment ${this.name}`);
}
if (
this.data[this.data.length - 1].margin < 100 &&
this.defaultVariation == null
) {
if (this.coveredPercent < 100) {
throw new Error(
`Experiments is not fully defined. Current data: ${LocalExperiment.dataString(
`Experiment ${
this.name
} is not fully defined. Current data: ${LocalExperiment.dataString(
this.data,

@@ -100,9 +92,8 @@ )}`,

if (window.localStorage == null) {
// No storage support.
console.log(userId);
return this.defaultVariation || this.data[0].variation;
if (window?.localStorage == null) {
// No storage support. Return a consistent result.
return this.data[0].variation;
}
const key = `${LOCAL_STORAGE_EXPERIMENTS_PREFIX}${this.name}_${userId}`;
const key = `${LOCAL_STORAGE_EXPERIMENTS_PREFIX}${this.name}_${deviceId}`;
const value = window.localStorage.getItem(key);

@@ -115,4 +106,6 @@ if (value != null) {

let result: Variation | null = null;
let margin = 0;
for (const varData of this.data) {
if (dieRoll < varData.margin) {
margin += varData.targetPercent;
if (dieRoll < margin) {
result = varData.variation;

@@ -123,4 +116,9 @@ break;

if (result == null) {
result = this.defaultVariation!;
throw new Error(
`Variations implementation problem: ${LocalExperiment.dataString(
this.data,
)}`,
);
}
window.localStorage.setItem(key, result);

@@ -127,0 +125,0 @@ if (this.analytics != null) {

@@ -30,3 +30,3 @@ import { createClient } from '../src/client';

try {
exp.engage('test-user');
exp.engage('test-device');
} catch (e) {

@@ -39,10 +39,10 @@ expect(e.message).toContain('fully');

const twoVariants = new LocalExperiment<'var1' | 'var2'>('test');
twoVariants.define('var1', 50).defineDefault('var2');
twoVariants.define('var1', 50).define('var2', 50);
test('engage user idempotence', () => {
const variation = twoVariants.engage('test-user-id');
const variation = twoVariants.engage('test-device-id');
expect(variation).toBeTruthy();
expect(twoVariants.engage('test-user-id')).toStrictEqual(variation);
expect(twoVariants.engage('test-user-id')).toStrictEqual(variation);
expect(twoVariants.engage('test-device-id')).toStrictEqual(variation);
expect(twoVariants.engage('test-device-id')).toStrictEqual(variation);
});

@@ -54,3 +54,3 @@

for (let i = 0; i < 100; i++) {
if (twoVariants.engage(`test-user-id-${i}`) === 'var1') {
if (twoVariants.engage(`test-device-id-${i}`) === 'var1') {
var1Counter++;

@@ -83,3 +83,3 @@ } else {

it('sets user property', () => {
exp.engage('test-user-1');
exp.engage('test-device-1');
expect(identifyCallsCount).toStrictEqual(1);

@@ -86,0 +86,0 @@ });

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc