Skip to content

DO NOT REVIEW - SEE https://github.com/optimizely/javascript-sdk/pull/288 #216

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

Closed
wants to merge 14 commits into from

Conversation

jamesopti
Copy link
Contributor

@jamesopti jamesopti commented Feb 12, 2019

Summary

This PR adds the ability for an SDK consumer to pass their own set of condition evaluators when creating an optimizely instance.

The core motivator for this is to be able to evaluate WEB audience conditions using the Javascript SDK as part of the EDGE project (go/edge).

Future fullstack use cases for this functionality might include audience condition plugins, where the customer would define their own match types and provide their own matchers.

/**
 * Condition evaluators for conditions that can be evaluated in a Cloudflare worker using
 * information like cookies, query parameters, userAgent, etc...
 */
function getConditionEvaluators(inputs) {
  return {
    cookies: {
      evaluate: matchCookies.match.bind(this, { cookies: inputs.cookies }),
    },
    device: {
      evaluate: matchDevice.match.bind(this, { device: Detect.parseUA(inputs.ua).device }),
    },
    ...
    /* Other custom condition evaluators */
  }
}


var optlyInstance = optimizely.createInstance({
  datafile: datafile,
  conditionEvaluators: getConditionEvaluators(inputs),
  skipJSONValidation: true
});

Test plan

Unit tests would be pretty straightforward to add for this

Issues

@jamesopti jamesopti force-pushed the james/custom_condition_evaluators branch from 6c02325 to 2ae49e1 Compare February 12, 2019 22:11
function AudienceEvaluator(conditionEvaluators) {
this.typeToEvaluatorMap = Object.assign({
'custom_attribute': customAttributeConditionEvaluator
}, conditionEvaluators);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we just want allow for additional condition types beyond those supported by the javascript-sdk, or do we also want to allow customers to modify how the built-in condition types are evaluated?

If the former, I wonder if we can add some kind of namespacing (as in Optimizely Web audience integrations) to protect custom conditions types from conflicting with (existing and future) built-in condition types.

If the latter, I have serious reservations, since that could make it difficult for us to modify our own condition types without affecting existing customers.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yikes, we don't seem to catch errors when constructing the decision service. So if we apply any validation at this stage, or do anything that could throw an error, that error will bubble up into the customer's application.

Maybe we should do something about that! Though for the moment, I'm just pointing this out so that we remain careful not to throw.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the approach of not allowing the built in evaluators to be overridden. If you need to do that, then you'll be able to write your own.

Good call on the error catching. Added an error message for that which is used in a try/catch now.

function AudienceEvaluator(conditionEvaluators) {
this.typeToEvaluatorMap = Object.assign({
'custom_attribute': customAttributeConditionEvaluator
}, conditionEvaluators);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: additional unit tests, eventually

@coveralls
Copy link

Coverage Status

Coverage decreased (-0.05%) to 97.534% when pulling 2ae49e1 on james/custom_condition_evaluators into de83b9b on master.

@mikeproeng37
Copy link
Contributor

Unit tests aren't passing in Travis, might wanna fix that and also merge master in. I will be reviewing this PR in the meantime!

Copy link
Contributor

@mikeproeng37 mikeproeng37 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the approach is fine so far. CCing @mjc1283 and @jordangarcia who have been doing work on extensibility and new components.

return null;
}
try {
return evaluator.evaluate(condition, userAttributes, this.logger);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the evaluator we get is not the custom_attribute, we should log a debug message to let the user know that. Might be helpful when trying to debug. Maybe we can even just log which evaluator we ended using (by key)

@@ -52,6 +52,7 @@ function DecisionService(options) {
this.configObj = options.configObj;
this.userProfileService = options.userProfileService || null;
this.logger = options.logger;
this.audienceEvaluator = new AudienceEvaluator(options.logger, options.__exploratoryConditionEvaluators);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like we are polluting our factory method haha but I see no other easy way to integrate into the current setup.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean the constructor?

Yea in the current setup, all this state must be threaded through (config, logger instance, etc...)

Would be nice to have a global registration system by instance ID so that this kinda global state can be easily accessed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is totally fine, by adding configuration to the AudienceEvaluator we must capture that state, so making this an instantiatable class makes sense.

As far as global state, things like audience evaluators should not be considered as global state. If some sort of global registry existed you'd still have to know the instance ID here to resolve the audience evaluators (which would have to be threaded through).

However things like the logger and errorHandler should be global and having to thread them through here is not cool (this is changing soon in another PR)

@mikeproeng37 mikeproeng37 requested a review from mjc1283 February 21, 2019 23:39
Copy link
Contributor

@jordangarcia jordangarcia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few minor things about naming, but I think the structure of how the evaluator was made extensible looks good.

In terms of naming I've seen React use the prefix UNSTABLE_ in the public API and think that probably gets the point across more the "exploratory".

@@ -238,6 +238,7 @@ describe('lib/optimizely', function() {
configObj: optlyInstance.configObj,
userProfileService: userProfileServiceInstance,
logger: createdLogger,
__exploratoryConditionEvaluators: undefined
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is kind of a weird test assertion. It's probably fine, but I also think it makes sense for __exporatoryConditionEvaluators to be required in new Optimizely or Optimizely defaults this to {}

@@ -52,6 +52,7 @@ function DecisionService(options) {
this.configObj = options.configObj;
this.userProfileService = options.userProfileService || null;
this.logger = options.logger;
this.audienceEvaluator = new AudienceEvaluator(options.logger, options.__exploratoryConditionEvaluators);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is totally fine, by adding configuration to the AudienceEvaluator we must capture that state, so making this an instantiatable class makes sense.

As far as global state, things like audience evaluators should not be considered as global state. If some sort of global registry existed you'd still have to know the instance ID here to resolve the audience evaluators (which would have to be threaded through).

However things like the logger and errorHandler should be global and having to thread them through here is not cool (this is changing soon in another PR)

@@ -56,7 +56,6 @@ EVALUATORS_BY_MATCH_TYPE[SUBSTRING_MATCH_TYPE] = substringEvaluator;
*/
function evaluate(condition, userAttributes, logger) {
if (condition.type !== CUSTOM_ATTRIBUTE_CONDITION_TYPE) {
logger.log(LOG_LEVEL.WARNING, sprintf(LOG_MESSAGES.UNKNOWN_CONDITION_TYPE, MODULE_NAME, JSON.stringify(condition)));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jamesopti I see this log message now in AudienceEvaluator.prototype.evaluateConditionWithUserAttributes - what is the purpose of this evaluate function, now?

@lpappone
Copy link

lpappone commented Jun 7, 2019

DO NOT REVIEW - SEE #288

@lpappone lpappone changed the title feat (AudienceEvaluator): Add the ability to provide custom condition evaluators DO NOT REVIEW - SEE https://github.com/optimizely/javascript-sdk/pull/288 Jun 7, 2019
@mikeproeng37
Copy link
Contributor

Obsolete

@mikeproeng37 mikeproeng37 deleted the james/custom_condition_evaluators branch September 23, 2019 22:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants