Skip to content

Commit

Permalink
Added BotFiltering (#184)
Browse files Browse the repository at this point in the history
  • Loading branch information
mnoman09 authored and aliabbasrizvi committed Jun 13, 2018
1 parent 9803b3c commit 3485072
Show file tree
Hide file tree
Showing 17 changed files with 447 additions and 75 deletions.
7 changes: 4 additions & 3 deletions core-api/src/main/java/com/optimizely/ab/Optimizely.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import com.optimizely.ab.event.internal.BuildVersionInfo;
import com.optimizely.ab.event.internal.EventBuilder;
import com.optimizely.ab.event.internal.payload.EventBatch.ClientEngine;
import com.optimizely.ab.internal.ControlAttribute;
import com.optimizely.ab.notification.NotificationCenter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -748,8 +749,8 @@ public UserProfileService getUserProfileService() {
* {@link ProjectConfig}.
*
* @param projectConfig the current project config
* @param attributes the attributes map to validate and potentially filter. The reserved key for bucketing id
* {@link DecisionService#BUCKETING_ATTRIBUTE} is kept.
* @param attributes the attributes map to validate and potentially filter. Attributes which starts with reserved key
* {@link ProjectConfig#RESERVED_ATTRIBUTE_PREFIX} are kept.
* @return the filtered attributes map (containing only attributes that are present in the project config) or an
* empty map if a null attributes object is passed in
*/
Expand All @@ -765,7 +766,7 @@ private Map<String, String> filterAttributes(@Nonnull ProjectConfig projectConfi
Map<String, Attribute> attributeKeyMapping = projectConfig.getAttributeKeyMapping();
for (Map.Entry<String, String> attribute : attributes.entrySet()) {
if (!attributeKeyMapping.containsKey(attribute.getKey()) &&
attribute.getKey() != com.optimizely.ab.bucketing.DecisionService.BUCKETING_ATTRIBUTE) {
!attribute.getKey().startsWith(ProjectConfig.RESERVED_ATTRIBUTE_PREFIX)) {
if (unknownAttributes == null) {
unknownAttributes = new ArrayList<String>();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/****************************************************************************
* Copyright 2017, Optimizely, Inc. and contributors *
* Copyright 2017-2018, Optimizely, Inc. and contributors *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); *
* you may not use this file except in compliance with the License. *
Expand All @@ -24,6 +24,7 @@
import com.optimizely.ab.config.audience.Audience;
import com.optimizely.ab.error.ErrorHandler;
import com.optimizely.ab.internal.ExperimentUtils;
import com.optimizely.ab.internal.ControlAttribute;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -46,7 +47,6 @@
*/
public class DecisionService {

public static final String BUCKETING_ATTRIBUTE = "$opt_bucketing_id";
private final Bucketer bucketer;
private final ErrorHandler errorHandler;
private final ProjectConfig projectConfig;
Expand Down Expand Up @@ -130,8 +130,8 @@ public DecisionService(@Nonnull Bucketer bucketer,

if (ExperimentUtils.isUserInExperiment(projectConfig, experiment, filteredAttributes)) {
String bucketingId = userId;
if (filteredAttributes.containsKey(BUCKETING_ATTRIBUTE)) {
bucketingId = filteredAttributes.get(BUCKETING_ATTRIBUTE);
if (filteredAttributes.containsKey(ControlAttribute.BUCKETING_ATTRIBUTE.toString())) {
bucketingId = filteredAttributes.get(ControlAttribute.BUCKETING_ATTRIBUTE.toString());
}
variation = bucketer.bucket(experiment, bucketingId);

Expand Down Expand Up @@ -211,8 +211,8 @@ public DecisionService(@Nonnull Bucketer bucketer,
// for all rules before the everyone else rule
int rolloutRulesLength = rollout.getExperiments().size();
String bucketingId = userId;
if (filteredAttributes.containsKey(BUCKETING_ATTRIBUTE)) {
bucketingId = filteredAttributes.get(BUCKETING_ATTRIBUTE);
if (filteredAttributes.containsKey(ControlAttribute.BUCKETING_ATTRIBUTE.toString())) {
bucketingId = filteredAttributes.get(ControlAttribute.BUCKETING_ATTRIBUTE.toString());
}
Variation variation;
for (int i = 0; i < rolloutRulesLength - 1; i++) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
*
* Copyright 2016-2017, Optimizely and contributors
* Copyright 2016-2018, Optimizely and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -24,6 +24,7 @@
import com.optimizely.ab.error.ErrorHandler;
import com.optimizely.ab.error.NoOpErrorHandler;
import com.optimizely.ab.error.RaiseExceptionErrorHandler;
import com.optimizely.ab.internal.ControlAttribute;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -73,6 +74,7 @@ public String toString() {
private final String revision;
private final String version;
private final boolean anonymizeIP;
private final Boolean botFiltering;
private final List<Attribute> attributes;
private final List<Audience> audiences;
private final List<EventType> events;
Expand Down Expand Up @@ -100,6 +102,8 @@ public String toString() {
private final Map<String, Map<String, LiveVariableUsageInstance>> variationToLiveVariableUsageInstanceMapping;
private final Map<String, Experiment> variationIdToExperimentMapping;

public final static String RESERVED_ATTRIBUTE_PREFIX = "$opt_";

/**
* Forced variations supersede any other mappings. They are transient and are not persistent or part of
* the actual datafile. This contains all the forced variations
Expand All @@ -123,6 +127,7 @@ public ProjectConfig(String accountId, String projectId, String version, String
this(
accountId,
anonymizeIP,
null,
projectId,
revision,
version,
Expand All @@ -140,6 +145,7 @@ public ProjectConfig(String accountId, String projectId, String version, String
// v4 constructor
public ProjectConfig(String accountId,
boolean anonymizeIP,
Boolean botFiltering,
String projectId,
String revision,
String version,
Expand All @@ -157,6 +163,7 @@ public ProjectConfig(String accountId,
this.version = version;
this.revision = revision;
this.anonymizeIP = anonymizeIP;
this.botFiltering = botFiltering;

this.attributes = Collections.unmodifiableList(attributes);
this.audiences = Collections.unmodifiableList(audiences);
Expand Down Expand Up @@ -285,6 +292,30 @@ private List<Experiment> aggregateGroupExperiments(List<Group> groups) {
return groupExperiments;
}

/**
* Checks is attributeKey is reserved or not and if it exist in attributeKeyMapping
* @param attributeKey
* @return AttributeId corresponding to AttributeKeyMapping, AttributeKey when it's a reserved attribute and
* null when attributeKey is equal to BOT_FILTERING_ATTRIBUTE key.
*/
public String getAttributeId(ProjectConfig projectConfig, String attributeKey) {
String attributeIdOrKey = null;
com.optimizely.ab.config.Attribute attribute = projectConfig.getAttributeKeyMapping().get(attributeKey);
boolean hasReservedPrefix = attributeKey.startsWith(RESERVED_ATTRIBUTE_PREFIX);
if (attribute != null) {
if (hasReservedPrefix) {
logger.warn("Attribute {} unexpectedly has reserved prefix {}; using attribute ID instead of reserved attribute name.",
attributeKey, RESERVED_ATTRIBUTE_PREFIX);
}
attributeIdOrKey = attribute.getId();
} else if (hasReservedPrefix) {
attributeIdOrKey = attributeKey;
} else {
logger.debug("Unrecognized Attribute \"{}\"", attributeKey);
}
return attributeIdOrKey;
}

public String getAccountId() {
return accountId;
}
Expand All @@ -305,6 +336,10 @@ public boolean getAnonymizeIP() {
return anonymizeIP;
}

public Boolean getBotFiltering() {
return botFiltering;
}

public List<Group> getGroups() {
return groups;
}
Expand Down Expand Up @@ -540,6 +575,7 @@ public String toString() {
", revision='" + revision + '\'' +
", version='" + version + '\'' +
", anonymizeIP=" + anonymizeIP +
", botFiltering=" + botFiltering +
", attributes=" + attributes +
", audiences=" + audiences +
", events=" + events +
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
*
* Copyright 2016-2017, Optimizely and contributors
* Copyright 2016-2018, Optimizely and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -81,14 +81,18 @@ public ProjectConfig parseProjectConfig(@Nonnull String json) throws ConfigParse

List<FeatureFlag> featureFlags = null;
List<Rollout> rollouts = null;
Boolean botFiltering = null;
if (datafileVersion >= Integer.parseInt(ProjectConfig.Version.V4.toString())) {
featureFlags = parseFeatureFlags(rootObject.getJSONArray("featureFlags"));
rollouts = parseRollouts(rootObject.getJSONArray("rollouts"));
if(rootObject.has("botFiltering"))
botFiltering = rootObject.getBoolean("botFiltering");
}

return new ProjectConfig(
accountId,
anonymizeIP,
botFiltering,
projectId,
revision,
version,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
*
* Copyright 2016-2017, Optimizely and contributors
* Copyright 2016-2018, Optimizely and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -83,14 +83,18 @@ public ProjectConfig parseProjectConfig(@Nonnull String json) throws ConfigParse

List<FeatureFlag> featureFlags = null;
List<Rollout> rollouts = null;
Boolean botFiltering = null;
if (datafileVersion >= Integer.parseInt(ProjectConfig.Version.V4.toString())) {
featureFlags = parseFeatureFlags((JSONArray) rootObject.get("featureFlags"));
rollouts = parseRollouts((JSONArray) rootObject.get("rollouts"));
if(rootObject.containsKey("botFiltering"))
botFiltering = (Boolean) rootObject.get("botFiltering");
}

return new ProjectConfig(
accountId,
anonymizeIP,
botFiltering,
projectId,
revision,
version,
Expand All @@ -103,6 +107,8 @@ public ProjectConfig parseProjectConfig(@Nonnull String json) throws ConfigParse
liveVariables,
rollouts
);
} catch (RuntimeException ex){
throw new ConfigParseException("Unable to parse datafile: " + json, ex);
} catch (Exception e) {
throw new ConfigParseException("Unable to parse datafile: " + json, e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
*
* Copyright 2016-2017, Optimizely and contributors
* Copyright 2016-2018, Optimizely and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -82,16 +82,20 @@ public ProjectConfig deserialize(JsonElement json, Type typeOfT, JsonDeserializa

List<FeatureFlag> featureFlags = null;
List<Rollout> rollouts = null;
Boolean botFiltering = null;
if (datafileVersion >= Integer.parseInt(ProjectConfig.Version.V4.toString())) {
Type featureFlagsType = new TypeToken<List<FeatureFlag>>() {}.getType();
featureFlags = context.deserialize(jsonObject.getAsJsonArray("featureFlags"), featureFlagsType);
Type rolloutsType = new TypeToken<List<Rollout>>() {}.getType();
rollouts = context.deserialize(jsonObject.get("rollouts").getAsJsonArray(), rolloutsType);
if(jsonObject.has("botFiltering"))
botFiltering = jsonObject.get("botFiltering").getAsBoolean();
}

return new ProjectConfig(
accountId,
anonymizeIP,
botFiltering,
projectId,
revision,
version,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
*
* Copyright 2016-2017, Optimizely and contributors
* Copyright 2016-2018, Optimizely and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -76,16 +76,20 @@ public ProjectConfig deserialize(JsonParser parser, DeserializationContext conte

List<FeatureFlag> featureFlags = null;
List<Rollout> rollouts = null;
Boolean botFiltering = null;
if (datafileVersion >= Integer.parseInt(ProjectConfig.Version.V4.toString())) {
featureFlags = mapper.readValue(node.get("featureFlags").toString(),
new TypeReference<List<FeatureFlag>>() {});
rollouts = mapper.readValue(node.get("rollouts").toString(),
new TypeReference<List<Rollout>>(){});
if (node.hasNonNull("botFiltering"))
botFiltering = node.get("botFiltering").asBoolean();
}

return new ProjectConfig(
accountId,
anonymizeIP,
botFiltering,
projectId,
revision,
version,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package com.optimizely.ab.event.internal;

import com.optimizely.ab.annotations.VisibleForTesting;
import com.optimizely.ab.bucketing.DecisionService;
import com.optimizely.ab.config.EventType;
import com.optimizely.ab.config.Experiment;
import com.optimizely.ab.config.ProjectConfig;
Expand All @@ -32,6 +31,7 @@
import com.optimizely.ab.event.internal.serializer.DefaultJsonSerializer;
import com.optimizely.ab.event.internal.serializer.Serializer;
import com.optimizely.ab.internal.EventTagUtils;
import com.optimizely.ab.internal.ControlAttribute;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
Expand All @@ -44,7 +44,6 @@

public class EventBuilder {
private static final Logger logger = LoggerFactory.getLogger(EventBuilder.class);
static final String ATTRIBUTE_KEY_FOR_BUCKETING_ATTRIBUTE = "optimizely_bucketing_id";
static final String EVENT_ENDPOINT = "https://logx.optimizely.com/v1/events";
static final String ACTIVATE_EVENT_KEY = "campaign_activated";

Expand Down Expand Up @@ -118,17 +117,25 @@ public LogEvent createConversionEvent(@Nonnull ProjectConfig projectConfig,
private List<Attribute> buildAttributeList(ProjectConfig projectConfig, Map<String, String> attributes) {
List<Attribute> attributesList = new ArrayList<Attribute>();

Map<String, com.optimizely.ab.config.Attribute> attributeMap = projectConfig.getAttributeKeyMapping();
for (Map.Entry<String, String> entry : attributes.entrySet()) {
com.optimizely.ab.config.Attribute projectAttribute = attributeMap.get(entry.getKey());
Attribute attribute = new Attribute((projectAttribute != null ? projectAttribute.getId() : null),
entry.getKey(), Attribute.CUSTOM_ATTRIBUTE_TYPE, entry.getValue());

if (entry.getKey() == DecisionService.BUCKETING_ATTRIBUTE) {
attribute = new Attribute(com.optimizely.ab.bucketing.DecisionService.BUCKETING_ATTRIBUTE,
ATTRIBUTE_KEY_FOR_BUCKETING_ATTRIBUTE, Attribute.CUSTOM_ATTRIBUTE_TYPE, entry.getValue());
String attributeId = projectConfig.getAttributeId(projectConfig, entry.getKey());
if(attributeId != null) {
Attribute attribute = new Attribute(attributeId,
entry.getKey(),
Attribute.CUSTOM_ATTRIBUTE_TYPE,
entry.getValue());
attributesList.add(attribute);
}
}

//checks if botFiltering value is not set in the project config file.
if(projectConfig.getBotFiltering() != null) {
Attribute attribute = new Attribute(
ControlAttribute.BOT_FILTERING_ATTRIBUTE.toString(),
ControlAttribute.BOT_FILTERING_ATTRIBUTE.toString(),
Attribute.CUSTOM_ATTRIBUTE_TYPE,
projectConfig.getBotFiltering()
);
attributesList.add(attribute);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ public class Attribute {
String entityId;
String key;
String type;
String value;
Object value;

public Attribute() {

}

public Attribute(String entityId, String key, String type, String value) {
public Attribute(String entityId, String key, String type, Object value) {
this.entityId = entityId;
this.key = key;
this.type = type;
Expand Down Expand Up @@ -63,7 +63,7 @@ public void setType(String type) {
this.type = type;
}

public String getValue() {
public Object getValue() {
return value;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@

import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.Objects;

public class Decision {
@JsonProperty("campaign_id")
String campaignId;
Expand Down
Loading

0 comments on commit 3485072

Please sign in to comment.