-
Notifications
You must be signed in to change notification settings - Fork 83
Use attributes for sticky bucketing #179
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d21f52e
ba24310
fd2c39d
f4847df
c57c0cb
c7a107f
c571d2b
04dca74
1b8c5e8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,6 +29,7 @@ var LOG_MESSAGES = enums.LOG_MESSAGES; | |
var DECISION_SOURCES = enums.DECISION_SOURCES; | ||
|
||
|
||
|
||
/** | ||
* Optimizely's decision service that determines which variation of an experiment the user will be allocated to. | ||
* | ||
|
@@ -79,8 +80,8 @@ DecisionService.prototype.getVariation = function(experimentKey, userId, attribu | |
} | ||
|
||
// check for sticky bucketing | ||
var userProfile = this.__getUserProfile(userId); | ||
variation = this.__getStoredVariation(experiment, userProfile); | ||
var experimentBucketMap = this.__resolveExperimentBucketMap(userId, attributes); | ||
variation = this.__getStoredVariation(experiment, userId, experimentBucketMap); | ||
if (!!variation) { | ||
this.logger.log(LOG_LEVEL.INFO, sprintf(LOG_MESSAGES.RETURNING_STORED_VARIATION, MODULE_NAME, variation.key, experimentKey, userId)); | ||
return variation.key; | ||
|
@@ -99,11 +100,24 @@ DecisionService.prototype.getVariation = function(experimentKey, userId, attribu | |
} | ||
|
||
// persist bucketing | ||
this.__saveUserProfile(userProfile, experiment, variation); | ||
this.__saveUserProfile(experiment, variation, userId, experimentBucketMap); | ||
|
||
return variation.key; | ||
}; | ||
|
||
/** | ||
* Merges attributes from attributes[STICKY_BUCKETING_KEY] and userProfileService | ||
* @param {Object} attributes | ||
* @return {Object} finalized copy of experiment_bucket_map | ||
*/ | ||
DecisionService.prototype.__resolveExperimentBucketMap = function(userId, attributes) { | ||
attributes = attributes || {} | ||
var userProfile = this.__getUserProfile(userId) || {}; | ||
var attributeExperimentBucketMap = attributes[enums.CONTROL_ATTRIBUTES.STICKY_BUCKETING_KEY]; | ||
return fns.assignIn({}, userProfile.experiment_bucket_map, attributeExperimentBucketMap); | ||
}; | ||
|
||
|
||
/** | ||
* Checks whether the experiment is running or launched | ||
* @param {string} experimentKey Key of experiment being validated | ||
|
@@ -184,23 +198,20 @@ DecisionService.prototype.__buildBucketerParams = function(experimentKey, bucket | |
}; | ||
|
||
/** | ||
* Get the stored variation from the user profile for the given experiment | ||
* Pull the stored variation out of the experimentBucketMap for an experiment/userId | ||
* @param {Object} experiment | ||
* @param {Object} userProfile | ||
* @param {String} userId | ||
* @param {Object} experimentBucketMap mapping experiment => { variation_id: <variationId> } | ||
* @return {Object} the stored variation or null if the user profile does not have one for the given experiment | ||
*/ | ||
DecisionService.prototype.__getStoredVariation = function(experiment, userProfile) { | ||
if (!userProfile || !userProfile.experiment_bucket_map) { | ||
return null; | ||
} | ||
|
||
if (userProfile.experiment_bucket_map.hasOwnProperty(experiment.id)) { | ||
var decision = userProfile.experiment_bucket_map[experiment.id]; | ||
DecisionService.prototype.__getStoredVariation = function(experiment, userId, experimentBucketMap) { | ||
mjc1283 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (experimentBucketMap.hasOwnProperty(experiment.id)) { | ||
var decision = experimentBucketMap[experiment.id]; | ||
var variationId = decision.variation_id; | ||
if (this.configObj.variationIdMap.hasOwnProperty(variationId)) { | ||
return this.configObj.variationIdMap[decision.variation_id]; | ||
} else { | ||
this.logger.log(LOG_LEVEL.INFO, sprintf(LOG_MESSAGES.SAVED_VARIATION_NOT_FOUND, MODULE_NAME, userProfile.user_id, variationId, experiment.key)); | ||
this.logger.log(LOG_LEVEL.INFO, sprintf(LOG_MESSAGES.SAVED_VARIATION_NOT_FOUND, MODULE_NAME, userId, variationId, experiment.key)); | ||
} | ||
} | ||
|
||
|
@@ -210,7 +221,7 @@ DecisionService.prototype.__getStoredVariation = function(experiment, userProfil | |
/** | ||
* Get the user profile with the given user ID | ||
* @param {string} userId | ||
* @return {Object} the stored user profile or an empty one if not found | ||
* @return {Object|undefined} the stored user profile or undefined if one isn't found | ||
*/ | ||
DecisionService.prototype.__getUserProfile = function(userId) { | ||
var userProfile = { | ||
|
@@ -223,33 +234,38 @@ DecisionService.prototype.__getUserProfile = function(userId) { | |
} | ||
|
||
try { | ||
userProfile = this.userProfileService.lookup(userId) || userProfile; // only assign if the lookup is successful | ||
return this.userProfileService.lookup(userId); | ||
} catch (ex) { | ||
this.logger.log(LOG_LEVEL.ERROR, sprintf(ERROR_MESSAGES.USER_PROFILE_LOOKUP_ERROR, MODULE_NAME, userId, ex.message)); | ||
} | ||
return userProfile; | ||
mjc1283 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}; | ||
|
||
/** | ||
* Saves the bucketing decision to the user profile | ||
* @param {Object} userProfile | ||
* @param {Object} experiment | ||
* @param {Object} variation | ||
* @param {Object} experimentBucketMap | ||
*/ | ||
DecisionService.prototype.__saveUserProfile = function(userProfile, experiment, variation) { | ||
DecisionService.prototype.__saveUserProfile = function(experiment, variation, userId, experimentBucketMap) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: another doc comment changed |
||
if (!this.userProfileService) { | ||
return; | ||
} | ||
|
||
try { | ||
userProfile.experiment_bucket_map[experiment.id] = { | ||
variation_id: variation.id, | ||
var newBucketMap = fns.cloneDeep(experimentBucketMap); | ||
newBucketMap[experiment.id] = { | ||
variation_id: variation.id | ||
}; | ||
|
||
this.userProfileService.save(userProfile); | ||
this.logger.log(LOG_LEVEL.INFO, sprintf(LOG_MESSAGES.SAVED_VARIATION, MODULE_NAME, variation.key, experiment.key, userProfile.user_id)); | ||
this.userProfileService.save({ | ||
user_id: userId, | ||
experiment_bucket_map: newBucketMap, | ||
}); | ||
|
||
this.logger.log(LOG_LEVEL.INFO, sprintf(LOG_MESSAGES.SAVED_VARIATION, MODULE_NAME, variation.key, experiment.key, userId)); | ||
} catch (ex) { | ||
this.logger.log(LOG_LEVEL.ERROR, sprintf(ERROR_MESSAGES.USER_PROFILE_SAVE_ERROR, MODULE_NAME, userProfile.user_id, ex.message)); | ||
this.logger.log(LOG_LEVEL.ERROR, sprintf(ERROR_MESSAGES.USER_PROFILE_SAVE_ERROR, MODULE_NAME, userId, ex.message)); | ||
} | ||
}; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -135,6 +135,7 @@ exports.RESERVED_EVENT_KEYWORDS = { | |
exports.CONTROL_ATTRIBUTES = { | ||
BOT_FILTERING: '$opt_bot_filtering', | ||
BUCKETING_ID: '$opt_bucketing_id', | ||
STICKY_BUCKETING_KEY: '$opt_experiment_bucket_map', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit. Alphabetize. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is really a nit |
||
USER_AGENT: '$opt_user_agent', | ||
}; | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The description of the function is no longer accurate. Not sure it was very accurate in the first place either because it wasn't really "stored"