Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 3 additions & 13 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,27 +1,17 @@
language: java
jdk:
- openjdk7
- oraclejdk7
- oraclejdk8
install: true
before_script:
- git config user.name "Travis-CI"
- git config user.email "[email protected]"
- git checkout $TRAVIS_BRANCH
script:
- "./gradlew clean build check"
# - '[ "${TRAVIS_PULL_REQUEST}" != "false" ] || ./gradlew release -Prelease.useAutomaticVersion=true'
env:
global:
- TERM=dumb
- secure: "JVJ1w5ImgQkBwplEjlyTq0UaJQZncQEa35ggIbCffTon/FnWauy+HL9rzjBR6vTtSRlPxbKKxOdR0fGZQg5hQU10TEadwNBF5nLlFKK+mAz32z0RjgWAJhiFqjvFYdfGPuPZpAUZLIe7UxtcpQZPF2cAkc4Zw/r1HTI0GKS5L/H8FwJWlR1txu9O6GWfT58ntrD18LzrHQ9M5PC3gc4U/Af7kiPjeXQf06zliy7PMaMw8m1XLicjwzEHW2cNVXB0WrZwgcSrblnidRxPJj05k5clULu5VsetqMkLHCxuPbnjR0+1sZ6IX/3yhH5u/dm/vVtWikCqfpH2bLQw0vcYqisBPaJ/RlOa4r7ROL+G8LRo94LqwEVmZ+fbEFCqigP+Wv9GlLKL+LKTPDYWEnrBcHsE4EOwpLviEZRixHf/HtKvMfuwLyzyIAVyKjU0MrFDsOvDRorC1HIyg66CwjkKg5LJwW4IOTNTOldOpiGhB4PR4bcMUU8wuB5ts7eGGYCShthS4kOtuHlSuxfv6qKGIz4z5nD8/JPxVUU+P3RtrvoGYoeIqd6SGC40cvYupCzULJIYwrCKrRTpimrSwdrEEtLp91WjyrrDfznlGzoDOdCS5XaActcn4lh+ztkpYD/Qj2j31955kOkxq0/9TP5qi9qNvJAeFwkhW3VSmJVRbLw="
- secure: "3UGbdSed51Ul024Wj1l2zojolyMxCzlTERSJjXrh5mdcNAHxImZTNURUuKaQsJghnirjB3q124woWyymvepE8XdSH0Y7DYkqV247vczXm1nHhbUpzVQ8xsDWTE6AXIyq1CzGzKFK8Ws0C+xXOZoBr2l9dy6oF4rBxTYTBlL8BvqGtcZw3d3eWNI9wQ+l6XGbQDw3Ex2iJaUr9NOI1kD4Q0gcUSX+Zgowe1xlaySrRLXg5iZVUCLNbitJEJ91Xd3oCksqfKBBe2b24M6edhCLNOMQ4u0MTEu28D6gltqojqmp1UHI6fJD8+P79h6PfpMPA+3vB3kTQ9vwG/5L5JoTe5JJPp+yUgbEBH5QzI7si6oVTFHb8oTN2/TMJV+N4QjILrasfreAYUA2kHXzxmxlodNsILNvl5OgF0ValwOvgPDZfkGytMnvLWow6gw8gSYtiTYJjNSMXnpa9kjCLMUyizDFRn4PpcMi2bfml/vR/qlVMnln3AwSukPGqQ4uZhRnIkzBJQdXoxoYTaVOcfsgF8dFWwOShin/RTygwwg5cjm0Nhn6RmkFtt81arLeVho/SeqOjEkvcCV6a7bhhmCZpZvYZu57dLfF/jPHYmW30X3Pg9e+7VsQ61xQWnlVV1g8KjM0qVnThoUVj8j62bXIo/P5scaKkxg9j5/Mdq6t02E="
cache:
gradle: true
directories:
- "$HOME/.gradle/caches"
- "$HOME/.gradle/wrapper"
after_script:
- git tag travis-build-$TRAVIS_BUILD_NUMBER
- git push --tags
branches:
only:
- master
- devel
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 1.2.0

- Change position of `activateExperiment` parameter in the method signatures of `getVariableString`, `getVariableBoolean`, `getVariableInteger`, and `getVariableFloat`
- Change `UserExperimentRecord` to `UserProfile`
- Add support for IP anonymization
- Add `NotificationListener` for SDK events

