@xylabs/sdk-react 2.12.8 → 2.12.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/common/ExperimentsHelper.d.ts +18 -0
- package/dist/cjs/common/ExperimentsHelper.js +101 -0
- package/dist/cjs/common/ExperimentsHelper.js.map +1 -0
- package/dist/cjs/common/index.d.ts +1 -0
- package/dist/cjs/common/index.js +5 -0
- package/dist/cjs/common/index.js.map +1 -0
- package/dist/cjs/components/Experiments/index.d.ts +1 -0
- package/dist/cjs/components/Experiments/index.js +1 -0
- package/dist/cjs/components/Experiments/index.js.map +1 -1
- package/dist/cjs/hooks/useExperiment.d.ts +8 -0
- package/dist/cjs/hooks/useExperiment.js +29 -0
- package/dist/cjs/hooks/useExperiment.js.map +1 -0
- package/dist/cjs/lib/Tracking/Drip/BaseEvent.js +1 -0
- package/dist/cjs/lib/Tracking/Drip/BaseEvent.js.map +1 -1
- package/dist/cjs/lib/Tracking/Tapjoy/StandardEvents.d.ts +1 -1
- package/dist/cjs/lib/Tracking/Tapjoy/StandardEvents.js +2 -2
- package/dist/cjs/lib/Tracking/Tapjoy/StandardEvents.js.map +1 -1
- package/dist/docs.json +15694 -15421
- package/dist/esm/common/ExperimentsHelper.d.ts +18 -0
- package/dist/esm/common/ExperimentsHelper.js +97 -0
- package/dist/esm/common/ExperimentsHelper.js.map +1 -0
- package/dist/esm/common/index.d.ts +1 -0
- package/dist/esm/common/index.js +2 -0
- package/dist/esm/common/index.js.map +1 -0
- package/dist/esm/components/Experiments/index.d.ts +1 -0
- package/dist/esm/components/Experiments/index.js +1 -0
- package/dist/esm/components/Experiments/index.js.map +1 -1
- package/dist/esm/hooks/useExperiment.d.ts +8 -0
- package/dist/esm/hooks/useExperiment.js +24 -0
- package/dist/esm/hooks/useExperiment.js.map +1 -0
- package/dist/esm/lib/Tracking/Drip/BaseEvent.js +1 -0
- package/dist/esm/lib/Tracking/Drip/BaseEvent.js.map +1 -1
- package/dist/esm/lib/Tracking/Tapjoy/StandardEvents.d.ts +1 -1
- package/dist/esm/lib/Tracking/Tapjoy/StandardEvents.js +2 -2
- package/dist/esm/lib/Tracking/Tapjoy/StandardEvents.js.map +1 -1
- package/package.json +1 -1
- package/src/common/ExperimentsHelper.ts +109 -0
- package/src/common/index.ts +1 -0
- package/src/components/Experiments/index.tsx +1 -0
- package/src/hooks/useExperiment.ts +30 -0
- package/src/hooks/useExperiments.stories.tsx +43 -0
- package/src/lib/Tracking/Drip/BaseEvent.ts +1 -0
- package/src/lib/Tracking/Tapjoy/StandardEvents.ts +2 -2
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Log } from '@xylabs/sdk-js';
|
|
2
|
+
import { ReactElement } from 'react';
|
|
3
|
+
import { ExperimentProps, ExperimentsData, OutcomesData, VariantData } from '../components';
|
|
4
|
+
declare const ExperimentsHelper: {
|
|
5
|
+
buildLocalStorageKey: (localStorageProp: boolean | string) => string;
|
|
6
|
+
calcTotalWeight: (variants: VariantData[]) => number;
|
|
7
|
+
calculateExperiment: (name: string, localStorageProp: string | boolean, variants: VariantData[]) => VariantData | undefined;
|
|
8
|
+
getExperiment: (name: string) => import("../components").ExperimentData;
|
|
9
|
+
getSelectedVariant: (name: string) => VariantData | undefined;
|
|
10
|
+
loadOutcomes: () => OutcomesData;
|
|
11
|
+
makeChildrenArray: (children: ReactElement<ExperimentProps>[] | ReactElement<ExperimentProps>) => ReactElement<ExperimentProps, string | import("react").JSXElementConstructor<any>>[];
|
|
12
|
+
mergeData: (data: {
|
|
13
|
+
[index: string]: string;
|
|
14
|
+
}, log?: Log | undefined) => string;
|
|
15
|
+
saveExperimentRanges: (name: string, totalWeight: number, variants: VariantData[]) => ExperimentsData;
|
|
16
|
+
saveOutcomes: () => void;
|
|
17
|
+
};
|
|
18
|
+
export { ExperimentsHelper };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { ExperimentsLocalStorageKey, OutcomesLocalStorageKey } from '../components';
|
|
2
|
+
import { getLocalStorageObject, setLocalStorageObject } from '../lib';
|
|
3
|
+
const defaultLocalStorageKey = 'testData';
|
|
4
|
+
const experimentsTestData = {};
|
|
5
|
+
let outcomes = {}; //prevent multi-outcome
|
|
6
|
+
// TODO - some expire logic around experiments
|
|
7
|
+
const ExperimentsHelper = {
|
|
8
|
+
buildLocalStorageKey: (localStorageProp) => {
|
|
9
|
+
return localStorageProp === true ? defaultLocalStorageKey : typeof localStorageProp === 'string' ? localStorageProp ?? defaultLocalStorageKey : '';
|
|
10
|
+
},
|
|
11
|
+
calcTotalWeight: (variants) => {
|
|
12
|
+
return variants.reduce((sum, variant) => {
|
|
13
|
+
return sum + variant.weight;
|
|
14
|
+
}, 0);
|
|
15
|
+
},
|
|
16
|
+
calculateExperiment: (name, localStorageProp, variants) => {
|
|
17
|
+
//TODO - user events, it needs to be in the hook, all other compatibility should
|
|
18
|
+
ExperimentsHelper.loadOutcomes();
|
|
19
|
+
const localStorageKey = ExperimentsHelper.buildLocalStorageKey(localStorageProp);
|
|
20
|
+
const totalWeight = ExperimentsHelper.calcTotalWeight(variants);
|
|
21
|
+
ExperimentsHelper.saveExperimentRanges(name, totalWeight, variants);
|
|
22
|
+
const firstTime = name in outcomes;
|
|
23
|
+
let targetWeight = outcomes[name] ?? Math.random() * totalWeight;
|
|
24
|
+
outcomes[name] = targetWeight;
|
|
25
|
+
ExperimentsHelper.saveOutcomes();
|
|
26
|
+
for (const variant of variants) {
|
|
27
|
+
targetWeight -= variant.weight;
|
|
28
|
+
if (targetWeight > 0)
|
|
29
|
+
continue;
|
|
30
|
+
if (!variant.name) {
|
|
31
|
+
throw new Error('Experiment Elements must have Keys');
|
|
32
|
+
}
|
|
33
|
+
experimentsTestData[name] = variant.name;
|
|
34
|
+
if (firstTime) {
|
|
35
|
+
localStorage.setItem(localStorageKey, ExperimentsHelper.mergeData(experimentsTestData));
|
|
36
|
+
}
|
|
37
|
+
// if (userEvents) {
|
|
38
|
+
// forget(userEvents.testStarted({ name, variation: variant.name }))
|
|
39
|
+
// }
|
|
40
|
+
return variant;
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
getExperiment: (name) => {
|
|
44
|
+
ExperimentsHelper.loadOutcomes();
|
|
45
|
+
const experiments = getLocalStorageObject(ExperimentsLocalStorageKey) || {};
|
|
46
|
+
return experiments[name];
|
|
47
|
+
},
|
|
48
|
+
getSelectedVariant: (name) => {
|
|
49
|
+
const outcomes = ExperimentsHelper.loadOutcomes();
|
|
50
|
+
const experiment = ExperimentsHelper.getExperiment(name);
|
|
51
|
+
let total = 0;
|
|
52
|
+
if (experiment && outcomes) {
|
|
53
|
+
const targetWeight = outcomes[name];
|
|
54
|
+
for (let i = 0; i < experiment.variants.length; i++) {
|
|
55
|
+
const variant = experiment.variants[i];
|
|
56
|
+
total += variant.weight;
|
|
57
|
+
if (total >= targetWeight) {
|
|
58
|
+
return variant;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
loadOutcomes: () => {
|
|
64
|
+
outcomes = getLocalStorageObject(OutcomesLocalStorageKey);
|
|
65
|
+
return outcomes;
|
|
66
|
+
},
|
|
67
|
+
makeChildrenArray: (children) => {
|
|
68
|
+
if (Array.isArray(children)) {
|
|
69
|
+
return children;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
return [children];
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
mergeData: (data, log) => {
|
|
76
|
+
const dataArray = [];
|
|
77
|
+
for (const key in data) {
|
|
78
|
+
dataArray.push(`${key}-${data[key]}`);
|
|
79
|
+
}
|
|
80
|
+
log?.info('MergeData', dataArray.join('|'));
|
|
81
|
+
return dataArray.join('|');
|
|
82
|
+
},
|
|
83
|
+
saveExperimentRanges: (name, totalWeight, variants) => {
|
|
84
|
+
const experiments = getLocalStorageObject(ExperimentsLocalStorageKey) || {};
|
|
85
|
+
experiments[name] = {
|
|
86
|
+
totalWeight,
|
|
87
|
+
variants,
|
|
88
|
+
};
|
|
89
|
+
setLocalStorageObject(ExperimentsLocalStorageKey, experiments);
|
|
90
|
+
return experiments;
|
|
91
|
+
},
|
|
92
|
+
saveOutcomes: () => {
|
|
93
|
+
setLocalStorageObject(OutcomesLocalStorageKey, outcomes);
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
export { ExperimentsHelper };
|
|
97
|
+
//# sourceMappingURL=ExperimentsHelper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExperimentsHelper.js","sourceRoot":"","sources":["../../../src/common/ExperimentsHelper.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ExperimentsHelper';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/common/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/components/Experiments/index.tsx"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,eAAe,CAAA;AAC7B,cAAc,uBAAuB,CAAA;AACrC,cAAc,oBAAoB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/components/Experiments/index.tsx"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,eAAe,CAAA;AAC7B,cAAc,uBAAuB,CAAA;AACrC,cAAc,oBAAoB,CAAA;AAClC,cAAc,UAAU,CAAA"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { VariantData } from '../components';
|
|
3
|
+
declare const selectVariantForExperiment: (name: string, variants: Record<string, ReactNode>, defaultNode: ReactNode) => ReactNode;
|
|
4
|
+
declare const useExperiments: (name: string, experiments: VariantData[]) => {
|
|
5
|
+
experimentName: string;
|
|
6
|
+
selectVariant: (variants: Record<string, ReactNode>, defaultNode: ReactNode) => ReactNode;
|
|
7
|
+
};
|
|
8
|
+
export { selectVariantForExperiment, useExperiments };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { ExperimentsHelper } from '../common';
|
|
3
|
+
const selectVariant = (current) => (variants, defaultNode) => {
|
|
4
|
+
if (current && current in variants) {
|
|
5
|
+
return variants[current];
|
|
6
|
+
}
|
|
7
|
+
return defaultNode;
|
|
8
|
+
};
|
|
9
|
+
const selectVariantForExperiment = (name, variants, defaultNode) => {
|
|
10
|
+
const variant = ExperimentsHelper.getSelectedVariant(name);
|
|
11
|
+
if (variants[variant?.name ?? '']) {
|
|
12
|
+
return variants[variant?.name ?? ''];
|
|
13
|
+
}
|
|
14
|
+
return defaultNode;
|
|
15
|
+
};
|
|
16
|
+
const useExperiments = (name, experiments) => {
|
|
17
|
+
const [activeExperiment] = useState(ExperimentsHelper.calculateExperiment(name, true, experiments));
|
|
18
|
+
return {
|
|
19
|
+
experimentName: name,
|
|
20
|
+
selectVariant: selectVariant(activeExperiment?.name),
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
export { selectVariantForExperiment, useExperiments };
|
|
24
|
+
//# sourceMappingURL=useExperiment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useExperiment.js","sourceRoot":"","sources":["../../../src/hooks/useExperiment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,QAAQ,EAAE,MAAM,OAAO,CAAA;AAE3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAG7C,MAAM,aAAa,GAAG,CAAC,OAAgB,EAAE,EAAE,CAAC,CAAC,QAAmC,EAAE,WAAsB,EAAE,EAAE;IAC1G,IAAI,OAAO,IAAI,OAAO,IAAI,QAAQ,EAAE;QAClC,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAA;KACzB;IACD,OAAO,WAAW,CAAA;AACpB,CAAC,CAAA;AAED,MAAM,0BAA0B,GAAG,CAAC,IAAY,EAAE,QAAmC,EAAE,WAAsB,EAAE,EAAE;IAC/G,MAAM,OAAO,GAAG,iBAAiB,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAA;IAC1D,IAAI,QAAQ,CAAC,OAAO,EAAE,IAAI,IAAI,EAAE,CAAC,EAAE;QACjC,OAAO,QAAQ,CAAC,OAAO,EAAE,IAAI,IAAI,EAAE,CAAC,CAAA;KACrC;IACD,OAAO,WAAW,CAAA;AACpB,CAAC,CAAA;AAED,MAAM,cAAc,GAAG,CAAC,IAAY,EAAE,WAA0B,EAAE,EAAE;IAClE,MAAM,CAAC,gBAAgB,CAAC,GAAG,QAAQ,CAAC,iBAAiB,CAAC,mBAAmB,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC,CAAA;IAEnG,OAAO;QACL,cAAc,EAAE,IAAI;QACpB,aAAa,EAAE,aAAa,CAAC,gBAAgB,EAAE,IAAI,CAAC;KACrD,CAAA;AACH,CAAC,CAAA;AAED,OAAO,EAAE,0BAA0B,EAAE,cAAc,EAAE,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BaseEvent.js","sourceRoot":"","sources":["../../../../../src/lib/Tracking/Drip/BaseEvent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAItC,MAAM,OAAO,aAAa;IAKxB,YAAY,IAAY,EAAE,KAAc;QACtC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;QAClB,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,CAAA;QACxB,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,CAAA;IAC1B,CAAC;IAEM,KAAK,CAAC,IAAI,CAAC,IAAO;QACvB,MAAM,OAAO,GAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC3C,IAAI,IAAI,CAAC,KAAK,EAAE;YACd,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;SACzB;QACD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAClB,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QACtB,MAAM,KAAK,CAAC,CAAC,CAAC,CAAA;IAChB,CAAC;IAEO,MAAM;QACZ,8DAA8D;QAC9D,MAAM,MAAM,GAAG,MAAa,CAAA;QAC5B,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE;YAChB,MAAM,KAAK,CAAC,eAAe,CAAC,CAAA;SAC7B;QACD,OAAO,MAAM,CAAC,IAAsB,CAAA;IACtC,CAAC;IAEO,MAAM;QACZ,8DAA8D;QAC9D,MAAM,MAAM,GAAG,MAAa,CAAA;QAC5B,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE;YAChB,MAAM,KAAK,CAAC,eAAe,CAAC,CAAA;SAC7B;QACD,OAAO,MAAM,CAAC,IAAsB,CAAA;IACtC,CAAC;CACF"}
|
|
1
|
+
{"version":3,"file":"BaseEvent.js","sourceRoot":"","sources":["../../../../../src/lib/Tracking/Drip/BaseEvent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAItC,MAAM,OAAO,aAAa;IAKxB,YAAY,IAAY,EAAE,KAAc;QACtC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;QAClB,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,CAAA;QACxB,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,CAAA;IAC1B,CAAC;IAEM,KAAK,CAAC,IAAI,CAAC,IAAO;QACvB,MAAM,OAAO,GAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC3C,IAAI,IAAI,CAAC,KAAK,EAAE;YACd,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;SACzB;QACD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAClB,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,OAAO,CAAC,CAAA;QACjC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QACtB,MAAM,KAAK,CAAC,CAAC,CAAC,CAAA;IAChB,CAAC;IAEO,MAAM;QACZ,8DAA8D;QAC9D,MAAM,MAAM,GAAG,MAAa,CAAA;QAC5B,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE;YAChB,MAAM,KAAK,CAAC,eAAe,CAAC,CAAA;SAC7B;QACD,OAAO,MAAM,CAAC,IAAsB,CAAA;IACtC,CAAC;IAEO,MAAM;QACZ,8DAA8D;QAC9D,MAAM,MAAM,GAAG,MAAa,CAAA;QAC5B,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE;YAChB,MAAM,KAAK,CAAC,eAAe,CAAC,CAAA;SAC7B;QACD,OAAO,MAAM,CAAC,IAAsB,CAAA;IACtC,CAAC;CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StandardEvents.js","sourceRoot":"","sources":["../../../../../src/lib/Tracking/Tapjoy/StandardEvents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAErD,MAAM,oBAAoB;IACjB,
|
|
1
|
+
{"version":3,"file":"StandardEvents.js","sourceRoot":"","sources":["../../../../../src/lib/Tracking/Tapjoy/StandardEvents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAErD,MAAM,oBAAoB;IACjB,cAAc;QACnB,OAAO,IAAI,mBAAmB,CAAI,YAAY,CAAC,CAAA;IACjD,CAAC;CACF;AAED,OAAO,EAAE,oBAAoB,EAAE,CAAA"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { Log } from '@xylabs/sdk-js'
|
|
2
|
+
import { ReactElement } from 'react'
|
|
3
|
+
|
|
4
|
+
import { ExperimentProps, ExperimentsData, ExperimentsLocalStorageKey, OutcomesData, OutcomesLocalStorageKey, VariantData } from '../components'
|
|
5
|
+
import { getLocalStorageObject, setLocalStorageObject } from '../lib'
|
|
6
|
+
|
|
7
|
+
const defaultLocalStorageKey = 'testData'
|
|
8
|
+
const experimentsTestData: { [index: string]: string } = {}
|
|
9
|
+
let outcomes: OutcomesData = {} //prevent multi-outcome
|
|
10
|
+
|
|
11
|
+
// TODO - some expire logic around experiments
|
|
12
|
+
const ExperimentsHelper = {
|
|
13
|
+
buildLocalStorageKey: (localStorageProp: boolean | string) => {
|
|
14
|
+
return localStorageProp === true ? defaultLocalStorageKey : typeof localStorageProp === 'string' ? localStorageProp ?? defaultLocalStorageKey : ''
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
calcTotalWeight: (variants: VariantData[]) => {
|
|
18
|
+
return variants.reduce((sum, variant) => {
|
|
19
|
+
return sum + variant.weight
|
|
20
|
+
}, 0)
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
calculateExperiment: (name: string, localStorageProp: string | boolean, variants: VariantData[]) => {
|
|
24
|
+
//TODO - user events, it needs to be in the hook, all other compatibility should
|
|
25
|
+
ExperimentsHelper.loadOutcomes()
|
|
26
|
+
const localStorageKey = ExperimentsHelper.buildLocalStorageKey(localStorageProp)
|
|
27
|
+
const totalWeight = ExperimentsHelper.calcTotalWeight(variants)
|
|
28
|
+
ExperimentsHelper.saveExperimentRanges(name, totalWeight, variants)
|
|
29
|
+
const firstTime = name in outcomes
|
|
30
|
+
let targetWeight = outcomes[name] ?? Math.random() * totalWeight
|
|
31
|
+
outcomes[name] = targetWeight
|
|
32
|
+
ExperimentsHelper.saveOutcomes()
|
|
33
|
+
for (const variant of variants) {
|
|
34
|
+
targetWeight -= variant.weight
|
|
35
|
+
if (targetWeight > 0) continue
|
|
36
|
+
if (!variant.name) {
|
|
37
|
+
throw new Error('Experiment Elements must have Keys')
|
|
38
|
+
}
|
|
39
|
+
experimentsTestData[name] = variant.name
|
|
40
|
+
if (firstTime) {
|
|
41
|
+
localStorage.setItem(localStorageKey, ExperimentsHelper.mergeData(experimentsTestData))
|
|
42
|
+
}
|
|
43
|
+
// if (userEvents) {
|
|
44
|
+
// forget(userEvents.testStarted({ name, variation: variant.name }))
|
|
45
|
+
// }
|
|
46
|
+
return variant
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
getExperiment: (name: string) => {
|
|
51
|
+
ExperimentsHelper.loadOutcomes()
|
|
52
|
+
const experiments = getLocalStorageObject<ExperimentsData>(ExperimentsLocalStorageKey) || {}
|
|
53
|
+
return experiments[name]
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
getSelectedVariant: (name: string) => {
|
|
57
|
+
const outcomes = ExperimentsHelper.loadOutcomes()
|
|
58
|
+
const experiment = ExperimentsHelper.getExperiment(name)
|
|
59
|
+
let total = 0
|
|
60
|
+
if (experiment && outcomes) {
|
|
61
|
+
const targetWeight = outcomes[name]
|
|
62
|
+
for (let i = 0; i < experiment.variants.length; i++) {
|
|
63
|
+
const variant = experiment.variants[i]
|
|
64
|
+
total += variant.weight
|
|
65
|
+
if (total >= targetWeight) {
|
|
66
|
+
return variant
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
loadOutcomes: () => {
|
|
73
|
+
outcomes = getLocalStorageObject(OutcomesLocalStorageKey)
|
|
74
|
+
return outcomes
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
makeChildrenArray: (children: ReactElement<ExperimentProps>[] | ReactElement<ExperimentProps>) => {
|
|
78
|
+
if (Array.isArray(children)) {
|
|
79
|
+
return children as ReactElement<ExperimentProps>[]
|
|
80
|
+
} else {
|
|
81
|
+
return [children] as ReactElement<ExperimentProps>[]
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
mergeData: (data: { [index: string]: string }, log?: Log) => {
|
|
86
|
+
const dataArray: string[] = []
|
|
87
|
+
for (const key in data) {
|
|
88
|
+
dataArray.push(`${key}-${data[key]}`)
|
|
89
|
+
}
|
|
90
|
+
log?.info('MergeData', dataArray.join('|'))
|
|
91
|
+
return dataArray.join('|')
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
saveExperimentRanges: (name: string, totalWeight: number, variants: VariantData[]) => {
|
|
95
|
+
const experiments = getLocalStorageObject<ExperimentsData>(ExperimentsLocalStorageKey) || {}
|
|
96
|
+
experiments[name] = {
|
|
97
|
+
totalWeight,
|
|
98
|
+
variants,
|
|
99
|
+
}
|
|
100
|
+
setLocalStorageObject(ExperimentsLocalStorageKey, experiments)
|
|
101
|
+
return experiments
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
saveOutcomes: () => {
|
|
105
|
+
setLocalStorageObject(OutcomesLocalStorageKey, outcomes)
|
|
106
|
+
},
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export { ExperimentsHelper }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ExperimentsHelper'
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ReactNode, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import { ExperimentsHelper } from '../common'
|
|
4
|
+
import { VariantData } from '../components'
|
|
5
|
+
|
|
6
|
+
const selectVariant = (current?: string) => (variants: Record<string, ReactNode>, defaultNode: ReactNode) => {
|
|
7
|
+
if (current && current in variants) {
|
|
8
|
+
return variants[current]
|
|
9
|
+
}
|
|
10
|
+
return defaultNode
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const selectVariantForExperiment = (name: string, variants: Record<string, ReactNode>, defaultNode: ReactNode) => {
|
|
14
|
+
const variant = ExperimentsHelper.getSelectedVariant(name)
|
|
15
|
+
if (variants[variant?.name ?? '']) {
|
|
16
|
+
return variants[variant?.name ?? '']
|
|
17
|
+
}
|
|
18
|
+
return defaultNode
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const useExperiments = (name: string, experiments: VariantData[]) => {
|
|
22
|
+
const [activeExperiment] = useState(ExperimentsHelper.calculateExperiment(name, true, experiments))
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
experimentName: name,
|
|
26
|
+
selectVariant: selectVariant(activeExperiment?.name),
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export { selectVariantForExperiment, useExperiments }
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ComponentMeta, ComponentStory } from '@storybook/react'
|
|
2
|
+
|
|
3
|
+
import { selectVariantForExperiment, useExperiments } from './useExperiment'
|
|
4
|
+
|
|
5
|
+
const View: React.FC = () => {
|
|
6
|
+
const { experimentName, selectVariant } = useExperiments('Storybook Test', [
|
|
7
|
+
{ name: 'a/legacy', weight: 33 },
|
|
8
|
+
{ name: 'b/testing', weight: 33 },
|
|
9
|
+
{ name: 'c/testing', weight: 33 },
|
|
10
|
+
])
|
|
11
|
+
const texts = { 'a/legacy': <p>a</p>, 'b/testing': <p>b</p>, 'c/testing': <p>c</p> }
|
|
12
|
+
const test0 = selectVariantForExperiment('Storybook Test Does not exists', texts, <p>DefaultNode</p>)
|
|
13
|
+
const test1 = selectVariant({ 'a/legacy': <p>a</p>, 'b/testing': <p>b</p>, 'c/testing': <p>c</p> }, <p>default node</p>)
|
|
14
|
+
const test2 = selectVariantForExperiment(experimentName, texts, <p>DefaultNode</p>)
|
|
15
|
+
return (
|
|
16
|
+
<div>
|
|
17
|
+
<div>{test0}</div>
|
|
18
|
+
<div>{test1}</div>
|
|
19
|
+
<div>{test2}</div>
|
|
20
|
+
</div>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const StorybookEntry = {
|
|
25
|
+
argTypes: {},
|
|
26
|
+
component: View,
|
|
27
|
+
parameters: {
|
|
28
|
+
docs: {
|
|
29
|
+
page: null,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
title: 'Hooks/useExperiments',
|
|
33
|
+
} as ComponentMeta<typeof View>
|
|
34
|
+
|
|
35
|
+
const Template: ComponentStory<typeof View> = (args) => <View {...args} />
|
|
36
|
+
|
|
37
|
+
const Default = Template.bind({})
|
|
38
|
+
Default.args = {}
|
|
39
|
+
|
|
40
|
+
export { Default }
|
|
41
|
+
|
|
42
|
+
// eslint-disable-next-line import/no-default-export
|
|
43
|
+
export default StorybookEntry
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { TapjoyStandardEvent } from './StandardEvent'
|
|
2
2
|
|
|
3
3
|
class TapjoyStandardEvents<T extends Record<string, unknown>> {
|
|
4
|
-
public
|
|
5
|
-
return new TapjoyStandardEvent<T>('
|
|
4
|
+
public accountCreated() {
|
|
5
|
+
return new TapjoyStandardEvent<T>('Conversion')
|
|
6
6
|
}
|
|
7
7
|
}
|
|
8
8
|
|