-
Notifications
You must be signed in to change notification settings - Fork 83
feat(AudienceEvaluator): Add the ability to provide custom condition evaluators #288
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
Merged
Merged
Changes from 5 commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
2ff9d2e
add the ability to provide custom condition evaluators
7015e7d
fix tests
db7addd
update lint command
lpappone fda872d
update function name
lpappone 713212d
remove unneccessary check
lpappone fcbc80e
use singleton logger
lpappone f7e8816
fix tests
lpappone 5ba2116
fix lint errors
lpappone e4fd82e
Update packages/optimizely-sdk/lib/utils/enums/index.js
lpappone b68e444
put back some accidentally deleted things
lpappone 69001d7
pass logger to evaluate
lpappone 0eeb31d
add todo
lpappone 4da5d4a
Merge branch 'master' into james/custom_condition_evaluators_OG
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,54 +16,88 @@ | |
var conditionTreeEvaluator = require('../condition_tree_evaluator'); | ||
var customAttributeConditionEvaluator = require('../custom_attribute_condition_evaluator'); | ||
var enums = require('../../utils/enums'); | ||
var fns = require('../../utils/fns'); | ||
var sprintf = require('@optimizely/js-sdk-utils').sprintf; | ||
|
||
var ERROR_MESSAGES = enums.ERROR_MESSAGES; | ||
var LOG_LEVEL = enums.LOG_LEVEL; | ||
var LOG_MESSAGES = enums.LOG_MESSAGES; | ||
var MODULE_NAME = 'AUDIENCE_EVALUATOR'; | ||
|
||
module.exports = { | ||
/** | ||
* Determine if the given user attributes satisfy the given audience conditions | ||
* @param {Array|String|null|undefined} audienceConditions Audience conditions to match the user attributes against - can be an array | ||
* of audience IDs, a nested array of conditions, or a single leaf condition. | ||
* Examples: ["5", "6"], ["and", ["or", "1", "2"], "3"], "1" | ||
* @param {Object} audiencesById Object providing access to full audience objects for audience IDs | ||
* contained in audienceConditions. Keys should be audience IDs, values | ||
* should be full audience objects with conditions properties | ||
* @param {Object} [userAttributes] User attributes which will be used in determining if audience conditions | ||
* are met. If not provided, defaults to an empty object | ||
* @param {Object} logger Logger instance. | ||
* @return {Boolean} true if the user attributes match the given audience conditions, false | ||
* otherwise | ||
*/ | ||
evaluate: function(audienceConditions, audiencesById, userAttributes, logger) { | ||
// if there are no audiences, return true because that means ALL users are included in the experiment | ||
if (!audienceConditions || audienceConditions.length === 0) { | ||
return true; | ||
} | ||
|
||
if (!userAttributes) { | ||
userAttributes = {}; | ||
} | ||
/** | ||
* Construct an instance of AudienceEvaluator with a given logger and options | ||
* @param {Logger} logger The Logger instance | ||
* @param {Object=} UNSTABLE_conditionEvaluators A map of condition evaluators provided by the consumer. This enables matching | ||
* condition types which are not supported natively by the SDK. Note that built in | ||
* Optimizely evaluators cannot be overridden. | ||
* @constructor | ||
*/ | ||
function AudienceEvaluator(logger, UNSTABLE_conditionEvaluators) { | ||
this.logger = logger; | ||
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. Instead of passing logger, we can import a singleton logger and use it directly (example from project_config_manager) 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. +1 |
||
this.typeToEvaluatorMap = fns.assignIn({}, UNSTABLE_conditionEvaluators, { | ||
'custom_attribute': customAttributeConditionEvaluator | ||
lpappone marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}); | ||
} | ||
|
||
var evaluateConditionWithUserAttributes = function(condition) { | ||
return customAttributeConditionEvaluator.evaluate(condition, userAttributes, logger); | ||
}; | ||
/** | ||
* Determine if the given user attributes satisfy the given audience conditions | ||
* @param {Array|String|null|undefined} audienceConditions Audience conditions to match the user attributes against - can be an array | ||
* of audience IDs, a nested array of conditions, or a single leaf condition. | ||
* Examples: ["5", "6"], ["and", ["or", "1", "2"], "3"], "1" | ||
* @param {Object} audiencesById Object providing access to full audience objects for audience IDs | ||
* contained in audienceConditions. Keys should be audience IDs, values | ||
* should be full audience objects with conditions properties | ||
* @param {Object} [userAttributes] User attributes which will be used in determining if audience conditions | ||
* are met. If not provided, defaults to an empty object | ||
* @return {Boolean} true if the user attributes match the given audience conditions, false | ||
* otherwise | ||
*/ | ||
AudienceEvaluator.prototype.evaluate = function(audienceConditions, audiencesById, userAttributes) { | ||
// if there are no audiences, return true because that means ALL users are included in the experiment | ||
if (!audienceConditions || audienceConditions.length === 0) { | ||
return true; | ||
} | ||
|
||
var evaluateAudience = function(audienceId) { | ||
var audience = audiencesById[audienceId]; | ||
if (audience) { | ||
logger.log(LOG_LEVEL.DEBUG, sprintf(LOG_MESSAGES.EVALUATING_AUDIENCE, MODULE_NAME, audienceId, JSON.stringify(audience.conditions))); | ||
var result = conditionTreeEvaluator.evaluate(audience.conditions, evaluateConditionWithUserAttributes); | ||
var resultText = result === null ? 'UNKNOWN' : result.toString().toUpperCase(); | ||
logger.log(LOG_LEVEL.INFO, sprintf(LOG_MESSAGES.AUDIENCE_EVALUATION_RESULT, MODULE_NAME, audienceId, resultText)); | ||
return result; | ||
} | ||
if (!userAttributes) { | ||
userAttributes = {}; | ||
} | ||
|
||
return null; | ||
}; | ||
var evaluateAudience = function(audienceId) { | ||
var audience = audiencesById[audienceId]; | ||
if (audience) { | ||
this.logger.log(LOG_LEVEL.DEBUG, sprintf(LOG_MESSAGES.EVALUATING_AUDIENCE, MODULE_NAME, audienceId, JSON.stringify(audience.conditions))); | ||
var result = conditionTreeEvaluator.evaluate(audience.conditions, this.evaluateConditionWithUserAttributes.bind(this, userAttributes)); | ||
var resultText = result === null ? 'UNKNOWN' : result.toString().toUpperCase(); | ||
this.logger.log(LOG_LEVEL.INFO, sprintf(LOG_MESSAGES.AUDIENCE_EVALUATION_RESULT, MODULE_NAME, audienceId, resultText)); | ||
return result; | ||
} | ||
|
||
return conditionTreeEvaluator.evaluate(audienceConditions, evaluateAudience) || false; | ||
}, | ||
return null; | ||
}.bind(this); | ||
|
||
return conditionTreeEvaluator.evaluate(audienceConditions, evaluateAudience) || false; | ||
}; | ||
|
||
/** | ||
lpappone marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* Wrapper around evaluator.evaluate that is passed to the conditionTreeEvaluator. | ||
* Evaluates the condition provided given the user attributes if an evaluator has been defined for the condition type. | ||
* @param {Object} userAttributes A map of user attributes. | ||
* @param {Object} condition A single condition object to evaluate. | ||
* @return {Boolean|null} true if the condition is satisfied, null if a matcher is not found. | ||
*/ | ||
AudienceEvaluator.prototype.evaluateConditionWithUserAttributes = function(userAttributes, condition) { | ||
var evaluator = this.typeToEvaluatorMap[condition.type]; | ||
if (!evaluator) { | ||
this.logger.log(LOG_LEVEL.WARNING, sprintf(LOG_MESSAGES.UNKNOWN_CONDITION_TYPE, MODULE_NAME, JSON.stringify(condition))); | ||
return null; | ||
} | ||
try { | ||
return evaluator.evaluate(condition, userAttributes, this.logger); | ||
} catch (err) { | ||
this.logger.log(LOG_LEVEL.ERROR, sprintf(ERROR_MESSAGES.CONDITION_EVALUATOR_ERROR, MODULE_NAME, condition.type, err.message)); | ||
} | ||
return null; | ||
}; | ||
|
||
module.exports = AudienceEvaluator; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.