@@ -26,34 +26,45 @@ const DEFAULT_EXPERIMENT_CONFIG: ExperimentConfig = {
26
26
27
27
const experimentConfigDisklet = makeReactNativeDisklet ( )
28
28
29
- // The probability (0-1) of a feature config being set to the first value(s):
30
- // the configuration that differs from the default feature configuration.
29
+ // The probability of an experiment config feature being set for a given key
31
30
const experimentDistribution = {
32
- swipeLastUsp : [ 0.5 ] ,
33
- createAccountType : [ 0.5 ] ,
34
- legacyLanding : [ 0 ] ,
35
- createAccountText : [ 0 .33, 0 .33] ,
36
- signupCaptcha : [ 0.5 ]
31
+ swipeLastUsp : [ 50 , 50 ] ,
32
+ createAccountType : [ 50 , 50 ] ,
33
+ legacyLanding : [ 100 ] ,
34
+ createAccountText : [ 33 .33, 33.33 , 33 .33] ,
35
+ signupCaptcha : [ 50 , 50 ]
37
36
}
38
37
39
38
/**
40
39
* Generate a random index value according to the experiment distribution to
41
40
* determine which variant gets used.
42
41
*/
43
42
const generateExperimentConfigVal = < T > ( key : keyof typeof experimentDistribution , configVals : T [ ] ) : T => {
44
- const variantProbability = experimentDistribution [ key ]
43
+ const variantNominations = experimentDistribution [ key ]
45
44
46
- if ( variantProbability . length !== configVals . length - 1 ) {
45
+ if ( variantNominations . length !== configVals . length ) {
47
46
console . error ( `Misconfigured experimentDistribution for: '${ key } '` )
48
47
} else {
48
+ // Distribute the probability of each config value
49
+ const variantDenomination = variantNominations . reduce ( ( sum , probability ) => sum + probability , 0 )
50
+ if ( variantDenomination === 0 ) {
51
+ throw new Error ( `Config values for '${ key } ' do not add up to 100%` )
52
+ } else if ( variantDenomination > 101 || variantDenomination < 99 ) {
53
+ console . warn ( `Config values for '${ key } ' do not add up to 100% +/- 1%` )
54
+ }
55
+ const distributedProbabilities = variantNominations . map ( variantNomination => variantNomination / variantDenomination )
56
+
49
57
// Generate a random number between 0 and 1
50
58
const random = Math . random ( )
51
59
52
60
// Check which index the random number falls into and return the configVal:
53
61
let lowerBound = 0
54
- for ( let i = 0 ; i < variantProbability . length ; i ++ ) {
55
- if ( random >= lowerBound && random < variantProbability [ i ] ) return configVals [ i ]
56
- lowerBound += variantProbability [ i ]
62
+ let upperBound = distributedProbabilities [ 0 ]
63
+ for ( let i = 0 ; i < distributedProbabilities . length ; i ++ ) {
64
+ if ( random >= lowerBound && random < upperBound ) return configVals [ i ]
65
+
66
+ lowerBound = upperBound
67
+ upperBound += distributedProbabilities [ i ]
57
68
}
58
69
}
59
70
@@ -81,17 +92,19 @@ const asExperimentConfig: Cleaner<ExperimentConfig> = asObject({
81
92
* This config value is available through the module's getter functions.
82
93
*/
83
94
const experimentConfigPromise : Promise < ExperimentConfig > = ( async ( ) : Promise < ExperimentConfig > => {
95
+ let currentConfig : ExperimentConfig
84
96
try {
85
97
const experimentConfigJson = await experimentConfigDisklet . getText ( LOCAL_EXPERIMENT_CONFIG )
86
- return asExperimentConfig ( JSON . parse ( experimentConfigJson ) )
98
+ currentConfig = asExperimentConfig ( JSON . parse ( experimentConfigJson ) )
87
99
} catch ( err ) {
88
- console . debug ( 'Experiment config not found/out of date. Regenerating...' )
100
+ console . log ( 'Experiment config not found/out of date. Regenerating...' )
89
101
// Not found or incompatible. Re-generate with random values according to
90
102
// the defined distribution.
91
- const generatedExperimentConfig = asExperimentConfig ( { } )
92
- await experimentConfigDisklet . setText ( LOCAL_EXPERIMENT_CONFIG , JSON . stringify ( generatedExperimentConfig ) )
93
- return generatedExperimentConfig
103
+ currentConfig = asExperimentConfig ( { } )
94
104
}
105
+
106
+ await experimentConfigDisklet . setText ( LOCAL_EXPERIMENT_CONFIG , JSON . stringify ( currentConfig ) )
107
+ return currentConfig
95
108
} ) ( )
96
109
97
110
/**
@@ -106,7 +119,7 @@ export const getExperimentConfig = async (): Promise<ExperimentConfig> => {
106
119
if ( isMaestro ( ) ) return DEFAULT_EXPERIMENT_CONFIG // Test with forced defaults
107
120
else if ( ENV . EXPERIMENT_CONFIG_OVERRIDE != null && Object . keys ( ENV . EXPERIMENT_CONFIG_OVERRIDE ) . length > 0 ) {
108
121
try {
109
- console . debug ( 'exp cfg override ')
122
+ console . log ( 'ENV.EXPERIMENT_CONFIG_OVERRIDE set ')
110
123
return asExperimentConfig ( ENV . EXPERIMENT_CONFIG_OVERRIDE )
111
124
} catch ( err ) {
112
125
console . error ( 'Error applying ENV.EXPERIMENT_CONFIG_OVERRIDE: ' , String ( err ) )
0 commit comments