-
Notifications
You must be signed in to change notification settings - Fork 32
feat(api): Accepting all types for attributes values #207
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
Conversation
Pull Request Test Coverage Report for Build 599
💛 - Coveralls |
build |
build-e2e |
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.
Let’s add a instance of check for type string before cast for the bucketing id.
@@ -131,7 +131,7 @@ public DecisionService(@Nonnull Bucketer bucketer, | |||
if (ExperimentUtils.isUserInExperiment(projectConfig, experiment, filteredAttributes)) { | |||
String bucketingId = userId; | |||
if (filteredAttributes.containsKey(ControlAttribute.BUCKETING_ATTRIBUTE.toString())) { | |||
bucketingId = filteredAttributes.get(ControlAttribute.BUCKETING_ATTRIBUTE.toString()); | |||
bucketingId = (String) filteredAttributes.get(ControlAttribute.BUCKETING_ATTRIBUTE.toString()); |
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.
We should probably check the type just to be safe.
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.
+1
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.
If it isn't a string, should we keep it as userId
?
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.
Correct, we don't use the bucketingId
if it's not a string and instead keep using the userId
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.
And I assume you're on top of this, but we should log a ~warning if we're ignoring the bucketingId
due to type mismatch.
And while you're at it...we should also log at ~info level in cases where do ingest a usable bucketingId
!
@@ -212,7 +212,7 @@ public DecisionService(@Nonnull Bucketer bucketer, | |||
int rolloutRulesLength = rollout.getExperiments().size(); | |||
String bucketingId = userId; | |||
if (filteredAttributes.containsKey(ControlAttribute.BUCKETING_ATTRIBUTE.toString())) { |
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.
I think we should check for null value and that the value is a string.
public boolean evaluate(Map<String, String> attributes) { | ||
String userAttributeValue = attributes.get(name); | ||
public boolean evaluate(Map<String, ?> attributes) { | ||
Object userAttributeValue = attributes.get(name); |
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.
Hmm should be careful with this. Eventually when we do accept objects and arrays to be passed in, the equals
evaluation below will likely perform a reference check as well. That said, I think it's fine for now, but just leave a message saying that we should be extra considerate when we decide to allow objects and arrays.
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.
Please add unit tests for the different value types that we support: integers, doubles, booleans, strings in the activate
API
@@ -765,7 +766,7 @@ public UserProfileService getUserProfileService() { | |||
if (unknownAttributes != null) { | |||
logger.warn("Attribute(s) {} not in the datafile.", unknownAttributes); | |||
// make a copy of the passed through attributes, then remove the unknown list | |||
attributes = new HashMap<String, String>(attributes); | |||
attributes = new HashMap<String, Object>(attributes); |
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.
You should add a test that actually removes unknown attributes to see what happens here. But my understanding is that Object
is actually different from ?
.
See https://stackoverflow.com/questions/678822/what-is-the-difference-between-and-object-in-java-generics
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.
You can't use a question mark in a constructor.
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.
I actually think that the proper way to use the constructor is HashMap<>(attributes). So, @wangjoshuah is correct.
Please update to something like:
HashMap<String, ?> attr = new HashMap<>();
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.
Looks like <>
was not introduced until Java 7 and we're currently on Java 6. Thoughts?
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.
Okay, it sounds like
new HashMap<String, Object>();
is indeed the way to go...
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.
#208 is the way to go. Good call @thomaszurkan-optimizely
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.
lets change that constructor to new HashMap<>(attributes)
@@ -765,7 +766,7 @@ public UserProfileService getUserProfileService() { | |||
if (unknownAttributes != null) { | |||
logger.warn("Attribute(s) {} not in the datafile.", unknownAttributes); | |||
// make a copy of the passed through attributes, then remove the unknown list | |||
attributes = new HashMap<String, String>(attributes); | |||
attributes = new HashMap<String, Object>(attributes); |
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.
I actually think that the proper way to use the constructor is HashMap<>(attributes). So, @wangjoshuah is correct.
Please update to something like:
HashMap<String, ?> attr = new HashMap<>();
…k into pshih/attribute-types
build |
1 similar comment
build |
build |
1 similar comment
build |
build |
Testing the changesThis e2e test does not have the proper configuration and the appropriate configuration is on Jenkins Testing the original codeI pieced together some material to hopefully override the current failing of the e2e tests.
ComparisonIf we compare the consoles of the two tests, we would notice that the errors from the first test that includes changes to attributes are the same as the errors from the original code (but there is one more error that shows up in the original code*). This may suggest that the changes in this PR does not cause the errors. *The extra error caused is with @mikeng13 @thomaszurkan-optimizely |
logger.info("bucketingId is valid"); | ||
} | ||
else { | ||
logger.warn("bucketingID has type mismatch, defaulted to userId "); |
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.
nit: This should just say BucketingID attribute is not a string. Defaulted to userId.
bucketingId = filteredAttributes.get(ControlAttribute.BUCKETING_ATTRIBUTE.toString()); | ||
if (String.class.isInstance(filteredAttributes.get(ControlAttribute.BUCKETING_ATTRIBUTE.toString()))) { | ||
bucketingId = (String) filteredAttributes.get(ControlAttribute.BUCKETING_ATTRIBUTE.toString()); | ||
logger.info("bucketingId is valid"); |
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.
nit: would be useful to print out what that bucketing ID is. Also, this should be a debug
message.
@@ -212,7 +218,13 @@ public DecisionService(@Nonnull Bucketer bucketer, | |||
int rolloutRulesLength = rollout.getExperiments().size(); | |||
String bucketingId = userId; | |||
if (filteredAttributes.containsKey(ControlAttribute.BUCKETING_ATTRIBUTE.toString())) { | |||
bucketingId = filteredAttributes.get(ControlAttribute.BUCKETING_ATTRIBUTE.toString()); | |||
if (String.class.isInstance(filteredAttributes.get(ControlAttribute.BUCKETING_ATTRIBUTE.toString()))) { |
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.
This logic is repeated from up above. Can you consolidate into a helper function?
@@ -23,5 +23,5 @@ | |||
*/ | |||
public interface Condition { | |||
|
|||
boolean evaluate(Map<String, String> attributes); | |||
boolean evaluate(Map<String, ?> attributes); |
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.
Update the header on this file to be 2016-2018
. Please also do it for other files you've touched in this commit.
@@ -37,7 +37,7 @@ public Condition getCondition() { | |||
return condition; | |||
} | |||
|
|||
public boolean evaluate(Map<String, String> attributes) { | |||
public boolean evaluate(Map<String, ?> attributes) { |
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.
Do we need to update the toString
method on this class to account for non-string values? Seems like it could lead to exceptions
@@ -36,7 +36,7 @@ public OrCondition(@Nonnull List<Condition> conditions) { | |||
return conditions; | |||
} | |||
|
|||
public boolean evaluate(Map<String, String> attributes) { | |||
public boolean evaluate(Map<String, ?> attributes) { |
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.
Same as above re: the toString method
|
||
public UserAttribute(@Nonnull String name, @Nonnull String type, @Nullable String value) { | ||
public UserAttribute(@Nonnull String name, @Nonnull String type, @Nullable Object value) { |
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.
@thomaszurkan-optimizely do we need to overload this constructor to allow you to pass in a String value
? I don't think this change will break current usage of the class.
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.
Did you mean Object value
?
Would this change be more appropriate when dealing with audience conditions?
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.
Nevermind, I think this will be fine as is. Can ignore my previous comment.
@@ -43,6 +43,7 @@ | |||
import com.optimizely.ab.notification.NotificationCenter; | |||
import com.optimizely.ab.notification.TrackNotificationListener; | |||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; | |||
import org.hamcrest.Matcher; |
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.
Is this still needed? Maybe I am missing where it's used
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.
Accidentally left that in, my bad!
@@ -70,7 +70,7 @@ public void onActivate(@Nonnull Experiment experiment, @Nonnull String userId, @ | |||
public void testAddActivateNotification() { | |||
int notificationId = notificationCenter.addActivateNotificationListener(new ActivateNotificationListenerInterface() { | |||
@Override | |||
public void onActivate(@Nonnull Experiment experiment, @Nonnull String userId, @Nonnull Map<String, String> attributes, @Nonnull Variation variation, @Nonnull LogEvent event) { | |||
public void onActivate(@Nonnull Experiment experiment, @Nonnull String userId, @Nonnull Map<String, ?> attributes, @Nonnull Variation variation, @Nonnull LogEvent event) { |
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.
Will this break current usage of the notification listeners? If someone has already implemented notification listeners with just Map<String, String>
, will they suddenly see build errors?
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.
It would break current usage (I had to make changes in unit test and the java-testapp). Does it make sense to leave it as Map<String, String> though? I think there would be errors if someone tries to use Notification Listeners on attributes with Map<String, Object> if we leave this as Map<String, String>.
Maybe provide both as abstract methods?
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.
Let's hold off on this until we discuss it today. My inclination is that we leave Map<String, String>
, and in our notification center, coerce all values to String types
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.
From today's discussion -- looks like we're using Map<String, ?>
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.
Yep we are.
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.
Almost there! I just have a few small suggestions
@@ -343,4 +337,25 @@ void saveVariation(@Nonnull Experiment experiment, | |||
} | |||
} | |||
} | |||
|
|||
/** | |||
* @param userId The userId of the user. |
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.
Please provide a description for this method. Also, please make it private as it is not used outside this class.
if (filteredAttributes.containsKey(ControlAttribute.BUCKETING_ATTRIBUTE.toString())) { | ||
bucketingId = filteredAttributes.get(ControlAttribute.BUCKETING_ATTRIBUTE.toString()); | ||
} | ||
String bucketingId = getBucketingId(userId,filteredAttributes); |
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.
nit: space after filteredAttributes
@@ -1,6 +1,6 @@ | |||
/** | |||
* | |||
* Copyright 2016-2017, Optimizely and contributors | |||
* Copyright 2016-2018, Optimizely and contributors |
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.
You didn't change this class so no need to update the year.
…k into pshih/attribute-types
@@ -1,6 +1,6 @@ | |||
/** | |||
* | |||
* Copyright 2016-2017, Optimizely and contributors | |||
* Copyright 2016-2018, Optimizely and contributors |
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.
Please also revert this change since we aren't touching this file this year
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.
lgtm
Summary
As a part of Audience Match Type, I changed all relevant function signatures so that it accepts an
Object
variable rather than just aString
for functions that accept and usesattributes
. Any filtering of types would be done later, when evaluating audience conditions.Testing
Passes all current unit tests. Further testing of other types for attribute values (like filtering out types that aren't
Boolean
,String
, orNumber
will be done when dealing with audience conditions.