Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.

Commit 71954e7

Browse files
author
Matt Carroll
committed
Merge branch 'master' into devel
2 parents 6a77bb2 + 6d1a320 commit 71954e7

File tree

4 files changed

+102
-103
lines changed

4 files changed

+102
-103
lines changed

packages/optimizely-sdk/lib/core/decision_service/index.js

Lines changed: 49 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -43,33 +43,32 @@ var DECISION_SOURCES = enums.DECISION_SOURCES;
4343
*
4444
* @constructor
4545
* @param {Object} options
46-
* @param {Object} options.configObj The parsed project configuration object that contains all the experiment configurations.
4746
* @param {Object} options.userProfileService An instance of the user profile service for sticky bucketing.
4847
* @param {Object} options.logger An instance of a logger to log messages with.
4948
* @returns {Object}
5049
*/
5150
function DecisionService(options) {
52-
this.configObj = options.configObj;
5351
this.userProfileService = options.userProfileService || null;
5452
this.logger = options.logger;
5553
}
5654

5755
/**
5856
* Gets variation where visitor will be bucketed.
57+
* @param {Object} configObj The parsed project configuration object
5958
* @param {string} experimentKey
6059
* @param {string} userId
6160
* @param {Object} attributes
6261
* @return {string|null} the variation the user is bucketed into.
6362
*/
64-
DecisionService.prototype.getVariation = function(experimentKey, userId, attributes) {
63+
DecisionService.prototype.getVariation = function(configObj, experimentKey, userId, attributes) {
6564
// by default, the bucketing ID should be the user ID
6665
var bucketingId = this._getBucketingId(userId, attributes);
6766

68-
if (!this.__checkIfExperimentIsActive(experimentKey, userId)) {
67+
if (!this.__checkIfExperimentIsActive(configObj, experimentKey, userId)) {
6968
return null;
7069
}
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);
7372
if (!!forcedVariationKey) {
7473
return forcedVariationKey;
7574
}
@@ -81,20 +80,20 @@ DecisionService.prototype.getVariation = function(experimentKey, userId, attribu
8180

8281
// check for sticky bucketing
8382
var experimentBucketMap = this.__resolveExperimentBucketMap(userId, attributes);
84-
variation = this.__getStoredVariation(experiment, userId, experimentBucketMap);
83+
variation = this.__getStoredVariation(configObj, experiment, userId, experimentBucketMap);
8584
if (!!variation) {
8685
this.logger.log(LOG_LEVEL.INFO, sprintf(LOG_MESSAGES.RETURNING_STORED_VARIATION, MODULE_NAME, variation.key, experimentKey, userId));
8786
return variation.key;
8887
}
8988

9089
// Perform regular targeting and bucketing
91-
if (!this.__checkIfUserIsInAudience(experimentKey, userId, attributes)) {
90+
if (!this.__checkIfUserIsInAudience(configObj, experimentKey, userId, attributes)) {
9291
return null;
9392
}
9493

95-
var bucketerParams = this.__buildBucketerParams(experimentKey, bucketingId, userId);
94+
var bucketerParams = this.__buildBucketerParams(configObj, experimentKey, bucketingId, userId);
9695
var variationId = bucketer.bucket(bucketerParams);
97-
variation = this.configObj.variationIdMap[variationId];
96+
variation = configObj.variationIdMap[variationId];
9897
if (!variation) {
9998
return null;
10099
}
@@ -120,12 +119,13 @@ DecisionService.prototype.__resolveExperimentBucketMap = function(userId, attrib
120119

121120
/**
122121
* Checks whether the experiment is running or launched
122+
* @param {Object} configObj The parsed project configuration object
123123
* @param {string} experimentKey Key of experiment being validated
124124
* @param {string} userId ID of user
125125
* @return {boolean} True if experiment is running
126126
*/
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)) {
129129
var experimentNotRunningLogMessage = sprintf(LOG_MESSAGES.EXPERIMENT_NOT_RUNNING, MODULE_NAME, experimentKey);
130130
this.logger.log(LOG_LEVEL.INFO, experimentNotRunningLogMessage);
131131
return false;
@@ -159,14 +159,15 @@ DecisionService.prototype.__getWhitelistedVariation = function(experiment, userI
159159

160160
/**
161161
* Checks whether the user is included in experiment audience
162+
* @param {Object} configObj The parsed project configuration object
162163
* @param {string} experimentKey Key of experiment being validated
163164
* @param {string} userId ID of user
164165
* @param {Object} attributes Optional parameter for user's attributes
165166
* @return {boolean} True if user meets audience conditions
166167
*/
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);
170171
this.logger.log(LOG_LEVEL.DEBUG, sprintf(LOG_MESSAGES.EVALUATING_AUDIENCES_COMBINED, MODULE_NAME, experimentKey, JSON.stringify(experimentAudienceConditions)));
171172
var result = audienceEvaluator.evaluate(experimentAudienceConditions, audiencesById, attributes, this.logger);
172173
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
182183

183184
/**
184185
* Given an experiment key and user ID, returns params used in bucketer call
186+
* @param configObj The parsed project configuration object
185187
* @param experimentKey Experiment key used for bucketer
186188
* @param bucketingId ID to bucket user into
187189
* @param userId ID of user to be bucketed
188190
* @return {Object}
189191
*/
190-
DecisionService.prototype.__buildBucketerParams = function(experimentKey, bucketingId, userId) {
192+
DecisionService.prototype.__buildBucketerParams = function(configObj, experimentKey, bucketingId, userId) {
191193
var bucketerParams = {};
192194
bucketerParams.experimentKey = experimentKey;
193-
bucketerParams.experimentId = projectConfig.getExperimentId(this.configObj, experimentKey);
195+
bucketerParams.experimentId = projectConfig.getExperimentId(configObj, experimentKey);
194196
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;
199201
bucketerParams.logger = this.logger;
200202
bucketerParams.bucketingId = bucketingId;
201203
return bucketerParams;
202204
};
203205

204206
/**
205207
* Pull the stored variation out of the experimentBucketMap for an experiment/userId
208+
* @param {Object} configObj The parsed project configuration object
206209
* @param {Object} experiment
207210
* @param {String} userId
208211
* @param {Object} experimentBucketMap mapping experiment => { variation_id: <variationId> }
209212
* @return {Object} the stored variation or null if the user profile does not have one for the given experiment
210213
*/
211-
DecisionService.prototype.__getStoredVariation = function(experiment, userId, experimentBucketMap) {
214+
DecisionService.prototype.__getStoredVariation = function(configObj, experiment, userId, experimentBucketMap) {
212215
if (experimentBucketMap.hasOwnProperty(experiment.id)) {
213216
var decision = experimentBucketMap[experiment.id];
214217
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];
217220
} else {
218221
this.logger.log(LOG_LEVEL.INFO, sprintf(LOG_MESSAGES.SAVED_VARIATION_NOT_FOUND, MODULE_NAME, userId, variationId, experiment.key));
219222
}
@@ -280,23 +283,24 @@ DecisionService.prototype.__saveUserProfile = function(experiment, variation, us
280283
* experiment properties (both objects), as well as a decisionSource property.
281284
* decisionSource indicates whether the decision was due to a rollout or an
282285
* experiment.
286+
* @param {Object} configObj The parsed project configuration object
283287
* @param {Object} feature A feature flag object from project configuration
284288
* @param {String} userId A string identifying the user, for bucketing
285289
* @param {Object} attributes Optional user attributes
286290
* @return {Object} An object with experiment, variation, and decisionSource
287291
* properties. If the user was not bucketed into a variation, the variation
288292
* property is null.
289293
*/
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);
292296
if (experimentDecision.variation !== null) {
293297
this.logger.log(LOG_LEVEL.DEBUG, sprintf(LOG_MESSAGES.USER_IN_FEATURE_EXPERIMENT, MODULE_NAME, userId, experimentDecision.variation.key, experimentDecision.experiment.key, feature.key));
294298
return experimentDecision;
295299
}
296300

297301
this.logger.log(LOG_LEVEL.DEBUG, sprintf(LOG_MESSAGES.USER_NOT_IN_FEATURE_EXPERIMENT, MODULE_NAME, userId, feature.key));
298302

299-
var rolloutDecision = this._getVariationForRollout(feature, userId, attributes);
303+
var rolloutDecision = this._getVariationForRollout(configObj, feature, userId, attributes);
300304
if (rolloutDecision.variation !== null) {
301305
this.logger.log(LOG_LEVEL.DEBUG, sprintf(LOG_MESSAGES.USER_IN_ROLLOUT, MODULE_NAME, userId, feature.key));
302306
return rolloutDecision;
@@ -306,24 +310,24 @@ DecisionService.prototype.getVariationForFeature = function(feature, userId, att
306310
return rolloutDecision;
307311
};
308312

309-
DecisionService.prototype._getVariationForFeatureExperiment = function(feature, userId, attributes) {
313+
DecisionService.prototype._getVariationForFeatureExperiment = function(configObj, feature, userId, attributes) {
310314
var experiment = null;
311315
var variationKey = null;
312316

313317
if (feature.hasOwnProperty('groupId')) {
314-
var group = this.configObj.groupIdMap[feature.groupId];
318+
var group = configObj.groupIdMap[feature.groupId];
315319
if (group) {
316-
experiment = this._getExperimentInGroup(group, userId);
320+
experiment = this._getExperimentInGroup(configObj, group, userId);
317321
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);
319323
}
320324
}
321325
} else if (feature.experimentIds.length > 0) {
322326
// If the feature does not have a group ID, then it can only be associated
323327
// 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);
325329
if (experiment) {
326-
variationKey = this.getVariation(experiment.key, userId, attributes);
330+
variationKey = this.getVariation(configObj, experiment.key, userId, attributes);
327331
}
328332
} else {
329333
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,
340344
};
341345
};
342346

343-
DecisionService.prototype._getExperimentInGroup = function(group, userId) {
347+
DecisionService.prototype._getExperimentInGroup = function(configObj, group, userId) {
344348
var experimentId = bucketer.bucketUserIntoExperiment(group, userId, userId, this.logger);
345349
if (experimentId !== null) {
346350
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);
348352
if (experiment) {
349353
return experiment;
350354
}
@@ -354,7 +358,7 @@ DecisionService.prototype._getExperimentInGroup = function(group, userId) {
354358
return null;
355359
};
356360

357-
DecisionService.prototype._getVariationForRollout = function(feature, userId, attributes) {
361+
DecisionService.prototype._getVariationForRollout = function(configObj, feature, userId, attributes) {
358362
if (!feature.rolloutId) {
359363
this.logger.log(LOG_LEVEL.DEBUG, sprintf(LOG_MESSAGES.NO_ROLLOUT_EXISTS, MODULE_NAME, feature.key));
360364
return {
@@ -364,7 +368,7 @@ DecisionService.prototype._getVariationForRollout = function(feature, userId, at
364368
};
365369
}
366370

367-
var rollout = this.configObj.rolloutIdMap[feature.rolloutId];
371+
var rollout = configObj.rolloutIdMap[feature.rolloutId];
368372
if (!rollout) {
369373
this.logger.log(LOG_LEVEL.ERROR, sprintf(ERROR_MESSAGES.INVALID_ROLLOUT_ID, MODULE_NAME, feature.rolloutId, feature.key));
370374
return {
@@ -394,17 +398,17 @@ DecisionService.prototype._getVariationForRollout = function(feature, userId, at
394398
var variationId;
395399
var variation;
396400
for (index = 0; index < endIndex; index++) {
397-
experiment = this.configObj.experimentKeyMap[rollout.experiments[index].key];
401+
experiment = configObj.experimentKeyMap[rollout.experiments[index].key];
398402

399-
if (!this.__checkIfUserIsInAudience(experiment.key, userId, attributes)) {
403+
if (!this.__checkIfUserIsInAudience(configObj, experiment.key, userId, attributes)) {
400404
this.logger.log(LOG_LEVEL.DEBUG, sprintf(LOG_MESSAGES.USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, MODULE_NAME, userId, index + 1));
401405
continue;
402406
}
403407

404408
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);
406410
variationId = bucketer.bucket(bucketerParams);
407-
variation = this.configObj.variationIdMap[variationId];
411+
variation = configObj.variationIdMap[variationId];
408412
if (variation) {
409413
this.logger.log(LOG_LEVEL.DEBUG, sprintf(LOG_MESSAGES.USER_BUCKETED_INTO_TARGETING_RULE, MODULE_NAME, userId, index + 1));
410414
return {
@@ -418,11 +422,11 @@ DecisionService.prototype._getVariationForRollout = function(feature, userId, at
418422
}
419423
}
420424

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);
424428
variationId = bucketer.bucket(bucketerParams);
425-
variation = this.configObj.variationIdMap[variationId];
429+
variation = configObj.variationIdMap[variationId];
426430
if (variation) {
427431
this.logger.log(LOG_LEVEL.DEBUG, sprintf(LOG_MESSAGES.USER_BUCKETED_INTO_EVERYONE_TARGETING_RULE, MODULE_NAME, userId));
428432
return {
@@ -468,7 +472,6 @@ module.exports = {
468472
/**
469473
* Creates an instance of the DecisionService.
470474
* @param {Object} options Configuration options
471-
* @param {Object} options.configObj
472475
* @param {Object} options.userProfileService
473476
* @param {Object} options.logger
474477
* @return {Object} An instance of the DecisionService

0 commit comments

Comments
 (0)