@@ -43,33 +43,32 @@ var DECISION_SOURCES = enums.DECISION_SOURCES;
43
43
*
44
44
* @constructor
45
45
* @param {Object } options
46
- * @param {Object } options.configObj The parsed project configuration object that contains all the experiment configurations.
47
46
* @param {Object } options.userProfileService An instance of the user profile service for sticky bucketing.
48
47
* @param {Object } options.logger An instance of a logger to log messages with.
49
48
* @returns {Object }
50
49
*/
51
50
function DecisionService ( options ) {
52
- this . configObj = options . configObj ;
53
51
this . userProfileService = options . userProfileService || null ;
54
52
this . logger = options . logger ;
55
53
}
56
54
57
55
/**
58
56
* Gets variation where visitor will be bucketed.
57
+ * @param {Object } configObj The parsed project configuration object
59
58
* @param {string } experimentKey
60
59
* @param {string } userId
61
60
* @param {Object } attributes
62
61
* @return {string|null } the variation the user is bucketed into.
63
62
*/
64
- DecisionService . prototype . getVariation = function ( experimentKey , userId , attributes ) {
63
+ DecisionService . prototype . getVariation = function ( configObj , experimentKey , userId , attributes ) {
65
64
// by default, the bucketing ID should be the user ID
66
65
var bucketingId = this . _getBucketingId ( userId , attributes ) ;
67
66
68
- if ( ! this . __checkIfExperimentIsActive ( experimentKey , userId ) ) {
67
+ if ( ! this . __checkIfExperimentIsActive ( configObj , experimentKey , userId ) ) {
69
68
return null ;
70
69
}
71
- var experiment = this . configObj . experimentKeyMap [ experimentKey ] ;
72
- var forcedVariationKey = projectConfig . getForcedVariation ( this . configObj , experimentKey , userId , this . logger ) ;
70
+ var experiment = configObj . experimentKeyMap [ experimentKey ] ;
71
+ var forcedVariationKey = projectConfig . getForcedVariation ( configObj , experimentKey , userId , this . logger ) ;
73
72
if ( ! ! forcedVariationKey ) {
74
73
return forcedVariationKey ;
75
74
}
@@ -81,20 +80,20 @@ DecisionService.prototype.getVariation = function(experimentKey, userId, attribu
81
80
82
81
// check for sticky bucketing
83
82
var experimentBucketMap = this . __resolveExperimentBucketMap ( userId , attributes ) ;
84
- variation = this . __getStoredVariation ( experiment , userId , experimentBucketMap ) ;
83
+ variation = this . __getStoredVariation ( configObj , experiment , userId , experimentBucketMap ) ;
85
84
if ( ! ! variation ) {
86
85
this . logger . log ( LOG_LEVEL . INFO , sprintf ( LOG_MESSAGES . RETURNING_STORED_VARIATION , MODULE_NAME , variation . key , experimentKey , userId ) ) ;
87
86
return variation . key ;
88
87
}
89
88
90
89
// Perform regular targeting and bucketing
91
- if ( ! this . __checkIfUserIsInAudience ( experimentKey , userId , attributes ) ) {
90
+ if ( ! this . __checkIfUserIsInAudience ( configObj , experimentKey , userId , attributes ) ) {
92
91
return null ;
93
92
}
94
93
95
- var bucketerParams = this . __buildBucketerParams ( experimentKey , bucketingId , userId ) ;
94
+ var bucketerParams = this . __buildBucketerParams ( configObj , experimentKey , bucketingId , userId ) ;
96
95
var variationId = bucketer . bucket ( bucketerParams ) ;
97
- variation = this . configObj . variationIdMap [ variationId ] ;
96
+ variation = configObj . variationIdMap [ variationId ] ;
98
97
if ( ! variation ) {
99
98
return null ;
100
99
}
@@ -120,12 +119,13 @@ DecisionService.prototype.__resolveExperimentBucketMap = function(userId, attrib
120
119
121
120
/**
122
121
* Checks whether the experiment is running or launched
122
+ * @param {Object } configObj The parsed project configuration object
123
123
* @param {string } experimentKey Key of experiment being validated
124
124
* @param {string } userId ID of user
125
125
* @return {boolean } True if experiment is running
126
126
*/
127
- DecisionService . prototype . __checkIfExperimentIsActive = function ( experimentKey , userId ) {
128
- if ( ! projectConfig . isActive ( this . configObj , experimentKey ) ) {
127
+ DecisionService . prototype . __checkIfExperimentIsActive = function ( configObj , experimentKey , userId ) {
128
+ if ( ! projectConfig . isActive ( configObj , experimentKey ) ) {
129
129
var experimentNotRunningLogMessage = sprintf ( LOG_MESSAGES . EXPERIMENT_NOT_RUNNING , MODULE_NAME , experimentKey ) ;
130
130
this . logger . log ( LOG_LEVEL . INFO , experimentNotRunningLogMessage ) ;
131
131
return false ;
@@ -159,14 +159,15 @@ DecisionService.prototype.__getWhitelistedVariation = function(experiment, userI
159
159
160
160
/**
161
161
* Checks whether the user is included in experiment audience
162
+ * @param {Object } configObj The parsed project configuration object
162
163
* @param {string } experimentKey Key of experiment being validated
163
164
* @param {string } userId ID of user
164
165
* @param {Object } attributes Optional parameter for user's attributes
165
166
* @return {boolean } True if user meets audience conditions
166
167
*/
167
- DecisionService . prototype . __checkIfUserIsInAudience = function ( experimentKey , userId , attributes ) {
168
- var experimentAudienceConditions = projectConfig . getExperimentAudienceConditions ( this . configObj , experimentKey ) ;
169
- var audiencesById = projectConfig . getAudiencesById ( this . configObj ) ;
168
+ DecisionService . prototype . __checkIfUserIsInAudience = function ( configObj , experimentKey , userId , attributes ) {
169
+ var experimentAudienceConditions = projectConfig . getExperimentAudienceConditions ( configObj , experimentKey ) ;
170
+ var audiencesById = projectConfig . getAudiencesById ( configObj ) ;
170
171
this . logger . log ( LOG_LEVEL . DEBUG , sprintf ( LOG_MESSAGES . EVALUATING_AUDIENCES_COMBINED , MODULE_NAME , experimentKey , JSON . stringify ( experimentAudienceConditions ) ) ) ;
171
172
var result = audienceEvaluator . evaluate ( experimentAudienceConditions , audiencesById , attributes , this . logger ) ;
172
173
this . logger . log ( LOG_LEVEL . INFO , sprintf ( LOG_MESSAGES . AUDIENCE_EVALUATION_RESULT_COMBINED , MODULE_NAME , experimentKey , result . toString ( ) . toUpperCase ( ) ) ) ;
@@ -182,38 +183,40 @@ DecisionService.prototype.__checkIfUserIsInAudience = function(experimentKey, us
182
183
183
184
/**
184
185
* Given an experiment key and user ID, returns params used in bucketer call
186
+ * @param configObj The parsed project configuration object
185
187
* @param experimentKey Experiment key used for bucketer
186
188
* @param bucketingId ID to bucket user into
187
189
* @param userId ID of user to be bucketed
188
190
* @return {Object }
189
191
*/
190
- DecisionService . prototype . __buildBucketerParams = function ( experimentKey , bucketingId , userId ) {
192
+ DecisionService . prototype . __buildBucketerParams = function ( configObj , experimentKey , bucketingId , userId ) {
191
193
var bucketerParams = { } ;
192
194
bucketerParams . experimentKey = experimentKey ;
193
- bucketerParams . experimentId = projectConfig . getExperimentId ( this . configObj , experimentKey ) ;
195
+ bucketerParams . experimentId = projectConfig . getExperimentId ( configObj , experimentKey ) ;
194
196
bucketerParams . userId = userId ;
195
- bucketerParams . trafficAllocationConfig = projectConfig . getTrafficAllocation ( this . configObj , experimentKey ) ;
196
- bucketerParams . experimentKeyMap = this . configObj . experimentKeyMap ;
197
- bucketerParams . groupIdMap = this . configObj . groupIdMap ;
198
- bucketerParams . variationIdMap = this . configObj . variationIdMap ;
197
+ bucketerParams . trafficAllocationConfig = projectConfig . getTrafficAllocation ( configObj , experimentKey ) ;
198
+ bucketerParams . experimentKeyMap = configObj . experimentKeyMap ;
199
+ bucketerParams . groupIdMap = configObj . groupIdMap ;
200
+ bucketerParams . variationIdMap = configObj . variationIdMap ;
199
201
bucketerParams . logger = this . logger ;
200
202
bucketerParams . bucketingId = bucketingId ;
201
203
return bucketerParams ;
202
204
} ;
203
205
204
206
/**
205
207
* Pull the stored variation out of the experimentBucketMap for an experiment/userId
208
+ * @param {Object } configObj The parsed project configuration object
206
209
* @param {Object } experiment
207
210
* @param {String } userId
208
211
* @param {Object } experimentBucketMap mapping experiment => { variation_id: <variationId> }
209
212
* @return {Object } the stored variation or null if the user profile does not have one for the given experiment
210
213
*/
211
- DecisionService . prototype . __getStoredVariation = function ( experiment , userId , experimentBucketMap ) {
214
+ DecisionService . prototype . __getStoredVariation = function ( configObj , experiment , userId , experimentBucketMap ) {
212
215
if ( experimentBucketMap . hasOwnProperty ( experiment . id ) ) {
213
216
var decision = experimentBucketMap [ experiment . id ] ;
214
217
var variationId = decision . variation_id ;
215
- if ( this . configObj . variationIdMap . hasOwnProperty ( variationId ) ) {
216
- return this . configObj . variationIdMap [ decision . variation_id ] ;
218
+ if ( configObj . variationIdMap . hasOwnProperty ( variationId ) ) {
219
+ return configObj . variationIdMap [ decision . variation_id ] ;
217
220
} else {
218
221
this . logger . log ( LOG_LEVEL . INFO , sprintf ( LOG_MESSAGES . SAVED_VARIATION_NOT_FOUND , MODULE_NAME , userId , variationId , experiment . key ) ) ;
219
222
}
@@ -280,23 +283,24 @@ DecisionService.prototype.__saveUserProfile = function(experiment, variation, us
280
283
* experiment properties (both objects), as well as a decisionSource property.
281
284
* decisionSource indicates whether the decision was due to a rollout or an
282
285
* experiment.
286
+ * @param {Object } configObj The parsed project configuration object
283
287
* @param {Object } feature A feature flag object from project configuration
284
288
* @param {String } userId A string identifying the user, for bucketing
285
289
* @param {Object } attributes Optional user attributes
286
290
* @return {Object } An object with experiment, variation, and decisionSource
287
291
* properties. If the user was not bucketed into a variation, the variation
288
292
* property is null.
289
293
*/
290
- DecisionService . prototype . getVariationForFeature = function ( feature , userId , attributes ) {
291
- var experimentDecision = this . _getVariationForFeatureExperiment ( feature , userId , attributes ) ;
294
+ DecisionService . prototype . getVariationForFeature = function ( configObj , feature , userId , attributes ) {
295
+ var experimentDecision = this . _getVariationForFeatureExperiment ( configObj , feature , userId , attributes ) ;
292
296
if ( experimentDecision . variation !== null ) {
293
297
this . logger . log ( LOG_LEVEL . DEBUG , sprintf ( LOG_MESSAGES . USER_IN_FEATURE_EXPERIMENT , MODULE_NAME , userId , experimentDecision . variation . key , experimentDecision . experiment . key , feature . key ) ) ;
294
298
return experimentDecision ;
295
299
}
296
300
297
301
this . logger . log ( LOG_LEVEL . DEBUG , sprintf ( LOG_MESSAGES . USER_NOT_IN_FEATURE_EXPERIMENT , MODULE_NAME , userId , feature . key ) ) ;
298
302
299
- var rolloutDecision = this . _getVariationForRollout ( feature , userId , attributes ) ;
303
+ var rolloutDecision = this . _getVariationForRollout ( configObj , feature , userId , attributes ) ;
300
304
if ( rolloutDecision . variation !== null ) {
301
305
this . logger . log ( LOG_LEVEL . DEBUG , sprintf ( LOG_MESSAGES . USER_IN_ROLLOUT , MODULE_NAME , userId , feature . key ) ) ;
302
306
return rolloutDecision ;
@@ -306,24 +310,24 @@ DecisionService.prototype.getVariationForFeature = function(feature, userId, att
306
310
return rolloutDecision ;
307
311
} ;
308
312
309
- DecisionService . prototype . _getVariationForFeatureExperiment = function ( feature , userId , attributes ) {
313
+ DecisionService . prototype . _getVariationForFeatureExperiment = function ( configObj , feature , userId , attributes ) {
310
314
var experiment = null ;
311
315
var variationKey = null ;
312
316
313
317
if ( feature . hasOwnProperty ( 'groupId' ) ) {
314
- var group = this . configObj . groupIdMap [ feature . groupId ] ;
318
+ var group = configObj . groupIdMap [ feature . groupId ] ;
315
319
if ( group ) {
316
- experiment = this . _getExperimentInGroup ( group , userId ) ;
320
+ experiment = this . _getExperimentInGroup ( configObj , group , userId ) ;
317
321
if ( experiment && feature . experimentIds . indexOf ( experiment . id ) !== - 1 ) {
318
- variationKey = this . getVariation ( experiment . key , userId , attributes ) ;
322
+ variationKey = this . getVariation ( configObj , experiment . key , userId , attributes ) ;
319
323
}
320
324
}
321
325
} else if ( feature . experimentIds . length > 0 ) {
322
326
// If the feature does not have a group ID, then it can only be associated
323
327
// with one experiment, so we look at the first experiment ID only
324
- experiment = projectConfig . getExperimentFromId ( this . configObj , feature . experimentIds [ 0 ] , this . logger ) ;
328
+ experiment = projectConfig . getExperimentFromId ( configObj , feature . experimentIds [ 0 ] , this . logger ) ;
325
329
if ( experiment ) {
326
- variationKey = this . getVariation ( experiment . key , userId , attributes ) ;
330
+ variationKey = this . getVariation ( configObj , experiment . key , userId , attributes ) ;
327
331
}
328
332
} else {
329
333
this . logger . log ( LOG_LEVEL . DEBUG , sprintf ( LOG_MESSAGES . FEATURE_HAS_NO_EXPERIMENTS , MODULE_NAME , feature . key ) ) ;
@@ -340,11 +344,11 @@ DecisionService.prototype._getVariationForFeatureExperiment = function(feature,
340
344
} ;
341
345
} ;
342
346
343
- DecisionService . prototype . _getExperimentInGroup = function ( group , userId ) {
347
+ DecisionService . prototype . _getExperimentInGroup = function ( configObj , group , userId ) {
344
348
var experimentId = bucketer . bucketUserIntoExperiment ( group , userId , userId , this . logger ) ;
345
349
if ( experimentId !== null ) {
346
350
this . logger . log ( LOG_LEVEL . INFO , sprintf ( LOG_MESSAGES . USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP , MODULE_NAME , userId , experimentId , group . id ) ) ;
347
- var experiment = projectConfig . getExperimentFromId ( this . configObj , experimentId , this . logger ) ;
351
+ var experiment = projectConfig . getExperimentFromId ( configObj , experimentId , this . logger ) ;
348
352
if ( experiment ) {
349
353
return experiment ;
350
354
}
@@ -354,7 +358,7 @@ DecisionService.prototype._getExperimentInGroup = function(group, userId) {
354
358
return null ;
355
359
} ;
356
360
357
- DecisionService . prototype . _getVariationForRollout = function ( feature , userId , attributes ) {
361
+ DecisionService . prototype . _getVariationForRollout = function ( configObj , feature , userId , attributes ) {
358
362
if ( ! feature . rolloutId ) {
359
363
this . logger . log ( LOG_LEVEL . DEBUG , sprintf ( LOG_MESSAGES . NO_ROLLOUT_EXISTS , MODULE_NAME , feature . key ) ) ;
360
364
return {
@@ -364,7 +368,7 @@ DecisionService.prototype._getVariationForRollout = function(feature, userId, at
364
368
} ;
365
369
}
366
370
367
- var rollout = this . configObj . rolloutIdMap [ feature . rolloutId ] ;
371
+ var rollout = configObj . rolloutIdMap [ feature . rolloutId ] ;
368
372
if ( ! rollout ) {
369
373
this . logger . log ( LOG_LEVEL . ERROR , sprintf ( ERROR_MESSAGES . INVALID_ROLLOUT_ID , MODULE_NAME , feature . rolloutId , feature . key ) ) ;
370
374
return {
@@ -394,17 +398,17 @@ DecisionService.prototype._getVariationForRollout = function(feature, userId, at
394
398
var variationId ;
395
399
var variation ;
396
400
for ( index = 0 ; index < endIndex ; index ++ ) {
397
- experiment = this . configObj . experimentKeyMap [ rollout . experiments [ index ] . key ] ;
401
+ experiment = configObj . experimentKeyMap [ rollout . experiments [ index ] . key ] ;
398
402
399
- if ( ! this . __checkIfUserIsInAudience ( experiment . key , userId , attributes ) ) {
403
+ if ( ! this . __checkIfUserIsInAudience ( configObj , experiment . key , userId , attributes ) ) {
400
404
this . logger . log ( LOG_LEVEL . DEBUG , sprintf ( LOG_MESSAGES . USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE , MODULE_NAME , userId , index + 1 ) ) ;
401
405
continue ;
402
406
}
403
407
404
408
this . logger . log ( LOG_LEVEL . DEBUG , sprintf ( LOG_MESSAGES . USER_MEETS_CONDITIONS_FOR_TARGETING_RULE , MODULE_NAME , userId , index + 1 ) ) ;
405
- bucketerParams = this . __buildBucketerParams ( experiment . key , bucketingId , userId ) ;
409
+ bucketerParams = this . __buildBucketerParams ( configObj , experiment . key , bucketingId , userId ) ;
406
410
variationId = bucketer . bucket ( bucketerParams ) ;
407
- variation = this . configObj . variationIdMap [ variationId ] ;
411
+ variation = configObj . variationIdMap [ variationId ] ;
408
412
if ( variation ) {
409
413
this . logger . log ( LOG_LEVEL . DEBUG , sprintf ( LOG_MESSAGES . USER_BUCKETED_INTO_TARGETING_RULE , MODULE_NAME , userId , index + 1 ) ) ;
410
414
return {
@@ -418,11 +422,11 @@ DecisionService.prototype._getVariationForRollout = function(feature, userId, at
418
422
}
419
423
}
420
424
421
- var everyoneElseExperiment = this . configObj . experimentKeyMap [ rollout . experiments [ endIndex ] . key ] ;
422
- if ( this . __checkIfUserIsInAudience ( everyoneElseExperiment . key , userId , attributes ) ) {
423
- bucketerParams = this . __buildBucketerParams ( everyoneElseExperiment . key , bucketingId , userId ) ;
425
+ var everyoneElseExperiment = configObj . experimentKeyMap [ rollout . experiments [ endIndex ] . key ] ;
426
+ if ( this . __checkIfUserIsInAudience ( configObj , everyoneElseExperiment . key , userId , attributes ) ) {
427
+ bucketerParams = this . __buildBucketerParams ( configObj , everyoneElseExperiment . key , bucketingId , userId ) ;
424
428
variationId = bucketer . bucket ( bucketerParams ) ;
425
- variation = this . configObj . variationIdMap [ variationId ] ;
429
+ variation = configObj . variationIdMap [ variationId ] ;
426
430
if ( variation ) {
427
431
this . logger . log ( LOG_LEVEL . DEBUG , sprintf ( LOG_MESSAGES . USER_BUCKETED_INTO_EVERYONE_TARGETING_RULE , MODULE_NAME , userId ) ) ;
428
432
return {
@@ -468,7 +472,6 @@ module.exports = {
468
472
/**
469
473
* Creates an instance of the DecisionService.
470
474
* @param {Object } options Configuration options
471
- * @param {Object } options.configObj
472
475
* @param {Object } options.userProfileService
473
476
* @param {Object } options.logger
474
477
* @return {Object } An instance of the DecisionService
0 commit comments