## 1.1.0

- Add support for live variables
Expand Down
90 changes: 61 additions & 29 deletions core-api/src/main/java/com/optimizely/ab/Optimizely.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

import com.optimizely.ab.annotations.VisibleForTesting;
import com.optimizely.ab.bucketing.Bucketer;
import com.optimizely.ab.bucketing.UserExperimentRecord;
import com.optimizely.ab.bucketing.UserProfile;
import com.optimizely.ab.config.Attribute;
import com.optimizely.ab.config.EventType;
import com.optimizely.ab.config.Experiment;
Expand All @@ -39,6 +39,8 @@
import com.optimizely.ab.event.internal.EventBuilderV2;
import com.optimizely.ab.event.internal.payload.Event.ClientEngine;
import com.optimizely.ab.internal.ProjectValidationUtils;
import com.optimizely.ab.notification.NotificationListener;
import com.optimizely.ab.notification.NotificationBroadcaster;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -89,6 +91,7 @@ public class Optimizely {
@VisibleForTesting final ProjectConfig projectConfig;
@VisibleForTesting final EventHandler eventHandler;
@VisibleForTesting final ErrorHandler errorHandler;
@VisibleForTesting final NotificationBroadcaster notificationBroadcaster = new NotificationBroadcaster();

private Optimizely(@Nonnull ProjectConfig projectConfig,
@Nonnull Bucketer bucketer,
Expand All @@ -104,7 +107,7 @@ private Optimizely(@Nonnull ProjectConfig projectConfig,

// Do work here that should be done once per Optimizely lifecycle
@VisibleForTesting void initialize() {
bucketer.cleanUserExperimentRecords();
bucketer.cleanUserProfiles();
}

//======== activate calls ========//
Expand Down Expand Up @@ -180,6 +183,8 @@ private Optimizely(@Nonnull ProjectConfig projectConfig,
logger.error("Unexpected exception in event dispatcher", e);
}

notificationBroadcaster.broadcastExperimentActivated(experiment, userId, attributes, variation);

return variation;
}

Expand Down Expand Up @@ -258,15 +263,15 @@ private void track(@Nonnull String eventName,
//======== live variable getters ========//

public @Nullable String getVariableString(@Nonnull String variableKey,
boolean activateExperiment,
@Nonnull String userId) throws UnknownLiveVariableException {
return getVariableString(variableKey, activateExperiment, userId, Collections.<String, String>emptyMap());
@Nonnull String userId,
boolean activateExperiment) throws UnknownLiveVariableException {
return getVariableString(variableKey, userId, Collections.<String, String>emptyMap(), activateExperiment);
}

public @Nullable String getVariableString(@Nonnull String variableKey,
boolean activateExperiment,
@Nonnull String userId,
@Nonnull Map<String, String> attributes)
@Nonnull Map<String, String> attributes,
boolean activateExperiment)
throws UnknownLiveVariableException {

LiveVariable variable = getLiveVariableOrThrow(projectConfig, variableKey);
Expand Down Expand Up @@ -303,18 +308,18 @@ private void track(@Nonnull String eventName,
}

public @Nullable Boolean getVariableBoolean(@Nonnull String variableKey,
boolean activateExperiment,
@Nonnull String userId) throws UnknownLiveVariableException {
return getVariableBoolean(variableKey, activateExperiment, userId, Collections.<String, String>emptyMap());
@Nonnull String userId,
boolean activateExperiment) throws UnknownLiveVariableException {
return getVariableBoolean(variableKey, userId, Collections.<String, String>emptyMap(), activateExperiment);
}

public @Nullable Boolean getVariableBoolean(@Nonnull String variableKey,
boolean activateExperiment,
@Nonnull String userId,
@Nonnull Map<String, String> attributes)
@Nonnull Map<String, String> attributes,
boolean activateExperiment)
throws UnknownLiveVariableException {

String variableValueString = getVariableString(variableKey, activateExperiment, userId, attributes);
String variableValueString = getVariableString(variableKey, userId, attributes, activateExperiment);
if (variableValueString != null) {
return Boolean.parseBoolean(variableValueString);
}
Expand All @@ -323,18 +328,18 @@ private void track(@Nonnull String eventName,
}

public @Nullable Integer getVariableInteger(@Nonnull String variableKey,
boolean activateExperiment,
@Nonnull String userId) throws UnknownLiveVariableException {
return getVariableInteger(variableKey, activateExperiment, userId, Collections.<String, String>emptyMap());
@Nonnull String userId,
boolean activateExperiment) throws UnknownLiveVariableException {
return getVariableInteger(variableKey, userId, Collections.<String, String>emptyMap(), activateExperiment);
}

public @Nullable Integer getVariableInteger(@Nonnull String variableKey,
boolean activateExperiment,
@Nonnull String userId,
@Nonnull Map<String, String> attributes)
@Nonnull Map<String, String> attributes,
boolean activateExperiment)
throws UnknownLiveVariableException {

String variableValueString = getVariableString(variableKey, activateExperiment, userId, attributes);
String variableValueString = getVariableString(variableKey, userId, attributes, activateExperiment);
if (variableValueString != null) {
try {
return Integer.parseInt(variableValueString);
Expand All @@ -348,18 +353,18 @@ private void track(@Nonnull String eventName,
}

public @Nullable Float getVariableFloat(@Nonnull String variableKey,
boolean activateExperiment,
@Nonnull String userId) throws UnknownLiveVariableException {
return getVariableFloat(variableKey, activateExperiment, userId, Collections.<String, String>emptyMap());
@Nonnull String userId,
boolean activateExperiment) throws UnknownLiveVariableException {
return getVariableFloat(variableKey, userId, Collections.<String, String>emptyMap(), activateExperiment);
}

public @Nullable Float getVariableFloat(@Nonnull String variableKey,
boolean activateExperiment,
@Nonnull String userId,
@Nonnull Map<String, String> attributes)
@Nonnull Map<String, String> attributes,
boolean activateExperiment)
throws UnknownLiveVariableException {

String variableValueString = getVariableString(variableKey, activateExperiment, userId, attributes);
String variableValueString = getVariableString(variableKey, userId, attributes, activateExperiment);
if (variableValueString != null) {
try {
return Float.parseFloat(variableValueString);
Expand Down Expand Up @@ -438,6 +443,33 @@ private static ProjectConfig getProjectConfig(String datafile) throws ConfigPars
return DefaultConfigParser.getInstance().parseProjectConfig(datafile);
}

//======== Notification listeners ========//

/**
* Add a {@link NotificationListener} if it does not exist already.
*
* @param listener listener to add
*/
public void addNotificationListener(@Nonnull NotificationListener listener) {
notificationBroadcaster.addListener(listener);
}

/**
* Remove a {@link NotificationListener} if it exists.
*
* @param listener listener to remove
*/
public void removeNotificationListener(@Nonnull NotificationListener listener) {
notificationBroadcaster.removeListener(listener);
}

/**
* Remove all {@link NotificationListener}.
*/
public void clearNotificationListeners() {
notificationBroadcaster.clearListeners();
}

//======== Helper methods ========//

/**
Expand Down Expand Up @@ -596,7 +628,7 @@ public static class Builder {

private String datafile;
private Bucketer bucketer;
private UserExperimentRecord userExperimentRecord;
private UserProfile userProfile;
private ErrorHandler errorHandler;
private EventHandler eventHandler;
private EventBuilder eventBuilder;
Expand All @@ -615,8 +647,8 @@ public Builder withErrorHandler(ErrorHandler errorHandler) {
return this;
}

public Builder withUserExperimentRecord(UserExperimentRecord userExperimentRecord) {
this.userExperimentRecord = userExperimentRecord;
public Builder withUserProfile(UserProfile userProfile) {
this.userProfile = userProfile;
return this;
}

Expand Down Expand Up @@ -653,7 +685,7 @@ public Optimizely build() throws ConfigParseException {

// use the default bucketer and event builder, if no overrides were provided
if (bucketer == null) {
bucketer = new Bucketer(projectConfig, userExperimentRecord);
bucketer = new Bucketer(projectConfig, userProfile);
}

if (clientEngine == null) {
Expand Down
36 changes: 18 additions & 18 deletions core-api/src/main/java/com/optimizely/ab/bucketing/Bucketer.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public class Bucketer {

private final ProjectConfig projectConfig;

@Nullable private final UserExperimentRecord userExperimentRecord;
@Nullable private final UserProfile userProfile;

private static final Logger logger = LoggerFactory.getLogger(Bucketer.class);

Expand All @@ -63,9 +63,9 @@ public Bucketer(ProjectConfig projectConfig) {
this(projectConfig, null);
}

public Bucketer(ProjectConfig projectConfig, @Nullable UserExperimentRecord userExperimentRecord) {
public Bucketer(ProjectConfig projectConfig, @Nullable UserProfile userProfile) {
this.projectConfig = projectConfig;
this.userExperimentRecord = userExperimentRecord;
this.userProfile = userProfile;
}

private String bucketToEntity(int bucketValue, List<TrafficAllocation> trafficAllocations) {
Expand Down Expand Up @@ -112,12 +112,12 @@ private Variation bucketToVariation(@Nonnull Experiment experiment,
String experimentKey = experiment.getKey();
String combinedBucketId = userId + experimentId;

// If a user experiment record instance is present then check it for a saved variation
if (userExperimentRecord != null) {
String variationKey = userExperimentRecord.lookup(userId, experimentKey);
// If a user profile instance is present then check it for a saved variation
if (userProfile != null) {
String variationKey = userProfile.lookup(userId, experimentKey);
if (variationKey != null) {
logger.info("Returning previously activated variation \"{}\" of experiment \"{}\" "
+ "for user \"{}\" from user experiment record.",
+ "for user \"{}\" from user profile.",
variationKey, experimentKey, userId);
// A variation is stored for this combined bucket id
return projectConfig
Expand All @@ -127,7 +127,7 @@ private Variation bucketToVariation(@Nonnull Experiment experiment,
.get(variationKey);
} else {
logger.info("No previously activated variation of experiment \"{}\" "
+ "for user \"{}\" found in user experiment record.",
+ "for user \"{}\" found in user profile.",
experimentKey, userId);
}
}
Expand All @@ -145,9 +145,9 @@ private Variation bucketToVariation(@Nonnull Experiment experiment,
logger.info("User \"{}\" is in variation \"{}\" of experiment \"{}\".", userId, variationKey,
experimentKey);

// If a user experiment record is present give it a variation to store
if (userExperimentRecord != null) {
boolean saved = userExperimentRecord.save(userId, experiment.getKey(), variationKey);
// If a user profile is present give it a variation to store
if (userProfile != null) {
boolean saved = userProfile.save(userId, experiment.getKey(), variationKey);
if (saved) {
logger.info("Saved variation \"{}\" of experiment \"{}\" for user \"{}\".",
variationKey, experimentKey, userId);
Expand Down Expand Up @@ -224,23 +224,23 @@ int generateBucketValue(int hashCode) {
}

@Nullable
public UserExperimentRecord getUserExperimentRecord() {
return userExperimentRecord;
public UserProfile getUserProfile() {
return userProfile;
}

/**
* Gives implementations of {@link UserExperimentRecord} a chance to remove records
* Gives implementations of {@link UserProfile} a chance to remove records
* of experiments that are deleted or not running.
*/
public void cleanUserExperimentRecords() {
if (userExperimentRecord != null) {
Map<String, Map<String,String>> records = userExperimentRecord.getAllRecords();
public void cleanUserProfiles() {
if (userProfile != null) {
Map<String, Map<String,String>> records = userProfile.getAllRecords();
if (records != null) {
for (Map.Entry<String,Map<String,String>> record : records.entrySet()) {
for (String experimentKey : record.getValue().keySet()) {
Experiment experiment = projectConfig.getExperimentKeyMapping().get(experimentKey);
if (experiment == null || !experiment.isRunning()) {
userExperimentRecord.remove(record.getKey(), experimentKey);
userProfile.remove(record.getKey(), experimentKey);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
* user experience after changing traffic allocations. Also, this interface gives users
* a hook to keep track of activation history.
*/
public interface UserExperimentRecord {
public interface UserProfile {

/**
* Called when implementors should save an activation
Expand All @@ -47,7 +47,7 @@ public interface UserExperimentRecord {
String lookup(String userId, String experimentKey);

/**
* Called when user experiment record should be removed
* Called when user profile should be removed
*
* Records should be removed when an experiment is not running or when an experiment has been
* deleted.
Expand Down
Loading