From 21a7a9d63cc63408d365a2d77d470768178fbc84 Mon Sep 17 00:00:00 2001 From: Thomas Zurkan Date: Tue, 23 Jan 2018 20:54:04 -0800 Subject: [PATCH 1/9] initial implementation of new v3 event endpoint and unit tests --- .../ab/event/internal/EventBuilderV3.java | 116 +++ .../ab/event/internal/payload/Attribute.java | 72 ++ .../ab/event/internal/payload/DecisionV3.java | 69 ++ .../ab/event/internal/payload/EventBatch.java | 81 ++ .../ab/event/internal/payload/EventV3.java | 132 ++++ .../ab/event/internal/payload/Snapshot.java | 50 ++ .../ab/event/internal/payload/Visitor.java | 71 ++ .../internal/serializer/GsonSerializer.java | 2 +- .../serializer/JacksonSerializer.java | 2 +- .../internal/serializer/JsonSerializer.java | 2 +- .../serializer/JsonSimpleSerializer.java | 2 +- .../event/internal/serializer/Serializer.java | 2 +- .../ab/event/internal/EventBuilderV3Test.java | 727 ++++++++++++++++++ 13 files changed, 1323 insertions(+), 5 deletions(-) create mode 100644 core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilderV3.java create mode 100644 core-api/src/main/java/com/optimizely/ab/event/internal/payload/Attribute.java create mode 100644 core-api/src/main/java/com/optimizely/ab/event/internal/payload/DecisionV3.java create mode 100644 core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventBatch.java create mode 100644 core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventV3.java create mode 100644 core-api/src/main/java/com/optimizely/ab/event/internal/payload/Snapshot.java create mode 100644 core-api/src/main/java/com/optimizely/ab/event/internal/payload/Visitor.java create mode 100644 core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderV3Test.java diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilderV3.java b/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilderV3.java new file mode 100644 index 000000000..ea3c3d0d9 --- /dev/null +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilderV3.java @@ -0,0 +1,116 @@ +package com.optimizely.ab.event.internal; + +import com.optimizely.ab.bucketing.DecisionService; +import com.optimizely.ab.config.EventType; +import com.optimizely.ab.config.Experiment; +import com.optimizely.ab.config.ProjectConfig; +import com.optimizely.ab.config.Variation; +import com.optimizely.ab.event.LogEvent; +import com.optimizely.ab.event.internal.payload.Attribute; +import com.optimizely.ab.event.internal.payload.DecisionV3; +import com.optimizely.ab.event.internal.payload.Event; +import com.optimizely.ab.event.internal.payload.EventBatch; +import com.optimizely.ab.event.internal.payload.EventV3; +import com.optimizely.ab.event.internal.payload.Snapshot; +import com.optimizely.ab.event.internal.payload.Visitor; +import com.optimizely.ab.event.internal.serializer.DefaultJsonSerializer; +import com.optimizely.ab.event.internal.serializer.Serializer; +import com.optimizely.ab.internal.EventTagUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public class EventBuilderV3 extends EventBuilder { + private static final Logger logger = LoggerFactory.getLogger(EventBuilderV3.class); + static final String EVENT_ENDPOINT = "https://logx.optimizely.com/v1/events"; + static final String ACTIVATE_EVENT_KEY = "campaign_activated"; + private Serializer serializer; + private String clientVersion; + private Event.ClientEngine clientEngine; + + public EventBuilderV3() { + this(Event.ClientEngine.JAVA_SDK, BuildVersionInfo.VERSION); + } + + public EventBuilderV3(Event.ClientEngine clientEngine, String clientVersion) { + this.clientEngine = clientEngine; + this.clientVersion = clientVersion; + this.serializer = DefaultJsonSerializer.getInstance(); + } + + + public LogEvent createImpressionEvent(@Nonnull ProjectConfig projectConfig, + @Nonnull Experiment activatedExperiment, + @Nonnull Variation variation, + @Nonnull String userId, + @Nonnull Map attributes) { + + DecisionV3 decisionV3 = new DecisionV3(activatedExperiment.getLayerId(), activatedExperiment.getId(), + variation.getId(), false); + EventV3 eventV3 = new EventV3(System.currentTimeMillis(),UUID.randomUUID().toString(), activatedExperiment.getLayerId(), + ACTIVATE_EVENT_KEY, null, null, null, ACTIVATE_EVENT_KEY, null); + Snapshot snapshot = new Snapshot(Arrays.asList(decisionV3), Arrays.asList(eventV3)); + + Visitor visitor = new Visitor(userId, null, buildAttributeList(projectConfig, attributes), Arrays.asList(snapshot)); + List visitors = Arrays.asList(visitor); + EventBatch eventBatch = new EventBatch(projectConfig.getAccountId(), visitors, projectConfig.getAnonymizeIP(), projectConfig.getProjectId(), projectConfig.getRevision()); + String payload = this.serializer.serialize(eventBatch); + return new LogEvent(LogEvent.RequestMethod.POST, EVENT_ENDPOINT, Collections.emptyMap(), payload); + } + + public LogEvent createConversionEvent(@Nonnull ProjectConfig projectConfig, + @Nonnull Map experimentVariationMap, + @Nonnull String userId, + @Nonnull String eventId, + @Nonnull String eventName, + @Nonnull Map attributes, + @Nonnull Map eventTags) { + + ArrayList decisionV3s = new ArrayList(); + ArrayList eventV3s = new ArrayList(); + for (Experiment experiment : experimentVariationMap.keySet()) { + Variation variation = experimentVariationMap.get(experiment); + DecisionV3 decisionV3 = new DecisionV3(experiment.getLayerId(), experiment.getId(), variation.getId(), false); + decisionV3s.add(decisionV3); + } + + EventType eventType = projectConfig.getEventNameMapping().get(eventName); + + EventV3 eventV3 = new EventV3(System.currentTimeMillis(),UUID.randomUUID().toString(), eventType.getId(), + eventType.getKey(), null, EventTagUtils.getRevenueValue(eventTags), eventTags, eventType.getKey(), EventTagUtils.getNumericValue(eventTags)); + Snapshot snapshot = new Snapshot(decisionV3s, Arrays.asList(eventV3)); + + Visitor visitor = new Visitor(userId, null, buildAttributeList(projectConfig, attributes), Arrays.asList(snapshot)); + List visitors = Arrays.asList(visitor); + EventBatch eventBatch = new EventBatch(projectConfig.getAccountId(), visitors, projectConfig.getAnonymizeIP(), projectConfig.getProjectId(), projectConfig.getRevision()); + String payload = this.serializer.serialize(eventBatch); + return new LogEvent(LogEvent.RequestMethod.POST, EVENT_ENDPOINT, Collections.emptyMap(), payload); + } + + private List buildAttributeList(ProjectConfig projectConfig, Map attributes) { + List attributes1 = new ArrayList(); + + Map attributeMap = projectConfig.getAttributeKeyMapping(); + for (String key : attributes.keySet()) { + com.optimizely.ab.config.Attribute projectAttribute = attributeMap.get(key); + Attribute attribute = new Attribute((projectAttribute != null ? projectAttribute.getId() : null), + key, Attribute.CUSTOM_ATTRIBUTE_TYPE, attributes.get(key)); + + if (key == DecisionService.BUCKETING_ATTRIBUTE) { + attribute = new Attribute(com.optimizely.ab.bucketing.DecisionService.BUCKETING_ATTRIBUTE, + EventBuilderV2.ATTRIBUTE_KEY_FOR_BUCKETING_ATTRIBUTE, Attribute.CUSTOM_ATTRIBUTE_TYPE, attributes.get(key)); + } + + attributes1.add(attribute); + } + + return attributes1; + } +} diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Attribute.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Attribute.java new file mode 100644 index 000000000..910fb5f95 --- /dev/null +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Attribute.java @@ -0,0 +1,72 @@ +package com.optimizely.ab.event.internal.payload; + +public class Attribute { + public static final String CUSTOM_ATTRIBUTE_TYPE = "custom"; + public static final String CUSTOM_EVENT_TYPE = "custom"; + + String entityId; + String key; + String type; + String value; + + public Attribute(String entityId, String key, String type, String value) { + this.entityId = entityId; + this.key = key; + this.type = type; + this.value = value; + } + + public String getEntityId() { + return entityId; + } + + public void setEntityId(String entityId) { + this.entityId = entityId; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Attribute attribute = (Attribute) o; + + if (!entityId.equals(attribute.entityId)) return false; + if (!key.equals(attribute.key)) return false; + if (!type.equals(attribute.type)) return false; + return value.equals(attribute.value); + } + + @Override + public int hashCode() { + int result = entityId.hashCode(); + result = 31 * result + key.hashCode(); + result = 31 * result + type.hashCode(); + result = 31 * result + value.hashCode(); + return result; + } +} diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/DecisionV3.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/DecisionV3.java new file mode 100644 index 000000000..89b3811de --- /dev/null +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/DecisionV3.java @@ -0,0 +1,69 @@ +package com.optimizely.ab.event.internal.payload; + +public class DecisionV3 { + String campaignId; + String experimentId; + String variationId; + boolean isCampaignHoldback; + + public DecisionV3(String campaignId, String experimentId, String variationId, boolean isCampaignHoldback) { + this.campaignId = campaignId; + this.experimentId = experimentId; + this.variationId = variationId; + this.isCampaignHoldback = isCampaignHoldback; + } + + public String getCampaignId() { + return campaignId; + } + + public void setCampaignId(String campaignId) { + this.campaignId = campaignId; + } + + public String getExperimentId() { + return experimentId; + } + + public void setExperimentId(String experimentId) { + this.experimentId = experimentId; + } + + public String getVariationId() { + return variationId; + } + + public void setVariationId(String variationId) { + this.variationId = variationId; + } + + public boolean getCampaignHoldback() { + return isCampaignHoldback; + } + + public void setCampaignHoldback(boolean campaignHoldback) { + isCampaignHoldback = campaignHoldback; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + DecisionV3 that = (DecisionV3) o; + + if (isCampaignHoldback != that.isCampaignHoldback) return false; + if (!campaignId.equals(that.campaignId)) return false; + if (!experimentId.equals(that.experimentId)) return false; + return variationId.equals(that.variationId); + } + + @Override + public int hashCode() { + int result = campaignId.hashCode(); + result = 31 * result + experimentId.hashCode(); + result = 31 * result + variationId.hashCode(); + result = 31 * result + (isCampaignHoldback ? 1 : 0); + return result; + } +} diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventBatch.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventBatch.java new file mode 100644 index 000000000..f81d3ccf6 --- /dev/null +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventBatch.java @@ -0,0 +1,81 @@ +package com.optimizely.ab.event.internal.payload; + +import com.optimizely.ab.event.internal.BuildVersionInfo; + +import java.util.List; + +public class EventBatch { + String accountId; + List visitors; + Boolean anonymizeIp; + String clientName = Event.ClientEngine.JAVA_SDK.getClientEngineValue(); + String clientVersion = BuildVersionInfo.VERSION; + String projectId; + String revision; + + public EventBatch(String accountId, List visitors, Boolean anonymizeIp, String projectId, String revision) { + this.accountId = accountId; + this.visitors = visitors; + this.anonymizeIp = anonymizeIp; + this.clientName = clientName; + this.clientVersion = clientVersion; + this.projectId = projectId; + this.revision = revision; + } + + public String getAccountId() { + return accountId; + } + + public void setAccountId(String accountId) { + this.accountId = accountId; + } + + public List getVisitors() { + return visitors; + } + + public void setVisitors(List visitors) { + this.visitors = visitors; + } + + public Boolean getAnonymizeIp() { + return anonymizeIp; + } + + public void setAnonymizeIp(Boolean anonymizeIp) { + this.anonymizeIp = anonymizeIp; + } + + public String getClientName() { + return clientName; + } + + public void setClientName(String clientName) { + this.clientName = clientName; + } + + public String getClientVersion() { + return clientVersion; + } + + public void setClientVersion(String clientVersion) { + this.clientVersion = clientVersion; + } + + public String getProjectId() { + return projectId; + } + + public void setProjectId(String projectId) { + this.projectId = projectId; + } + + public String getRevision() { + return revision; + } + + public void setRevision(String revision) { + this.revision = revision; + } +} diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventV3.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventV3.java new file mode 100644 index 000000000..701d10ade --- /dev/null +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventV3.java @@ -0,0 +1,132 @@ +package com.optimizely.ab.event.internal.payload; + +import java.util.Map; + +public class EventV3 { + long timestamp; + String uuid; + String entityId; + String key; + Number quantity; + Number revenue; + Map tags; + String type; + Number value; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + EventV3 eventV3 = (EventV3) o; + + if (timestamp != eventV3.timestamp) return false; + if (!uuid.equals(eventV3.uuid)) return false; + if (entityId != null ? !entityId.equals(eventV3.entityId) : eventV3.entityId != null) return false; + if (key != null ? !key.equals(eventV3.key) : eventV3.key != null) return false; + if (quantity != null ? !quantity.equals(eventV3.quantity) : eventV3.quantity != null) return false; + if (revenue != null ? !revenue.equals(eventV3.revenue) : eventV3.revenue != null) return false; + if (tags != null ? !tags.equals(eventV3.tags) : eventV3.tags != null) return false; + if (!type.equals(eventV3.type)) return false; + return value != null ? value.equals(eventV3.value) : eventV3.value == null; + } + + @Override + public int hashCode() { + int result = (int) (timestamp ^ (timestamp >>> 32)); + result = 31 * result + uuid.hashCode(); + result = 31 * result + (entityId != null ? entityId.hashCode() : 0); + result = 31 * result + (key != null ? key.hashCode() : 0); + result = 31 * result + (quantity != null ? quantity.hashCode() : 0); + result = 31 * result + (revenue != null ? revenue.hashCode() : 0); + result = 31 * result + (tags != null ? tags.hashCode() : 0); + result = 31 * result + type.hashCode(); + result = 31 * result + (value != null ? value.hashCode() : 0); + return result; + } + + public EventV3(long timestamp, String uuid, String entityId, String key, Number quantity, + Number revenue, Map tags, String type, Number value) { + this.timestamp = timestamp; + this.uuid = uuid; + this.entityId = entityId; + this.key = key; + this.quantity = quantity; + this.revenue = revenue; + this.tags = tags; + this.type = type; + this.value = value; + } + + public long getTimestamp() { + return timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getEntityId() { + return entityId; + } + + public void setEntityId(String entityId) { + this.entityId = entityId; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public Number getQuantity() { + return quantity; + } + + public void setQuantity(Number quantity) { + this.quantity = quantity; + } + + public Number getRevenue() { + return revenue; + } + + public void setRevenue(Number revenue) { + this.revenue = revenue; + } + + public Map getTags() { + return tags; + } + + public void setTags(Map tags) { + this.tags = tags; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public Number getValue() { + return value; + } + + public void setValue(Number value) { + this.value = value; + } +} diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Snapshot.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Snapshot.java new file mode 100644 index 000000000..7c0985bed --- /dev/null +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Snapshot.java @@ -0,0 +1,50 @@ +package com.optimizely.ab.event.internal.payload; + +import java.util.List; + +public class Snapshot { + List decisions; + List events; + long activationTimestamp; + + public Snapshot(List decisions, List events) { + this.decisions = decisions; + this.events = events; + } + + public List getDecisions() { + return decisions; + } + + public void setDecisions(List decisions) { + this.decisions = decisions; + } + + public List getEvents() { + return events; + } + + public void setEvents(List events) { + this.events = events; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Snapshot snapshot = (Snapshot) o; + + if (activationTimestamp != snapshot.activationTimestamp) return false; + if (!decisions.equals(snapshot.decisions)) return false; + return events.equals(snapshot.events); + } + + @Override + public int hashCode() { + int result = decisions.hashCode(); + result = 31 * result + events.hashCode(); + result = 31 * result + (int) (activationTimestamp ^ (activationTimestamp >>> 32)); + return result; + } +} diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Visitor.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Visitor.java new file mode 100644 index 000000000..fa8e9a5b9 --- /dev/null +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Visitor.java @@ -0,0 +1,71 @@ +package com.optimizely.ab.event.internal.payload; + +import java.util.List; + +public class Visitor { + String visitorId; + String sessionId; + List attributes; + List snapshots; + + public Visitor(String visitorId, String sessionId, List attributes, List snapshots) { + this.visitorId = visitorId; + this.sessionId = sessionId; + this.attributes = attributes; + this.snapshots = snapshots; + } + + public String getVisitorId() { + return visitorId; + } + + public void setVisitorId(String visitorId) { + this.visitorId = visitorId; + } + + public String getSessionId() { + return sessionId; + } + + public void setSessionId(String sessionId) { + this.sessionId = sessionId; + } + + public List getAttributes() { + return attributes; + } + + public void setAttributes(List attributes) { + this.attributes = attributes; + } + + public List getSnapshots() { + return snapshots; + } + + public void setSnapshots(List snapshots) { + this.snapshots = snapshots; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Visitor visitor = (Visitor) o; + + if (!visitorId.equals(visitor.visitorId)) return false; + if (sessionId != null ? !sessionId.equals(visitor.sessionId) : visitor.sessionId != null) return false; + if (attributes != null ? !attributes.equals(visitor.attributes) : visitor.attributes != null) return false; + return snapshots.equals(visitor.snapshots); + } + + @Override + public int hashCode() { + int result = visitorId.hashCode(); + result = 31 * result + (sessionId != null ? sessionId.hashCode() : 0); + result = 31 * result + (attributes != null ? attributes.hashCode() : 0); + result = 31 * result + snapshots.hashCode(); + return result; + } +} diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/GsonSerializer.java b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/GsonSerializer.java index a5a7aafba..203f50852 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/GsonSerializer.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/GsonSerializer.java @@ -24,7 +24,7 @@ class GsonSerializer implements Serializer { private Gson gson = new Gson(); - public String serialize(T payload) { + public String serialize(T payload) { return gson.toJson(payload); } } diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JacksonSerializer.java b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JacksonSerializer.java index afc316d34..e221b9745 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JacksonSerializer.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JacksonSerializer.java @@ -26,7 +26,7 @@ class JacksonSerializer implements Serializer { private final ObjectMapper mapper = new ObjectMapper(); - public String serialize(T payload) { + public String serialize(T payload) { mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); try { return mapper.writeValueAsString(payload); diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSerializer.java b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSerializer.java index eb1fa71e9..0ce0fd920 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSerializer.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSerializer.java @@ -21,7 +21,7 @@ class JsonSerializer implements Serializer { - public String serialize(T payload) { + public String serialize(T payload) { JSONObject payloadJsonObject = new JSONObject(payload); return payloadJsonObject.toString(); } diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSimpleSerializer.java b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSimpleSerializer.java index 549fc230e..d972e618e 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSimpleSerializer.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSimpleSerializer.java @@ -32,7 +32,7 @@ @SuppressWarnings("unchecked") class JsonSimpleSerializer implements Serializer { - public String serialize(T payload) { + public String serialize(T payload) { JSONObject payloadJsonObj; if (payload instanceof Impression) { payloadJsonObj = serializeImpression((Impression)payload); diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/Serializer.java b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/Serializer.java index 01bd4c123..ca3cd9934 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/Serializer.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/Serializer.java @@ -19,5 +19,5 @@ import com.optimizely.ab.event.internal.payload.Event; public interface Serializer { - String serialize(T payload) throws SerializationException; + String serialize(T payload) throws SerializationException; } diff --git a/core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderV3Test.java b/core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderV3Test.java new file mode 100644 index 000000000..a7537263a --- /dev/null +++ b/core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderV3Test.java @@ -0,0 +1,727 @@ +package com.optimizely.ab.event.internal; + +import com.google.gson.Gson; +import com.google.gson.internal.LazilyParsedNumber; +import com.optimizely.ab.bucketing.Bucketer; +import com.optimizely.ab.bucketing.DecisionService; +import com.optimizely.ab.bucketing.UserProfileService; +import com.optimizely.ab.config.Attribute; +import com.optimizely.ab.config.EventType; +import com.optimizely.ab.config.Experiment; +import com.optimizely.ab.config.ProjectConfig; +import com.optimizely.ab.config.Variation; +import com.optimizely.ab.error.ErrorHandler; +import com.optimizely.ab.error.NoOpErrorHandler; +import com.optimizely.ab.event.LogEvent; +import com.optimizely.ab.event.internal.payload.Conversion; +import com.optimizely.ab.event.internal.payload.Decision; +import com.optimizely.ab.event.internal.payload.DecisionV3; +import com.optimizely.ab.event.internal.payload.Event; +import com.optimizely.ab.event.internal.payload.EventBatch; +import com.optimizely.ab.event.internal.payload.EventMetric; +import com.optimizely.ab.event.internal.payload.Feature; +import com.optimizely.ab.event.internal.payload.Impression; +import com.optimizely.ab.event.internal.payload.LayerState; +import com.optimizely.ab.internal.ReservedEventKey; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.optimizely.ab.config.ProjectConfigTestUtils.validProjectConfigV2; +import static com.optimizely.ab.config.ProjectConfigTestUtils.validProjectConfigV4; +import static com.optimizely.ab.config.ValidProjectConfigV4.AUDIENCE_GRYFFINDOR_VALUE; +import static com.optimizely.ab.config.ValidProjectConfigV4.EVENT_BASIC_EVENT_KEY; +import static com.optimizely.ab.config.ValidProjectConfigV4.EVENT_PAUSED_EXPERIMENT_KEY; +import static com.optimizely.ab.config.ValidProjectConfigV4.MULTIVARIATE_EXPERIMENT_FORCED_VARIATION_USER_ID_GRED; +import static com.optimizely.ab.config.ValidProjectConfigV4.PAUSED_EXPERIMENT_FORCED_VARIATION_USER_ID_CONTROL; +import static junit.framework.Assert.assertNotNull; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.closeTo; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(Parameterized.class) +public class EventBuilderV3Test { + + @Parameterized.Parameters + public static Collection data() throws IOException { + return Arrays.asList(new Object[][] { + { + 2, + validProjectConfigV2() + }, + { + 4, + validProjectConfigV4() + } + }); + } + + private Gson gson = new Gson(); + private EventBuilderV3 builder = new EventBuilderV3(); + + private static String userId = "userId"; + private int datafileVersion; + private ProjectConfig validProjectConfig; + + public EventBuilderV3Test(int datafileVersion, + ProjectConfig validProjectConfig) { + this.datafileVersion = datafileVersion; + this.validProjectConfig = validProjectConfig; + } + + /** + * Verify {@link com.optimizely.ab.event.internal.payload.Impression} event creation + */ + @Test + public void createImpressionEvent() throws Exception { + // use the "valid" project config and its associated experiment, variation, and attributes + ProjectConfig projectConfig = validProjectConfigV2(); + Experiment activatedExperiment = projectConfig.getExperiments().get(0); + Variation bucketedVariation = activatedExperiment.getVariations().get(0); + Attribute attribute = projectConfig.getAttributes().get(0); + String userId = "userId"; + Map attributeMap = Collections.singletonMap(attribute.getKey(), "value"); + DecisionV3 expectedDecision = new DecisionV3(activatedExperiment.getLayerId(), activatedExperiment.getId(), bucketedVariation.getId(), false); + com.optimizely.ab.event.internal.payload.Attribute feature = new com.optimizely.ab.event.internal.payload.Attribute(attribute.getId(), + attribute.getKey(), Feature.CUSTOM_ATTRIBUTE_FEATURE_TYPE, + "value"); + List expectedUserFeatures = Collections.singletonList(feature); + + LogEvent impressionEvent = builder.createImpressionEvent(projectConfig, activatedExperiment, bucketedVariation, + userId, attributeMap); + + // verify that request endpoint is correct + assertThat(impressionEvent.getEndpointUrl(), is(EventBuilderV3.EVENT_ENDPOINT)); + + EventBatch eventBatch = gson.fromJson(impressionEvent.getBody(), EventBatch.class); + + // verify payload information + assertThat(eventBatch.getVisitors().get(0).getVisitorId(), is(userId)); + //assertThat((double) eventBatch.getVisitors().get(0).getSnapshots().get(0).getEvents().get(0).getTimestamp(), closeTo((double)System.currentTimeMillis(), 60.0)); + assertFalse(eventBatch.getVisitors().get(0).getSnapshots().get(0).getDecisions().get(0).getCampaignHoldback()); + assertThat(eventBatch.getAnonymizeIp(), is(projectConfig.getAnonymizeIP())); + assertThat(eventBatch.getProjectId(), is(projectConfig.getProjectId())); + assertThat(eventBatch.getVisitors().get(0).getSnapshots().get(0).getDecisions().get(0), is(expectedDecision)); + assertThat(eventBatch.getVisitors().get(0).getSnapshots().get(0).getDecisions().get(0).getCampaignId(), + is(activatedExperiment.getLayerId())); + assertThat(eventBatch.getAccountId(), is(projectConfig.getAccountId())); + assertThat(eventBatch.getVisitors().get(0).getAttributes(), is(expectedUserFeatures)); + assertThat(eventBatch.getClientName(), is(Event.ClientEngine.JAVA_SDK.getClientEngineValue())); + assertThat(eventBatch.getClientVersion(), is(BuildVersionInfo.VERSION)); + assertNull(eventBatch.getVisitors().get(0).getSessionId()); + } + + /** + * Verify that passing through an unknown attribute causes that attribute to be ignored, rather than + * causing an exception to be thrown. + */ + @Test + public void createImpressionEventIgnoresUnknownAttributes() throws Exception { + // use the "valid" project config and its associated experiment, variation, and attributes + ProjectConfig projectConfig = validProjectConfigV2(); + Experiment activatedExperiment = projectConfig.getExperiments().get(0); + Variation bucketedVariation = activatedExperiment.getVariations().get(0); + + LogEvent impressionEvent = + builder.createImpressionEvent(projectConfig, activatedExperiment, bucketedVariation, "userId", + Collections.singletonMap("unknownAttribute", "blahValue")); + + EventBatch impression = gson.fromJson(impressionEvent.getBody(), EventBatch.class); + + // verify that no Feature is created for "unknownAtrribute" -> "blahValue" + for (com.optimizely.ab.event.internal.payload.Attribute feature : impression.getVisitors().get(0).getAttributes()) { + assertFalse(feature.getKey() == "unknownAttribute"); + assertFalse(feature.getValue() == "blahValue"); + } + } + + /** + * Verify that supplying {@link EventBuilderV2} with a custom client engine and client version results in impression + * events being sent with the overriden values. + */ + @Test + public void createImpressionEventAndroidClientEngineClientVersion() throws Exception { + EventBuilderV3 builder = new EventBuilderV3(Event.ClientEngine.ANDROID_SDK, "0.0.0"); + ProjectConfig projectConfig = validProjectConfigV2(); + Experiment activatedExperiment = projectConfig.getExperiments().get(0); + Variation bucketedVariation = activatedExperiment.getVariations().get(0); + Attribute attribute = projectConfig.getAttributes().get(0); + String userId = "userId"; + Map attributeMap = Collections.singletonMap(attribute.getKey(), "value"); + + LogEvent impressionEvent = builder.createImpressionEvent(projectConfig, activatedExperiment, bucketedVariation, + userId, attributeMap); + EventBatch impression = gson.fromJson(impressionEvent.getBody(), EventBatch.class); + + assertThat(impression.getClientName(), is(Event.ClientEngine.ANDROID_SDK.getClientEngineValue())); + assertThat(impression.getClientVersion(), is("0.0.0")); + } + + /** + * Verify that supplying {@link EventBuilderV2} with a custom Android TV client engine and client version + * results in impression events being sent with the overriden values. + */ + @Test + public void createImpressionEventAndroidTVClientEngineClientVersion() throws Exception { + String clientVersion = "0.0.0"; + EventBuilderV3 builder = new EventBuilderV3(Event.ClientEngine.ANDROID_TV_SDK, clientVersion); + ProjectConfig projectConfig = validProjectConfigV2(); + Experiment activatedExperiment = projectConfig.getExperiments().get(0); + Variation bucketedVariation = activatedExperiment.getVariations().get(0); + Attribute attribute = projectConfig.getAttributes().get(0); + String userId = "userId"; + Map attributeMap = Collections.singletonMap(attribute.getKey(), "value"); + + LogEvent impressionEvent = builder.createImpressionEvent(projectConfig, activatedExperiment, bucketedVariation, + userId, attributeMap); + EventBatch impression = gson.fromJson(impressionEvent.getBody(), EventBatch.class); + + assertThat(impression.getClientName(), is(Event.ClientEngine.ANDROID_TV_SDK.getClientEngineValue())); + assertThat(impression.getClientVersion(), is(clientVersion)); + } + + /** + * Verify {@link com.optimizely.ab.event.internal.payload.Conversion} event creation + */ + @Test + public void createConversionEvent() throws Exception { + // use the "valid" project config and its associated experiment, variation, and attributes + Attribute attribute = validProjectConfig.getAttributes().get(0); + EventType eventType = validProjectConfig.getEventTypes().get(0); + String userId = "userId"; + + Bucketer mockBucketAlgorithm = mock(Bucketer.class); + + List allExperiments = validProjectConfig.getExperiments(); + List experimentsForEventKey = validProjectConfig.getExperimentsForEventKey(eventType.getKey()); + + // Bucket to the first variation for all experiments. However, only a subset of the experiments will actually + // call the bucket function. + for (Experiment experiment : allExperiments) { + when(mockBucketAlgorithm.bucket(experiment, userId)) + .thenReturn(experiment.getVariations().get(0)); + } + DecisionService decisionService = new DecisionService( + mockBucketAlgorithm, + mock(ErrorHandler.class), + validProjectConfig, + mock(UserProfileService.class) + ); + + Map attributeMap = Collections.singletonMap(attribute.getKey(), AUDIENCE_GRYFFINDOR_VALUE); + Map eventTagMap = new HashMap(); + eventTagMap.put("boolean_param", false); + eventTagMap.put("string_param", "123"); + Map experimentVariationMap = createExperimentVariationMap( + validProjectConfig, + decisionService, + eventType.getKey(), + userId, + attributeMap); + LogEvent conversionEvent = builder.createConversionEvent( + validProjectConfig, + experimentVariationMap, + userId, + eventType.getId(), + eventType.getKey(), + attributeMap, + eventTagMap); + + List expectedDecisions = new ArrayList(); + + for (Experiment experiment : experimentsForEventKey) { + if (experiment.isRunning()) { + DecisionV3 layerState = new DecisionV3(experiment.getLayerId(), experiment.getId(), + experiment.getVariations().get(0).getId(), false); + expectedDecisions.add(layerState); + } + } + + // verify that the request endpoint is correct + assertThat(conversionEvent.getEndpointUrl(), is(EventBuilderV3.EVENT_ENDPOINT)); + + EventBatch conversion = gson.fromJson(conversionEvent.getBody(), EventBatch.class); + + // verify payload information + assertThat(conversion.getVisitors().get(0).getVisitorId(), is(userId)); + assertThat((double)conversion.getVisitors().get(0).getSnapshots().get(0).getEvents().get(0).getTimestamp(), + closeTo((double)System.currentTimeMillis(), 120.0)); + assertThat(conversion.getProjectId(), is(validProjectConfig.getProjectId())); + assertThat(conversion.getAccountId(), is(validProjectConfig.getAccountId())); + + com.optimizely.ab.event.internal.payload.Attribute feature = new com.optimizely.ab.event.internal.payload.Attribute(attribute.getId(), attribute.getKey(), Feature.CUSTOM_ATTRIBUTE_FEATURE_TYPE, + AUDIENCE_GRYFFINDOR_VALUE); + List expectedUserFeatures = Collections.singletonList(feature); + + // Event Features + List expectedEventFeatures = new ArrayList(); + expectedEventFeatures.add(new Feature("", "boolean_param", Feature.EVENT_FEATURE_TYPE, + false, false)); + expectedEventFeatures.add(new Feature("", "string_param", Feature.EVENT_FEATURE_TYPE, + "123", false)); + + assertEquals(conversion.getVisitors().get(0).getAttributes(), expectedUserFeatures); + assertThat(conversion.getVisitors().get(0).getSnapshots().get(0).getDecisions(), containsInAnyOrder(expectedDecisions.toArray())); + assertEquals(conversion.getVisitors().get(0).getSnapshots().get(0).getEvents().get(0).getEntityId(), eventType.getId()); + assertEquals(conversion.getVisitors().get(0).getSnapshots().get(0).getEvents().get(0).getKey(), eventType.getKey()); + assertEquals(conversion.getVisitors().get(0).getSnapshots().get(0).getEvents().get(0).getRevenue(), null); + assertTrue(conversion.getVisitors().get(0).getAttributes().containsAll(expectedEventFeatures)); + //assertTrue(expectedEventFeatures.containsAll(conversion.getEventFeatures())); + assertFalse(conversion.getVisitors().get(0).getSnapshots().get(0).getDecisions().get(0).getCampaignHoldback()); + assertEquals(conversion.getAnonymizeIp(), validProjectConfig.getAnonymizeIP()); + assertEquals(conversion.getClientName(), Event.ClientEngine.JAVA_SDK.getClientEngineValue()); + assertEquals(conversion.getClientVersion(), BuildVersionInfo.VERSION); + } + + /** + * Verify that "revenue" and "value" are properly recorded in a conversion request as {@link EventMetric} objects. + * "revenue" is fixed-point and "value" is floating-point. + */ + @Test + public void createConversionParamsWithEventMetrics() throws Exception { + Long revenue = 1234L; + Double value = 13.37; + + // use the "valid" project config and its associated experiment, variation, and attributes + Attribute attribute = validProjectConfig.getAttributes().get(0); + EventType eventType = validProjectConfig.getEventTypes().get(0); + + Bucketer mockBucketAlgorithm = mock(Bucketer.class); + + // Bucket to the first variation for all experiments. + for (Experiment experiment : validProjectConfig.getExperiments()) { + when(mockBucketAlgorithm.bucket(experiment, userId)) + .thenReturn(experiment.getVariations().get(0)); + } + DecisionService decisionService = new DecisionService( + mockBucketAlgorithm, + mock(ErrorHandler.class), + validProjectConfig, + mock(UserProfileService.class) + ); + + Map attributeMap = Collections.singletonMap(attribute.getKey(), "value"); + Map eventTagMap = new HashMap(); + eventTagMap.put(ReservedEventKey.REVENUE.toString(), revenue); + eventTagMap.put(ReservedEventKey.VALUE.toString(), value); + Map experimentVariationMap = createExperimentVariationMap( + validProjectConfig, + decisionService, + eventType.getKey(), + userId, + attributeMap); + LogEvent conversionEvent = builder.createConversionEvent(validProjectConfig, experimentVariationMap, userId, + eventType.getId(), eventType.getKey(), attributeMap, + eventTagMap); + + EventBatch conversion = gson.fromJson(conversionEvent.getBody(), EventBatch.class); + List eventMetrics = Arrays.asList( + new EventMetric(EventMetric.REVENUE_METRIC_TYPE, new LazilyParsedNumber(revenue.toString())), + new EventMetric(EventMetric.NUMERIC_METRIC_TYPE, new LazilyParsedNumber(value.toString()))); + // we're not going to verify everything, only the event metrics + assertThat(conversion.getVisitors().get(0).getSnapshots().get(0).getEvents().get(0).getRevenue().longValue(), is(revenue)); + assertThat(conversion.getVisitors().get(0).getSnapshots().get(0).getEvents().get(0).getValue().doubleValue(), is(value)); + } + + /** + * Verify that precedence is given to forced variation bucketing over audience evaluation when constructing a + * conversion event. + */ + @Test + public void createConversionEventForcedVariationBucketingPrecedesAudienceEval() { + EventType eventType; + String whitelistedUserId; + if (datafileVersion == 4) { + eventType = validProjectConfig.getEventNameMapping().get(EVENT_BASIC_EVENT_KEY); + whitelistedUserId = MULTIVARIATE_EXPERIMENT_FORCED_VARIATION_USER_ID_GRED; + } + else { + eventType = validProjectConfig.getEventTypes().get(0); + whitelistedUserId = "testUser1"; + } + + DecisionService decisionService = new DecisionService( + new Bucketer(validProjectConfig), + new NoOpErrorHandler(), + validProjectConfig, + mock(UserProfileService.class) + ); + + // attributes are empty so user won't be in the audience for experiment using the event, but bucketing + // will still take place + Map experimentVariationMap = createExperimentVariationMap( + validProjectConfig, + decisionService, + eventType.getKey(), + whitelistedUserId, + Collections.emptyMap()); + LogEvent conversionEvent = builder.createConversionEvent( + validProjectConfig, + experimentVariationMap, + whitelistedUserId, + eventType.getId(), + eventType.getKey(), + Collections.emptyMap(), + Collections.emptyMap()); + assertNotNull(conversionEvent); + + EventBatch conversion = gson.fromJson(conversionEvent.getBody(), EventBatch.class); + if (datafileVersion == 4) { + // 2 experiments use the event + // basic experiment has no audience + // user is whitelisted in to one audience + assertEquals(2, conversion.getVisitors().get(0).getSnapshots().get(0).getDecisions().size()); + } + else { + assertEquals(1, conversion.getVisitors().get(0).getSnapshots().get(0).getDecisions().size()); + } + } + + /** + * Verify that precedence is given to experiment status over forced variation bucketing when constructing a + * conversion event. + */ + @Test + public void createConversionEventExperimentStatusPrecedesForcedVariation() { + EventType eventType; + if (datafileVersion == 4) { + eventType = validProjectConfig.getEventNameMapping().get(EVENT_PAUSED_EXPERIMENT_KEY); + } + else { + eventType = validProjectConfig.getEventTypes().get(3); + } + String whitelistedUserId = PAUSED_EXPERIMENT_FORCED_VARIATION_USER_ID_CONTROL; + + Bucketer bucketer = spy(new Bucketer(validProjectConfig)); + DecisionService decisionService = new DecisionService( + bucketer, + mock(ErrorHandler.class), + validProjectConfig, + mock(UserProfileService.class) + ); + + Map experimentVariationMap = createExperimentVariationMap( + validProjectConfig, + decisionService, + eventType.getKey(), + whitelistedUserId, + Collections.emptyMap()); + LogEvent conversionEvent = builder.createConversionEvent( + validProjectConfig, + experimentVariationMap, + whitelistedUserId, + eventType.getId(), + eventType.getKey(), + Collections.emptyMap(), + Collections.emptyMap()); + + for (Experiment experiment : validProjectConfig.getExperiments()) { + verify(bucketer, never()).bucket(experiment, whitelistedUserId); + } + + assertNull(conversionEvent); + } + + /** + * Verify that supplying {@link EventBuilderV2} with a custom client engine and client version results in conversion + * events being sent with the overriden values. + */ + @Test + public void createConversionEventAndroidClientEngineClientVersion() throws Exception { + EventBuilderV2 builder = new EventBuilderV2(Event.ClientEngine.ANDROID_SDK, "0.0.0"); + Attribute attribute = validProjectConfig.getAttributes().get(0); + EventType eventType = validProjectConfig.getEventTypes().get(0); + + Bucketer mockBucketAlgorithm = mock(Bucketer.class); + for (Experiment experiment : validProjectConfig.getExperiments()) { + when(mockBucketAlgorithm.bucket(experiment, userId)) + .thenReturn(experiment.getVariations().get(0)); + } + DecisionService decisionService = new DecisionService( + mockBucketAlgorithm, + mock(ErrorHandler.class), + validProjectConfig, + mock(UserProfileService.class) + ); + + Map attributeMap = Collections.singletonMap(attribute.getKey(), "value"); + Map experimentVariationMap = createExperimentVariationMap( + validProjectConfig, + decisionService, + eventType.getKey(), + userId, + attributeMap); + LogEvent conversionEvent = builder.createConversionEvent( + validProjectConfig, + experimentVariationMap, + userId, + eventType.getId(), + eventType.getKey(), + attributeMap, + Collections.emptyMap()); + + Conversion conversion = gson.fromJson(conversionEvent.getBody(), Conversion.class); + + assertThat(conversion.getClientEngine(), is(Event.ClientEngine.ANDROID_SDK.getClientEngineValue())); + assertThat(conversion.getClientVersion(), is("0.0.0")); + } + + /** + * Verify that supplying {@link EventBuilderV2} with a Android TV client engine and client version results in + * conversion events being sent with the overriden values. + */ + @Test + public void createConversionEventAndroidTVClientEngineClientVersion() throws Exception { + String clientVersion = "0.0.0"; + EventBuilderV2 builder = new EventBuilderV2(Event.ClientEngine.ANDROID_TV_SDK, clientVersion); + ProjectConfig projectConfig = validProjectConfigV2(); + Attribute attribute = projectConfig.getAttributes().get(0); + EventType eventType = projectConfig.getEventTypes().get(0); + String userId = "userId"; + + Bucketer mockBucketAlgorithm = mock(Bucketer.class); + for (Experiment experiment : projectConfig.getExperiments()) { + when(mockBucketAlgorithm.bucket(experiment, userId)) + .thenReturn(experiment.getVariations().get(0)); + } + + Map attributeMap = Collections.singletonMap(attribute.getKey(), "value"); + List experimentList = projectConfig.getExperimentsForEventKey(eventType.getKey()); + Map experimentVariationMap = new HashMap(experimentList.size()); + for (Experiment experiment : experimentList) { + experimentVariationMap.put(experiment, experiment.getVariations().get(0)); + } + + LogEvent conversionEvent = builder.createConversionEvent( + projectConfig, + experimentVariationMap, + userId, + eventType.getId(), + eventType.getKey(), + attributeMap, + Collections.emptyMap()); + Conversion conversion = gson.fromJson(conversionEvent.getBody(), Conversion.class); + + assertThat(conversion.getClientEngine(), is(Event.ClientEngine.ANDROID_TV_SDK.getClientEngineValue())); + assertThat(conversion.getClientVersion(), is(clientVersion)); + } + + /** + * Verify that supplying an empty Experiment Variation map to + * {@link EventBuilderV2#createConversionEvent(ProjectConfig, Map, String, String, String, Map, Map)} + * returns a null {@link LogEvent}. + */ + @Test + public void createConversionEventReturnsNullWhenExperimentVariationMapIsEmpty() { + EventType eventType = validProjectConfig.getEventTypes().get(0); + EventBuilderV2 builder = new EventBuilderV2(); + + LogEvent conversionEvent = builder.createConversionEvent( + validProjectConfig, + Collections.emptyMap(), + userId, + eventType.getId(), + eventType.getKey(), + Collections.emptyMap(), + Collections.emptyMap() + ); + + assertNull(conversionEvent); + } + + /** + * Verify {@link Impression} event creation + */ + @Test + public void createImpressionEventWithBucketingId() throws Exception { + // use the "valid" project config and its associated experiment, variation, and attributes + ProjectConfig projectConfig = validProjectConfigV2(); + Experiment activatedExperiment = projectConfig.getExperiments().get(0); + Variation bucketedVariation = activatedExperiment.getVariations().get(0); + Attribute attribute = projectConfig.getAttributes().get(0); + String userId = "userId"; + Map attributeMap = new HashMap(); + attributeMap.put(attribute.getKey(), "value"); + + attributeMap.put(com.optimizely.ab.bucketing.DecisionService.BUCKETING_ATTRIBUTE, "variation"); + + Decision expectedDecision = new Decision(bucketedVariation.getId(), false, activatedExperiment.getId()); + Feature feature = new Feature(attribute.getId(), attribute.getKey(), Feature.CUSTOM_ATTRIBUTE_FEATURE_TYPE, + "value", true); + Feature feature1 = new Feature(com.optimizely.ab.bucketing.DecisionService.BUCKETING_ATTRIBUTE, + com.optimizely.ab.event.internal.EventBuilderV2.ATTRIBUTE_KEY_FOR_BUCKETING_ATTRIBUTE, + Feature.CUSTOM_ATTRIBUTE_FEATURE_TYPE, + "variation", true); + List expectedUserFeatures = new java.util.ArrayList(); + expectedUserFeatures.add(feature); + expectedUserFeatures.add(feature1); + + LogEvent impressionEvent = builder.createImpressionEvent(projectConfig, activatedExperiment, bucketedVariation, + userId, attributeMap); + + // verify that request endpoint is correct + assertThat(impressionEvent.getEndpointUrl(), is(EventBuilderV2.IMPRESSION_ENDPOINT)); + + Impression impression = gson.fromJson(impressionEvent.getBody(), Impression.class); + + // verify payload information + assertThat(impression.getVisitorId(), is(userId)); + assertThat((double)impression.getTimestamp(), closeTo((double)System.currentTimeMillis(), 60.0)); + assertFalse(impression.getIsGlobalHoldback()); + assertThat(impression.getAnonymizeIP(), is(projectConfig.getAnonymizeIP())); + assertThat(impression.getProjectId(), is(projectConfig.getProjectId())); + assertThat(impression.getDecision(), is(expectedDecision)); + assertThat(impression.getLayerId(), is(activatedExperiment.getLayerId())); + assertThat(impression.getAccountId(), is(projectConfig.getAccountId())); + + assertThat(impression.getUserFeatures(), is(expectedUserFeatures)); + assertThat(impression.getClientEngine(), is(Event.ClientEngine.JAVA_SDK.getClientEngineValue())); + assertThat(impression.getClientVersion(), is(BuildVersionInfo.VERSION)); + assertNull(impression.getSessionId()); + } + + /** + * Verify {@link Conversion} event creation + */ + @Test + public void createConversionEventWithBucketingId() throws Exception { + // use the "valid" project config and its associated experiment, variation, and attributes + Attribute attribute = validProjectConfig.getAttributes().get(0); + EventType eventType = validProjectConfig.getEventTypes().get(0); + String userId = "userId"; + String bucketingId = "bucketingId"; + + Bucketer mockBucketAlgorithm = mock(Bucketer.class); + + List allExperiments = validProjectConfig.getExperiments(); + List experimentsForEventKey = validProjectConfig.getExperimentsForEventKey(eventType.getKey()); + + // Bucket to the first variation for all experiments. However, only a subset of the experiments will actually + // call the bucket function. + for (Experiment experiment : allExperiments) { + when(mockBucketAlgorithm.bucket(experiment, bucketingId)) + .thenReturn(experiment.getVariations().get(0)); + } + DecisionService decisionService = new DecisionService( + mockBucketAlgorithm, + mock(ErrorHandler.class), + validProjectConfig, + mock(UserProfileService.class) + ); + + Map attributeMap = new java.util.HashMap(); + attributeMap.put(attribute.getKey(), AUDIENCE_GRYFFINDOR_VALUE); + attributeMap.put(com.optimizely.ab.bucketing.DecisionService.BUCKETING_ATTRIBUTE, bucketingId); + + Map eventTagMap = new HashMap(); + eventTagMap.put("boolean_param", false); + eventTagMap.put("string_param", "123"); + Map experimentVariationMap = createExperimentVariationMap( + validProjectConfig, + decisionService, + eventType.getKey(), + userId, + attributeMap); + LogEvent conversionEvent = builder.createConversionEvent( + validProjectConfig, + experimentVariationMap, + userId, + eventType.getId(), + eventType.getKey(), + attributeMap, + eventTagMap); + + List expectedLayerStates = new ArrayList(); + + for (Experiment experiment : experimentsForEventKey) { + if (experiment.isRunning()) { + LayerState layerState = new LayerState(experiment.getLayerId(), validProjectConfig.getRevision(), + new Decision(experiment.getVariations().get(0).getId(), false, experiment.getId()), true); + expectedLayerStates.add(layerState); + } + } + + // verify that the request endpoint is correct + assertThat(conversionEvent.getEndpointUrl(), is(EventBuilderV2.CONVERSION_ENDPOINT)); + + Conversion conversion = gson.fromJson(conversionEvent.getBody(), Conversion.class); + + // verify payload information + assertThat(conversion.getVisitorId(), is(userId)); + assertThat((double)conversion.getTimestamp(), closeTo((double)System.currentTimeMillis(), 120.0)); + assertThat(conversion.getProjectId(), is(validProjectConfig.getProjectId())); + assertThat(conversion.getAccountId(), is(validProjectConfig.getAccountId())); + + Feature feature = new Feature(attribute.getId(), attribute.getKey(), Feature.CUSTOM_ATTRIBUTE_FEATURE_TYPE, + AUDIENCE_GRYFFINDOR_VALUE, true); + Feature feature1 = new Feature(com.optimizely.ab.bucketing.DecisionService.BUCKETING_ATTRIBUTE, + com.optimizely.ab.event.internal.EventBuilderV2.ATTRIBUTE_KEY_FOR_BUCKETING_ATTRIBUTE, + Feature.CUSTOM_ATTRIBUTE_FEATURE_TYPE, + bucketingId, true); + List expectedUserFeatures = new ArrayList(); + expectedUserFeatures.add(feature); + expectedUserFeatures.add(feature1); + + // Event Features + List expectedEventFeatures = new ArrayList(); + expectedEventFeatures.add(new Feature("", "boolean_param", Feature.EVENT_FEATURE_TYPE, + false, false)); + expectedEventFeatures.add(new Feature("", "string_param", Feature.EVENT_FEATURE_TYPE, + "123", false)); + + assertEquals(conversion.getUserFeatures(), expectedUserFeatures); + assertThat(conversion.getLayerStates(), containsInAnyOrder(expectedLayerStates.toArray())); + assertEquals(conversion.getEventEntityId(), eventType.getId()); + assertEquals(conversion.getEventName(), eventType.getKey()); + assertEquals(conversion.getEventMetrics(), Collections.emptyList()); + assertTrue(conversion.getEventFeatures().containsAll(expectedEventFeatures)); + assertTrue(expectedEventFeatures.containsAll(conversion.getEventFeatures())); + assertFalse(conversion.getIsGlobalHoldback()); + assertEquals(conversion.getAnonymizeIP(), validProjectConfig.getAnonymizeIP()); + assertEquals(conversion.getClientEngine(), Event.ClientEngine.JAVA_SDK.getClientEngineValue()); + assertEquals(conversion.getClientVersion(), BuildVersionInfo.VERSION); + } + + + //========== helper methods =========// + public static Map createExperimentVariationMap(ProjectConfig projectConfig, + DecisionService decisionService, + String eventName, + String userId, + @Nullable Map attributes) { + + List eventExperiments = projectConfig.getExperimentsForEventKey(eventName); + Map experimentVariationMap = new HashMap(eventExperiments.size()); + for (Experiment experiment : eventExperiments) { + if (experiment.isRunning()) { + Variation variation = decisionService.getVariation(experiment, userId, attributes); + if (variation != null) { + experimentVariationMap.put(experiment, variation); + } + } + } + + return experimentVariationMap; + } +} + From 65b41460d93fab39c6dbb12a8b3449e41861dee1 Mon Sep 17 00:00:00 2001 From: Thomas Zurkan Date: Wed, 24 Jan 2018 13:03:10 -0800 Subject: [PATCH 2/9] fix all the event builder V3 tests --- .../ab/event/internal/EventBuilderV3.java | 8 +- .../ab/event/internal/payload/DecisionV3.java | 4 +- .../ab/event/internal/payload/EventBatch.java | 10 ++ .../ab/event/internal/EventBuilderV3Test.java | 127 ++++++++---------- 4 files changed, 74 insertions(+), 75 deletions(-) diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilderV3.java b/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilderV3.java index ea3c3d0d9..1eff3efa7 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilderV3.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilderV3.java @@ -60,7 +60,7 @@ public LogEvent createImpressionEvent(@Nonnull ProjectConfig projectConfig, Visitor visitor = new Visitor(userId, null, buildAttributeList(projectConfig, attributes), Arrays.asList(snapshot)); List visitors = Arrays.asList(visitor); - EventBatch eventBatch = new EventBatch(projectConfig.getAccountId(), visitors, projectConfig.getAnonymizeIP(), projectConfig.getProjectId(), projectConfig.getRevision()); + EventBatch eventBatch = new EventBatch(clientEngine.getClientEngineValue(), clientVersion, projectConfig.getAccountId(), visitors, projectConfig.getAnonymizeIP(), projectConfig.getProjectId(), projectConfig.getRevision()); String payload = this.serializer.serialize(eventBatch); return new LogEvent(LogEvent.RequestMethod.POST, EVENT_ENDPOINT, Collections.emptyMap(), payload); } @@ -73,6 +73,10 @@ public LogEvent createConversionEvent(@Nonnull ProjectConfig projectConfig, @Nonnull Map attributes, @Nonnull Map eventTags) { + if (experimentVariationMap.isEmpty()) { + return null; + } + ArrayList decisionV3s = new ArrayList(); ArrayList eventV3s = new ArrayList(); for (Experiment experiment : experimentVariationMap.keySet()) { @@ -89,7 +93,7 @@ public LogEvent createConversionEvent(@Nonnull ProjectConfig projectConfig, Visitor visitor = new Visitor(userId, null, buildAttributeList(projectConfig, attributes), Arrays.asList(snapshot)); List visitors = Arrays.asList(visitor); - EventBatch eventBatch = new EventBatch(projectConfig.getAccountId(), visitors, projectConfig.getAnonymizeIP(), projectConfig.getProjectId(), projectConfig.getRevision()); + EventBatch eventBatch = new EventBatch(clientEngine.getClientEngineValue(), clientVersion, projectConfig.getAccountId(), visitors, projectConfig.getAnonymizeIP(), projectConfig.getProjectId(), projectConfig.getRevision()); String payload = this.serializer.serialize(eventBatch); return new LogEvent(LogEvent.RequestMethod.POST, EVENT_ENDPOINT, Collections.emptyMap(), payload); } diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/DecisionV3.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/DecisionV3.java index 89b3811de..8471f5229 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/DecisionV3.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/DecisionV3.java @@ -37,11 +37,11 @@ public void setVariationId(String variationId) { this.variationId = variationId; } - public boolean getCampaignHoldback() { + public boolean getIsCampaignHoldback() { return isCampaignHoldback; } - public void setCampaignHoldback(boolean campaignHoldback) { + public void setIsCampaignHoldback(boolean campaignHoldback) { isCampaignHoldback = campaignHoldback; } diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventBatch.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventBatch.java index f81d3ccf6..e2b29cd8f 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventBatch.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventBatch.java @@ -23,6 +23,16 @@ public EventBatch(String accountId, List visitors, Boolean anonymizeIp, this.revision = revision; } + public EventBatch(String clientName, String clientVersion, String accountId, List visitors, Boolean anonymizeIp, String projectId, String revision) { + this.accountId = accountId; + this.visitors = visitors; + this.anonymizeIp = anonymizeIp; + this.clientName = clientName; + this.clientVersion = clientVersion; + this.projectId = projectId; + this.revision = revision; + } + public String getAccountId() { return accountId; } diff --git a/core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderV3Test.java b/core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderV3Test.java index a7537263a..e9162e0b7 100644 --- a/core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderV3Test.java +++ b/core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderV3Test.java @@ -117,8 +117,8 @@ public void createImpressionEvent() throws Exception { // verify payload information assertThat(eventBatch.getVisitors().get(0).getVisitorId(), is(userId)); - //assertThat((double) eventBatch.getVisitors().get(0).getSnapshots().get(0).getEvents().get(0).getTimestamp(), closeTo((double)System.currentTimeMillis(), 60.0)); - assertFalse(eventBatch.getVisitors().get(0).getSnapshots().get(0).getDecisions().get(0).getCampaignHoldback()); + assertThat((double) eventBatch.getVisitors().get(0).getSnapshots().get(0).getEvents().get(0).getTimestamp(), closeTo((double)System.currentTimeMillis(), 1000.0)); + assertFalse(eventBatch.getVisitors().get(0).getSnapshots().get(0).getDecisions().get(0).getIsCampaignHoldback()); assertThat(eventBatch.getAnonymizeIp(), is(projectConfig.getAnonymizeIP())); assertThat(eventBatch.getProjectId(), is(projectConfig.getProjectId())); assertThat(eventBatch.getVisitors().get(0).getSnapshots().get(0).getDecisions().get(0), is(expectedDecision)); @@ -273,21 +273,14 @@ public void createConversionEvent() throws Exception { AUDIENCE_GRYFFINDOR_VALUE); List expectedUserFeatures = Collections.singletonList(feature); - // Event Features - List expectedEventFeatures = new ArrayList(); - expectedEventFeatures.add(new Feature("", "boolean_param", Feature.EVENT_FEATURE_TYPE, - false, false)); - expectedEventFeatures.add(new Feature("", "string_param", Feature.EVENT_FEATURE_TYPE, - "123", false)); - assertEquals(conversion.getVisitors().get(0).getAttributes(), expectedUserFeatures); assertThat(conversion.getVisitors().get(0).getSnapshots().get(0).getDecisions(), containsInAnyOrder(expectedDecisions.toArray())); assertEquals(conversion.getVisitors().get(0).getSnapshots().get(0).getEvents().get(0).getEntityId(), eventType.getId()); assertEquals(conversion.getVisitors().get(0).getSnapshots().get(0).getEvents().get(0).getKey(), eventType.getKey()); assertEquals(conversion.getVisitors().get(0).getSnapshots().get(0).getEvents().get(0).getRevenue(), null); - assertTrue(conversion.getVisitors().get(0).getAttributes().containsAll(expectedEventFeatures)); - //assertTrue(expectedEventFeatures.containsAll(conversion.getEventFeatures())); - assertFalse(conversion.getVisitors().get(0).getSnapshots().get(0).getDecisions().get(0).getCampaignHoldback()); + assertTrue(conversion.getVisitors().get(0).getAttributes().containsAll(expectedUserFeatures)); + assertTrue(conversion.getVisitors().get(0).getSnapshots().get(0).getEvents().get(0).getTags().equals(eventTagMap)); + assertFalse(conversion.getVisitors().get(0).getSnapshots().get(0).getDecisions().get(0).getIsCampaignHoldback()); assertEquals(conversion.getAnonymizeIp(), validProjectConfig.getAnonymizeIP()); assertEquals(conversion.getClientName(), Event.ClientEngine.JAVA_SDK.getClientEngineValue()); assertEquals(conversion.getClientVersion(), BuildVersionInfo.VERSION); @@ -448,7 +441,7 @@ public void createConversionEventExperimentStatusPrecedesForcedVariation() { */ @Test public void createConversionEventAndroidClientEngineClientVersion() throws Exception { - EventBuilderV2 builder = new EventBuilderV2(Event.ClientEngine.ANDROID_SDK, "0.0.0"); + EventBuilderV3 builder = new EventBuilderV3(Event.ClientEngine.ANDROID_SDK, "0.0.0"); Attribute attribute = validProjectConfig.getAttributes().get(0); EventType eventType = validProjectConfig.getEventTypes().get(0); @@ -480,9 +473,9 @@ public void createConversionEventAndroidClientEngineClientVersion() throws Excep attributeMap, Collections.emptyMap()); - Conversion conversion = gson.fromJson(conversionEvent.getBody(), Conversion.class); + EventBatch conversion = gson.fromJson(conversionEvent.getBody(), EventBatch.class); - assertThat(conversion.getClientEngine(), is(Event.ClientEngine.ANDROID_SDK.getClientEngineValue())); + assertThat(conversion.getClientName(), is(Event.ClientEngine.ANDROID_SDK.getClientEngineValue())); assertThat(conversion.getClientVersion(), is("0.0.0")); } @@ -493,7 +486,7 @@ public void createConversionEventAndroidClientEngineClientVersion() throws Excep @Test public void createConversionEventAndroidTVClientEngineClientVersion() throws Exception { String clientVersion = "0.0.0"; - EventBuilderV2 builder = new EventBuilderV2(Event.ClientEngine.ANDROID_TV_SDK, clientVersion); + EventBuilderV3 builder = new EventBuilderV3(Event.ClientEngine.ANDROID_TV_SDK, clientVersion); ProjectConfig projectConfig = validProjectConfigV2(); Attribute attribute = projectConfig.getAttributes().get(0); EventType eventType = projectConfig.getEventTypes().get(0); @@ -520,9 +513,9 @@ public void createConversionEventAndroidTVClientEngineClientVersion() throws Exc eventType.getKey(), attributeMap, Collections.emptyMap()); - Conversion conversion = gson.fromJson(conversionEvent.getBody(), Conversion.class); + EventBatch conversion = gson.fromJson(conversionEvent.getBody(), EventBatch.class); - assertThat(conversion.getClientEngine(), is(Event.ClientEngine.ANDROID_TV_SDK.getClientEngineValue())); + assertThat(conversion.getClientName(), is(Event.ClientEngine.ANDROID_TV_SDK.getClientEngineValue())); assertThat(conversion.getClientVersion(), is(clientVersion)); } @@ -534,7 +527,7 @@ public void createConversionEventAndroidTVClientEngineClientVersion() throws Exc @Test public void createConversionEventReturnsNullWhenExperimentVariationMapIsEmpty() { EventType eventType = validProjectConfig.getEventTypes().get(0); - EventBuilderV2 builder = new EventBuilderV2(); + EventBuilderV3 builder = new EventBuilderV3(); LogEvent conversionEvent = builder.createConversionEvent( validProjectConfig, @@ -565,39 +558,39 @@ public void createImpressionEventWithBucketingId() throws Exception { attributeMap.put(com.optimizely.ab.bucketing.DecisionService.BUCKETING_ATTRIBUTE, "variation"); - Decision expectedDecision = new Decision(bucketedVariation.getId(), false, activatedExperiment.getId()); - Feature feature = new Feature(attribute.getId(), attribute.getKey(), Feature.CUSTOM_ATTRIBUTE_FEATURE_TYPE, - "value", true); - Feature feature1 = new Feature(com.optimizely.ab.bucketing.DecisionService.BUCKETING_ATTRIBUTE, + DecisionV3 expectedDecision = new DecisionV3(activatedExperiment.getLayerId(), activatedExperiment.getId(), bucketedVariation.getId(), false); + + com.optimizely.ab.event.internal.payload.Attribute feature = new com.optimizely.ab.event.internal.payload.Attribute(attribute.getId(), attribute.getKey(), Feature.CUSTOM_ATTRIBUTE_FEATURE_TYPE, + "value"); + com.optimizely.ab.event.internal.payload.Attribute feature1 = new com.optimizely.ab.event.internal.payload.Attribute(com.optimizely.ab.bucketing.DecisionService.BUCKETING_ATTRIBUTE, com.optimizely.ab.event.internal.EventBuilderV2.ATTRIBUTE_KEY_FOR_BUCKETING_ATTRIBUTE, Feature.CUSTOM_ATTRIBUTE_FEATURE_TYPE, - "variation", true); - List expectedUserFeatures = new java.util.ArrayList(); - expectedUserFeatures.add(feature); - expectedUserFeatures.add(feature1); + "variation"); + + List expectedUserFeatures = Arrays.asList(feature, feature1); LogEvent impressionEvent = builder.createImpressionEvent(projectConfig, activatedExperiment, bucketedVariation, userId, attributeMap); // verify that request endpoint is correct - assertThat(impressionEvent.getEndpointUrl(), is(EventBuilderV2.IMPRESSION_ENDPOINT)); + assertThat(impressionEvent.getEndpointUrl(), is(EventBuilderV3.EVENT_ENDPOINT)); - Impression impression = gson.fromJson(impressionEvent.getBody(), Impression.class); + EventBatch impression = gson.fromJson(impressionEvent.getBody(), EventBatch.class); // verify payload information - assertThat(impression.getVisitorId(), is(userId)); - assertThat((double)impression.getTimestamp(), closeTo((double)System.currentTimeMillis(), 60.0)); - assertFalse(impression.getIsGlobalHoldback()); - assertThat(impression.getAnonymizeIP(), is(projectConfig.getAnonymizeIP())); + assertThat(impression.getVisitors().get(0).getVisitorId(), is(userId)); + assertThat((double)impression.getVisitors().get(0).getSnapshots().get(0).getEvents().get(0).getTimestamp(), closeTo((double)System.currentTimeMillis(), 1000.0)); + assertFalse(impression.getVisitors().get(0).getSnapshots().get(0).getDecisions().get(0).getIsCampaignHoldback()); + assertThat(impression.getAnonymizeIp(), is(projectConfig.getAnonymizeIP())); assertThat(impression.getProjectId(), is(projectConfig.getProjectId())); - assertThat(impression.getDecision(), is(expectedDecision)); - assertThat(impression.getLayerId(), is(activatedExperiment.getLayerId())); + assertThat(impression.getVisitors().get(0).getSnapshots().get(0).getDecisions().get(0), is(expectedDecision)); + assertThat(impression.getVisitors().get(0).getSnapshots().get(0).getDecisions().get(0).getCampaignId(), is(activatedExperiment.getLayerId())); assertThat(impression.getAccountId(), is(projectConfig.getAccountId())); - assertThat(impression.getUserFeatures(), is(expectedUserFeatures)); - assertThat(impression.getClientEngine(), is(Event.ClientEngine.JAVA_SDK.getClientEngineValue())); + assertThat(impression.getVisitors().get(0).getAttributes(), is(expectedUserFeatures)); + assertThat(impression.getClientName(), is(Event.ClientEngine.JAVA_SDK.getClientEngineValue())); assertThat(impression.getClientVersion(), is(BuildVersionInfo.VERSION)); - assertNull(impression.getSessionId()); + assertNull(impression.getVisitors().get(0).getSessionId()); } /** @@ -651,54 +644,46 @@ public void createConversionEventWithBucketingId() throws Exception { attributeMap, eventTagMap); - List expectedLayerStates = new ArrayList(); + List expectedDecisions = new ArrayList(); for (Experiment experiment : experimentsForEventKey) { if (experiment.isRunning()) { - LayerState layerState = new LayerState(experiment.getLayerId(), validProjectConfig.getRevision(), - new Decision(experiment.getVariations().get(0).getId(), false, experiment.getId()), true); - expectedLayerStates.add(layerState); + DecisionV3 decisionV3 = new DecisionV3(experiment.getLayerId(), experiment.getId(), + experiment.getVariations().get(0).getId(), false); + expectedDecisions.add(decisionV3); } } // verify that the request endpoint is correct - assertThat(conversionEvent.getEndpointUrl(), is(EventBuilderV2.CONVERSION_ENDPOINT)); + assertThat(conversionEvent.getEndpointUrl(), is(EventBuilderV3.EVENT_ENDPOINT)); - Conversion conversion = gson.fromJson(conversionEvent.getBody(), Conversion.class); + EventBatch conversion = gson.fromJson(conversionEvent.getBody(), EventBatch.class); // verify payload information - assertThat(conversion.getVisitorId(), is(userId)); - assertThat((double)conversion.getTimestamp(), closeTo((double)System.currentTimeMillis(), 120.0)); + assertThat(conversion.getVisitors().get(0).getVisitorId(), is(userId)); + assertThat((double)conversion.getVisitors().get(0).getSnapshots().get(0).getEvents().get(0).getTimestamp(), closeTo((double)System.currentTimeMillis(), 1000.0)); assertThat(conversion.getProjectId(), is(validProjectConfig.getProjectId())); assertThat(conversion.getAccountId(), is(validProjectConfig.getAccountId())); - Feature feature = new Feature(attribute.getId(), attribute.getKey(), Feature.CUSTOM_ATTRIBUTE_FEATURE_TYPE, - AUDIENCE_GRYFFINDOR_VALUE, true); - Feature feature1 = new Feature(com.optimizely.ab.bucketing.DecisionService.BUCKETING_ATTRIBUTE, + com.optimizely.ab.event.internal.payload.Attribute attribute1 = new com.optimizely.ab.event.internal.payload.Attribute(attribute.getId(), attribute.getKey(), Feature.CUSTOM_ATTRIBUTE_FEATURE_TYPE, + AUDIENCE_GRYFFINDOR_VALUE); + com.optimizely.ab.event.internal.payload.Attribute attribute2 = new com.optimizely.ab.event.internal.payload.Attribute(com.optimizely.ab.bucketing.DecisionService.BUCKETING_ATTRIBUTE, com.optimizely.ab.event.internal.EventBuilderV2.ATTRIBUTE_KEY_FOR_BUCKETING_ATTRIBUTE, Feature.CUSTOM_ATTRIBUTE_FEATURE_TYPE, - bucketingId, true); - List expectedUserFeatures = new ArrayList(); - expectedUserFeatures.add(feature); - expectedUserFeatures.add(feature1); - - // Event Features - List expectedEventFeatures = new ArrayList(); - expectedEventFeatures.add(new Feature("", "boolean_param", Feature.EVENT_FEATURE_TYPE, - false, false)); - expectedEventFeatures.add(new Feature("", "string_param", Feature.EVENT_FEATURE_TYPE, - "123", false)); - - assertEquals(conversion.getUserFeatures(), expectedUserFeatures); - assertThat(conversion.getLayerStates(), containsInAnyOrder(expectedLayerStates.toArray())); - assertEquals(conversion.getEventEntityId(), eventType.getId()); - assertEquals(conversion.getEventName(), eventType.getKey()); - assertEquals(conversion.getEventMetrics(), Collections.emptyList()); - assertTrue(conversion.getEventFeatures().containsAll(expectedEventFeatures)); - assertTrue(expectedEventFeatures.containsAll(conversion.getEventFeatures())); - assertFalse(conversion.getIsGlobalHoldback()); - assertEquals(conversion.getAnonymizeIP(), validProjectConfig.getAnonymizeIP()); - assertEquals(conversion.getClientEngine(), Event.ClientEngine.JAVA_SDK.getClientEngineValue()); + bucketingId); + List expectedUserFeatures = Arrays.asList(attribute1, attribute2); + + assertEquals(conversion.getVisitors().get(0).getAttributes(), expectedUserFeatures); + assertThat(conversion.getVisitors().get(0).getSnapshots().get(0).getDecisions(), containsInAnyOrder(expectedDecisions.toArray())); + assertEquals(conversion.getVisitors().get(0).getSnapshots().get(0).getEvents().get(0).getEntityId(), eventType.getId()); + assertEquals(conversion.getVisitors().get(0).getSnapshots().get(0).getEvents().get(0).getType(), eventType.getKey()); + assertEquals(conversion.getVisitors().get(0).getSnapshots().get(0).getEvents().get(0).getKey(), eventType.getKey()); + assertEquals(conversion.getVisitors().get(0).getSnapshots().get(0).getEvents().get(0).getRevenue(), null); + assertEquals(conversion.getVisitors().get(0).getSnapshots().get(0).getEvents().get(0).getQuantity(), null); + assertTrue(conversion.getVisitors().get(0).getSnapshots().get(0).getEvents().get(0).getTags().equals(eventTagMap)); + assertFalse(conversion.getVisitors().get(0).getSnapshots().get(0).getDecisions().get(0).getIsCampaignHoldback()); + assertEquals(conversion.getAnonymizeIp(), validProjectConfig.getAnonymizeIP()); + assertEquals(conversion.getClientName(), Event.ClientEngine.JAVA_SDK.getClientEngineValue()); assertEquals(conversion.getClientVersion(), BuildVersionInfo.VERSION); } From 294ec87519e2446e046ed0582171ef59d7d36213 Mon Sep 17 00:00:00 2001 From: Thomas Zurkan Date: Wed, 24 Jan 2018 13:18:51 -0800 Subject: [PATCH 3/9] cleanup lint errors --- .../ab/event/internal/EventBuilderV3.java | 16 +++++++--------- .../ab/event/internal/payload/EventBatch.java | 2 -- .../ab/event/internal/EventBuilderV3Test.java | 3 --- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilderV3.java b/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilderV3.java index 1eff3efa7..dbf4a21ec 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilderV3.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilderV3.java @@ -78,10 +78,8 @@ public LogEvent createConversionEvent(@Nonnull ProjectConfig projectConfig, } ArrayList decisionV3s = new ArrayList(); - ArrayList eventV3s = new ArrayList(); - for (Experiment experiment : experimentVariationMap.keySet()) { - Variation variation = experimentVariationMap.get(experiment); - DecisionV3 decisionV3 = new DecisionV3(experiment.getLayerId(), experiment.getId(), variation.getId(), false); + for (Map.Entry entry : experimentVariationMap.entrySet()) { + DecisionV3 decisionV3 = new DecisionV3(entry.getKey().getLayerId(), entry.getKey().getId(), entry.getValue().getId(), false); decisionV3s.add(decisionV3); } @@ -102,14 +100,14 @@ private List buildAttributeList(ProjectConfig projectConfig, Map attributes1 = new ArrayList(); Map attributeMap = projectConfig.getAttributeKeyMapping(); - for (String key : attributes.keySet()) { - com.optimizely.ab.config.Attribute projectAttribute = attributeMap.get(key); + for (Map.Entry entry : attributes.entrySet()) { + com.optimizely.ab.config.Attribute projectAttribute = attributeMap.get(entry.getKey()); Attribute attribute = new Attribute((projectAttribute != null ? projectAttribute.getId() : null), - key, Attribute.CUSTOM_ATTRIBUTE_TYPE, attributes.get(key)); + entry.getKey(), Attribute.CUSTOM_ATTRIBUTE_TYPE, entry.getValue()); - if (key == DecisionService.BUCKETING_ATTRIBUTE) { + if (entry.getKey() == DecisionService.BUCKETING_ATTRIBUTE) { attribute = new Attribute(com.optimizely.ab.bucketing.DecisionService.BUCKETING_ATTRIBUTE, - EventBuilderV2.ATTRIBUTE_KEY_FOR_BUCKETING_ATTRIBUTE, Attribute.CUSTOM_ATTRIBUTE_TYPE, attributes.get(key)); + EventBuilderV2.ATTRIBUTE_KEY_FOR_BUCKETING_ATTRIBUTE, Attribute.CUSTOM_ATTRIBUTE_TYPE, entry.getValue()); } attributes1.add(attribute); diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventBatch.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventBatch.java index e2b29cd8f..1658573ef 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventBatch.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventBatch.java @@ -17,8 +17,6 @@ public EventBatch(String accountId, List visitors, Boolean anonymizeIp, this.accountId = accountId; this.visitors = visitors; this.anonymizeIp = anonymizeIp; - this.clientName = clientName; - this.clientVersion = clientVersion; this.projectId = projectId; this.revision = revision; } diff --git a/core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderV3Test.java b/core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderV3Test.java index e9162e0b7..04ecf2507 100644 --- a/core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderV3Test.java +++ b/core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderV3Test.java @@ -328,9 +328,6 @@ public void createConversionParamsWithEventMetrics() throws Exception { eventTagMap); EventBatch conversion = gson.fromJson(conversionEvent.getBody(), EventBatch.class); - List eventMetrics = Arrays.asList( - new EventMetric(EventMetric.REVENUE_METRIC_TYPE, new LazilyParsedNumber(revenue.toString())), - new EventMetric(EventMetric.NUMERIC_METRIC_TYPE, new LazilyParsedNumber(value.toString()))); // we're not going to verify everything, only the event metrics assertThat(conversion.getVisitors().get(0).getSnapshots().get(0).getEvents().get(0).getRevenue().longValue(), is(revenue)); assertThat(conversion.getVisitors().get(0).getSnapshots().get(0).getEvents().get(0).getValue().doubleValue(), is(value)); From c122d33d7f3b62a4117607ec341f63991fcc53a3 Mon Sep 17 00:00:00 2001 From: Thomas Zurkan Date: Wed, 24 Jan 2018 16:53:30 -0800 Subject: [PATCH 4/9] refactor, remove V2 only use V3. All unit tests passing --- .../java/com/optimizely/ab/Optimizely.java | 3 +- .../ab/event/internal/EventBuilder.java | 123 ++- .../ab/event/internal/EventBuilderV2.java | 233 ------ .../ab/event/internal/EventBuilderV3.java | 118 --- .../ab/event/internal/payload/Attribute.java | 4 + .../ab/event/internal/payload/Conversion.java | 239 ------ .../ab/event/internal/payload/Decision.java | 85 -- .../ab/event/internal/payload/DecisionV3.java | 4 + .../ab/event/internal/payload/EventBatch.java | 33 + .../event/internal/payload/EventMetric.java | 75 -- .../ab/event/internal/payload/EventV3.java | 63 +- .../ab/event/internal/payload/Feature.java | 114 --- .../ab/event/internal/payload/Impression.java | 203 ----- .../ab/event/internal/payload/LayerState.java | 89 --- .../ab/event/internal/payload/Snapshot.java | 23 +- .../ab/event/internal/payload/Visitor.java | 4 + .../serializer/JacksonSerializer.java | 2 - .../serializer/JsonSimpleSerializer.java | 172 ++-- .../optimizely/ab/OptimizelyBuilderTest.java | 12 +- .../com/optimizely/ab/OptimizelyTest.java | 7 +- .../ab/event/internal/EventBuilderV2Test.java | 745 ------------------ .../ab/event/internal/EventBuilderV3Test.java | 66 +- .../serializer/GsonSerializerTest.java | 27 +- .../serializer/JacksonSerializerTest.java | 28 +- .../serializer/JsonSerializerTest.java | 11 +- .../serializer/JsonSimpleSerializerTest.java | 12 +- .../serializer/SerializerTestUtils.java | 77 +- .../serializer/conversion-session-id.json | 70 +- .../test/resources/serializer/conversion.json | 68 +- .../serializer/impression-session-id.json | 55 +- .../test/resources/serializer/impression.json | 53 +- 31 files changed, 548 insertions(+), 2270 deletions(-) delete mode 100644 core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilderV2.java delete mode 100644 core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilderV3.java delete mode 100644 core-api/src/main/java/com/optimizely/ab/event/internal/payload/Conversion.java delete mode 100644 core-api/src/main/java/com/optimizely/ab/event/internal/payload/Decision.java delete mode 100644 core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventMetric.java delete mode 100644 core-api/src/main/java/com/optimizely/ab/event/internal/payload/Feature.java delete mode 100644 core-api/src/main/java/com/optimizely/ab/event/internal/payload/Impression.java delete mode 100644 core-api/src/main/java/com/optimizely/ab/event/internal/payload/LayerState.java delete mode 100644 core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderV2Test.java diff --git a/core-api/src/main/java/com/optimizely/ab/Optimizely.java b/core-api/src/main/java/com/optimizely/ab/Optimizely.java index 6e36624bc..5103bd017 100644 --- a/core-api/src/main/java/com/optimizely/ab/Optimizely.java +++ b/core-api/src/main/java/com/optimizely/ab/Optimizely.java @@ -37,7 +37,6 @@ import com.optimizely.ab.event.LogEvent; import com.optimizely.ab.event.internal.BuildVersionInfo; import com.optimizely.ab.event.internal.EventBuilder; -import com.optimizely.ab.event.internal.EventBuilderV2; import com.optimizely.ab.event.internal.payload.Event.ClientEngine; import com.optimizely.ab.internal.EventTagUtils; import com.optimizely.ab.notification.NotificationBroadcaster; @@ -948,7 +947,7 @@ public Optimizely build() throws ConfigParseException { if (eventBuilder == null) { - eventBuilder = new EventBuilderV2(clientEngine, clientVersion); + eventBuilder = new EventBuilder(clientEngine, clientVersion); } if (errorHandler == null) { diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilder.java b/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilder.java index 05da9e084..19e45ed12 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilder.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilder.java @@ -1,42 +1,123 @@ -/** - * - * Copyright 2016-2017, 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ 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; import com.optimizely.ab.config.Variation; import com.optimizely.ab.event.LogEvent; +import com.optimizely.ab.event.internal.payload.Attribute; +import com.optimizely.ab.event.internal.payload.DecisionV3; +import com.optimizely.ab.event.internal.payload.Event; +import com.optimizely.ab.event.internal.payload.EventBatch; +import com.optimizely.ab.event.internal.payload.EventV3; +import com.optimizely.ab.event.internal.payload.Snapshot; +import com.optimizely.ab.event.internal.payload.Visitor; +import com.optimizely.ab.event.internal.serializer.DefaultJsonSerializer; +import com.optimizely.ab.event.internal.serializer.Serializer; +import com.optimizely.ab.internal.EventTagUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Map; +import java.util.UUID; -public abstract class EventBuilder { +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"; - public abstract LogEvent createImpressionEvent(@Nonnull ProjectConfig projectConfig, + private Serializer serializer; + @VisibleForTesting + public final String clientVersion; + @VisibleForTesting + public final Event.ClientEngine clientEngine; + + public EventBuilder() { + this(Event.ClientEngine.JAVA_SDK, BuildVersionInfo.VERSION); + } + + public EventBuilder(Event.ClientEngine clientEngine, String clientVersion) { + this.clientEngine = clientEngine; + this.clientVersion = clientVersion; + this.serializer = DefaultJsonSerializer.getInstance(); + } + + + public LogEvent createImpressionEvent(@Nonnull ProjectConfig projectConfig, @Nonnull Experiment activatedExperiment, @Nonnull Variation variation, @Nonnull String userId, - @Nonnull Map attributes); + @Nonnull Map attributes) { - public abstract LogEvent createConversionEvent(@Nonnull ProjectConfig projectConfig, + DecisionV3 decisionV3 = new DecisionV3(activatedExperiment.getLayerId(), activatedExperiment.getId(), + variation.getId(), false); + EventV3 eventV3 = new EventV3(System.currentTimeMillis(),UUID.randomUUID().toString(), activatedExperiment.getLayerId(), + ACTIVATE_EVENT_KEY, null, null, null, ACTIVATE_EVENT_KEY, null); + Snapshot snapshot = new Snapshot(Arrays.asList(decisionV3), Arrays.asList(eventV3)); + + Visitor visitor = new Visitor(userId, null, buildAttributeList(projectConfig, attributes), Arrays.asList(snapshot)); + List visitors = Arrays.asList(visitor); + EventBatch eventBatch = new EventBatch(clientEngine.getClientEngineValue(), clientVersion, projectConfig.getAccountId(), visitors, projectConfig.getAnonymizeIP(), projectConfig.getProjectId(), projectConfig.getRevision()); + String payload = this.serializer.serialize(eventBatch); + return new LogEvent(LogEvent.RequestMethod.POST, EVENT_ENDPOINT, Collections.emptyMap(), payload); + } + + public LogEvent createConversionEvent(@Nonnull ProjectConfig projectConfig, @Nonnull Map experimentVariationMap, @Nonnull String userId, @Nonnull String eventId, @Nonnull String eventName, @Nonnull Map attributes, - @Nonnull Map eventTags); + @Nonnull Map eventTags) { + + if (experimentVariationMap.isEmpty()) { + return null; + } + + ArrayList decisionV3s = new ArrayList(); + for (Map.Entry entry : experimentVariationMap.entrySet()) { + DecisionV3 decisionV3 = new DecisionV3(entry.getKey().getLayerId(), entry.getKey().getId(), entry.getValue().getId(), false); + decisionV3s.add(decisionV3); + } + + EventType eventType = projectConfig.getEventNameMapping().get(eventName); + + EventV3 eventV3 = new EventV3(System.currentTimeMillis(),UUID.randomUUID().toString(), eventType.getId(), + eventType.getKey(), null, EventTagUtils.getRevenueValue(eventTags), eventTags, eventType.getKey(), EventTagUtils.getNumericValue(eventTags)); + Snapshot snapshot = new Snapshot(decisionV3s, Arrays.asList(eventV3)); + + Visitor visitor = new Visitor(userId, null, buildAttributeList(projectConfig, attributes), Arrays.asList(snapshot)); + List visitors = Arrays.asList(visitor); + EventBatch eventBatch = new EventBatch(clientEngine.getClientEngineValue(), clientVersion, projectConfig.getAccountId(), visitors, projectConfig.getAnonymizeIP(), projectConfig.getProjectId(), projectConfig.getRevision()); + String payload = this.serializer.serialize(eventBatch); + return new LogEvent(LogEvent.RequestMethod.POST, EVENT_ENDPOINT, Collections.emptyMap(), payload); + } + + private List buildAttributeList(ProjectConfig projectConfig, Map attributes) { + List attributes1 = new ArrayList(); + + Map attributeMap = projectConfig.getAttributeKeyMapping(); + for (Map.Entry 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()); + } + + attributes1.add(attribute); + } + + return attributes1; + } } diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilderV2.java b/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilderV2.java deleted file mode 100644 index 2a538c33a..000000000 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilderV2.java +++ /dev/null @@ -1,233 +0,0 @@ -/** - * - * Copyright 2016-2017, 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.optimizely.ab.event.internal; - -import com.optimizely.ab.annotations.VisibleForTesting; -import com.optimizely.ab.bucketing.DecisionService; -import com.optimizely.ab.config.Attribute; -import com.optimizely.ab.config.Experiment; -import com.optimizely.ab.config.ProjectConfig; -import com.optimizely.ab.config.Variation; -import com.optimizely.ab.event.LogEvent; -import com.optimizely.ab.event.internal.payload.Conversion; -import com.optimizely.ab.event.internal.payload.Decision; -import com.optimizely.ab.event.internal.payload.Event.ClientEngine; -import com.optimizely.ab.event.internal.payload.EventMetric; -import com.optimizely.ab.event.internal.payload.Feature; -import com.optimizely.ab.event.internal.payload.Impression; -import com.optimizely.ab.event.internal.payload.LayerState; -import com.optimizely.ab.event.internal.serializer.DefaultJsonSerializer; -import com.optimizely.ab.event.internal.serializer.Serializer; -import com.optimizely.ab.internal.EventTagUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.annotation.Nonnull; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import static com.optimizely.ab.event.LogEvent.RequestMethod; - -public class EventBuilderV2 extends EventBuilder { - - private static final Logger logger = LoggerFactory.getLogger(EventBuilderV2.class); - - static final String IMPRESSION_ENDPOINT = "https://logx.optimizely.com/log/decision"; - static final String CONVERSION_ENDPOINT = "https://logx.optimizely.com/log/event"; - static final String ATTRIBUTE_KEY_FOR_BUCKETING_ATTRIBUTE = "optimizely_bucketing_id"; - - @VisibleForTesting - public final ClientEngine clientEngine; - - @VisibleForTesting - public final String clientVersion; - - private Serializer serializer; - - public EventBuilderV2() { - this(ClientEngine.JAVA_SDK, BuildVersionInfo.VERSION); - } - - public EventBuilderV2(ClientEngine clientEngine, String clientVersion) { - this.clientEngine = clientEngine; - this.clientVersion = clientVersion; - this.serializer = DefaultJsonSerializer.getInstance(); - } - - public LogEvent createImpressionEvent(@Nonnull ProjectConfig projectConfig, - @Nonnull Experiment activatedExperiment, - @Nonnull Variation variation, - @Nonnull String userId, - @Nonnull Map attributes) { - - Impression impressionPayload = new Impression(); - impressionPayload.setVisitorId(userId); - impressionPayload.setTimestamp(System.currentTimeMillis()); - impressionPayload.setIsGlobalHoldback(false); - impressionPayload.setProjectId(projectConfig.getProjectId()); - - Decision decision = new Decision(); - decision.setVariationId(variation.getId()); - decision.setIsLayerHoldback(false); - decision.setExperimentId(activatedExperiment.getId()); - impressionPayload.setDecision(decision); - - impressionPayload.setLayerId(activatedExperiment.getLayerId()); - impressionPayload.setAccountId(projectConfig.getAccountId()); - impressionPayload.setUserFeatures(createUserFeatures(attributes, projectConfig)); - impressionPayload.setClientEngine(clientEngine); - impressionPayload.setClientVersion(clientVersion); - impressionPayload.setAnonymizeIP(projectConfig.getAnonymizeIP()); - impressionPayload.setRevision(projectConfig.getRevision()); - - String payload = this.serializer.serialize(impressionPayload); - return new LogEvent(RequestMethod.POST, IMPRESSION_ENDPOINT, Collections.emptyMap(), payload); - } - - public LogEvent createConversionEvent(@Nonnull ProjectConfig projectConfig, - @Nonnull Map experimentVariationMap, - @Nonnull String userId, - @Nonnull String eventId, - @Nonnull String eventName, - @Nonnull Map attributes, - @Nonnull Map eventTags) { - - if (experimentVariationMap.isEmpty()) { - return null; - } - - List layerStates = createLayerStates(projectConfig, experimentVariationMap); - - List eventMetrics = new ArrayList(); - - Long revenueValue = EventTagUtils.getRevenueValue(eventTags); - if (revenueValue != null) { - eventMetrics.add(new EventMetric(EventMetric.REVENUE_METRIC_TYPE, revenueValue)); - } - - Double numericMetricValue = EventTagUtils.getNumericValue(eventTags); - if (numericMetricValue != null) { - eventMetrics.add(new EventMetric(EventMetric.NUMERIC_METRIC_TYPE, numericMetricValue)); - } - - Conversion conversionPayload = new Conversion(); - conversionPayload.setAccountId(projectConfig.getAccountId()); - conversionPayload.setAnonymizeIP(projectConfig.getAnonymizeIP()); - conversionPayload.setClientEngine(clientEngine); - conversionPayload.setClientVersion(clientVersion); - conversionPayload.setEventEntityId(eventId); - conversionPayload.setEventFeatures(createEventFeatures(eventTags)); - conversionPayload.setEventName(eventName); - conversionPayload.setEventMetrics(eventMetrics); - conversionPayload.setIsGlobalHoldback(false); - conversionPayload.setLayerStates(layerStates); - conversionPayload.setProjectId(projectConfig.getProjectId()); - conversionPayload.setRevision(projectConfig.getRevision()); - conversionPayload.setTimestamp(System.currentTimeMillis()); - conversionPayload.setUserFeatures(createUserFeatures(attributes, projectConfig)); - conversionPayload.setVisitorId(userId); - - String payload = this.serializer.serialize(conversionPayload); - return new LogEvent(RequestMethod.POST, CONVERSION_ENDPOINT, Collections.emptyMap(), payload); - } - - /** - * Helper method to generate {@link Feature} objects from the given {@code {attributeKey -> value}} mapping. - * - * @param attributes the {@code {attributeKey -> value}} mapping - * @param projectConfig the current project config - */ - private List createUserFeatures(Map attributes, ProjectConfig projectConfig) { - Map attributeKeyMapping = projectConfig.getAttributeKeyMapping(); - List features = new ArrayList(); - - for (Map.Entry attributeEntry : attributes.entrySet()) { - String attributeKey = attributeEntry.getKey(); - Attribute attribute = attributeKeyMapping.get(attributeKey); - - if (attributeEntry.getKey() == DecisionService.BUCKETING_ATTRIBUTE) { - features.add(new Feature(com.optimizely.ab.bucketing.DecisionService.BUCKETING_ATTRIBUTE, - ATTRIBUTE_KEY_FOR_BUCKETING_ATTRIBUTE, - Feature.CUSTOM_ATTRIBUTE_FEATURE_TYPE, - attributeEntry.getValue(), true)); - continue; - } - - if (attribute == null) { - logger.warn("Attempting to use unknown attribute key: {}. Attribute will be ignored", attributeKey); - continue; - } - - features.add(new Feature(attribute.getId(), attributeKey, Feature.CUSTOM_ATTRIBUTE_FEATURE_TYPE, - attributeEntry.getValue(), true)); - - } - - return features; - } - - /** - * Helper method to generate {@link Feature} objects from the given {@code {eventTagKey-> value}} mapping. - * - * @param eventTags the {@code {eventTagKey -> value}} mapping - */ - private List createEventFeatures(Map eventTags) { - List features = new ArrayList(); - - for (Map.Entry eventTagEntry : eventTags.entrySet()) { - String eventTagKey = eventTagEntry.getKey(); - features.add(new Feature("", eventTagKey, Feature.EVENT_FEATURE_TYPE, eventTagEntry.getValue(), false)); - } - return features; - } - - /** - * Helper method to create {@link LayerState} objects for all experiments mapped to an event. - *

- * Note, although the user may not have activated all experiments, we include all possible experiment - * mappings so that activated experiment state doesn't have to be maintained and passed around server-side. - *

- * For example, in a project with multiple experiments, if user 'user1' activates experiment 'exp1' and then - * converts, we need to ensure we attribute that conversion to 'exp1'. We can either do that by recording activation - * and passing that state around so it's available on conversion, or we can send all possible activations at - * conversion-time and attribute conversions only to the experiments for which we also received an - * impression. - *

- * This is referred to as "visitor first counting". It's important to filter the bucket map as much as possible, - * as any experiments sent through that don't have a corresponding impression are simply occupying space for - * no good reason. - * - * @param projectConfig the current project config - * @param experimentVariationMap the mapping of experiments associated with this event - * and the variations the user was bucketed into for that experiment - * - */ - private List createLayerStates(ProjectConfig projectConfig, Map experimentVariationMap) { - List layerStates = new ArrayList(); - - for (Map.Entry entry : experimentVariationMap.entrySet()) { - Experiment experiment = entry.getKey(); - Variation variation = entry.getValue(); - Decision decision = new Decision(variation.getId(), false, experiment.getId()); - layerStates.add(new LayerState(experiment.getLayerId(), projectConfig.getRevision(), decision, true)); - } - - return layerStates; - } -} diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilderV3.java b/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilderV3.java deleted file mode 100644 index dbf4a21ec..000000000 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilderV3.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.optimizely.ab.event.internal; - -import com.optimizely.ab.bucketing.DecisionService; -import com.optimizely.ab.config.EventType; -import com.optimizely.ab.config.Experiment; -import com.optimizely.ab.config.ProjectConfig; -import com.optimizely.ab.config.Variation; -import com.optimizely.ab.event.LogEvent; -import com.optimizely.ab.event.internal.payload.Attribute; -import com.optimizely.ab.event.internal.payload.DecisionV3; -import com.optimizely.ab.event.internal.payload.Event; -import com.optimizely.ab.event.internal.payload.EventBatch; -import com.optimizely.ab.event.internal.payload.EventV3; -import com.optimizely.ab.event.internal.payload.Snapshot; -import com.optimizely.ab.event.internal.payload.Visitor; -import com.optimizely.ab.event.internal.serializer.DefaultJsonSerializer; -import com.optimizely.ab.event.internal.serializer.Serializer; -import com.optimizely.ab.internal.EventTagUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.annotation.Nonnull; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -public class EventBuilderV3 extends EventBuilder { - private static final Logger logger = LoggerFactory.getLogger(EventBuilderV3.class); - static final String EVENT_ENDPOINT = "https://logx.optimizely.com/v1/events"; - static final String ACTIVATE_EVENT_KEY = "campaign_activated"; - private Serializer serializer; - private String clientVersion; - private Event.ClientEngine clientEngine; - - public EventBuilderV3() { - this(Event.ClientEngine.JAVA_SDK, BuildVersionInfo.VERSION); - } - - public EventBuilderV3(Event.ClientEngine clientEngine, String clientVersion) { - this.clientEngine = clientEngine; - this.clientVersion = clientVersion; - this.serializer = DefaultJsonSerializer.getInstance(); - } - - - public LogEvent createImpressionEvent(@Nonnull ProjectConfig projectConfig, - @Nonnull Experiment activatedExperiment, - @Nonnull Variation variation, - @Nonnull String userId, - @Nonnull Map attributes) { - - DecisionV3 decisionV3 = new DecisionV3(activatedExperiment.getLayerId(), activatedExperiment.getId(), - variation.getId(), false); - EventV3 eventV3 = new EventV3(System.currentTimeMillis(),UUID.randomUUID().toString(), activatedExperiment.getLayerId(), - ACTIVATE_EVENT_KEY, null, null, null, ACTIVATE_EVENT_KEY, null); - Snapshot snapshot = new Snapshot(Arrays.asList(decisionV3), Arrays.asList(eventV3)); - - Visitor visitor = new Visitor(userId, null, buildAttributeList(projectConfig, attributes), Arrays.asList(snapshot)); - List visitors = Arrays.asList(visitor); - EventBatch eventBatch = new EventBatch(clientEngine.getClientEngineValue(), clientVersion, projectConfig.getAccountId(), visitors, projectConfig.getAnonymizeIP(), projectConfig.getProjectId(), projectConfig.getRevision()); - String payload = this.serializer.serialize(eventBatch); - return new LogEvent(LogEvent.RequestMethod.POST, EVENT_ENDPOINT, Collections.emptyMap(), payload); - } - - public LogEvent createConversionEvent(@Nonnull ProjectConfig projectConfig, - @Nonnull Map experimentVariationMap, - @Nonnull String userId, - @Nonnull String eventId, - @Nonnull String eventName, - @Nonnull Map attributes, - @Nonnull Map eventTags) { - - if (experimentVariationMap.isEmpty()) { - return null; - } - - ArrayList decisionV3s = new ArrayList(); - for (Map.Entry entry : experimentVariationMap.entrySet()) { - DecisionV3 decisionV3 = new DecisionV3(entry.getKey().getLayerId(), entry.getKey().getId(), entry.getValue().getId(), false); - decisionV3s.add(decisionV3); - } - - EventType eventType = projectConfig.getEventNameMapping().get(eventName); - - EventV3 eventV3 = new EventV3(System.currentTimeMillis(),UUID.randomUUID().toString(), eventType.getId(), - eventType.getKey(), null, EventTagUtils.getRevenueValue(eventTags), eventTags, eventType.getKey(), EventTagUtils.getNumericValue(eventTags)); - Snapshot snapshot = new Snapshot(decisionV3s, Arrays.asList(eventV3)); - - Visitor visitor = new Visitor(userId, null, buildAttributeList(projectConfig, attributes), Arrays.asList(snapshot)); - List visitors = Arrays.asList(visitor); - EventBatch eventBatch = new EventBatch(clientEngine.getClientEngineValue(), clientVersion, projectConfig.getAccountId(), visitors, projectConfig.getAnonymizeIP(), projectConfig.getProjectId(), projectConfig.getRevision()); - String payload = this.serializer.serialize(eventBatch); - return new LogEvent(LogEvent.RequestMethod.POST, EVENT_ENDPOINT, Collections.emptyMap(), payload); - } - - private List buildAttributeList(ProjectConfig projectConfig, Map attributes) { - List attributes1 = new ArrayList(); - - Map attributeMap = projectConfig.getAttributeKeyMapping(); - for (Map.Entry 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, - EventBuilderV2.ATTRIBUTE_KEY_FOR_BUCKETING_ATTRIBUTE, Attribute.CUSTOM_ATTRIBUTE_TYPE, entry.getValue()); - } - - attributes1.add(attribute); - } - - return attributes1; - } -} diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Attribute.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Attribute.java index 910fb5f95..91a5fcb66 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Attribute.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Attribute.java @@ -9,6 +9,10 @@ public class Attribute { String type; String value; + public Attribute() { + + } + public Attribute(String entityId, String key, String type, String value) { this.entityId = entityId; this.key = key; diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Conversion.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Conversion.java deleted file mode 100644 index 724dcc2b0..000000000 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Conversion.java +++ /dev/null @@ -1,239 +0,0 @@ -/** - * - * Copyright 2016-2017, 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.optimizely.ab.event.internal.payload; - -import java.util.List; - -public class Conversion extends Event { - - private String accountId; - private boolean anonymizeIP; - private String eventEntityId; - private List eventFeatures; - private List eventMetrics; - private String eventName; - private boolean isGlobalHoldback; - private List layerStates; - private String projectId; - private String revision; - private String sessionId; - private long timestamp; - private List userFeatures; - private String visitorId; - - public Conversion() { } - - public Conversion(String visitorId, long timestamp, String projectId, String accountId, List userFeatures, - List layerStates, String eventEntityId, String eventName, - List eventMetrics, List eventFeatures, boolean isGlobalHoldback, - String revision, boolean anonymizeIP) { - this(visitorId, timestamp, projectId, accountId, userFeatures, layerStates, eventEntityId, eventName, - eventMetrics, eventFeatures, isGlobalHoldback, anonymizeIP, revision, null); - } - - public Conversion(String visitorId, long timestamp, String projectId, String accountId, List userFeatures, - List layerStates, String eventEntityId, String eventName, - List eventMetrics, List eventFeatures, boolean isGlobalHoldback, - boolean anonymizeIP, String revision, String sessionId) { - this.accountId = accountId; - this.anonymizeIP = anonymizeIP; - this.eventEntityId = eventEntityId; - this.eventFeatures = eventFeatures; - this.eventMetrics = eventMetrics; - this.eventName = eventName; - this.isGlobalHoldback = isGlobalHoldback; - this.layerStates = layerStates; - this.projectId = projectId; - this.revision = revision; - this.sessionId = sessionId; - this.timestamp = timestamp; - this.userFeatures = userFeatures; - this.visitorId = visitorId; - } - - public String getVisitorId() { - return visitorId; - } - - public void setVisitorId(String visitorId) { - this.visitorId = visitorId; - } - - public long getTimestamp() { - return timestamp; - } - - public void setTimestamp(long timestamp) { - this.timestamp = timestamp; - } - - public String getProjectId() { - return projectId; - } - - public void setProjectId(String projectId) { - this.projectId = projectId; - } - - public String getAccountId() { - return accountId; - } - - public void setAccountId(String accountId) { - this.accountId = accountId; - } - - public List getUserFeatures() { - return userFeatures; - } - - public void setUserFeatures(List userFeatures) { - this.userFeatures = userFeatures; - } - - public List getLayerStates() { - return layerStates; - } - - public void setLayerStates(List layerStates) { - this.layerStates = layerStates; - } - - public String getEventEntityId() { - return eventEntityId; - } - - public void setEventEntityId(String eventEntityId) { - this.eventEntityId = eventEntityId; - } - - public String getEventName() { - return eventName; - } - - public void setEventName(String eventName) { - this.eventName = eventName; - } - - public List getEventMetrics() { - return eventMetrics; - } - - public void setEventMetrics(List eventMetrics) { - this.eventMetrics = eventMetrics; - } - - public List getEventFeatures() { - return eventFeatures; - } - - public void setEventFeatures(List eventFeatures) { - this.eventFeatures = eventFeatures; - } - - public boolean getIsGlobalHoldback() { - return isGlobalHoldback; - } - - public void setIsGlobalHoldback(boolean globalHoldback) { - this.isGlobalHoldback = globalHoldback; - } - - public boolean getAnonymizeIP() { return anonymizeIP; } - - public void setAnonymizeIP(boolean anonymizeIP) { this.anonymizeIP = anonymizeIP; } - - public String getSessionId() { - return sessionId; - } - - public void setSessionId(String sessionId) { - this.sessionId = sessionId; - } - - public String getRevision() { - return revision; - } - - public void setRevision(String revision) { - this.revision = revision; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; - - Conversion that = (Conversion) o; - - if (timestamp != that.timestamp) return false; - if (isGlobalHoldback != that.isGlobalHoldback) return false; - if (anonymizeIP != that.anonymizeIP) return false; - if (!visitorId.equals(that.visitorId)) return false; - if (!projectId.equals(that.projectId)) return false; - if (!accountId.equals(that.accountId)) return false; - if (!userFeatures.equals(that.userFeatures)) return false; - if (!layerStates.equals(that.layerStates)) return false; - if (!eventEntityId.equals(that.eventEntityId)) return false; - if (!eventName.equals(that.eventName)) return false; - if (!eventMetrics.equals(that.eventMetrics)) return false; - if (!eventFeatures.equals(that.eventFeatures)) return false; - if (sessionId != null ? !sessionId.equals(that.sessionId) : that.sessionId != null) return false; - return revision.equals(that.revision); - } - - @Override - public int hashCode() { - int result = super.hashCode(); - result = 31 * result + visitorId.hashCode(); - result = 31 * result + (int) (timestamp ^ (timestamp >>> 32)); - result = 31 * result + projectId.hashCode(); - result = 31 * result + accountId.hashCode(); - result = 31 * result + userFeatures.hashCode(); - result = 31 * result + layerStates.hashCode(); - result = 31 * result + eventEntityId.hashCode(); - result = 31 * result + eventName.hashCode(); - result = 31 * result + eventMetrics.hashCode(); - result = 31 * result + eventFeatures.hashCode(); - result = 31 * result + (isGlobalHoldback ? 1 : 0); - result = 31 * result + (anonymizeIP ? 1 : 0); - result = 31 * result + (sessionId != null ? sessionId.hashCode() : 0); - result = 31 * result + revision.hashCode(); - return result; - } - - @Override - public String toString() { - return "Conversion{" + - "accountId='" + accountId + '\'' + - ", anonymizeIP=" + anonymizeIP + - ", eventEntityId='" + eventEntityId + '\'' + - ", eventFeatures=" + eventFeatures + - ", eventMetrics=" + eventMetrics + - ", eventName='" + eventName + '\'' + - ", isGlobalHoldback=" + isGlobalHoldback + - ", layerStates=" + layerStates + - ", projectId='" + projectId + '\'' + - ", revision='" + revision + '\'' + - ", sessionId='" + sessionId + '\'' + - ", timestamp=" + timestamp + - ", userFeatures=" + userFeatures + - ", visitorId='" + visitorId + '\'' + - '}'; - } -} diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Decision.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Decision.java deleted file mode 100644 index d641552a1..000000000 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Decision.java +++ /dev/null @@ -1,85 +0,0 @@ -/** - * - * Copyright 2016-2017, 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.optimizely.ab.event.internal.payload; - -public class Decision { - - private String variationId; - private boolean isLayerHoldback; - private String experimentId; - - public Decision() {} - - public Decision(String variationId, boolean isLayerHoldback, String experimentId) { - this.variationId = variationId; - this.isLayerHoldback = isLayerHoldback; - this.experimentId = experimentId; - } - - public String getVariationId() { - return variationId; - } - - public void setVariationId(String variationId) { - this.variationId = variationId; - } - - public boolean getIsLayerHoldback() { - return isLayerHoldback; - } - - public void setIsLayerHoldback(boolean layerHoldback) { - this.isLayerHoldback = layerHoldback; - } - - public String getExperimentId() { - return experimentId; - } - - public void setExperimentId(String experimentId) { - this.experimentId = experimentId; - } - - @Override - public boolean equals(Object other) { - if (!(other instanceof Decision)) - return false; - - Decision otherDecision = (Decision)other; - - return variationId.equals(otherDecision.getVariationId()) && - isLayerHoldback == otherDecision.getIsLayerHoldback() && - experimentId.equals(otherDecision.getExperimentId()); - } - - @Override - public int hashCode() { - int result = variationId.hashCode(); - result = 31 * result + (isLayerHoldback ? 1 : 0); - result = 31 * result + experimentId.hashCode(); - return result; - } - - @Override - public String toString() { - return "Decision{" + - "variationId='" + variationId + '\'' + - ", isLayerHoldback=" + isLayerHoldback + - ", experimentId='" + experimentId + '\'' + - '}'; - } -} diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/DecisionV3.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/DecisionV3.java index 8471f5229..a1f10a715 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/DecisionV3.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/DecisionV3.java @@ -13,6 +13,10 @@ public DecisionV3(String campaignId, String experimentId, String variationId, bo this.isCampaignHoldback = isCampaignHoldback; } + public DecisionV3() { + + } + public String getCampaignId() { return campaignId; } diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventBatch.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventBatch.java index 1658573ef..7618adc6c 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventBatch.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventBatch.java @@ -13,6 +13,10 @@ public class EventBatch { String projectId; String revision; + public EventBatch() { + + } + public EventBatch(String accountId, List visitors, Boolean anonymizeIp, String projectId, String revision) { this.accountId = accountId; this.visitors = visitors; @@ -86,4 +90,33 @@ public String getRevision() { public void setRevision(String revision) { this.revision = revision; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + EventBatch that = (EventBatch) o; + + if (!accountId.equals(that.accountId)) return false; + if (!visitors.equals(that.visitors)) return false; + if (anonymizeIp != null ? !anonymizeIp.equals(that.anonymizeIp) : that.anonymizeIp != null) return false; + if (clientName != null ? !clientName.equals(that.clientName) : that.clientName != null) return false; + if (clientVersion != null ? !clientVersion.equals(that.clientVersion) : that.clientVersion != null) + return false; + if (projectId != null ? !projectId.equals(that.projectId) : that.projectId != null) return false; + return revision != null ? revision.equals(that.revision) : that.revision == null; + } + + @Override + public int hashCode() { + int result = accountId.hashCode(); + result = 31 * result + visitors.hashCode(); + result = 31 * result + (anonymizeIp != null ? anonymizeIp.hashCode() : 0); + result = 31 * result + (clientName != null ? clientName.hashCode() : 0); + result = 31 * result + (clientVersion != null ? clientVersion.hashCode() : 0); + result = 31 * result + (projectId != null ? projectId.hashCode() : 0); + result = 31 * result + (revision != null ? revision.hashCode() : 0); + return result; + } } diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventMetric.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventMetric.java deleted file mode 100644 index f77f7e578..000000000 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventMetric.java +++ /dev/null @@ -1,75 +0,0 @@ -/** - * - * Copyright 2016-2017, 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.optimizely.ab.event.internal.payload; - -public class EventMetric { - - public static final String REVENUE_METRIC_TYPE = "revenue"; - public static final String NUMERIC_METRIC_TYPE = "value"; - - private String name; - private Number value; - - public EventMetric() { } - - public EventMetric(String name, Number value) { - this.name = name; - this.value = value; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Number getValue() { - return value; - } - - public void setValue(Number value) { - this.value = value; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null || getClass() != obj.getClass()) return false; - - EventMetric that = (EventMetric) obj; - - if (!name.equals(that.name)) return false; - return value.equals(that.value); - } - - @Override - public int hashCode() { - int result = name.hashCode(); - result = 31 * result + value.hashCode(); - return result; - } - - @Override - public String toString() { - return "EventMetric{" + - "name='" + name + '\'' + - ", value=" + value + - '}'; - } -} diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventV3.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventV3.java index 701d10ade..637c93332 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventV3.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventV3.java @@ -13,36 +13,8 @@ public class EventV3 { String type; Number value; - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + public EventV3() { - EventV3 eventV3 = (EventV3) o; - - if (timestamp != eventV3.timestamp) return false; - if (!uuid.equals(eventV3.uuid)) return false; - if (entityId != null ? !entityId.equals(eventV3.entityId) : eventV3.entityId != null) return false; - if (key != null ? !key.equals(eventV3.key) : eventV3.key != null) return false; - if (quantity != null ? !quantity.equals(eventV3.quantity) : eventV3.quantity != null) return false; - if (revenue != null ? !revenue.equals(eventV3.revenue) : eventV3.revenue != null) return false; - if (tags != null ? !tags.equals(eventV3.tags) : eventV3.tags != null) return false; - if (!type.equals(eventV3.type)) return false; - return value != null ? value.equals(eventV3.value) : eventV3.value == null; - } - - @Override - public int hashCode() { - int result = (int) (timestamp ^ (timestamp >>> 32)); - result = 31 * result + uuid.hashCode(); - result = 31 * result + (entityId != null ? entityId.hashCode() : 0); - result = 31 * result + (key != null ? key.hashCode() : 0); - result = 31 * result + (quantity != null ? quantity.hashCode() : 0); - result = 31 * result + (revenue != null ? revenue.hashCode() : 0); - result = 31 * result + (tags != null ? tags.hashCode() : 0); - result = 31 * result + type.hashCode(); - result = 31 * result + (value != null ? value.hashCode() : 0); - return result; } public EventV3(long timestamp, String uuid, String entityId, String key, Number quantity, @@ -129,4 +101,37 @@ public Number getValue() { public void setValue(Number value) { this.value = value; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + EventV3 eventV3 = (EventV3) o; + + if (timestamp != eventV3.timestamp) return false; + if (!uuid.equals(eventV3.uuid)) return false; + if (entityId != null ? !entityId.equals(eventV3.entityId) : eventV3.entityId != null) return false; + if (key != null ? !key.equals(eventV3.key) : eventV3.key != null) return false; + if (quantity != null ? !quantity.equals(eventV3.quantity) : eventV3.quantity != null) return false; + if (revenue != null ? !revenue.equals(eventV3.revenue) : eventV3.revenue != null) return false; + if (tags != null ? !tags.equals(eventV3.tags) : eventV3.tags != null) return false; + if (!type.equals(eventV3.type)) return false; + return value != null ? value.equals(eventV3.value) : eventV3.value == null; + } + + @Override + public int hashCode() { + int result = (int) (timestamp ^ (timestamp >>> 32)); + result = 31 * result + uuid.hashCode(); + result = 31 * result + (entityId != null ? entityId.hashCode() : 0); + result = 31 * result + (key != null ? key.hashCode() : 0); + result = 31 * result + (quantity != null ? quantity.hashCode() : 0); + result = 31 * result + (revenue != null ? revenue.hashCode() : 0); + result = 31 * result + (tags != null ? tags.hashCode() : 0); + result = 31 * result + type.hashCode(); + result = 31 * result + (value != null ? value.hashCode() : 0); + return result; + } + } diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Feature.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Feature.java deleted file mode 100644 index 161ee2271..000000000 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Feature.java +++ /dev/null @@ -1,114 +0,0 @@ -/** - * - * Copyright 2016-2017, 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.optimizely.ab.event.internal.payload; - -public class Feature { - - public static final String CUSTOM_ATTRIBUTE_FEATURE_TYPE = "custom"; - public static final String EVENT_FEATURE_TYPE = "custom"; - - private String id; - private String name; - private String type; - private Object value; - private boolean shouldIndex; - - public Feature() { } - - public Feature(String id, String name, String type, Object value, boolean shouldIndex) { - this.id = id; - this.name = name; - this.type = type; - this.value = value; - this.shouldIndex = shouldIndex; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public Object getValue() { - return value; - } - - public void setValue(String value) { - this.value = value; - } - - public boolean getShouldIndex() { - return shouldIndex; - } - - public void setShouldIndex(boolean shouldIndex) { - this.shouldIndex = shouldIndex; - } - - @Override - public boolean equals(Object other) { - if (!(other instanceof Feature)) - return false; - - Feature otherFeature = (Feature)other; - - return id.equals(otherFeature.getId()) && - name.equals(otherFeature.getName()) && - type.equals(otherFeature.getType()) && - value.equals(otherFeature.getValue()) && - shouldIndex == otherFeature.getShouldIndex(); - } - - @Override - public int hashCode() { - int result = id.hashCode(); - result = 31 * result + name.hashCode(); - result = 31 * result + type.hashCode(); - result = 31 * result + value.hashCode(); - result = 31 * result + (shouldIndex ? 1 : 0); - return result; - } - - @Override - public String toString() { - return "FeatureFlag{" + - "id='" + id + '\'' + - ", name='" + name + '\'' + - ", type='" + type + '\'' + - ", value='" + value + '\'' + - ", shouldIndex=" + shouldIndex + - '}'; - } -} diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Impression.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Impression.java deleted file mode 100644 index 33878a09f..000000000 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Impression.java +++ /dev/null @@ -1,203 +0,0 @@ -/** - * - * Copyright 2016-2017, 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.optimizely.ab.event.internal.payload; - -import java.util.List; - -public class Impression extends Event { - - private String visitorId; - private long timestamp; - private boolean isGlobalHoldback; - private String projectId; - private Decision decision; - private String layerId; - private String accountId; - private List userFeatures; - private boolean anonymizeIP; - private String sessionId; - private String revision; - - public Impression() { } - - public Impression(String visitorId, long timestamp, boolean isGlobalHoldback, String projectId, Decision decision, - String layerId, String accountId, List userFeatures, boolean anonymizeIP, - String revision) { - this(visitorId, timestamp, isGlobalHoldback, projectId, decision, layerId, accountId, userFeatures, - anonymizeIP, revision, null); - } - - public Impression(String visitorId, long timestamp, boolean isGlobalHoldback, String projectId, Decision decision, - String layerId, String accountId, List userFeatures, boolean anonymizeIP, - String revision, String sessionId) { - this.visitorId = visitorId; - this.timestamp = timestamp; - this.isGlobalHoldback = isGlobalHoldback; - this.projectId = projectId; - this.decision = decision; - this.layerId = layerId; - this.accountId = accountId; - this.userFeatures = userFeatures; - this.anonymizeIP = anonymizeIP; - this.revision = revision; - this.sessionId = sessionId; - } - - public String getVisitorId() { - return visitorId; - } - - public void setVisitorId(String visitorId) { - this.visitorId = visitorId; - } - - public long getTimestamp() { - return timestamp; - } - - public void setTimestamp(long timestamp) { - this.timestamp = timestamp; - } - - public boolean getIsGlobalHoldback() { - return isGlobalHoldback; - } - - public void setIsGlobalHoldback(boolean globalHoldback) { - this.isGlobalHoldback = globalHoldback; - } - - public String getProjectId() { - return projectId; - } - - public void setProjectId(String projectId) { - this.projectId = projectId; - } - - public Decision getDecision() { - return decision; - } - - public void setDecision(Decision decision) { - this.decision = decision; - } - - public String getLayerId() { - return layerId; - } - - public void setLayerId(String layerId) { - this.layerId = layerId; - } - - public String getAccountId() { - return accountId; - } - - public void setAccountId(String accountId) { - this.accountId = accountId; - } - - public List getUserFeatures() { - return userFeatures; - } - - public void setUserFeatures(List userFeatures) { - this.userFeatures = userFeatures; - } - - public boolean getAnonymizeIP() { - return anonymizeIP; - } - - public void setAnonymizeIP(boolean anonymizeIP) { - this.anonymizeIP = anonymizeIP; - } - - public String getSessionId() { - return sessionId; - } - - public void setSessionId(String sessionId) { - this.sessionId = sessionId; - } - - public String getRevision() { - return revision; - } - - public void setRevision(String revision) { - this.revision = revision; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; - - Impression that = (Impression) o; - - if (timestamp != that.timestamp) return false; - if (isGlobalHoldback != that.isGlobalHoldback) return false; - if (anonymizeIP != that.anonymizeIP) return false; - if (!visitorId.equals(that.visitorId)) return false; - if (!projectId.equals(that.projectId)) return false; - if (!decision.equals(that.decision)) return false; - if (!layerId.equals(that.layerId)) return false; - if (!accountId.equals(that.accountId)) return false; - if (!userFeatures.equals(that.userFeatures)) return false; - if (sessionId != null ? !sessionId.equals(that.sessionId) : that.sessionId != null) return false; - return revision.equals(that.revision); - - } - - @Override - public int hashCode() { - int result = super.hashCode(); - result = 31 * result + visitorId.hashCode(); - result = 31 * result + (int) (timestamp ^ (timestamp >>> 32)); - result = 31 * result + (isGlobalHoldback ? 1 : 0); - result = 31 * result + projectId.hashCode(); - result = 31 * result + decision.hashCode(); - result = 31 * result + layerId.hashCode(); - result = 31 * result + accountId.hashCode(); - result = 31 * result + userFeatures.hashCode(); - result = 31 * result + (anonymizeIP ? 1 : 0); - result = 31 * result + (sessionId != null ? sessionId.hashCode() : 0); - result = 31 * result + revision.hashCode(); - return result; - } - - @Override - public String toString() { - return "Impression{" + - "visitorId='" + visitorId + '\'' + - ", timestamp=" + timestamp + - ", isGlobalHoldback=" + isGlobalHoldback + - ", projectId='" + projectId + '\'' + - ", decision=" + decision + - ", layerId='" + layerId + '\'' + - ", accountId='" + accountId + '\'' + - ", userFeatures=" + userFeatures + - ", anonymizeIP=" + anonymizeIP + - ", sessionId='" + sessionId + '\'' + - ", revision='" + revision + '\'' + - '}'; - } -} diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/LayerState.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/LayerState.java deleted file mode 100644 index 589197be7..000000000 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/LayerState.java +++ /dev/null @@ -1,89 +0,0 @@ -/** - * - * Copyright 2016-2017, 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.optimizely.ab.event.internal.payload; - -public class LayerState { - - private String layerId; - private String revision; - private Decision decision; - private boolean actionTriggered; - - public LayerState() { } - - public LayerState(String layerId, String revision, Decision decision, boolean actionTriggered) { - this.layerId = layerId; - this.revision = revision; - this.decision = decision; - this.actionTriggered = actionTriggered; - } - - public String getLayerId() { - return layerId; - } - - public void setLayerId(String layerId) { - this.layerId = layerId; - } - - public String getRevision() { - return revision; - } - - public void setRevision(String revision) { - this.revision = revision; - } - - public Decision getDecision() { - return decision; - } - - public void setDecision(Decision decision) { - this.decision = decision; - } - - public boolean getActionTriggered() { - return actionTriggered; - } - - public void setActionTriggered(boolean actionTriggered) { - this.actionTriggered = actionTriggered; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - LayerState that = (LayerState) o; - - if (actionTriggered != that.actionTriggered) return false; - if (!layerId.equals(that.layerId)) return false; - if (!revision.equals(that.revision)) return false; - return decision.equals(that.decision); - - } - - @Override - public int hashCode() { - int result = layerId.hashCode(); - result = 31 * result + revision.hashCode(); - result = 31 * result + decision.hashCode(); - result = 31 * result + (actionTriggered ? 1 : 0); - return result; - } -} diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Snapshot.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Snapshot.java index 7c0985bed..558f7517e 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Snapshot.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Snapshot.java @@ -5,11 +5,24 @@ public class Snapshot { List decisions; List events; - long activationTimestamp; + Long activationTimestamp; + + public Snapshot() { + + } public Snapshot(List decisions, List events) { this.decisions = decisions; this.events = events; + this.activationTimestamp = null; + } + + public Long getActivationTimestamp() { + return activationTimestamp; + } + + public void setActivationTimestamp(Long activationTimestamp) { + this.activationTimestamp = activationTimestamp; } public List getDecisions() { @@ -35,7 +48,9 @@ public boolean equals(Object o) { Snapshot snapshot = (Snapshot) o; - if (activationTimestamp != snapshot.activationTimestamp) return false; + if (activationTimestamp != null ? + !activationTimestamp.equals(snapshot.activationTimestamp) : + snapshot.activationTimestamp != null) return false; if (!decisions.equals(snapshot.decisions)) return false; return events.equals(snapshot.events); } @@ -44,7 +59,9 @@ public boolean equals(Object o) { public int hashCode() { int result = decisions.hashCode(); result = 31 * result + events.hashCode(); - result = 31 * result + (int) (activationTimestamp ^ (activationTimestamp >>> 32)); + if (activationTimestamp != null) { + result = 31 * result + (int) (activationTimestamp ^ (activationTimestamp >>> 32)); + } return result; } } diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Visitor.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Visitor.java index fa8e9a5b9..15f07cb30 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Visitor.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Visitor.java @@ -8,6 +8,10 @@ public class Visitor { List attributes; List snapshots; + public Visitor() { + + } + public Visitor(String visitorId, String sessionId, List attributes, List snapshots) { this.visitorId = visitorId; this.sessionId = sessionId; diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JacksonSerializer.java b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JacksonSerializer.java index e221b9745..322c51c4f 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JacksonSerializer.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JacksonSerializer.java @@ -20,8 +20,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.optimizely.ab.event.internal.payload.Event; - class JacksonSerializer implements Serializer { private final ObjectMapper mapper = new ObjectMapper(); diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSimpleSerializer.java b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSimpleSerializer.java index d972e618e..e8fae248d 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSimpleSerializer.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSimpleSerializer.java @@ -16,142 +16,160 @@ */ package com.optimizely.ab.event.internal.serializer; -import com.optimizely.ab.event.internal.payload.Conversion; -import com.optimizely.ab.event.internal.payload.Decision; -import com.optimizely.ab.event.internal.payload.EventMetric; -import com.optimizely.ab.event.internal.payload.Feature; -import com.optimizely.ab.event.internal.payload.Impression; -import com.optimizely.ab.event.internal.payload.LayerState; -import com.optimizely.ab.event.internal.payload.Event; +import com.optimizely.ab.event.internal.payload.Attribute; +import com.optimizely.ab.event.internal.payload.DecisionV3; +import com.optimizely.ab.event.internal.payload.EventBatch; +import com.optimizely.ab.event.internal.payload.EventV3; +import com.optimizely.ab.event.internal.payload.Snapshot; +import com.optimizely.ab.event.internal.payload.Visitor; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import java.util.List; +import java.util.Map; @SuppressWarnings("unchecked") class JsonSimpleSerializer implements Serializer { public String serialize(T payload) { - JSONObject payloadJsonObj; - if (payload instanceof Impression) { - payloadJsonObj = serializeImpression((Impression)payload); - } else { - payloadJsonObj = serializeConversion((Conversion)payload); - } + JSONObject payloadJsonObj = serializeEventBatch((EventBatch)payload); return payloadJsonObj.toJSONString(); } - private JSONObject serializeImpression(Impression impression) { + private JSONObject serializeEventBatch(EventBatch eventBatch) { JSONObject jsonObject = new JSONObject(); - jsonObject.put("visitorId", impression.getVisitorId()); - jsonObject.put("timestamp", impression.getTimestamp()); - jsonObject.put("isGlobalHoldback", impression.getIsGlobalHoldback()); - jsonObject.put("anonymizeIP", impression.getAnonymizeIP()); - jsonObject.put("projectId", impression.getProjectId()); - jsonObject.put("decision", serializeDecision(impression.getDecision())); - jsonObject.put("layerId", impression.getLayerId()); - jsonObject.put("accountId", impression.getAccountId()); - jsonObject.put("userFeatures", serializeFeatures(impression.getUserFeatures())); - jsonObject.put("clientEngine", impression.getClientEngine()); - jsonObject.put("clientVersion", impression.getClientVersion()); - jsonObject.put("revision", impression.getRevision()); - - if (impression.getSessionId() != null) { - jsonObject.put("sessionId", impression.getSessionId()); - } + + jsonObject.put("accountId", eventBatch.getAccountId()); + jsonObject.put("visitors", serializeVisitors(eventBatch.getVisitors())); + if (eventBatch.getAnonymizeIp() != null) jsonObject.put("anonymizeIp", eventBatch.getAnonymizeIp()); + if (eventBatch.getClientName() != null) jsonObject.put("clientName", eventBatch.getClientName()); + if (eventBatch.getClientVersion() != null) jsonObject.put("clientVersion", eventBatch.getClientVersion()); + if (eventBatch.getProjectId() != null) jsonObject.put("projectId", eventBatch.getProjectId()); + if (eventBatch.getRevision() != null) jsonObject.put("revision", eventBatch.getRevision()); return jsonObject; + } - private JSONObject serializeConversion(Conversion conversion) { - JSONObject jsonObject = new JSONObject(); - jsonObject.put("visitorId", conversion.getVisitorId()); - jsonObject.put("timestamp", conversion.getTimestamp()); - jsonObject.put("projectId", conversion.getProjectId()); - jsonObject.put("accountId", conversion.getAccountId()); - jsonObject.put("userFeatures", serializeFeatures(conversion.getUserFeatures())); - jsonObject.put("layerStates", serializeLayerStates(conversion.getLayerStates())); - jsonObject.put("eventEntityId", conversion.getEventEntityId()); - jsonObject.put("eventName", conversion.getEventName()); - jsonObject.put("eventMetrics", serializeEventMetrics(conversion.getEventMetrics())); - jsonObject.put("eventFeatures", serializeFeatures(conversion.getEventFeatures())); - jsonObject.put("isGlobalHoldback", conversion.getIsGlobalHoldback()); - jsonObject.put("anonymizeIP", conversion.getAnonymizeIP()); - jsonObject.put("clientEngine", conversion.getClientEngine()); - jsonObject.put("clientVersion", conversion.getClientVersion()); - jsonObject.put("revision", conversion.getRevision()); - - if (conversion.getSessionId() != null) { - jsonObject.put("sessionId", conversion.getSessionId()); + private JSONArray serializeVisitors(List visitors) { + JSONArray jsonArray = new JSONArray(); + + for (Visitor v: visitors) { + jsonArray.add(serializeVisitor(v)); } + return jsonArray; + } + + private JSONObject serializeVisitor(Visitor visitor) { + JSONObject jsonObject = new JSONObject(); + + jsonObject.put("visitorId", visitor.getVisitorId()); + + if (visitor.getSessionId() != null) jsonObject.put("sessionId", visitor.getSessionId()); + + if (visitor.getAttributes() != null) jsonObject.put("attributes", serializeFeatures(visitor.getAttributes())); + + jsonObject.put("snapshots", serializeSnapshots(visitor.getSnapshots())); + return jsonObject; } - private JSONObject serializeDecision(Decision decision) { + private JSONArray serializeSnapshots(List snapshots) { + JSONArray jsonArray = new JSONArray(); + + for (Snapshot snapshot : snapshots) { + jsonArray.add(serializeSnapshot(snapshot)); + } + + return jsonArray; + } + + private JSONObject serializeSnapshot(Snapshot snapshot) { JSONObject jsonObject = new JSONObject(); - jsonObject.put("variationId", decision.getVariationId()); - jsonObject.put("isLayerHoldback", decision.getIsLayerHoldback()); - jsonObject.put("experimentId", decision.getExperimentId()); + + jsonObject.put("decisions", serializeDecisions(snapshot.getDecisions())); + jsonObject.put("events", serializeEvents(snapshot.getEvents())); return jsonObject; } - private JSONArray serializeFeatures(List features) { + private JSONArray serializeEvents(List eventV3s) { JSONArray jsonArray = new JSONArray(); - for (Feature feature : features) { - jsonArray.add(serializeFeature(feature)); + + for (EventV3 eventV3 : eventV3s) { + jsonArray.add(serializeEvent(eventV3)); } return jsonArray; } - private JSONObject serializeFeature(Feature feature) { + private JSONObject serializeEvent(EventV3 eventV3) { JSONObject jsonObject = new JSONObject(); - jsonObject.put("id", feature.getId()); - jsonObject.put("name", feature.getName()); - jsonObject.put("type", feature.getType()); - jsonObject.put("value", feature.getValue()); - jsonObject.put("shouldIndex", feature.getShouldIndex()); + + jsonObject.put("timestamp",eventV3.getTimestamp()); + jsonObject.put("uuid",eventV3.getUuid()); + jsonObject.put("key", eventV3.getKey()); + + if (eventV3.getEntityId() != null) jsonObject.put("entityId",eventV3.getEntityId()); + if (eventV3.getQuantity() != null) jsonObject.put("quantity",eventV3.getQuantity()); + if (eventV3.getRevenue() != null) jsonObject.put("revenue",eventV3.getRevenue()); + if (eventV3.getTags() != null) jsonObject.put("tags",serializeTags(eventV3.getTags())); + if (eventV3.getType() != null) jsonObject.put("type",eventV3.getType()); + if (eventV3.getValue() != null) jsonObject.put("value",eventV3.getValue()); return jsonObject; } - private JSONArray serializeLayerStates(List layerStates) { + private JSONArray serializeTags(Map tags) { JSONArray jsonArray = new JSONArray(); - for (LayerState layerState : layerStates) { - jsonArray.add(serializeLayerState(layerState)); + for (Map.Entry entry : tags.entrySet()) { + if (entry.getValue() != null) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put(entry.getKey(), entry.getValue()); + } } return jsonArray; } - private JSONObject serializeLayerState(LayerState layerState) { + private JSONObject serializeDecision(DecisionV3 decision) { JSONObject jsonObject = new JSONObject(); - jsonObject.put("layerId", layerState.getLayerId()); - jsonObject.put("revision", layerState.getRevision()); - jsonObject.put("decision", serializeDecision(layerState.getDecision())); - jsonObject.put("actionTriggered", layerState.getActionTriggered()); + jsonObject.put("campaignId", decision.getCampaignId()); + if (decision.getExperimentId() != null) jsonObject.put("experimentId", decision.getExperimentId()); + if (decision.getVariationId() != null) jsonObject.put("variationId", decision.getVariationId()); + jsonObject.put("isCampaignHoldback", decision.getIsCampaignHoldback()); return jsonObject; } - private JSONArray serializeEventMetrics(List eventMetrics) { + private JSONArray serializeFeatures(List features) { JSONArray jsonArray = new JSONArray(); - for (EventMetric eventMetric : eventMetrics) { - jsonArray.add(serializeEventMetric(eventMetric)); + for (Attribute feature : features) { + jsonArray.add(serializeFeature(feature)); } return jsonArray; } - private JSONObject serializeEventMetric(EventMetric eventMetric) { + private JSONObject serializeFeature(Attribute feature) { JSONObject jsonObject = new JSONObject(); - jsonObject.put("name", eventMetric.getName()); - jsonObject.put("value", eventMetric.getValue()); + jsonObject.put("type", feature.getType()); + jsonObject.put("value", feature.getValue()); + if (feature.getEntityId() != null) jsonObject.put("entityId", feature.getEntityId()); + if (feature.getKey() != null) jsonObject.put("key", feature.getKey()); return jsonObject; } + + private JSONArray serializeDecisions(List layerStates) { + JSONArray jsonArray = new JSONArray(); + for (DecisionV3 layerState : layerStates) { + jsonArray.add(serializeDecision(layerState)); + } + + return jsonArray; + } } diff --git a/core-api/src/test/java/com/optimizely/ab/OptimizelyBuilderTest.java b/core-api/src/test/java/com/optimizely/ab/OptimizelyBuilderTest.java index b0ca8ce9a..5b83d7cf0 100644 --- a/core-api/src/test/java/com/optimizely/ab/OptimizelyBuilderTest.java +++ b/core-api/src/test/java/com/optimizely/ab/OptimizelyBuilderTest.java @@ -24,7 +24,7 @@ import com.optimizely.ab.error.NoOpErrorHandler; import com.optimizely.ab.event.EventHandler; import com.optimizely.ab.event.internal.BuildVersionInfo; -import com.optimizely.ab.event.internal.EventBuilderV2; +import com.optimizely.ab.event.internal.EventBuilder; import com.optimizely.ab.event.internal.payload.Event.ClientEngine; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.junit.BeforeClass; @@ -129,7 +129,7 @@ public void withDefaultClientEngine() throws Exception { Optimizely optimizelyClient = Optimizely.builder(validConfigJsonV2(), mockEventHandler) .build(); - assertThat(((EventBuilderV2)optimizelyClient.eventBuilder).clientEngine, is(ClientEngine.JAVA_SDK)); + assertThat(((EventBuilder)optimizelyClient.eventBuilder).clientEngine, is(ClientEngine.JAVA_SDK)); } @Test @@ -138,7 +138,7 @@ public void withAndroidSDKClientEngine() throws Exception { .withClientEngine(ClientEngine.ANDROID_SDK) .build(); - assertThat(((EventBuilderV2)optimizelyClient.eventBuilder).clientEngine, is(ClientEngine.ANDROID_SDK)); + assertThat(((EventBuilder)optimizelyClient.eventBuilder).clientEngine, is(ClientEngine.ANDROID_SDK)); } @Test @@ -147,7 +147,7 @@ public void withAndroidTVSDKClientEngine() throws Exception { .withClientEngine(ClientEngine.ANDROID_TV_SDK) .build(); - assertThat(((EventBuilderV2)optimizelyClient.eventBuilder).clientEngine, is(ClientEngine.ANDROID_TV_SDK)); + assertThat(((EventBuilder)optimizelyClient.eventBuilder).clientEngine, is(ClientEngine.ANDROID_TV_SDK)); } @Test @@ -155,7 +155,7 @@ public void withDefaultClientVersion() throws Exception { Optimizely optimizelyClient = Optimizely.builder(validConfigJsonV2(), mockEventHandler) .build(); - assertThat(((EventBuilderV2)optimizelyClient.eventBuilder).clientVersion, is(BuildVersionInfo.VERSION)); + assertThat(((EventBuilder)optimizelyClient.eventBuilder).clientVersion, is(BuildVersionInfo.VERSION)); } @Test @@ -164,7 +164,7 @@ public void withCustomClientVersion() throws Exception { .withClientVersion("0.0.0") .build(); - assertThat(((EventBuilderV2)optimizelyClient.eventBuilder).clientVersion, is("0.0.0")); + assertThat(((EventBuilder)optimizelyClient.eventBuilder).clientVersion, is("0.0.0")); } @SuppressFBWarnings(value="NP_NONNULL_PARAM_VIOLATION", justification="Testing nullness contract violation") diff --git a/core-api/src/test/java/com/optimizely/ab/OptimizelyTest.java b/core-api/src/test/java/com/optimizely/ab/OptimizelyTest.java index 27d213e61..597977b19 100644 --- a/core-api/src/test/java/com/optimizely/ab/OptimizelyTest.java +++ b/core-api/src/test/java/com/optimizely/ab/OptimizelyTest.java @@ -36,7 +36,6 @@ import com.optimizely.ab.event.EventHandler; import com.optimizely.ab.event.LogEvent; import com.optimizely.ab.event.internal.EventBuilder; -import com.optimizely.ab.event.internal.EventBuilderV2; import com.optimizely.ab.internal.LogbackVerifier; import com.optimizely.ab.notification.ActivateNotification; import com.optimizely.ab.notification.NotificationCenter; @@ -101,7 +100,7 @@ import static com.optimizely.ab.config.ValidProjectConfigV4.VARIATION_MULTIVARIATE_EXPERIMENT_GRED; import static com.optimizely.ab.config.ValidProjectConfigV4.VARIATION_MULTIVARIATE_EXPERIMENT_GRED_KEY; import static com.optimizely.ab.event.LogEvent.RequestMethod; -import static com.optimizely.ab.event.internal.EventBuilderV2Test.createExperimentVariationMap; +import static com.optimizely.ab.event.internal.EventBuilderV3Test.createExperimentVariationMap; import static java.util.Arrays.asList; import static junit.framework.TestCase.assertTrue; import static org.hamcrest.CoreMatchers.is; @@ -1132,7 +1131,7 @@ public void trackEventEndToEndForced() throws Exception { } List allExperiments = new ArrayList(); allExperiments.add(config.getExperiments().get(0)); - EventBuilder eventBuilderV2 = new EventBuilderV2(); + EventBuilder eventBuilderV2 = new EventBuilder(); DecisionService spyDecisionService = spy(new DecisionService(mockBucketer, mockErrorHandler, config, @@ -1206,7 +1205,7 @@ public void trackEventEndToEnd() throws Exception { } List allExperiments = config.getExperiments(); - EventBuilder eventBuilderV2 = new EventBuilderV2(); + EventBuilder eventBuilderV2 = new EventBuilder(); DecisionService spyDecisionService = spy(new DecisionService(mockBucketer, mockErrorHandler, config, diff --git a/core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderV2Test.java b/core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderV2Test.java deleted file mode 100644 index 9eaf419e9..000000000 --- a/core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderV2Test.java +++ /dev/null @@ -1,745 +0,0 @@ -/** - * - * Copyright 2016-2017, 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.optimizely.ab.event.internal; - -import com.google.gson.Gson; -import com.google.gson.internal.LazilyParsedNumber; -import com.optimizely.ab.bucketing.Bucketer; -import com.optimizely.ab.bucketing.DecisionService; -import com.optimizely.ab.bucketing.UserProfileService; -import com.optimizely.ab.config.Attribute; -import com.optimizely.ab.config.EventType; -import com.optimizely.ab.config.Experiment; -import com.optimizely.ab.config.ProjectConfig; -import com.optimizely.ab.config.Variation; -import com.optimizely.ab.error.ErrorHandler; -import com.optimizely.ab.error.NoOpErrorHandler; -import com.optimizely.ab.event.LogEvent; -import com.optimizely.ab.event.internal.payload.Conversion; -import com.optimizely.ab.event.internal.payload.Decision; -import com.optimizely.ab.event.internal.payload.Event.ClientEngine; -import com.optimizely.ab.event.internal.payload.EventMetric; -import com.optimizely.ab.event.internal.payload.Feature; -import com.optimizely.ab.event.internal.payload.Impression; -import com.optimizely.ab.event.internal.payload.LayerState; -import com.optimizely.ab.internal.ReservedEventKey; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; - -import javax.annotation.Nullable; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static com.optimizely.ab.config.ProjectConfigTestUtils.noAudienceProjectConfigJsonV2; -import static com.optimizely.ab.config.ProjectConfigTestUtils.noAudienceProjectConfigV2; -import static com.optimizely.ab.config.ProjectConfigTestUtils.validConfigJsonV2; -import static com.optimizely.ab.config.ProjectConfigTestUtils.validConfigJsonV4; -import static com.optimizely.ab.config.ProjectConfigTestUtils.validProjectConfigV2; -import static com.optimizely.ab.config.ProjectConfigTestUtils.validProjectConfigV4; -import static com.optimizely.ab.config.ValidProjectConfigV4.AUDIENCE_GRYFFINDOR_VALUE; -import static com.optimizely.ab.config.ValidProjectConfigV4.EVENT_BASIC_EVENT_KEY; -import static com.optimizely.ab.config.ValidProjectConfigV4.EVENT_PAUSED_EXPERIMENT_KEY; -import static com.optimizely.ab.config.ValidProjectConfigV4.MULTIVARIATE_EXPERIMENT_FORCED_VARIATION_USER_ID_GRED; -import static com.optimizely.ab.config.ValidProjectConfigV4.PAUSED_EXPERIMENT_FORCED_VARIATION_USER_ID_CONTROL; -import static junit.framework.TestCase.assertNotNull; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.Matchers.closeTo; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -/** - * Test for {@link EventBuilderV2} - */ -@RunWith(Parameterized.class) -public class EventBuilderV2Test { - - @Parameters - public static Collection data() throws IOException { - return Arrays.asList(new Object[][] { - { - 2, - validProjectConfigV2() - }, - { - 4, - validProjectConfigV4() - } - }); - } - - private Gson gson = new Gson(); - private EventBuilderV2 builder = new EventBuilderV2(); - - private static String userId = "userId"; - private int datafileVersion; - private ProjectConfig validProjectConfig; - - public EventBuilderV2Test(int datafileVersion, - ProjectConfig validProjectConfig) { - this.datafileVersion = datafileVersion; - this.validProjectConfig = validProjectConfig; - } - - /** - * Verify {@link Impression} event creation - */ - @Test - public void createImpressionEvent() throws Exception { - // use the "valid" project config and its associated experiment, variation, and attributes - ProjectConfig projectConfig = validProjectConfigV2(); - Experiment activatedExperiment = projectConfig.getExperiments().get(0); - Variation bucketedVariation = activatedExperiment.getVariations().get(0); - Attribute attribute = projectConfig.getAttributes().get(0); - String userId = "userId"; - Map attributeMap = Collections.singletonMap(attribute.getKey(), "value"); - Decision expectedDecision = new Decision(bucketedVariation.getId(), false, activatedExperiment.getId()); - Feature feature = new Feature(attribute.getId(), attribute.getKey(), Feature.CUSTOM_ATTRIBUTE_FEATURE_TYPE, - "value", true); - List expectedUserFeatures = Collections.singletonList(feature); - - LogEvent impressionEvent = builder.createImpressionEvent(projectConfig, activatedExperiment, bucketedVariation, - userId, attributeMap); - - // verify that request endpoint is correct - assertThat(impressionEvent.getEndpointUrl(), is(EventBuilderV2.IMPRESSION_ENDPOINT)); - - Impression impression = gson.fromJson(impressionEvent.getBody(), Impression.class); - - // verify payload information - assertThat(impression.getVisitorId(), is(userId)); - assertThat((double)impression.getTimestamp(), closeTo((double)System.currentTimeMillis(), 60.0)); - assertFalse(impression.getIsGlobalHoldback()); - assertThat(impression.getAnonymizeIP(), is(projectConfig.getAnonymizeIP())); - assertThat(impression.getProjectId(), is(projectConfig.getProjectId())); - assertThat(impression.getDecision(), is(expectedDecision)); - assertThat(impression.getLayerId(), is(activatedExperiment.getLayerId())); - assertThat(impression.getAccountId(), is(projectConfig.getAccountId())); - assertThat(impression.getUserFeatures(), is(expectedUserFeatures)); - assertThat(impression.getClientEngine(), is(ClientEngine.JAVA_SDK.getClientEngineValue())); - assertThat(impression.getClientVersion(), is(BuildVersionInfo.VERSION)); - assertNull(impression.getSessionId()); - } - - /** - * Verify that passing through an unknown attribute causes that attribute to be ignored, rather than - * causing an exception to be thrown. - */ - @Test - public void createImpressionEventIgnoresUnknownAttributes() throws Exception { - // use the "valid" project config and its associated experiment, variation, and attributes - ProjectConfig projectConfig = validProjectConfigV2(); - Experiment activatedExperiment = projectConfig.getExperiments().get(0); - Variation bucketedVariation = activatedExperiment.getVariations().get(0); - - LogEvent impressionEvent = - builder.createImpressionEvent(projectConfig, activatedExperiment, bucketedVariation, "userId", - Collections.singletonMap("unknownAttribute", "blahValue")); - - Impression impression = gson.fromJson(impressionEvent.getBody(), Impression.class); - - // verify that no Feature is created for "unknownAtrribute" -> "blahValue" - for (Feature feature : impression.getUserFeatures()) { - assertFalse(feature.getName() == "unknownAttribute"); - assertFalse(feature.getValue() == "blahValue"); - } - } - - /** - * Verify that supplying {@link EventBuilderV2} with a custom client engine and client version results in impression - * events being sent with the overriden values. - */ - @Test - public void createImpressionEventAndroidClientEngineClientVersion() throws Exception { - EventBuilderV2 builder = new EventBuilderV2(ClientEngine.ANDROID_SDK, "0.0.0"); - ProjectConfig projectConfig = validProjectConfigV2(); - Experiment activatedExperiment = projectConfig.getExperiments().get(0); - Variation bucketedVariation = activatedExperiment.getVariations().get(0); - Attribute attribute = projectConfig.getAttributes().get(0); - String userId = "userId"; - Map attributeMap = Collections.singletonMap(attribute.getKey(), "value"); - - LogEvent impressionEvent = builder.createImpressionEvent(projectConfig, activatedExperiment, bucketedVariation, - userId, attributeMap); - Impression impression = gson.fromJson(impressionEvent.getBody(), Impression.class); - - assertThat(impression.getClientEngine(), is(ClientEngine.ANDROID_SDK.getClientEngineValue())); - assertThat(impression.getClientVersion(), is("0.0.0")); - } - - /** - * Verify that supplying {@link EventBuilderV2} with a custom Android TV client engine and client version - * results in impression events being sent with the overriden values. - */ - @Test - public void createImpressionEventAndroidTVClientEngineClientVersion() throws Exception { - String clientVersion = "0.0.0"; - EventBuilderV2 builder = new EventBuilderV2(ClientEngine.ANDROID_TV_SDK, clientVersion); - ProjectConfig projectConfig = validProjectConfigV2(); - Experiment activatedExperiment = projectConfig.getExperiments().get(0); - Variation bucketedVariation = activatedExperiment.getVariations().get(0); - Attribute attribute = projectConfig.getAttributes().get(0); - String userId = "userId"; - Map attributeMap = Collections.singletonMap(attribute.getKey(), "value"); - - LogEvent impressionEvent = builder.createImpressionEvent(projectConfig, activatedExperiment, bucketedVariation, - userId, attributeMap); - Impression impression = gson.fromJson(impressionEvent.getBody(), Impression.class); - - assertThat(impression.getClientEngine(), is(ClientEngine.ANDROID_TV_SDK.getClientEngineValue())); - assertThat(impression.getClientVersion(), is(clientVersion)); - } - - /** - * Verify {@link Conversion} event creation - */ - @Test - public void createConversionEvent() throws Exception { - // use the "valid" project config and its associated experiment, variation, and attributes - Attribute attribute = validProjectConfig.getAttributes().get(0); - EventType eventType = validProjectConfig.getEventTypes().get(0); - String userId = "userId"; - - Bucketer mockBucketAlgorithm = mock(Bucketer.class); - - List allExperiments = validProjectConfig.getExperiments(); - List experimentsForEventKey = validProjectConfig.getExperimentsForEventKey(eventType.getKey()); - - // Bucket to the first variation for all experiments. However, only a subset of the experiments will actually - // call the bucket function. - for (Experiment experiment : allExperiments) { - when(mockBucketAlgorithm.bucket(experiment, userId)) - .thenReturn(experiment.getVariations().get(0)); - } - DecisionService decisionService = new DecisionService( - mockBucketAlgorithm, - mock(ErrorHandler.class), - validProjectConfig, - mock(UserProfileService.class) - ); - - Map attributeMap = Collections.singletonMap(attribute.getKey(), AUDIENCE_GRYFFINDOR_VALUE); - Map eventTagMap = new HashMap(); - eventTagMap.put("boolean_param", false); - eventTagMap.put("string_param", "123"); - Map experimentVariationMap = createExperimentVariationMap( - validProjectConfig, - decisionService, - eventType.getKey(), - userId, - attributeMap); - LogEvent conversionEvent = builder.createConversionEvent( - validProjectConfig, - experimentVariationMap, - userId, - eventType.getId(), - eventType.getKey(), - attributeMap, - eventTagMap); - - List expectedLayerStates = new ArrayList(); - - for (Experiment experiment : experimentsForEventKey) { - if (experiment.isRunning()) { - LayerState layerState = new LayerState(experiment.getLayerId(), validProjectConfig.getRevision(), - new Decision(experiment.getVariations().get(0).getId(), false, experiment.getId()), true); - expectedLayerStates.add(layerState); - } - } - - // verify that the request endpoint is correct - assertThat(conversionEvent.getEndpointUrl(), is(EventBuilderV2.CONVERSION_ENDPOINT)); - - Conversion conversion = gson.fromJson(conversionEvent.getBody(), Conversion.class); - - // verify payload information - assertThat(conversion.getVisitorId(), is(userId)); - assertThat((double)conversion.getTimestamp(), closeTo((double)System.currentTimeMillis(), 120.0)); - assertThat(conversion.getProjectId(), is(validProjectConfig.getProjectId())); - assertThat(conversion.getAccountId(), is(validProjectConfig.getAccountId())); - - Feature feature = new Feature(attribute.getId(), attribute.getKey(), Feature.CUSTOM_ATTRIBUTE_FEATURE_TYPE, - AUDIENCE_GRYFFINDOR_VALUE, true); - List expectedUserFeatures = Collections.singletonList(feature); - - // Event Features - List expectedEventFeatures = new ArrayList(); - expectedEventFeatures.add(new Feature("", "boolean_param", Feature.EVENT_FEATURE_TYPE, - false, false)); - expectedEventFeatures.add(new Feature("", "string_param", Feature.EVENT_FEATURE_TYPE, - "123", false)); - - assertEquals(conversion.getUserFeatures(), expectedUserFeatures); - assertThat(conversion.getLayerStates(), containsInAnyOrder(expectedLayerStates.toArray())); - assertEquals(conversion.getEventEntityId(), eventType.getId()); - assertEquals(conversion.getEventName(), eventType.getKey()); - assertEquals(conversion.getEventMetrics(), Collections.emptyList()); - assertTrue(conversion.getEventFeatures().containsAll(expectedEventFeatures)); - assertTrue(expectedEventFeatures.containsAll(conversion.getEventFeatures())); - assertFalse(conversion.getIsGlobalHoldback()); - assertEquals(conversion.getAnonymizeIP(), validProjectConfig.getAnonymizeIP()); - assertEquals(conversion.getClientEngine(), ClientEngine.JAVA_SDK.getClientEngineValue()); - assertEquals(conversion.getClientVersion(), BuildVersionInfo.VERSION); - } - - /** - * Verify that "revenue" and "value" are properly recorded in a conversion request as {@link EventMetric} objects. - * "revenue" is fixed-point and "value" is floating-point. - */ - @Test - public void createConversionParamsWithEventMetrics() throws Exception { - Long revenue = 1234L; - Double value = 13.37; - - // use the "valid" project config and its associated experiment, variation, and attributes - Attribute attribute = validProjectConfig.getAttributes().get(0); - EventType eventType = validProjectConfig.getEventTypes().get(0); - - Bucketer mockBucketAlgorithm = mock(Bucketer.class); - - // Bucket to the first variation for all experiments. - for (Experiment experiment : validProjectConfig.getExperiments()) { - when(mockBucketAlgorithm.bucket(experiment, userId)) - .thenReturn(experiment.getVariations().get(0)); - } - DecisionService decisionService = new DecisionService( - mockBucketAlgorithm, - mock(ErrorHandler.class), - validProjectConfig, - mock(UserProfileService.class) - ); - - Map attributeMap = Collections.singletonMap(attribute.getKey(), "value"); - Map eventTagMap = new HashMap(); - eventTagMap.put(ReservedEventKey.REVENUE.toString(), revenue); - eventTagMap.put(ReservedEventKey.VALUE.toString(), value); - Map experimentVariationMap = createExperimentVariationMap( - validProjectConfig, - decisionService, - eventType.getKey(), - userId, - attributeMap); - LogEvent conversionEvent = builder.createConversionEvent(validProjectConfig, experimentVariationMap, userId, - eventType.getId(), eventType.getKey(), attributeMap, - eventTagMap); - - Conversion conversion = gson.fromJson(conversionEvent.getBody(), Conversion.class); - List eventMetrics = Arrays.asList( - new EventMetric(EventMetric.REVENUE_METRIC_TYPE, new LazilyParsedNumber(revenue.toString())), - new EventMetric(EventMetric.NUMERIC_METRIC_TYPE, new LazilyParsedNumber(value.toString()))); - // we're not going to verify everything, only the event metrics - assertThat(conversion.getEventMetrics(), is(eventMetrics)); - } - - /** - * Verify that precedence is given to forced variation bucketing over audience evaluation when constructing a - * conversion event. - */ - @Test - public void createConversionEventForcedVariationBucketingPrecedesAudienceEval() { - EventType eventType; - String whitelistedUserId; - if (datafileVersion == 4) { - eventType = validProjectConfig.getEventNameMapping().get(EVENT_BASIC_EVENT_KEY); - whitelistedUserId = MULTIVARIATE_EXPERIMENT_FORCED_VARIATION_USER_ID_GRED; - } - else { - eventType = validProjectConfig.getEventTypes().get(0); - whitelistedUserId = "testUser1"; - } - - DecisionService decisionService = new DecisionService( - new Bucketer(validProjectConfig), - new NoOpErrorHandler(), - validProjectConfig, - mock(UserProfileService.class) - ); - - // attributes are empty so user won't be in the audience for experiment using the event, but bucketing - // will still take place - Map experimentVariationMap = createExperimentVariationMap( - validProjectConfig, - decisionService, - eventType.getKey(), - whitelistedUserId, - Collections.emptyMap()); - LogEvent conversionEvent = builder.createConversionEvent( - validProjectConfig, - experimentVariationMap, - whitelistedUserId, - eventType.getId(), - eventType.getKey(), - Collections.emptyMap(), - Collections.emptyMap()); - assertNotNull(conversionEvent); - - Conversion conversion = gson.fromJson(conversionEvent.getBody(), Conversion.class); - if (datafileVersion == 4) { - // 2 experiments use the event - // basic experiment has no audience - // user is whitelisted in to one audience - assertEquals(2, conversion.getLayerStates().size()); - } - else { - assertEquals(1, conversion.getLayerStates().size()); - } - } - - /** - * Verify that precedence is given to experiment status over forced variation bucketing when constructing a - * conversion event. - */ - @Test - public void createConversionEventExperimentStatusPrecedesForcedVariation() { - EventType eventType; - if (datafileVersion == 4) { - eventType = validProjectConfig.getEventNameMapping().get(EVENT_PAUSED_EXPERIMENT_KEY); - } - else { - eventType = validProjectConfig.getEventTypes().get(3); - } - String whitelistedUserId = PAUSED_EXPERIMENT_FORCED_VARIATION_USER_ID_CONTROL; - - Bucketer bucketer = spy(new Bucketer(validProjectConfig)); - DecisionService decisionService = new DecisionService( - bucketer, - mock(ErrorHandler.class), - validProjectConfig, - mock(UserProfileService.class) - ); - - Map experimentVariationMap = createExperimentVariationMap( - validProjectConfig, - decisionService, - eventType.getKey(), - whitelistedUserId, - Collections.emptyMap()); - LogEvent conversionEvent = builder.createConversionEvent( - validProjectConfig, - experimentVariationMap, - whitelistedUserId, - eventType.getId(), - eventType.getKey(), - Collections.emptyMap(), - Collections.emptyMap()); - - for (Experiment experiment : validProjectConfig.getExperiments()) { - verify(bucketer, never()).bucket(experiment, whitelistedUserId); - } - - assertNull(conversionEvent); - } - - /** - * Verify that supplying {@link EventBuilderV2} with a custom client engine and client version results in conversion - * events being sent with the overriden values. - */ - @Test - public void createConversionEventAndroidClientEngineClientVersion() throws Exception { - EventBuilderV2 builder = new EventBuilderV2(ClientEngine.ANDROID_SDK, "0.0.0"); - Attribute attribute = validProjectConfig.getAttributes().get(0); - EventType eventType = validProjectConfig.getEventTypes().get(0); - - Bucketer mockBucketAlgorithm = mock(Bucketer.class); - for (Experiment experiment : validProjectConfig.getExperiments()) { - when(mockBucketAlgorithm.bucket(experiment, userId)) - .thenReturn(experiment.getVariations().get(0)); - } - DecisionService decisionService = new DecisionService( - mockBucketAlgorithm, - mock(ErrorHandler.class), - validProjectConfig, - mock(UserProfileService.class) - ); - - Map attributeMap = Collections.singletonMap(attribute.getKey(), "value"); - Map experimentVariationMap = createExperimentVariationMap( - validProjectConfig, - decisionService, - eventType.getKey(), - userId, - attributeMap); - LogEvent conversionEvent = builder.createConversionEvent( - validProjectConfig, - experimentVariationMap, - userId, - eventType.getId(), - eventType.getKey(), - attributeMap, - Collections.emptyMap()); - - Conversion conversion = gson.fromJson(conversionEvent.getBody(), Conversion.class); - - assertThat(conversion.getClientEngine(), is(ClientEngine.ANDROID_SDK.getClientEngineValue())); - assertThat(conversion.getClientVersion(), is("0.0.0")); - } - - /** - * Verify that supplying {@link EventBuilderV2} with a Android TV client engine and client version results in - * conversion events being sent with the overriden values. - */ - @Test - public void createConversionEventAndroidTVClientEngineClientVersion() throws Exception { - String clientVersion = "0.0.0"; - EventBuilderV2 builder = new EventBuilderV2(ClientEngine.ANDROID_TV_SDK, clientVersion); - ProjectConfig projectConfig = validProjectConfigV2(); - Attribute attribute = projectConfig.getAttributes().get(0); - EventType eventType = projectConfig.getEventTypes().get(0); - String userId = "userId"; - - Bucketer mockBucketAlgorithm = mock(Bucketer.class); - for (Experiment experiment : projectConfig.getExperiments()) { - when(mockBucketAlgorithm.bucket(experiment, userId)) - .thenReturn(experiment.getVariations().get(0)); - } - - Map attributeMap = Collections.singletonMap(attribute.getKey(), "value"); - List experimentList = projectConfig.getExperimentsForEventKey(eventType.getKey()); - Map experimentVariationMap = new HashMap(experimentList.size()); - for (Experiment experiment : experimentList) { - experimentVariationMap.put(experiment, experiment.getVariations().get(0)); - } - - LogEvent conversionEvent = builder.createConversionEvent( - projectConfig, - experimentVariationMap, - userId, - eventType.getId(), - eventType.getKey(), - attributeMap, - Collections.emptyMap()); - Conversion conversion = gson.fromJson(conversionEvent.getBody(), Conversion.class); - - assertThat(conversion.getClientEngine(), is(ClientEngine.ANDROID_TV_SDK.getClientEngineValue())); - assertThat(conversion.getClientVersion(), is(clientVersion)); - } - - /** - * Verify that supplying an empty Experiment Variation map to - * {@link EventBuilderV2#createConversionEvent(ProjectConfig, Map, String, String, String, Map, Map)} - * returns a null {@link LogEvent}. - */ - @Test - public void createConversionEventReturnsNullWhenExperimentVariationMapIsEmpty() { - EventType eventType = validProjectConfig.getEventTypes().get(0); - EventBuilderV2 builder = new EventBuilderV2(); - - LogEvent conversionEvent = builder.createConversionEvent( - validProjectConfig, - Collections.emptyMap(), - userId, - eventType.getId(), - eventType.getKey(), - Collections.emptyMap(), - Collections.emptyMap() - ); - - assertNull(conversionEvent); - } - - /** - * Verify {@link Impression} event creation - */ - @Test - public void createImpressionEventWithBucketingId() throws Exception { - // use the "valid" project config and its associated experiment, variation, and attributes - ProjectConfig projectConfig = validProjectConfigV2(); - Experiment activatedExperiment = projectConfig.getExperiments().get(0); - Variation bucketedVariation = activatedExperiment.getVariations().get(0); - Attribute attribute = projectConfig.getAttributes().get(0); - String userId = "userId"; - Map attributeMap = new HashMap(); - attributeMap.put(attribute.getKey(), "value"); - - attributeMap.put(com.optimizely.ab.bucketing.DecisionService.BUCKETING_ATTRIBUTE, "variation"); - - Decision expectedDecision = new Decision(bucketedVariation.getId(), false, activatedExperiment.getId()); - Feature feature = new Feature(attribute.getId(), attribute.getKey(), Feature.CUSTOM_ATTRIBUTE_FEATURE_TYPE, - "value", true); - Feature feature1 = new Feature(com.optimizely.ab.bucketing.DecisionService.BUCKETING_ATTRIBUTE, - com.optimizely.ab.event.internal.EventBuilderV2.ATTRIBUTE_KEY_FOR_BUCKETING_ATTRIBUTE, - Feature.CUSTOM_ATTRIBUTE_FEATURE_TYPE, - "variation", true); - List expectedUserFeatures = new java.util.ArrayList(); - expectedUserFeatures.add(feature); - expectedUserFeatures.add(feature1); - - LogEvent impressionEvent = builder.createImpressionEvent(projectConfig, activatedExperiment, bucketedVariation, - userId, attributeMap); - - // verify that request endpoint is correct - assertThat(impressionEvent.getEndpointUrl(), is(EventBuilderV2.IMPRESSION_ENDPOINT)); - - Impression impression = gson.fromJson(impressionEvent.getBody(), Impression.class); - - // verify payload information - assertThat(impression.getVisitorId(), is(userId)); - assertThat((double)impression.getTimestamp(), closeTo((double)System.currentTimeMillis(), 60.0)); - assertFalse(impression.getIsGlobalHoldback()); - assertThat(impression.getAnonymizeIP(), is(projectConfig.getAnonymizeIP())); - assertThat(impression.getProjectId(), is(projectConfig.getProjectId())); - assertThat(impression.getDecision(), is(expectedDecision)); - assertThat(impression.getLayerId(), is(activatedExperiment.getLayerId())); - assertThat(impression.getAccountId(), is(projectConfig.getAccountId())); - - assertThat(impression.getUserFeatures(), is(expectedUserFeatures)); - assertThat(impression.getClientEngine(), is(ClientEngine.JAVA_SDK.getClientEngineValue())); - assertThat(impression.getClientVersion(), is(BuildVersionInfo.VERSION)); - assertNull(impression.getSessionId()); - } - - /** - * Verify {@link Conversion} event creation - */ - @Test - public void createConversionEventWithBucketingId() throws Exception { - // use the "valid" project config and its associated experiment, variation, and attributes - Attribute attribute = validProjectConfig.getAttributes().get(0); - EventType eventType = validProjectConfig.getEventTypes().get(0); - String userId = "userId"; - String bucketingId = "bucketingId"; - - Bucketer mockBucketAlgorithm = mock(Bucketer.class); - - List allExperiments = validProjectConfig.getExperiments(); - List experimentsForEventKey = validProjectConfig.getExperimentsForEventKey(eventType.getKey()); - - // Bucket to the first variation for all experiments. However, only a subset of the experiments will actually - // call the bucket function. - for (Experiment experiment : allExperiments) { - when(mockBucketAlgorithm.bucket(experiment, bucketingId)) - .thenReturn(experiment.getVariations().get(0)); - } - DecisionService decisionService = new DecisionService( - mockBucketAlgorithm, - mock(ErrorHandler.class), - validProjectConfig, - mock(UserProfileService.class) - ); - - Map attributeMap = new java.util.HashMap(); - attributeMap.put(attribute.getKey(), AUDIENCE_GRYFFINDOR_VALUE); - attributeMap.put(com.optimizely.ab.bucketing.DecisionService.BUCKETING_ATTRIBUTE, bucketingId); - - Map eventTagMap = new HashMap(); - eventTagMap.put("boolean_param", false); - eventTagMap.put("string_param", "123"); - Map experimentVariationMap = createExperimentVariationMap( - validProjectConfig, - decisionService, - eventType.getKey(), - userId, - attributeMap); - LogEvent conversionEvent = builder.createConversionEvent( - validProjectConfig, - experimentVariationMap, - userId, - eventType.getId(), - eventType.getKey(), - attributeMap, - eventTagMap); - - List expectedLayerStates = new ArrayList(); - - for (Experiment experiment : experimentsForEventKey) { - if (experiment.isRunning()) { - LayerState layerState = new LayerState(experiment.getLayerId(), validProjectConfig.getRevision(), - new Decision(experiment.getVariations().get(0).getId(), false, experiment.getId()), true); - expectedLayerStates.add(layerState); - } - } - - // verify that the request endpoint is correct - assertThat(conversionEvent.getEndpointUrl(), is(EventBuilderV2.CONVERSION_ENDPOINT)); - - Conversion conversion = gson.fromJson(conversionEvent.getBody(), Conversion.class); - - // verify payload information - assertThat(conversion.getVisitorId(), is(userId)); - assertThat((double)conversion.getTimestamp(), closeTo((double)System.currentTimeMillis(), 120.0)); - assertThat(conversion.getProjectId(), is(validProjectConfig.getProjectId())); - assertThat(conversion.getAccountId(), is(validProjectConfig.getAccountId())); - - Feature feature = new Feature(attribute.getId(), attribute.getKey(), Feature.CUSTOM_ATTRIBUTE_FEATURE_TYPE, - AUDIENCE_GRYFFINDOR_VALUE, true); - Feature feature1 = new Feature(com.optimizely.ab.bucketing.DecisionService.BUCKETING_ATTRIBUTE, - com.optimizely.ab.event.internal.EventBuilderV2.ATTRIBUTE_KEY_FOR_BUCKETING_ATTRIBUTE, - Feature.CUSTOM_ATTRIBUTE_FEATURE_TYPE, - bucketingId, true); - List expectedUserFeatures = new ArrayList(); - expectedUserFeatures.add(feature); - expectedUserFeatures.add(feature1); - - // Event Features - List expectedEventFeatures = new ArrayList(); - expectedEventFeatures.add(new Feature("", "boolean_param", Feature.EVENT_FEATURE_TYPE, - false, false)); - expectedEventFeatures.add(new Feature("", "string_param", Feature.EVENT_FEATURE_TYPE, - "123", false)); - - assertEquals(conversion.getUserFeatures(), expectedUserFeatures); - assertThat(conversion.getLayerStates(), containsInAnyOrder(expectedLayerStates.toArray())); - assertEquals(conversion.getEventEntityId(), eventType.getId()); - assertEquals(conversion.getEventName(), eventType.getKey()); - assertEquals(conversion.getEventMetrics(), Collections.emptyList()); - assertTrue(conversion.getEventFeatures().containsAll(expectedEventFeatures)); - assertTrue(expectedEventFeatures.containsAll(conversion.getEventFeatures())); - assertFalse(conversion.getIsGlobalHoldback()); - assertEquals(conversion.getAnonymizeIP(), validProjectConfig.getAnonymizeIP()); - assertEquals(conversion.getClientEngine(), ClientEngine.JAVA_SDK.getClientEngineValue()); - assertEquals(conversion.getClientVersion(), BuildVersionInfo.VERSION); - } - - - //========== helper methods =========// - public static Map createExperimentVariationMap(ProjectConfig projectConfig, - DecisionService decisionService, - String eventName, - String userId, - @Nullable Map attributes) { - - List eventExperiments = projectConfig.getExperimentsForEventKey(eventName); - Map experimentVariationMap = new HashMap(eventExperiments.size()); - for (Experiment experiment : eventExperiments) { - if (experiment.isRunning()) { - Variation variation = decisionService.getVariation(experiment, userId, attributes); - if (variation != null) { - experimentVariationMap.put(experiment, variation); - } - } - } - - return experimentVariationMap; - } -} diff --git a/core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderV3Test.java b/core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderV3Test.java index 04ecf2507..0197204ce 100644 --- a/core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderV3Test.java +++ b/core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderV3Test.java @@ -1,7 +1,6 @@ package com.optimizely.ab.event.internal; import com.google.gson.Gson; -import com.google.gson.internal.LazilyParsedNumber; import com.optimizely.ab.bucketing.Bucketer; import com.optimizely.ab.bucketing.DecisionService; import com.optimizely.ab.bucketing.UserProfileService; @@ -13,15 +12,9 @@ import com.optimizely.ab.error.ErrorHandler; import com.optimizely.ab.error.NoOpErrorHandler; import com.optimizely.ab.event.LogEvent; -import com.optimizely.ab.event.internal.payload.Conversion; -import com.optimizely.ab.event.internal.payload.Decision; import com.optimizely.ab.event.internal.payload.DecisionV3; import com.optimizely.ab.event.internal.payload.Event; import com.optimizely.ab.event.internal.payload.EventBatch; -import com.optimizely.ab.event.internal.payload.EventMetric; -import com.optimizely.ab.event.internal.payload.Feature; -import com.optimizely.ab.event.internal.payload.Impression; -import com.optimizely.ab.event.internal.payload.LayerState; import com.optimizely.ab.internal.ReservedEventKey; import org.junit.Test; import org.junit.runner.RunWith; @@ -77,7 +70,7 @@ public static Collection data() throws IOException { } private Gson gson = new Gson(); - private EventBuilderV3 builder = new EventBuilderV3(); + private EventBuilder builder = new EventBuilder(); private static String userId = "userId"; private int datafileVersion; @@ -90,7 +83,7 @@ public EventBuilderV3Test(int datafileVersion, } /** - * Verify {@link com.optimizely.ab.event.internal.payload.Impression} event creation + * Verify {@link com.optimizely.ab.event.internal.payload.EventBatch} event creation */ @Test public void createImpressionEvent() throws Exception { @@ -103,7 +96,7 @@ public void createImpressionEvent() throws Exception { Map attributeMap = Collections.singletonMap(attribute.getKey(), "value"); DecisionV3 expectedDecision = new DecisionV3(activatedExperiment.getLayerId(), activatedExperiment.getId(), bucketedVariation.getId(), false); com.optimizely.ab.event.internal.payload.Attribute feature = new com.optimizely.ab.event.internal.payload.Attribute(attribute.getId(), - attribute.getKey(), Feature.CUSTOM_ATTRIBUTE_FEATURE_TYPE, + attribute.getKey(), com.optimizely.ab.event.internal.payload.Attribute.CUSTOM_ATTRIBUTE_TYPE, "value"); List expectedUserFeatures = Collections.singletonList(feature); @@ -111,7 +104,7 @@ public void createImpressionEvent() throws Exception { userId, attributeMap); // verify that request endpoint is correct - assertThat(impressionEvent.getEndpointUrl(), is(EventBuilderV3.EVENT_ENDPOINT)); + assertThat(impressionEvent.getEndpointUrl(), is(EventBuilder.EVENT_ENDPOINT)); EventBatch eventBatch = gson.fromJson(impressionEvent.getBody(), EventBatch.class); @@ -156,12 +149,12 @@ public void createImpressionEventIgnoresUnknownAttributes() throws Exception { } /** - * Verify that supplying {@link EventBuilderV2} with a custom client engine and client version results in impression + * Verify that supplying {@link EventBuilder} with a custom client engine and client version results in impression * events being sent with the overriden values. */ @Test public void createImpressionEventAndroidClientEngineClientVersion() throws Exception { - EventBuilderV3 builder = new EventBuilderV3(Event.ClientEngine.ANDROID_SDK, "0.0.0"); + EventBuilder builder = new EventBuilder(Event.ClientEngine.ANDROID_SDK, "0.0.0"); ProjectConfig projectConfig = validProjectConfigV2(); Experiment activatedExperiment = projectConfig.getExperiments().get(0); Variation bucketedVariation = activatedExperiment.getVariations().get(0); @@ -178,13 +171,13 @@ public void createImpressionEventAndroidClientEngineClientVersion() throws Excep } /** - * Verify that supplying {@link EventBuilderV2} with a custom Android TV client engine and client version + * Verify that supplying {@link EventBuilder} with a custom Android TV client engine and client version * results in impression events being sent with the overriden values. */ @Test public void createImpressionEventAndroidTVClientEngineClientVersion() throws Exception { String clientVersion = "0.0.0"; - EventBuilderV3 builder = new EventBuilderV3(Event.ClientEngine.ANDROID_TV_SDK, clientVersion); + EventBuilder builder = new EventBuilder(Event.ClientEngine.ANDROID_TV_SDK, clientVersion); ProjectConfig projectConfig = validProjectConfigV2(); Experiment activatedExperiment = projectConfig.getExperiments().get(0); Variation bucketedVariation = activatedExperiment.getVariations().get(0); @@ -201,7 +194,7 @@ public void createImpressionEventAndroidTVClientEngineClientVersion() throws Exc } /** - * Verify {@link com.optimizely.ab.event.internal.payload.Conversion} event creation + * Verify {@link com.optimizely.ab.event.internal.payload.EventBatch} event creation */ @Test public void createConversionEvent() throws Exception { @@ -258,7 +251,7 @@ public void createConversionEvent() throws Exception { } // verify that the request endpoint is correct - assertThat(conversionEvent.getEndpointUrl(), is(EventBuilderV3.EVENT_ENDPOINT)); + assertThat(conversionEvent.getEndpointUrl(), is(EventBuilder.EVENT_ENDPOINT)); EventBatch conversion = gson.fromJson(conversionEvent.getBody(), EventBatch.class); @@ -269,7 +262,8 @@ public void createConversionEvent() throws Exception { assertThat(conversion.getProjectId(), is(validProjectConfig.getProjectId())); assertThat(conversion.getAccountId(), is(validProjectConfig.getAccountId())); - com.optimizely.ab.event.internal.payload.Attribute feature = new com.optimizely.ab.event.internal.payload.Attribute(attribute.getId(), attribute.getKey(), Feature.CUSTOM_ATTRIBUTE_FEATURE_TYPE, + com.optimizely.ab.event.internal.payload.Attribute feature = new com.optimizely.ab.event.internal.payload.Attribute(attribute.getId(), attribute.getKey(), + com.optimizely.ab.event.internal.payload.Attribute.CUSTOM_ATTRIBUTE_TYPE, AUDIENCE_GRYFFINDOR_VALUE); List expectedUserFeatures = Collections.singletonList(feature); @@ -287,7 +281,7 @@ public void createConversionEvent() throws Exception { } /** - * Verify that "revenue" and "value" are properly recorded in a conversion request as {@link EventMetric} objects. + * Verify that "revenue" and "value" are properly recorded in a conversion request as {@link com.optimizely.ab.event.internal.payload.EventV3} objects. * "revenue" is fixed-point and "value" is floating-point. */ @Test @@ -433,12 +427,12 @@ public void createConversionEventExperimentStatusPrecedesForcedVariation() { } /** - * Verify that supplying {@link EventBuilderV2} with a custom client engine and client version results in conversion + * Verify that supplying {@link EventBuilder} with a custom client engine and client version results in conversion * events being sent with the overriden values. */ @Test public void createConversionEventAndroidClientEngineClientVersion() throws Exception { - EventBuilderV3 builder = new EventBuilderV3(Event.ClientEngine.ANDROID_SDK, "0.0.0"); + EventBuilder builder = new EventBuilder(Event.ClientEngine.ANDROID_SDK, "0.0.0"); Attribute attribute = validProjectConfig.getAttributes().get(0); EventType eventType = validProjectConfig.getEventTypes().get(0); @@ -477,13 +471,13 @@ public void createConversionEventAndroidClientEngineClientVersion() throws Excep } /** - * Verify that supplying {@link EventBuilderV2} with a Android TV client engine and client version results in + * Verify that supplying {@link EventBuilder} with a Android TV client engine and client version results in * conversion events being sent with the overriden values. */ @Test public void createConversionEventAndroidTVClientEngineClientVersion() throws Exception { String clientVersion = "0.0.0"; - EventBuilderV3 builder = new EventBuilderV3(Event.ClientEngine.ANDROID_TV_SDK, clientVersion); + EventBuilder builder = new EventBuilder(Event.ClientEngine.ANDROID_TV_SDK, clientVersion); ProjectConfig projectConfig = validProjectConfigV2(); Attribute attribute = projectConfig.getAttributes().get(0); EventType eventType = projectConfig.getEventTypes().get(0); @@ -518,13 +512,13 @@ public void createConversionEventAndroidTVClientEngineClientVersion() throws Exc /** * Verify that supplying an empty Experiment Variation map to - * {@link EventBuilderV2#createConversionEvent(ProjectConfig, Map, String, String, String, Map, Map)} + * {@link EventBuilder#createConversionEvent(ProjectConfig, Map, String, String, String, Map, Map)} * returns a null {@link LogEvent}. */ @Test public void createConversionEventReturnsNullWhenExperimentVariationMapIsEmpty() { EventType eventType = validProjectConfig.getEventTypes().get(0); - EventBuilderV3 builder = new EventBuilderV3(); + EventBuilder builder = new EventBuilder(); LogEvent conversionEvent = builder.createConversionEvent( validProjectConfig, @@ -540,7 +534,7 @@ public void createConversionEventReturnsNullWhenExperimentVariationMapIsEmpty() } /** - * Verify {@link Impression} event creation + * Verify {@link com.optimizely.ab.event.internal.payload.EventBatch} event creation */ @Test public void createImpressionEventWithBucketingId() throws Exception { @@ -557,11 +551,12 @@ public void createImpressionEventWithBucketingId() throws Exception { DecisionV3 expectedDecision = new DecisionV3(activatedExperiment.getLayerId(), activatedExperiment.getId(), bucketedVariation.getId(), false); - com.optimizely.ab.event.internal.payload.Attribute feature = new com.optimizely.ab.event.internal.payload.Attribute(attribute.getId(), attribute.getKey(), Feature.CUSTOM_ATTRIBUTE_FEATURE_TYPE, + com.optimizely.ab.event.internal.payload.Attribute feature = new com.optimizely.ab.event.internal.payload.Attribute(attribute.getId(), attribute.getKey(), + com.optimizely.ab.event.internal.payload.Attribute.CUSTOM_ATTRIBUTE_TYPE, "value"); com.optimizely.ab.event.internal.payload.Attribute feature1 = new com.optimizely.ab.event.internal.payload.Attribute(com.optimizely.ab.bucketing.DecisionService.BUCKETING_ATTRIBUTE, - com.optimizely.ab.event.internal.EventBuilderV2.ATTRIBUTE_KEY_FOR_BUCKETING_ATTRIBUTE, - Feature.CUSTOM_ATTRIBUTE_FEATURE_TYPE, + EventBuilder.ATTRIBUTE_KEY_FOR_BUCKETING_ATTRIBUTE, + com.optimizely.ab.event.internal.payload.Attribute.CUSTOM_ATTRIBUTE_TYPE, "variation"); List expectedUserFeatures = Arrays.asList(feature, feature1); @@ -570,7 +565,7 @@ public void createImpressionEventWithBucketingId() throws Exception { userId, attributeMap); // verify that request endpoint is correct - assertThat(impressionEvent.getEndpointUrl(), is(EventBuilderV3.EVENT_ENDPOINT)); + assertThat(impressionEvent.getEndpointUrl(), is(EventBuilder.EVENT_ENDPOINT)); EventBatch impression = gson.fromJson(impressionEvent.getBody(), EventBatch.class); @@ -591,7 +586,7 @@ public void createImpressionEventWithBucketingId() throws Exception { } /** - * Verify {@link Conversion} event creation + * Verify {@link EventBatch} event creation */ @Test public void createConversionEventWithBucketingId() throws Exception { @@ -652,7 +647,7 @@ public void createConversionEventWithBucketingId() throws Exception { } // verify that the request endpoint is correct - assertThat(conversionEvent.getEndpointUrl(), is(EventBuilderV3.EVENT_ENDPOINT)); + assertThat(conversionEvent.getEndpointUrl(), is(EventBuilder.EVENT_ENDPOINT)); EventBatch conversion = gson.fromJson(conversionEvent.getBody(), EventBatch.class); @@ -662,11 +657,12 @@ public void createConversionEventWithBucketingId() throws Exception { assertThat(conversion.getProjectId(), is(validProjectConfig.getProjectId())); assertThat(conversion.getAccountId(), is(validProjectConfig.getAccountId())); - com.optimizely.ab.event.internal.payload.Attribute attribute1 = new com.optimizely.ab.event.internal.payload.Attribute(attribute.getId(), attribute.getKey(), Feature.CUSTOM_ATTRIBUTE_FEATURE_TYPE, + com.optimizely.ab.event.internal.payload.Attribute attribute1 = new com.optimizely.ab.event.internal.payload.Attribute(attribute.getId(), attribute.getKey(), + com.optimizely.ab.event.internal.payload.Attribute.CUSTOM_ATTRIBUTE_TYPE, AUDIENCE_GRYFFINDOR_VALUE); com.optimizely.ab.event.internal.payload.Attribute attribute2 = new com.optimizely.ab.event.internal.payload.Attribute(com.optimizely.ab.bucketing.DecisionService.BUCKETING_ATTRIBUTE, - com.optimizely.ab.event.internal.EventBuilderV2.ATTRIBUTE_KEY_FOR_BUCKETING_ATTRIBUTE, - Feature.CUSTOM_ATTRIBUTE_FEATURE_TYPE, + EventBuilder.ATTRIBUTE_KEY_FOR_BUCKETING_ATTRIBUTE, + com.optimizely.ab.event.internal.payload.Attribute.CUSTOM_ATTRIBUTE_TYPE, bucketingId); List expectedUserFeatures = Arrays.asList(attribute1, attribute2); diff --git a/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/GsonSerializerTest.java b/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/GsonSerializerTest.java index a69340482..6c2f1253e 100644 --- a/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/GsonSerializerTest.java +++ b/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/GsonSerializerTest.java @@ -18,8 +18,7 @@ import com.google.gson.Gson; -import com.optimizely.ab.event.internal.payload.Conversion; -import com.optimizely.ab.event.internal.payload.Impression; +import com.optimizely.ab.event.internal.payload.EventBatch; import org.junit.Test; @@ -44,40 +43,40 @@ public class GsonSerializerTest { @Test public void serializeImpression() throws IOException { - Impression impression = generateImpression(); + EventBatch impression = generateImpression(); // can't compare JSON strings since orders could vary so compare objects instead - Impression actual = gson.fromJson(serializer.serialize(impression), Impression.class); - Impression expected = gson.fromJson(generateImpressionJson(), Impression.class); + EventBatch actual = gson.fromJson(serializer.serialize(impression), EventBatch.class); + EventBatch expected = gson.fromJson(generateImpressionJson(), EventBatch.class); assertThat(actual, is(expected)); } @Test public void serializeImpressionWithSessionId() throws IOException { - Impression impression = generateImpressionWithSessionId(); + EventBatch impression = generateImpressionWithSessionId(); // can't compare JSON strings since orders could vary so compare objects instead - Impression actual = gson.fromJson(serializer.serialize(impression), Impression.class); - Impression expected = gson.fromJson(generateImpressionWithSessionIdJson(), Impression.class); + EventBatch actual = gson.fromJson(serializer.serialize(impression), EventBatch.class); + EventBatch expected = gson.fromJson(generateImpressionWithSessionIdJson(), EventBatch.class); assertThat(actual, is(expected)); } @Test public void serializeConversion() throws IOException { - Conversion conversion = generateConversion(); + EventBatch conversion = generateConversion(); // can't compare JSON strings since orders could vary so compare objects instead - Conversion actual = gson.fromJson(serializer.serialize(conversion), Conversion.class); - Conversion expected = gson.fromJson(generateConversionJson(), Conversion.class); + EventBatch actual = gson.fromJson(serializer.serialize(conversion), EventBatch.class); + EventBatch expected = gson.fromJson(generateConversionJson(), EventBatch.class); assertThat(actual, is(expected)); } @Test public void serializeConversionWithSessionId() throws Exception { - Conversion conversion = generateConversionWithSessionId(); + EventBatch conversion = generateConversionWithSessionId(); // can't compare JSON strings since orders could vary so compare objects instead - Conversion actual = gson.fromJson(serializer.serialize(conversion), Conversion.class); - Conversion expected = gson.fromJson(generateConversionWithSessionIdJson(), Conversion.class); + EventBatch actual = gson.fromJson(serializer.serialize(conversion), EventBatch.class); + EventBatch expected = gson.fromJson(generateConversionWithSessionIdJson(), EventBatch.class); assertThat(actual, is(expected)); } diff --git a/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JacksonSerializerTest.java b/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JacksonSerializerTest.java index 03792b9e2..aa0517523 100644 --- a/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JacksonSerializerTest.java +++ b/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JacksonSerializerTest.java @@ -18,8 +18,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; -import com.optimizely.ab.event.internal.payload.Conversion; -import com.optimizely.ab.event.internal.payload.Impression; +import com.optimizely.ab.event.internal.EventBuilderV3Test; +import com.optimizely.ab.event.internal.payload.EventBatch; import org.junit.Test; @@ -44,40 +44,40 @@ public class JacksonSerializerTest { @Test public void serializeImpression() throws IOException { - Impression impression = generateImpression(); + EventBatch impression = generateImpression(); // can't compare JSON strings since orders could vary so compare objects instead - Impression actual = mapper.readValue(serializer.serialize(impression), Impression.class); - Impression expected = mapper.readValue(generateImpressionJson(), Impression.class); + EventBatch actual = mapper.readValue(serializer.serialize(impression), EventBatch.class); + EventBatch expected = mapper.readValue(generateImpressionJson(), EventBatch.class); assertThat(actual, is(expected)); } @Test public void serializeImpressionWithSessionId() throws IOException { - Impression impression = generateImpressionWithSessionId(); + EventBatch impression = generateImpressionWithSessionId(); // can't compare JSON strings since orders could vary so compare objects instead - Impression actual = mapper.readValue(serializer.serialize(impression), Impression.class); - Impression expected = mapper.readValue(generateImpressionWithSessionIdJson(), Impression.class); + EventBatch actual = mapper.readValue(serializer.serialize(impression), EventBatch.class); + EventBatch expected = mapper.readValue(generateImpressionWithSessionIdJson(), EventBatch.class); assertThat(actual, is(expected)); } @Test public void serializeConversion() throws IOException { - Conversion conversion = generateConversion(); + EventBatch conversion = generateConversion(); // can't compare JSON strings since orders could vary so compare objects instead - Conversion actual = mapper.readValue(serializer.serialize(conversion), Conversion.class); - Conversion expected = mapper.readValue(generateConversionJson(), Conversion.class); + EventBatch actual = mapper.readValue(serializer.serialize(conversion), EventBatch.class); + EventBatch expected = mapper.readValue(generateConversionJson(), EventBatch.class); assertThat(actual, is(expected)); } @Test public void serializeConversionWithSessionId() throws IOException { - Conversion conversion = generateConversionWithSessionId(); + EventBatch conversion = generateConversionWithSessionId(); // can't compare JSON strings since orders could vary so compare objects instead - Conversion actual = mapper.readValue(serializer.serialize(conversion), Conversion.class); - Conversion expected = mapper.readValue(generateConversionWithSessionIdJson(), Conversion.class); + EventBatch actual = mapper.readValue(serializer.serialize(conversion), EventBatch.class); + EventBatch expected = mapper.readValue(generateConversionWithSessionIdJson(), EventBatch.class); assertThat(actual, is(expected)); } diff --git a/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JsonSerializerTest.java b/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JsonSerializerTest.java index f76d02f77..ff86538a5 100644 --- a/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JsonSerializerTest.java +++ b/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JsonSerializerTest.java @@ -16,8 +16,7 @@ */ package com.optimizely.ab.event.internal.serializer; -import com.optimizely.ab.event.internal.payload.Conversion; -import com.optimizely.ab.event.internal.payload.Impression; +import com.optimizely.ab.event.internal.payload.EventBatch; import org.json.JSONObject; @@ -42,7 +41,7 @@ public class JsonSerializerTest { @Test public void serializeImpression() throws IOException { - Impression impression = generateImpression(); + EventBatch impression = generateImpression(); // can't compare JSON strings since orders could vary so compare JSONObjects instead JSONObject actual = new JSONObject(serializer.serialize(impression)); JSONObject expected = new JSONObject(generateImpressionJson()); @@ -52,7 +51,7 @@ public void serializeImpression() throws IOException { @Test public void serializeImpressionWithSessionId() throws IOException { - Impression impression = generateImpressionWithSessionId(); + EventBatch impression = generateImpressionWithSessionId(); // can't compare JSON strings since orders could vary so compare JSONObjects instead JSONObject actual = new JSONObject(serializer.serialize(impression)); JSONObject expected = new JSONObject(generateImpressionWithSessionIdJson()); @@ -62,7 +61,7 @@ public void serializeImpressionWithSessionId() throws IOException { @Test public void serializeConversion() throws IOException { - Conversion conversion = generateConversion(); + EventBatch conversion = generateConversion(); // can't compare JSON strings since orders could vary so compare JSONObjects instead JSONObject actual = new JSONObject(serializer.serialize(conversion)); JSONObject expected = new JSONObject(generateConversionJson()); @@ -72,7 +71,7 @@ public void serializeConversion() throws IOException { @Test public void serializeConversionWithSessionId() throws IOException { - Conversion conversion = generateConversionWithSessionId(); + EventBatch conversion = generateConversionWithSessionId(); // can't compare JSON strings since orders could vary so compare JSONObjects instead JSONObject actual = new JSONObject(serializer.serialize(conversion)); JSONObject expected = new JSONObject(generateConversionWithSessionIdJson()); diff --git a/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JsonSimpleSerializerTest.java b/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JsonSimpleSerializerTest.java index da286b2ba..2c1fcdfa3 100644 --- a/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JsonSimpleSerializerTest.java +++ b/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JsonSimpleSerializerTest.java @@ -16,9 +16,7 @@ */ package com.optimizely.ab.event.internal.serializer; -import com.optimizely.ab.event.internal.payload.Conversion; -import com.optimizely.ab.event.internal.payload.Impression; - +import com.optimizely.ab.event.internal.payload.EventBatch; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; @@ -46,7 +44,7 @@ public class JsonSimpleSerializerTest { @Test public void serializeImpression() throws IOException, ParseException { - Impression impression = generateImpression(); + EventBatch impression = generateImpression(); // can't compare JSON strings since orders could vary so compare JSONObjects instead JSONObject actual = (JSONObject)parser.parse(serializer.serialize(impression)); JSONObject expected = (JSONObject)parser.parse(generateImpressionJson()); @@ -56,7 +54,7 @@ public void serializeImpression() throws IOException, ParseException { @Test public void serializeImpressionWithSessionId() throws IOException, ParseException { - Impression impression = generateImpressionWithSessionId(); + EventBatch impression = generateImpressionWithSessionId(); // can't compare JSON strings since orders could vary so compare JSONObjects instead JSONObject actual = (JSONObject)parser.parse(serializer.serialize(impression)); JSONObject expected = (JSONObject)parser.parse(generateImpressionWithSessionIdJson()); @@ -66,7 +64,7 @@ public void serializeImpressionWithSessionId() throws IOException, ParseExceptio @Test public void serializeConversion() throws IOException, ParseException { - Conversion conversion = generateConversion(); + EventBatch conversion = generateConversion(); // can't compare JSON strings since orders could vary so compare JSONObjects instead JSONObject actual = (JSONObject)parser.parse(serializer.serialize(conversion)); JSONObject expected = (JSONObject)parser.parse(generateConversionJson()); @@ -76,7 +74,7 @@ public void serializeConversion() throws IOException, ParseException { @Test public void serializeConversionWithSessionId() throws IOException, ParseException { - Conversion conversion = generateConversionWithSessionId(); + EventBatch conversion = generateConversionWithSessionId(); // can't compare JSON strings since orders could vary so compare JSONObjects instead JSONObject actual = (JSONObject)parser.parse(serializer.serialize(conversion)); JSONObject expected = (JSONObject)parser.parse(generateConversionWithSessionIdJson()); diff --git a/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/SerializerTestUtils.java b/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/SerializerTestUtils.java index e39464fc5..78525f4e3 100644 --- a/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/SerializerTestUtils.java +++ b/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/SerializerTestUtils.java @@ -18,16 +18,19 @@ import com.google.common.base.Charsets; import com.google.common.io.Resources; -import com.optimizely.ab.event.internal.payload.Conversion; -import com.optimizely.ab.event.internal.payload.Decision; -import com.optimizely.ab.event.internal.payload.EventMetric; -import com.optimizely.ab.event.internal.payload.Feature; -import com.optimizely.ab.event.internal.payload.Impression; -import com.optimizely.ab.event.internal.payload.LayerState; +import com.optimizely.ab.event.internal.payload.Attribute; +import com.optimizely.ab.event.internal.payload.DecisionV3; +import com.optimizely.ab.event.internal.payload.EventBatch; +import com.optimizely.ab.event.internal.payload.EventV3; +import com.optimizely.ab.event.internal.payload.Snapshot; +import com.optimizely.ab.event.internal.payload.Visitor; import java.io.IOException; +import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; public class SerializerTestUtils { @@ -42,75 +45,59 @@ public class SerializerTestUtils { private static final String experimentId = "5"; private static final String sessionId = "sessionid"; private static final String revision = "1"; - private static final Decision decision = new Decision(variationId, isLayerHoldback, experimentId); + private static final DecisionV3 decision = new DecisionV3(layerId, experimentId, variationId, isLayerHoldback); private static final String featureId = "6"; private static final String featureName = "testfeature"; private static final String featureType = "custom"; private static final String featureValue = "testfeaturevalue"; private static final boolean shouldIndex = true; - private static final List userFeatures = Collections.singletonList( - new Feature(featureId, featureName, featureType, featureValue, shouldIndex)); + private static final List userFeatures = Collections.singletonList( + new Attribute(featureId, featureName, featureType, featureValue)); private static final boolean actionTriggered = true; - private static final List layerStates = - Collections.singletonList(new LayerState(layerId, revision, decision, actionTriggered)); private static final String eventEntityId = "7"; private static final String eventName = "testevent"; - private static final String eventMetricName = EventMetric.REVENUE_METRIC_TYPE; + private static final String eventMetricName = "revenue"; private static final long eventMetricValue = 5000L; - private static final List eventMetrics = Collections.singletonList( - new EventMetric(eventMetricName, eventMetricValue)); - private static final List eventFeatures = Collections.emptyList(); - - static Impression generateImpression() { - Impression impression = new Impression(); - impression.setVisitorId(visitorId); - impression.setTimestamp(timestamp); - impression.setIsGlobalHoldback(isGlobalHoldback); + + private static final List events = Collections.singletonList(new EventV3(timestamp, + "uuid", eventEntityId, eventName, null, 5000L, null, eventName, null)); + + static EventBatch generateImpression() { + Snapshot snapshot = new Snapshot(Arrays.asList(decision), events); + + Visitor vistor = new Visitor(visitorId, null, userFeatures, Arrays.asList(snapshot)); + EventBatch impression = new EventBatch(accountId, Arrays.asList(vistor), false, projectId,revision ); impression.setProjectId(projectId); - impression.setLayerId(layerId); impression.setAccountId(accountId); - impression.setDecision(decision); - impression.setUserFeatures(userFeatures); impression.setClientVersion("0.1.1"); - impression.setAnonymizeIP(true); + impression.setAnonymizeIp(true); impression.setRevision(revision); return impression; } - static Impression generateImpressionWithSessionId() { - Impression impression = generateImpression(); - impression.setSessionId(sessionId); + static EventBatch generateImpressionWithSessionId() { + EventBatch impression = generateImpression(); + impression.getVisitors().get(0).setSessionId(sessionId); return impression; } - static Conversion generateConversion() { - Conversion conversion = new Conversion(); - conversion.setVisitorId(visitorId); - conversion.setTimestamp(timestamp); - conversion.setProjectId(projectId); - conversion.setAccountId(accountId); - conversion.setUserFeatures(userFeatures); - conversion.setLayerStates(layerStates); - conversion.setEventEntityId(eventEntityId); - conversion.setEventName(eventName); - conversion.setEventMetrics(eventMetrics); - conversion.setEventFeatures(eventFeatures); - conversion.setIsGlobalHoldback(isGlobalHoldback); + static EventBatch generateConversion() { + EventBatch conversion = generateImpression(); conversion.setClientVersion("0.1.1"); - conversion.setAnonymizeIP(true); + conversion.setAnonymizeIp(true); conversion.setRevision(revision); return conversion; } - static Conversion generateConversionWithSessionId() { - Conversion conversion = generateConversion(); - conversion.setSessionId(sessionId); + static EventBatch generateConversionWithSessionId() { + EventBatch conversion = generateConversion(); + conversion.getVisitors().get(0).setSessionId(sessionId); return conversion; } diff --git a/core-api/src/test/resources/serializer/conversion-session-id.json b/core-api/src/test/resources/serializer/conversion-session-id.json index 8d3ae851f..a2480716d 100644 --- a/core-api/src/test/resources/serializer/conversion-session-id.json +++ b/core-api/src/test/resources/serializer/conversion-session-id.json @@ -1,42 +1,44 @@ { - "visitorId": "testvisitor", - "timestamp": 12345, - "projectId": "1", "accountId": "3", - "userFeatures": [ - { - "id": "6", - "name": "testfeature", - "type": "custom", - "value": "testfeaturevalue", - "shouldIndex": true - } - ], - "layerStates": [ + "visitors": [ { - "layerId": "2", - "revision": "1", - "decision": { - "variationId": "4", - "isLayerHoldback": false, - "experimentId": "5" - }, - "actionTriggered": true + "snapshots": [ + { + "decisions": [ + { + "variationId": "4", + "campaignId": "2", + "experimentId": "5", + "isCampaignHoldback": false + } + ], + "events": [ + { + "revenue": 5000, + "entityId": "7", + "type": "testevent", + "uuid": "uuid", + "key": "testevent", + "timestamp": 12345 + } + ] + } + ], + "attributes": [ + { + "entityId": "6", + "type": "custom", + "value": "testfeaturevalue", + "key": "testfeature" + } + ], + "visitorId": "testvisitor", + "sessionId": "sessionid" } ], - "eventEntityId": "7", - "eventName": "testevent", - "eventMetrics": [ - { - "name": "revenue", - "value": 5000 - } - ], - "eventFeatures": [], - "isGlobalHoldback": false, - "anonymizeIP": true, - "clientEngine": "java-sdk", + "clientName": "java-sdk", "clientVersion": "0.1.1", - "sessionId": "sessionid", + "anonymizeIp": true, + "projectId": "1", "revision": "1" } \ No newline at end of file diff --git a/core-api/src/test/resources/serializer/conversion.json b/core-api/src/test/resources/serializer/conversion.json index 5e432f3f4..006ae0a63 100644 --- a/core-api/src/test/resources/serializer/conversion.json +++ b/core-api/src/test/resources/serializer/conversion.json @@ -1,41 +1,43 @@ { - "visitorId": "testvisitor", - "timestamp": 12345, - "projectId": "1", "accountId": "3", - "userFeatures": [ - { - "id": "6", - "name": "testfeature", - "type": "custom", - "value": "testfeaturevalue", - "shouldIndex": true - } - ], - "layerStates": [ + "visitors": [ { - "layerId": "2", - "revision": "1", - "decision": { - "variationId": "4", - "isLayerHoldback": false, - "experimentId": "5" - }, - "actionTriggered": true + "snapshots": [ + { + "decisions": [ + { + "variationId": "4", + "campaignId": "2", + "experimentId": "5", + "isCampaignHoldback": false + } + ], + "events": [ + { + "revenue": 5000, + "entityId": "7", + "type": "testevent", + "uuid": "uuid", + "key": "testevent", + "timestamp": 12345 + } + ] + } + ], + "attributes": [ + { + "entityId": "6", + "type": "custom", + "value": "testfeaturevalue", + "key": "testfeature" + } + ], + "visitorId": "testvisitor" } ], - "eventEntityId": "7", - "eventName": "testevent", - "eventMetrics": [ - { - "name": "revenue", - "value": 5000 - } - ], - "eventFeatures": [], - "isGlobalHoldback": false, - "anonymizeIP": true, - "clientEngine": "java-sdk", + "clientName": "java-sdk", "clientVersion": "0.1.1", + "anonymizeIp": true, + "projectId": "1", "revision": "1" } \ No newline at end of file diff --git a/core-api/src/test/resources/serializer/impression-session-id.json b/core-api/src/test/resources/serializer/impression-session-id.json index 476a29e27..a2480716d 100644 --- a/core-api/src/test/resources/serializer/impression-session-id.json +++ b/core-api/src/test/resources/serializer/impression-session-id.json @@ -1,27 +1,44 @@ { - "visitorId": "testvisitor", - "timestamp": 12345, - "isGlobalHoldback": false, - "anonymizeIP": true, - "projectId": "1", - "decision": { - "variationId": "4", - "isLayerHoldback": false, - "experimentId": "5" - }, - "layerId": "2", "accountId": "3", - "userFeatures": [ + "visitors": [ { - "id": "6", - "name": "testfeature", - "type": "custom", - "value": "testfeaturevalue", - "shouldIndex": true + "snapshots": [ + { + "decisions": [ + { + "variationId": "4", + "campaignId": "2", + "experimentId": "5", + "isCampaignHoldback": false + } + ], + "events": [ + { + "revenue": 5000, + "entityId": "7", + "type": "testevent", + "uuid": "uuid", + "key": "testevent", + "timestamp": 12345 + } + ] + } + ], + "attributes": [ + { + "entityId": "6", + "type": "custom", + "value": "testfeaturevalue", + "key": "testfeature" + } + ], + "visitorId": "testvisitor", + "sessionId": "sessionid" } ], - "clientEngine": "java-sdk", + "clientName": "java-sdk", "clientVersion": "0.1.1", - "sessionId": "sessionid", + "anonymizeIp": true, + "projectId": "1", "revision": "1" } \ No newline at end of file diff --git a/core-api/src/test/resources/serializer/impression.json b/core-api/src/test/resources/serializer/impression.json index 8d2f183d5..006ae0a63 100644 --- a/core-api/src/test/resources/serializer/impression.json +++ b/core-api/src/test/resources/serializer/impression.json @@ -1,26 +1,43 @@ { - "visitorId": "testvisitor", - "timestamp": 12345, - "isGlobalHoldback": false, - "anonymizeIP": true, - "projectId": "1", - "decision": { - "variationId": "4", - "isLayerHoldback": false, - "experimentId": "5" - }, - "layerId": "2", "accountId": "3", - "userFeatures": [ + "visitors": [ { - "id": "6", - "name": "testfeature", - "type": "custom", - "value": "testfeaturevalue", - "shouldIndex": true + "snapshots": [ + { + "decisions": [ + { + "variationId": "4", + "campaignId": "2", + "experimentId": "5", + "isCampaignHoldback": false + } + ], + "events": [ + { + "revenue": 5000, + "entityId": "7", + "type": "testevent", + "uuid": "uuid", + "key": "testevent", + "timestamp": 12345 + } + ] + } + ], + "attributes": [ + { + "entityId": "6", + "type": "custom", + "value": "testfeaturevalue", + "key": "testfeature" + } + ], + "visitorId": "testvisitor" } ], - "clientEngine": "java-sdk", + "clientName": "java-sdk", "clientVersion": "0.1.1", + "anonymizeIp": true, + "projectId": "1", "revision": "1" } \ No newline at end of file From e6348668cecc318dd623594aefd5ef76a1eed572 Mon Sep 17 00:00:00 2001 From: Thomas Zurkan Date: Thu, 25 Jan 2018 13:37:54 -0800 Subject: [PATCH 5/9] refactor to remove V3 reference and add license comments at the top --- .../java/com/optimizely/ab/Optimizely.java | 2 +- .../ab/event/internal/EventBuilder.java | 27 ++-- .../ab/event/internal/payload/Attribute.java | 16 ++ .../{DecisionV3.java => Decision.java} | 24 ++- .../ab/event/internal/payload/Event.java | 145 +++++++++++++----- .../ab/event/internal/payload/EventBatch.java | 36 ++++- .../ab/event/internal/payload/EventV3.java | 137 ----------------- .../ab/event/internal/payload/Snapshot.java | 30 +++- .../internal/serializer/GsonSerializer.java | 2 - .../internal/serializer/JsonSerializer.java | 1 - .../serializer/JsonSimpleSerializer.java | 16 +- .../event/internal/serializer/Serializer.java | 2 - .../optimizely/ab/OptimizelyBuilderTest.java | 2 +- .../com/optimizely/ab/OptimizelyTest.java | 10 +- ...ilderV3Test.java => EventBuilderTest.java} | 49 +++--- .../serializer/JacksonSerializerTest.java | 1 - .../serializer/SerializerTestUtils.java | 10 +- 17 files changed, 260 insertions(+), 250 deletions(-) rename core-api/src/main/java/com/optimizely/ab/event/internal/payload/{DecisionV3.java => Decision.java} (67%) delete mode 100644 core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventV3.java rename core-api/src/test/java/com/optimizely/ab/event/internal/{EventBuilderV3Test.java => EventBuilderTest.java} (93%) diff --git a/core-api/src/main/java/com/optimizely/ab/Optimizely.java b/core-api/src/main/java/com/optimizely/ab/Optimizely.java index 5103bd017..3b2f66113 100644 --- a/core-api/src/main/java/com/optimizely/ab/Optimizely.java +++ b/core-api/src/main/java/com/optimizely/ab/Optimizely.java @@ -37,7 +37,7 @@ import com.optimizely.ab.event.LogEvent; import com.optimizely.ab.event.internal.BuildVersionInfo; import com.optimizely.ab.event.internal.EventBuilder; -import com.optimizely.ab.event.internal.payload.Event.ClientEngine; +import com.optimizely.ab.event.internal.payload.EventBatch.ClientEngine; import com.optimizely.ab.internal.EventTagUtils; import com.optimizely.ab.notification.NotificationBroadcaster; import com.optimizely.ab.notification.NotificationCenter; diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilder.java b/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilder.java index 19e45ed12..d65346361 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilder.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilder.java @@ -8,10 +8,9 @@ import com.optimizely.ab.config.Variation; import com.optimizely.ab.event.LogEvent; import com.optimizely.ab.event.internal.payload.Attribute; -import com.optimizely.ab.event.internal.payload.DecisionV3; -import com.optimizely.ab.event.internal.payload.Event; +import com.optimizely.ab.event.internal.payload.Decision; import com.optimizely.ab.event.internal.payload.EventBatch; -import com.optimizely.ab.event.internal.payload.EventV3; +import com.optimizely.ab.event.internal.payload.Event; import com.optimizely.ab.event.internal.payload.Snapshot; import com.optimizely.ab.event.internal.payload.Visitor; import com.optimizely.ab.event.internal.serializer.DefaultJsonSerializer; @@ -38,13 +37,13 @@ public class EventBuilder { @VisibleForTesting public final String clientVersion; @VisibleForTesting - public final Event.ClientEngine clientEngine; + public final EventBatch.ClientEngine clientEngine; public EventBuilder() { - this(Event.ClientEngine.JAVA_SDK, BuildVersionInfo.VERSION); + this(EventBatch.ClientEngine.JAVA_SDK, BuildVersionInfo.VERSION); } - public EventBuilder(Event.ClientEngine clientEngine, String clientVersion) { + public EventBuilder(EventBatch.ClientEngine clientEngine, String clientVersion) { this.clientEngine = clientEngine; this.clientVersion = clientVersion; this.serializer = DefaultJsonSerializer.getInstance(); @@ -57,11 +56,11 @@ public LogEvent createImpressionEvent(@Nonnull ProjectConfig projectConfig, @Nonnull String userId, @Nonnull Map attributes) { - DecisionV3 decisionV3 = new DecisionV3(activatedExperiment.getLayerId(), activatedExperiment.getId(), + Decision decision = new Decision(activatedExperiment.getLayerId(), activatedExperiment.getId(), variation.getId(), false); - EventV3 eventV3 = new EventV3(System.currentTimeMillis(),UUID.randomUUID().toString(), activatedExperiment.getLayerId(), + Event eventV3 = new Event(System.currentTimeMillis(),UUID.randomUUID().toString(), activatedExperiment.getLayerId(), ACTIVATE_EVENT_KEY, null, null, null, ACTIVATE_EVENT_KEY, null); - Snapshot snapshot = new Snapshot(Arrays.asList(decisionV3), Arrays.asList(eventV3)); + Snapshot snapshot = new Snapshot(Arrays.asList(decision), Arrays.asList(eventV3)); Visitor visitor = new Visitor(userId, null, buildAttributeList(projectConfig, attributes), Arrays.asList(snapshot)); List visitors = Arrays.asList(visitor); @@ -82,17 +81,17 @@ public LogEvent createConversionEvent(@Nonnull ProjectConfig projectConfig, return null; } - ArrayList decisionV3s = new ArrayList(); + ArrayList decisions = new ArrayList(); for (Map.Entry entry : experimentVariationMap.entrySet()) { - DecisionV3 decisionV3 = new DecisionV3(entry.getKey().getLayerId(), entry.getKey().getId(), entry.getValue().getId(), false); - decisionV3s.add(decisionV3); + Decision decision = new Decision(entry.getKey().getLayerId(), entry.getKey().getId(), entry.getValue().getId(), false); + decisions.add(decision); } EventType eventType = projectConfig.getEventNameMapping().get(eventName); - EventV3 eventV3 = new EventV3(System.currentTimeMillis(),UUID.randomUUID().toString(), eventType.getId(), + Event eventV3 = new Event(System.currentTimeMillis(),UUID.randomUUID().toString(), eventType.getId(), eventType.getKey(), null, EventTagUtils.getRevenueValue(eventTags), eventTags, eventType.getKey(), EventTagUtils.getNumericValue(eventTags)); - Snapshot snapshot = new Snapshot(decisionV3s, Arrays.asList(eventV3)); + Snapshot snapshot = new Snapshot(decisions, Arrays.asList(eventV3)); Visitor visitor = new Visitor(userId, null, buildAttributeList(projectConfig, attributes), Arrays.asList(snapshot)); List visitors = Arrays.asList(visitor); diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Attribute.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Attribute.java index 91a5fcb66..1d99aeeb0 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Attribute.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Attribute.java @@ -1,3 +1,19 @@ +/** + * + * Copyright 2017-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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.optimizely.ab.event.internal.payload; public class Attribute { diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/DecisionV3.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Decision.java similarity index 67% rename from core-api/src/main/java/com/optimizely/ab/event/internal/payload/DecisionV3.java rename to core-api/src/main/java/com/optimizely/ab/event/internal/payload/Decision.java index a1f10a715..9ee5e85e5 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/DecisionV3.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Decision.java @@ -1,19 +1,35 @@ +/** + * + * Copyright 2017-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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.optimizely.ab.event.internal.payload; -public class DecisionV3 { +public class Decision { String campaignId; String experimentId; String variationId; boolean isCampaignHoldback; - public DecisionV3(String campaignId, String experimentId, String variationId, boolean isCampaignHoldback) { + public Decision(String campaignId, String experimentId, String variationId, boolean isCampaignHoldback) { this.campaignId = campaignId; this.experimentId = experimentId; this.variationId = variationId; this.isCampaignHoldback = isCampaignHoldback; } - public DecisionV3() { + public Decision() { } @@ -54,7 +70,7 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - DecisionV3 that = (DecisionV3) o; + Decision that = (Decision) o; if (isCampaignHoldback != that.isCampaignHoldback) return false; if (!campaignId.equals(that.campaignId)) return false; diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Event.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Event.java index 974a72a4c..e895d182e 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Event.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Event.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016-2017, Optimizely and contributors + * Copyright 2017-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. @@ -16,63 +16,138 @@ */ package com.optimizely.ab.event.internal.payload; -import com.fasterxml.jackson.annotation.JsonValue; - -import com.optimizely.ab.event.internal.BuildVersionInfo; +import java.util.Map; public class Event { + long timestamp; + String uuid; + String entityId; + String key; + Number quantity; + Number revenue; + Map tags; + String type; + Number value; + + public Event() { - public enum ClientEngine { - JAVA_SDK ("java-sdk"), - ANDROID_SDK ("android-sdk"), - ANDROID_TV_SDK ("android-tv-sdk"); + } - private final String clientEngineValue; + public Event(long timestamp, String uuid, String entityId, String key, Number quantity, + Number revenue, Map tags, String type, Number value) { + this.timestamp = timestamp; + this.uuid = uuid; + this.entityId = entityId; + this.key = key; + this.quantity = quantity; + this.revenue = revenue; + this.tags = tags; + this.type = type; + this.value = value; + } - ClientEngine(String clientEngineValue) { - this.clientEngineValue = clientEngineValue; - } + public long getTimestamp() { + return timestamp; + } - @JsonValue - public String getClientEngineValue() { - return clientEngineValue; - } + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; } - String clientEngine = ClientEngine.JAVA_SDK.getClientEngineValue(); - String clientVersion = BuildVersionInfo.VERSION; + public String getUuid() { + return uuid; + } - public String getClientEngine() { - return clientEngine; + public void setUuid(String uuid) { + this.uuid = uuid; } - public void setClientEngine(ClientEngine clientEngine) { - this.clientEngine = clientEngine.getClientEngineValue(); + public String getEntityId() { + return entityId; } - public String getClientVersion() { - return clientVersion; + public void setEntityId(String entityId) { + this.entityId = entityId; } - public void setClientVersion(String clientVersion) { - this.clientVersion = clientVersion; + public String getKey() { + return key; } - @Override - public boolean equals(Object other) { - if (!(other instanceof Event)) - return false; + public void setKey(String key) { + this.key = key; + } + + public Number getQuantity() { + return quantity; + } + + public void setQuantity(Number quantity) { + this.quantity = quantity; + } + + public Number getRevenue() { + return revenue; + } + + public void setRevenue(Number revenue) { + this.revenue = revenue; + } + + public Map getTags() { + return tags; + } + + public void setTags(Map tags) { + this.tags = tags; + } + + public String getType() { + return type; + } - Event otherEvent = (Event)other; + public void setType(String type) { + this.type = type; + } + + public Number getValue() { + return value; + } - return clientEngine.equals(otherEvent.clientEngine) && - clientVersion.equals(otherEvent.clientVersion); + public void setValue(Number value) { + this.value = value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Event eventV3 = (Event) o; + + if (timestamp != eventV3.timestamp) return false; + if (!uuid.equals(eventV3.uuid)) return false; + if (entityId != null ? !entityId.equals(eventV3.entityId) : eventV3.entityId != null) return false; + if (key != null ? !key.equals(eventV3.key) : eventV3.key != null) return false; + if (quantity != null ? !quantity.equals(eventV3.quantity) : eventV3.quantity != null) return false; + if (revenue != null ? !revenue.equals(eventV3.revenue) : eventV3.revenue != null) return false; + if (tags != null ? !tags.equals(eventV3.tags) : eventV3.tags != null) return false; + if (!type.equals(eventV3.type)) return false; + return value != null ? value.equals(eventV3.value) : eventV3.value == null; } @Override public int hashCode() { - int result = clientEngine.hashCode(); - result = 31 * result + clientVersion.hashCode(); + int result = (int) (timestamp ^ (timestamp >>> 32)); + result = 31 * result + uuid.hashCode(); + result = 31 * result + (entityId != null ? entityId.hashCode() : 0); + result = 31 * result + (key != null ? key.hashCode() : 0); + result = 31 * result + (quantity != null ? quantity.hashCode() : 0); + result = 31 * result + (revenue != null ? revenue.hashCode() : 0); + result = 31 * result + (tags != null ? tags.hashCode() : 0); + result = 31 * result + type.hashCode(); + result = 31 * result + (value != null ? value.hashCode() : 0); return result; } + } diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventBatch.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventBatch.java index 7618adc6c..c5e905b90 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventBatch.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventBatch.java @@ -1,14 +1,48 @@ +/** + * + * Copyright 2017-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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.optimizely.ab.event.internal.payload; +import com.fasterxml.jackson.annotation.JsonValue; import com.optimizely.ab.event.internal.BuildVersionInfo; import java.util.List; public class EventBatch { + public enum ClientEngine { + JAVA_SDK ("java-sdk"), + ANDROID_SDK ("android-sdk"), + ANDROID_TV_SDK ("android-tv-sdk"); + + private final String clientEngineValue; + + ClientEngine(String clientEngineValue) { + this.clientEngineValue = clientEngineValue; + } + + @JsonValue + public String getClientEngineValue() { + return clientEngineValue; + } + } + String accountId; List visitors; Boolean anonymizeIp; - String clientName = Event.ClientEngine.JAVA_SDK.getClientEngineValue(); + String clientName = ClientEngine.JAVA_SDK.getClientEngineValue(); String clientVersion = BuildVersionInfo.VERSION; String projectId; String revision; diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventV3.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventV3.java deleted file mode 100644 index 637c93332..000000000 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventV3.java +++ /dev/null @@ -1,137 +0,0 @@ -package com.optimizely.ab.event.internal.payload; - -import java.util.Map; - -public class EventV3 { - long timestamp; - String uuid; - String entityId; - String key; - Number quantity; - Number revenue; - Map tags; - String type; - Number value; - - public EventV3() { - - } - - public EventV3(long timestamp, String uuid, String entityId, String key, Number quantity, - Number revenue, Map tags, String type, Number value) { - this.timestamp = timestamp; - this.uuid = uuid; - this.entityId = entityId; - this.key = key; - this.quantity = quantity; - this.revenue = revenue; - this.tags = tags; - this.type = type; - this.value = value; - } - - public long getTimestamp() { - return timestamp; - } - - public void setTimestamp(long timestamp) { - this.timestamp = timestamp; - } - - public String getUuid() { - return uuid; - } - - public void setUuid(String uuid) { - this.uuid = uuid; - } - - public String getEntityId() { - return entityId; - } - - public void setEntityId(String entityId) { - this.entityId = entityId; - } - - public String getKey() { - return key; - } - - public void setKey(String key) { - this.key = key; - } - - public Number getQuantity() { - return quantity; - } - - public void setQuantity(Number quantity) { - this.quantity = quantity; - } - - public Number getRevenue() { - return revenue; - } - - public void setRevenue(Number revenue) { - this.revenue = revenue; - } - - public Map getTags() { - return tags; - } - - public void setTags(Map tags) { - this.tags = tags; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public Number getValue() { - return value; - } - - public void setValue(Number value) { - this.value = value; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - EventV3 eventV3 = (EventV3) o; - - if (timestamp != eventV3.timestamp) return false; - if (!uuid.equals(eventV3.uuid)) return false; - if (entityId != null ? !entityId.equals(eventV3.entityId) : eventV3.entityId != null) return false; - if (key != null ? !key.equals(eventV3.key) : eventV3.key != null) return false; - if (quantity != null ? !quantity.equals(eventV3.quantity) : eventV3.quantity != null) return false; - if (revenue != null ? !revenue.equals(eventV3.revenue) : eventV3.revenue != null) return false; - if (tags != null ? !tags.equals(eventV3.tags) : eventV3.tags != null) return false; - if (!type.equals(eventV3.type)) return false; - return value != null ? value.equals(eventV3.value) : eventV3.value == null; - } - - @Override - public int hashCode() { - int result = (int) (timestamp ^ (timestamp >>> 32)); - result = 31 * result + uuid.hashCode(); - result = 31 * result + (entityId != null ? entityId.hashCode() : 0); - result = 31 * result + (key != null ? key.hashCode() : 0); - result = 31 * result + (quantity != null ? quantity.hashCode() : 0); - result = 31 * result + (revenue != null ? revenue.hashCode() : 0); - result = 31 * result + (tags != null ? tags.hashCode() : 0); - result = 31 * result + type.hashCode(); - result = 31 * result + (value != null ? value.hashCode() : 0); - return result; - } - -} diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Snapshot.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Snapshot.java index 558f7517e..c2ce5a3da 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Snapshot.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Snapshot.java @@ -1,17 +1,33 @@ +/** + * + * Copyright 2017-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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.optimizely.ab.event.internal.payload; import java.util.List; public class Snapshot { - List decisions; - List events; + List decisions; + List events; Long activationTimestamp; public Snapshot() { } - public Snapshot(List decisions, List events) { + public Snapshot(List decisions, List events) { this.decisions = decisions; this.events = events; this.activationTimestamp = null; @@ -25,19 +41,19 @@ public void setActivationTimestamp(Long activationTimestamp) { this.activationTimestamp = activationTimestamp; } - public List getDecisions() { + public List getDecisions() { return decisions; } - public void setDecisions(List decisions) { + public void setDecisions(List decisions) { this.decisions = decisions; } - public List getEvents() { + public List getEvents() { return events; } - public void setEvents(List events) { + public void setEvents(List events) { this.events = events; } diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/GsonSerializer.java b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/GsonSerializer.java index 203f50852..d7668de58 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/GsonSerializer.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/GsonSerializer.java @@ -18,8 +18,6 @@ import com.google.gson.Gson; -import com.optimizely.ab.event.internal.payload.Event; - class GsonSerializer implements Serializer { private Gson gson = new Gson(); diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSerializer.java b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSerializer.java index 0ce0fd920..6b87ca408 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSerializer.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSerializer.java @@ -16,7 +16,6 @@ */ package com.optimizely.ab.event.internal.serializer; -import com.optimizely.ab.event.internal.payload.Event; import org.json.JSONObject; class JsonSerializer implements Serializer { diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSimpleSerializer.java b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSimpleSerializer.java index e8fae248d..8041f3126 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSimpleSerializer.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSimpleSerializer.java @@ -17,10 +17,10 @@ package com.optimizely.ab.event.internal.serializer; import com.optimizely.ab.event.internal.payload.Attribute; -import com.optimizely.ab.event.internal.payload.DecisionV3; +import com.optimizely.ab.event.internal.payload.Decision; import com.optimizely.ab.event.internal.payload.EventBatch; -import com.optimizely.ab.event.internal.payload.EventV3; +import com.optimizely.ab.event.internal.payload.Event; import com.optimizely.ab.event.internal.payload.Snapshot; import com.optimizely.ab.event.internal.payload.Visitor; import org.json.simple.JSONArray; @@ -96,17 +96,17 @@ private JSONObject serializeSnapshot(Snapshot snapshot) { return jsonObject; } - private JSONArray serializeEvents(List eventV3s) { + private JSONArray serializeEvents(List eventV3s) { JSONArray jsonArray = new JSONArray(); - for (EventV3 eventV3 : eventV3s) { + for (Event eventV3 : eventV3s) { jsonArray.add(serializeEvent(eventV3)); } return jsonArray; } - private JSONObject serializeEvent(EventV3 eventV3) { + private JSONObject serializeEvent(Event eventV3) { JSONObject jsonObject = new JSONObject(); jsonObject.put("timestamp",eventV3.getTimestamp()); @@ -135,7 +135,7 @@ private JSONArray serializeTags(Map tags) { return jsonArray; } - private JSONObject serializeDecision(DecisionV3 decision) { + private JSONObject serializeDecision(Decision decision) { JSONObject jsonObject = new JSONObject(); jsonObject.put("campaignId", decision.getCampaignId()); if (decision.getExperimentId() != null) jsonObject.put("experimentId", decision.getExperimentId()); @@ -164,9 +164,9 @@ private JSONObject serializeFeature(Attribute feature) { return jsonObject; } - private JSONArray serializeDecisions(List layerStates) { + private JSONArray serializeDecisions(List layerStates) { JSONArray jsonArray = new JSONArray(); - for (DecisionV3 layerState : layerStates) { + for (Decision layerState : layerStates) { jsonArray.add(serializeDecision(layerState)); } diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/Serializer.java b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/Serializer.java index ca3cd9934..bc257fa64 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/Serializer.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/Serializer.java @@ -16,8 +16,6 @@ */ package com.optimizely.ab.event.internal.serializer; -import com.optimizely.ab.event.internal.payload.Event; - public interface Serializer { String serialize(T payload) throws SerializationException; } diff --git a/core-api/src/test/java/com/optimizely/ab/OptimizelyBuilderTest.java b/core-api/src/test/java/com/optimizely/ab/OptimizelyBuilderTest.java index 5b83d7cf0..4a48c7c4b 100644 --- a/core-api/src/test/java/com/optimizely/ab/OptimizelyBuilderTest.java +++ b/core-api/src/test/java/com/optimizely/ab/OptimizelyBuilderTest.java @@ -25,7 +25,7 @@ import com.optimizely.ab.event.EventHandler; import com.optimizely.ab.event.internal.BuildVersionInfo; import com.optimizely.ab.event.internal.EventBuilder; -import com.optimizely.ab.event.internal.payload.Event.ClientEngine; +import com.optimizely.ab.event.internal.payload.EventBatch.ClientEngine; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.junit.BeforeClass; import org.junit.Rule; diff --git a/core-api/src/test/java/com/optimizely/ab/OptimizelyTest.java b/core-api/src/test/java/com/optimizely/ab/OptimizelyTest.java index 597977b19..96e929690 100644 --- a/core-api/src/test/java/com/optimizely/ab/OptimizelyTest.java +++ b/core-api/src/test/java/com/optimizely/ab/OptimizelyTest.java @@ -100,7 +100,7 @@ import static com.optimizely.ab.config.ValidProjectConfigV4.VARIATION_MULTIVARIATE_EXPERIMENT_GRED; import static com.optimizely.ab.config.ValidProjectConfigV4.VARIATION_MULTIVARIATE_EXPERIMENT_GRED_KEY; import static com.optimizely.ab.event.LogEvent.RequestMethod; -import static com.optimizely.ab.event.internal.EventBuilderV3Test.createExperimentVariationMap; +import static com.optimizely.ab.event.internal.EventBuilderTest.createExperimentVariationMap; import static java.util.Arrays.asList; import static junit.framework.TestCase.assertTrue; import static org.hamcrest.CoreMatchers.is; @@ -1131,7 +1131,7 @@ public void trackEventEndToEndForced() throws Exception { } List allExperiments = new ArrayList(); allExperiments.add(config.getExperiments().get(0)); - EventBuilder eventBuilderV2 = new EventBuilder(); + EventBuilder eventBuilder = new EventBuilder(); DecisionService spyDecisionService = spy(new DecisionService(mockBucketer, mockErrorHandler, config, @@ -1139,7 +1139,7 @@ public void trackEventEndToEndForced() throws Exception { Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) .withDecisionService(spyDecisionService) - .withEventBuilder(eventBuilderV2) + .withEventBuilder(eventBuilder) .withConfig(noAudienceProjectConfig) .withErrorHandler(mockErrorHandler) .build(); @@ -1205,7 +1205,7 @@ public void trackEventEndToEnd() throws Exception { } List allExperiments = config.getExperiments(); - EventBuilder eventBuilderV2 = new EventBuilder(); + EventBuilder eventBuilder = new EventBuilder(); DecisionService spyDecisionService = spy(new DecisionService(mockBucketer, mockErrorHandler, config, @@ -1213,7 +1213,7 @@ public void trackEventEndToEnd() throws Exception { Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) .withDecisionService(spyDecisionService) - .withEventBuilder(eventBuilderV2) + .withEventBuilder(eventBuilder) .withConfig(noAudienceProjectConfig) .withErrorHandler(mockErrorHandler) .build(); diff --git a/core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderV3Test.java b/core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderTest.java similarity index 93% rename from core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderV3Test.java rename to core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderTest.java index 0197204ce..e88a90a64 100644 --- a/core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderV3Test.java +++ b/core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderTest.java @@ -12,8 +12,7 @@ import com.optimizely.ab.error.ErrorHandler; import com.optimizely.ab.error.NoOpErrorHandler; import com.optimizely.ab.event.LogEvent; -import com.optimizely.ab.event.internal.payload.DecisionV3; -import com.optimizely.ab.event.internal.payload.Event; +import com.optimizely.ab.event.internal.payload.Decision; import com.optimizely.ab.event.internal.payload.EventBatch; import com.optimizely.ab.internal.ReservedEventKey; import org.junit.Test; @@ -53,7 +52,7 @@ import static org.mockito.Mockito.when; @RunWith(Parameterized.class) -public class EventBuilderV3Test { +public class EventBuilderTest { @Parameterized.Parameters public static Collection data() throws IOException { @@ -76,8 +75,8 @@ public static Collection data() throws IOException { private int datafileVersion; private ProjectConfig validProjectConfig; - public EventBuilderV3Test(int datafileVersion, - ProjectConfig validProjectConfig) { + public EventBuilderTest(int datafileVersion, + ProjectConfig validProjectConfig) { this.datafileVersion = datafileVersion; this.validProjectConfig = validProjectConfig; } @@ -94,7 +93,7 @@ public void createImpressionEvent() throws Exception { Attribute attribute = projectConfig.getAttributes().get(0); String userId = "userId"; Map attributeMap = Collections.singletonMap(attribute.getKey(), "value"); - DecisionV3 expectedDecision = new DecisionV3(activatedExperiment.getLayerId(), activatedExperiment.getId(), bucketedVariation.getId(), false); + Decision expectedDecision = new Decision(activatedExperiment.getLayerId(), activatedExperiment.getId(), bucketedVariation.getId(), false); com.optimizely.ab.event.internal.payload.Attribute feature = new com.optimizely.ab.event.internal.payload.Attribute(attribute.getId(), attribute.getKey(), com.optimizely.ab.event.internal.payload.Attribute.CUSTOM_ATTRIBUTE_TYPE, "value"); @@ -119,7 +118,7 @@ public void createImpressionEvent() throws Exception { is(activatedExperiment.getLayerId())); assertThat(eventBatch.getAccountId(), is(projectConfig.getAccountId())); assertThat(eventBatch.getVisitors().get(0).getAttributes(), is(expectedUserFeatures)); - assertThat(eventBatch.getClientName(), is(Event.ClientEngine.JAVA_SDK.getClientEngineValue())); + assertThat(eventBatch.getClientName(), is(EventBatch.ClientEngine.JAVA_SDK.getClientEngineValue())); assertThat(eventBatch.getClientVersion(), is(BuildVersionInfo.VERSION)); assertNull(eventBatch.getVisitors().get(0).getSessionId()); } @@ -154,7 +153,7 @@ public void createImpressionEventIgnoresUnknownAttributes() throws Exception { */ @Test public void createImpressionEventAndroidClientEngineClientVersion() throws Exception { - EventBuilder builder = new EventBuilder(Event.ClientEngine.ANDROID_SDK, "0.0.0"); + EventBuilder builder = new EventBuilder(EventBatch.ClientEngine.ANDROID_SDK, "0.0.0"); ProjectConfig projectConfig = validProjectConfigV2(); Experiment activatedExperiment = projectConfig.getExperiments().get(0); Variation bucketedVariation = activatedExperiment.getVariations().get(0); @@ -166,7 +165,7 @@ public void createImpressionEventAndroidClientEngineClientVersion() throws Excep userId, attributeMap); EventBatch impression = gson.fromJson(impressionEvent.getBody(), EventBatch.class); - assertThat(impression.getClientName(), is(Event.ClientEngine.ANDROID_SDK.getClientEngineValue())); + assertThat(impression.getClientName(), is(EventBatch.ClientEngine.ANDROID_SDK.getClientEngineValue())); assertThat(impression.getClientVersion(), is("0.0.0")); } @@ -177,7 +176,7 @@ public void createImpressionEventAndroidClientEngineClientVersion() throws Excep @Test public void createImpressionEventAndroidTVClientEngineClientVersion() throws Exception { String clientVersion = "0.0.0"; - EventBuilder builder = new EventBuilder(Event.ClientEngine.ANDROID_TV_SDK, clientVersion); + EventBuilder builder = new EventBuilder(EventBatch.ClientEngine.ANDROID_TV_SDK, clientVersion); ProjectConfig projectConfig = validProjectConfigV2(); Experiment activatedExperiment = projectConfig.getExperiments().get(0); Variation bucketedVariation = activatedExperiment.getVariations().get(0); @@ -189,7 +188,7 @@ public void createImpressionEventAndroidTVClientEngineClientVersion() throws Exc userId, attributeMap); EventBatch impression = gson.fromJson(impressionEvent.getBody(), EventBatch.class); - assertThat(impression.getClientName(), is(Event.ClientEngine.ANDROID_TV_SDK.getClientEngineValue())); + assertThat(impression.getClientName(), is(EventBatch.ClientEngine.ANDROID_TV_SDK.getClientEngineValue())); assertThat(impression.getClientVersion(), is(clientVersion)); } @@ -240,11 +239,11 @@ public void createConversionEvent() throws Exception { attributeMap, eventTagMap); - List expectedDecisions = new ArrayList(); + List expectedDecisions = new ArrayList(); for (Experiment experiment : experimentsForEventKey) { if (experiment.isRunning()) { - DecisionV3 layerState = new DecisionV3(experiment.getLayerId(), experiment.getId(), + Decision layerState = new Decision(experiment.getLayerId(), experiment.getId(), experiment.getVariations().get(0).getId(), false); expectedDecisions.add(layerState); } @@ -276,12 +275,12 @@ public void createConversionEvent() throws Exception { assertTrue(conversion.getVisitors().get(0).getSnapshots().get(0).getEvents().get(0).getTags().equals(eventTagMap)); assertFalse(conversion.getVisitors().get(0).getSnapshots().get(0).getDecisions().get(0).getIsCampaignHoldback()); assertEquals(conversion.getAnonymizeIp(), validProjectConfig.getAnonymizeIP()); - assertEquals(conversion.getClientName(), Event.ClientEngine.JAVA_SDK.getClientEngineValue()); + assertEquals(conversion.getClientName(), EventBatch.ClientEngine.JAVA_SDK.getClientEngineValue()); assertEquals(conversion.getClientVersion(), BuildVersionInfo.VERSION); } /** - * Verify that "revenue" and "value" are properly recorded in a conversion request as {@link com.optimizely.ab.event.internal.payload.EventV3} objects. + * Verify that "revenue" and "value" are properly recorded in a conversion request as {@link com.optimizely.ab.event.internal.payload.Event} objects. * "revenue" is fixed-point and "value" is floating-point. */ @Test @@ -432,7 +431,7 @@ public void createConversionEventExperimentStatusPrecedesForcedVariation() { */ @Test public void createConversionEventAndroidClientEngineClientVersion() throws Exception { - EventBuilder builder = new EventBuilder(Event.ClientEngine.ANDROID_SDK, "0.0.0"); + EventBuilder builder = new EventBuilder(EventBatch.ClientEngine.ANDROID_SDK, "0.0.0"); Attribute attribute = validProjectConfig.getAttributes().get(0); EventType eventType = validProjectConfig.getEventTypes().get(0); @@ -466,7 +465,7 @@ public void createConversionEventAndroidClientEngineClientVersion() throws Excep EventBatch conversion = gson.fromJson(conversionEvent.getBody(), EventBatch.class); - assertThat(conversion.getClientName(), is(Event.ClientEngine.ANDROID_SDK.getClientEngineValue())); + assertThat(conversion.getClientName(), is(EventBatch.ClientEngine.ANDROID_SDK.getClientEngineValue())); assertThat(conversion.getClientVersion(), is("0.0.0")); } @@ -477,7 +476,7 @@ public void createConversionEventAndroidClientEngineClientVersion() throws Excep @Test public void createConversionEventAndroidTVClientEngineClientVersion() throws Exception { String clientVersion = "0.0.0"; - EventBuilder builder = new EventBuilder(Event.ClientEngine.ANDROID_TV_SDK, clientVersion); + EventBuilder builder = new EventBuilder(EventBatch.ClientEngine.ANDROID_TV_SDK, clientVersion); ProjectConfig projectConfig = validProjectConfigV2(); Attribute attribute = projectConfig.getAttributes().get(0); EventType eventType = projectConfig.getEventTypes().get(0); @@ -506,7 +505,7 @@ public void createConversionEventAndroidTVClientEngineClientVersion() throws Exc Collections.emptyMap()); EventBatch conversion = gson.fromJson(conversionEvent.getBody(), EventBatch.class); - assertThat(conversion.getClientName(), is(Event.ClientEngine.ANDROID_TV_SDK.getClientEngineValue())); + assertThat(conversion.getClientName(), is(EventBatch.ClientEngine.ANDROID_TV_SDK.getClientEngineValue())); assertThat(conversion.getClientVersion(), is(clientVersion)); } @@ -549,7 +548,7 @@ public void createImpressionEventWithBucketingId() throws Exception { attributeMap.put(com.optimizely.ab.bucketing.DecisionService.BUCKETING_ATTRIBUTE, "variation"); - DecisionV3 expectedDecision = new DecisionV3(activatedExperiment.getLayerId(), activatedExperiment.getId(), bucketedVariation.getId(), false); + Decision expectedDecision = new Decision(activatedExperiment.getLayerId(), activatedExperiment.getId(), bucketedVariation.getId(), false); com.optimizely.ab.event.internal.payload.Attribute feature = new com.optimizely.ab.event.internal.payload.Attribute(attribute.getId(), attribute.getKey(), com.optimizely.ab.event.internal.payload.Attribute.CUSTOM_ATTRIBUTE_TYPE, @@ -580,7 +579,7 @@ public void createImpressionEventWithBucketingId() throws Exception { assertThat(impression.getAccountId(), is(projectConfig.getAccountId())); assertThat(impression.getVisitors().get(0).getAttributes(), is(expectedUserFeatures)); - assertThat(impression.getClientName(), is(Event.ClientEngine.JAVA_SDK.getClientEngineValue())); + assertThat(impression.getClientName(), is(EventBatch.ClientEngine.JAVA_SDK.getClientEngineValue())); assertThat(impression.getClientVersion(), is(BuildVersionInfo.VERSION)); assertNull(impression.getVisitors().get(0).getSessionId()); } @@ -636,13 +635,13 @@ public void createConversionEventWithBucketingId() throws Exception { attributeMap, eventTagMap); - List expectedDecisions = new ArrayList(); + List expectedDecisions = new ArrayList(); for (Experiment experiment : experimentsForEventKey) { if (experiment.isRunning()) { - DecisionV3 decisionV3 = new DecisionV3(experiment.getLayerId(), experiment.getId(), + Decision decision = new Decision(experiment.getLayerId(), experiment.getId(), experiment.getVariations().get(0).getId(), false); - expectedDecisions.add(decisionV3); + expectedDecisions.add(decision); } } @@ -676,7 +675,7 @@ public void createConversionEventWithBucketingId() throws Exception { assertTrue(conversion.getVisitors().get(0).getSnapshots().get(0).getEvents().get(0).getTags().equals(eventTagMap)); assertFalse(conversion.getVisitors().get(0).getSnapshots().get(0).getDecisions().get(0).getIsCampaignHoldback()); assertEquals(conversion.getAnonymizeIp(), validProjectConfig.getAnonymizeIP()); - assertEquals(conversion.getClientName(), Event.ClientEngine.JAVA_SDK.getClientEngineValue()); + assertEquals(conversion.getClientName(), EventBatch.ClientEngine.JAVA_SDK.getClientEngineValue()); assertEquals(conversion.getClientVersion(), BuildVersionInfo.VERSION); } diff --git a/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JacksonSerializerTest.java b/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JacksonSerializerTest.java index aa0517523..41b1eae95 100644 --- a/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JacksonSerializerTest.java +++ b/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JacksonSerializerTest.java @@ -18,7 +18,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; -import com.optimizely.ab.event.internal.EventBuilderV3Test; import com.optimizely.ab.event.internal.payload.EventBatch; import org.junit.Test; diff --git a/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/SerializerTestUtils.java b/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/SerializerTestUtils.java index 78525f4e3..0e46737d8 100644 --- a/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/SerializerTestUtils.java +++ b/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/SerializerTestUtils.java @@ -19,18 +19,16 @@ import com.google.common.base.Charsets; import com.google.common.io.Resources; import com.optimizely.ab.event.internal.payload.Attribute; -import com.optimizely.ab.event.internal.payload.DecisionV3; +import com.optimizely.ab.event.internal.payload.Decision; import com.optimizely.ab.event.internal.payload.EventBatch; -import com.optimizely.ab.event.internal.payload.EventV3; +import com.optimizely.ab.event.internal.payload.Event; import com.optimizely.ab.event.internal.payload.Snapshot; import com.optimizely.ab.event.internal.payload.Visitor; import java.io.IOException; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; public class SerializerTestUtils { @@ -45,7 +43,7 @@ public class SerializerTestUtils { private static final String experimentId = "5"; private static final String sessionId = "sessionid"; private static final String revision = "1"; - private static final DecisionV3 decision = new DecisionV3(layerId, experimentId, variationId, isLayerHoldback); + private static final Decision decision = new Decision(layerId, experimentId, variationId, isLayerHoldback); private static final String featureId = "6"; private static final String featureName = "testfeature"; @@ -62,7 +60,7 @@ public class SerializerTestUtils { private static final String eventMetricName = "revenue"; private static final long eventMetricValue = 5000L; - private static final List events = Collections.singletonList(new EventV3(timestamp, + private static final List events = Collections.singletonList(new Event(timestamp, "uuid", eventEntityId, eventName, null, 5000L, null, eventName, null)); static EventBatch generateImpression() { From 800506c1a562b54dd9ffc80a8714f24975102924 Mon Sep 17 00:00:00 2001 From: Thomas Zurkan Date: Thu, 25 Jan 2018 13:41:50 -0800 Subject: [PATCH 6/9] add license at top of file. --- .../ab/event/internal/EventBuilder.java | 17 ++++++++++++++++- .../ab/event/internal/payload/Visitor.java | 16 ++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilder.java b/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilder.java index d65346361..92c5da224 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilder.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilder.java @@ -1,3 +1,19 @@ +/** + * + * Copyright 2017-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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.optimizely.ab.event.internal; import com.optimizely.ab.annotations.VisibleForTesting; @@ -18,7 +34,6 @@ import com.optimizely.ab.internal.EventTagUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.Arrays; diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Visitor.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Visitor.java index 15f07cb30..c6d4e021f 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Visitor.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Visitor.java @@ -1,3 +1,19 @@ +/** + * + * Copyright 2017-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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.optimizely.ab.event.internal.payload; import java.util.List; From 21afc63234cad367037794ccda753ecc6681adc3 Mon Sep 17 00:00:00 2001 From: Thomas Zurkan Date: Thu, 25 Jan 2018 14:33:16 -0800 Subject: [PATCH 7/9] updated from Mike's comments. All headers for new files say 2018. --- .../ab/event/internal/EventBuilder.java | 16 ++++++------- .../ab/event/internal/payload/Attribute.java | 2 +- .../ab/event/internal/payload/Decision.java | 2 +- .../ab/event/internal/payload/Event.java | 24 +++++++++---------- .../ab/event/internal/payload/EventBatch.java | 2 +- .../ab/event/internal/payload/Snapshot.java | 2 +- .../ab/event/internal/payload/Visitor.java | 2 +- 7 files changed, 25 insertions(+), 25 deletions(-) diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilder.java b/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilder.java index 92c5da224..87f2d3b76 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilder.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilder.java @@ -1,6 +1,6 @@ /** * - * Copyright 2017-2018, Optimizely and contributors + * Copyright 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. @@ -73,9 +73,9 @@ public LogEvent createImpressionEvent(@Nonnull ProjectConfig projectConfig, Decision decision = new Decision(activatedExperiment.getLayerId(), activatedExperiment.getId(), variation.getId(), false); - Event eventV3 = new Event(System.currentTimeMillis(),UUID.randomUUID().toString(), activatedExperiment.getLayerId(), + Event impressionEvent = new Event(System.currentTimeMillis(),UUID.randomUUID().toString(), activatedExperiment.getLayerId(), ACTIVATE_EVENT_KEY, null, null, null, ACTIVATE_EVENT_KEY, null); - Snapshot snapshot = new Snapshot(Arrays.asList(decision), Arrays.asList(eventV3)); + Snapshot snapshot = new Snapshot(Arrays.asList(decision), Arrays.asList(impressionEvent)); Visitor visitor = new Visitor(userId, null, buildAttributeList(projectConfig, attributes), Arrays.asList(snapshot)); List visitors = Arrays.asList(visitor); @@ -104,9 +104,9 @@ public LogEvent createConversionEvent(@Nonnull ProjectConfig projectConfig, EventType eventType = projectConfig.getEventNameMapping().get(eventName); - Event eventV3 = new Event(System.currentTimeMillis(),UUID.randomUUID().toString(), eventType.getId(), + Event conversionEvent = new Event(System.currentTimeMillis(),UUID.randomUUID().toString(), eventType.getId(), eventType.getKey(), null, EventTagUtils.getRevenueValue(eventTags), eventTags, eventType.getKey(), EventTagUtils.getNumericValue(eventTags)); - Snapshot snapshot = new Snapshot(decisions, Arrays.asList(eventV3)); + Snapshot snapshot = new Snapshot(decisions, Arrays.asList(conversionEvent)); Visitor visitor = new Visitor(userId, null, buildAttributeList(projectConfig, attributes), Arrays.asList(snapshot)); List visitors = Arrays.asList(visitor); @@ -116,7 +116,7 @@ public LogEvent createConversionEvent(@Nonnull ProjectConfig projectConfig, } private List buildAttributeList(ProjectConfig projectConfig, Map attributes) { - List attributes1 = new ArrayList(); + List attributesList = new ArrayList(); Map attributeMap = projectConfig.getAttributeKeyMapping(); for (Map.Entry entry : attributes.entrySet()) { @@ -129,9 +129,9 @@ private List buildAttributeList(ProjectConfig projectConfig, Map Date: Thu, 25 Jan 2018 14:36:52 -0800 Subject: [PATCH 8/9] update license date --- .../java/com/optimizely/ab/event/internal/EventBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilder.java b/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilder.java index 87f2d3b76..20206d343 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilder.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilder.java @@ -1,6 +1,6 @@ /** * - * Copyright 2018, 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. From 76ad5a60797fe45170b5808bf42cc3455e043fb2 Mon Sep 17 00:00:00 2001 From: Thomas Zurkan Date: Thu, 25 Jan 2018 16:28:12 -0800 Subject: [PATCH 9/9] update to use snake case for event sending --- .../ab/event/internal/payload/Attribute.java | 3 ++ .../ab/event/internal/payload/Decision.java | 6 ++++ .../ab/event/internal/payload/Event.java | 3 ++ .../ab/event/internal/payload/EventBatch.java | 6 ++++ .../ab/event/internal/payload/Snapshot.java | 3 ++ .../ab/event/internal/payload/Visitor.java | 4 +++ .../internal/serializer/GsonSerializer.java | 6 +++- .../serializer/JacksonSerializer.java | 5 ++- .../internal/serializer/JsonSerializer.java | 21 +++++++++++- .../serializer/JsonSimpleSerializer.java | 32 +++++++++---------- .../ab/event/internal/EventBuilderTest.java | 6 +++- .../serializer/GsonSerializerTest.java | 6 +++- .../serializer/JacksonSerializerTest.java | 6 +++- .../serializer/conversion-session-id.json | 26 +++++++-------- .../test/resources/serializer/conversion.json | 24 +++++++------- .../serializer/impression-session-id.json | 26 +++++++-------- .../test/resources/serializer/impression.json | 24 +++++++------- 17 files changed, 135 insertions(+), 72 deletions(-) diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Attribute.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Attribute.java index 1dff6b84c..71e93adba 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Attribute.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Attribute.java @@ -16,10 +16,13 @@ */ package com.optimizely.ab.event.internal.payload; +import com.fasterxml.jackson.annotation.JsonProperty; + public class Attribute { public static final String CUSTOM_ATTRIBUTE_TYPE = "custom"; public static final String CUSTOM_EVENT_TYPE = "custom"; + @JsonProperty("entity_id") String entityId; String key; String type; diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Decision.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Decision.java index 3f0e16198..8ffa8d23c 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Decision.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Decision.java @@ -16,10 +16,16 @@ */ package com.optimizely.ab.event.internal.payload; +import com.fasterxml.jackson.annotation.JsonProperty; + public class Decision { + @JsonProperty("campaign_id") String campaignId; + @JsonProperty("experiment_id") String experimentId; + @JsonProperty("variation_id") String variationId; + @JsonProperty("is_campaign_holdback") boolean isCampaignHoldback; public Decision(String campaignId, String experimentId, String variationId, boolean isCampaignHoldback) { diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Event.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Event.java index 1671917ef..9ce40541b 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Event.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Event.java @@ -16,11 +16,14 @@ */ package com.optimizely.ab.event.internal.payload; +import com.fasterxml.jackson.annotation.JsonProperty; + import java.util.Map; public class Event { long timestamp; String uuid; + @JsonProperty("entity_id") String entityId; String key; Number quantity; diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventBatch.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventBatch.java index a2e11cdf8..9cee5ead4 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventBatch.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventBatch.java @@ -16,6 +16,7 @@ */ package com.optimizely.ab.event.internal.payload; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonValue; import com.optimizely.ab.event.internal.BuildVersionInfo; @@ -39,11 +40,16 @@ public String getClientEngineValue() { } } + @JsonProperty("account_id") String accountId; List visitors; + @JsonProperty("anonymize_ip") Boolean anonymizeIp; + @JsonProperty("client_name") String clientName = ClientEngine.JAVA_SDK.getClientEngineValue(); + @JsonProperty("client_version") String clientVersion = BuildVersionInfo.VERSION; + @JsonProperty("project_id") String projectId; String revision; diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Snapshot.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Snapshot.java index 26ccf13aa..ee16f6984 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Snapshot.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Snapshot.java @@ -16,11 +16,14 @@ */ package com.optimizely.ab.event.internal.payload; +import com.fasterxml.jackson.annotation.JsonProperty; + import java.util.List; public class Snapshot { List decisions; List events; + @JsonProperty("activation_timestamp") Long activationTimestamp; public Snapshot() { diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Visitor.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Visitor.java index 941d70aaf..0eb815dbb 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Visitor.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Visitor.java @@ -16,10 +16,14 @@ */ package com.optimizely.ab.event.internal.payload; +import com.fasterxml.jackson.annotation.JsonProperty; + import java.util.List; public class Visitor { + @JsonProperty("visitor_id") String visitorId; + @JsonProperty("session_id") String sessionId; List attributes; List snapshots; diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/GsonSerializer.java b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/GsonSerializer.java index d7668de58..802e02515 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/GsonSerializer.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/GsonSerializer.java @@ -16,11 +16,15 @@ */ package com.optimizely.ab.event.internal.serializer; +import com.google.gson.FieldNamingPolicy; import com.google.gson.Gson; +import com.google.gson.GsonBuilder; class GsonSerializer implements Serializer { - private Gson gson = new Gson(); + private Gson gson = new GsonBuilder() + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .create(); public String serialize(T payload) { return gson.toJson(payload); diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JacksonSerializer.java b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JacksonSerializer.java index 322c51c4f..8a42908ce 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JacksonSerializer.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JacksonSerializer.java @@ -19,10 +19,13 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; class JacksonSerializer implements Serializer { - private final ObjectMapper mapper = new ObjectMapper(); + private final ObjectMapper mapper = + new ObjectMapper().setPropertyNamingStrategy( + PropertyNamingStrategy.SNAKE_CASE); public String serialize(T payload) { mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSerializer.java b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSerializer.java index 6b87ca408..53c8d478a 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSerializer.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSerializer.java @@ -22,6 +22,25 @@ class JsonSerializer implements Serializer { public String serialize(T payload) { JSONObject payloadJsonObject = new JSONObject(payload); - return payloadJsonObject.toString(); + String jsonResponse = payloadJsonObject.toString(); + StringBuilder stringBuilder = new StringBuilder(); + + for (int i = 0; i < jsonResponse.length(); i ++) { + Character ch = jsonResponse.charAt(i); + Character nextChar = null; + if (i +1 < jsonResponse.length()) { + nextChar = jsonResponse.charAt(i+1); + } + if ((Character.isLetter(ch) || Character.isDigit(ch)) && Character.isUpperCase(ch) && + ((Character.isLetter(nextChar) || Character.isDigit(nextChar)))) { + stringBuilder.append('_'); + stringBuilder.append(Character.toLowerCase(ch)); + } + else { + stringBuilder.append(ch); + } + } + + return stringBuilder.toString(); } } diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSimpleSerializer.java b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSimpleSerializer.java index 8041f3126..ea47970f3 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSimpleSerializer.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSimpleSerializer.java @@ -41,12 +41,12 @@ public String serialize(T payload) { private JSONObject serializeEventBatch(EventBatch eventBatch) { JSONObject jsonObject = new JSONObject(); - jsonObject.put("accountId", eventBatch.getAccountId()); + jsonObject.put("account_id", eventBatch.getAccountId()); jsonObject.put("visitors", serializeVisitors(eventBatch.getVisitors())); - if (eventBatch.getAnonymizeIp() != null) jsonObject.put("anonymizeIp", eventBatch.getAnonymizeIp()); - if (eventBatch.getClientName() != null) jsonObject.put("clientName", eventBatch.getClientName()); - if (eventBatch.getClientVersion() != null) jsonObject.put("clientVersion", eventBatch.getClientVersion()); - if (eventBatch.getProjectId() != null) jsonObject.put("projectId", eventBatch.getProjectId()); + if (eventBatch.getAnonymizeIp() != null) jsonObject.put("anonymize_ip", eventBatch.getAnonymizeIp()); + if (eventBatch.getClientName() != null) jsonObject.put("client_name", eventBatch.getClientName()); + if (eventBatch.getClientVersion() != null) jsonObject.put("client_version", eventBatch.getClientVersion()); + if (eventBatch.getProjectId() != null) jsonObject.put("project_id", eventBatch.getProjectId()); if (eventBatch.getRevision() != null) jsonObject.put("revision", eventBatch.getRevision()); return jsonObject; @@ -66,9 +66,9 @@ private JSONArray serializeVisitors(List visitors) { private JSONObject serializeVisitor(Visitor visitor) { JSONObject jsonObject = new JSONObject(); - jsonObject.put("visitorId", visitor.getVisitorId()); + jsonObject.put("visitor_id", visitor.getVisitorId()); - if (visitor.getSessionId() != null) jsonObject.put("sessionId", visitor.getSessionId()); + if (visitor.getSessionId() != null) jsonObject.put("session_id", visitor.getSessionId()); if (visitor.getAttributes() != null) jsonObject.put("attributes", serializeFeatures(visitor.getAttributes())); @@ -96,11 +96,11 @@ private JSONObject serializeSnapshot(Snapshot snapshot) { return jsonObject; } - private JSONArray serializeEvents(List eventV3s) { + private JSONArray serializeEvents(List events) { JSONArray jsonArray = new JSONArray(); - for (Event eventV3 : eventV3s) { - jsonArray.add(serializeEvent(eventV3)); + for (Event event : events) { + jsonArray.add(serializeEvent(event)); } return jsonArray; @@ -113,7 +113,7 @@ private JSONObject serializeEvent(Event eventV3) { jsonObject.put("uuid",eventV3.getUuid()); jsonObject.put("key", eventV3.getKey()); - if (eventV3.getEntityId() != null) jsonObject.put("entityId",eventV3.getEntityId()); + if (eventV3.getEntityId() != null) jsonObject.put("entity_id",eventV3.getEntityId()); if (eventV3.getQuantity() != null) jsonObject.put("quantity",eventV3.getQuantity()); if (eventV3.getRevenue() != null) jsonObject.put("revenue",eventV3.getRevenue()); if (eventV3.getTags() != null) jsonObject.put("tags",serializeTags(eventV3.getTags())); @@ -137,10 +137,10 @@ private JSONArray serializeTags(Map tags) { private JSONObject serializeDecision(Decision decision) { JSONObject jsonObject = new JSONObject(); - jsonObject.put("campaignId", decision.getCampaignId()); - if (decision.getExperimentId() != null) jsonObject.put("experimentId", decision.getExperimentId()); - if (decision.getVariationId() != null) jsonObject.put("variationId", decision.getVariationId()); - jsonObject.put("isCampaignHoldback", decision.getIsCampaignHoldback()); + jsonObject.put("campaign_id", decision.getCampaignId()); + if (decision.getExperimentId() != null) jsonObject.put("experiment_id", decision.getExperimentId()); + if (decision.getVariationId() != null) jsonObject.put("variation_id", decision.getVariationId()); + jsonObject.put("is_campaign_holdback", decision.getIsCampaignHoldback()); return jsonObject; } @@ -158,7 +158,7 @@ private JSONObject serializeFeature(Attribute feature) { JSONObject jsonObject = new JSONObject(); jsonObject.put("type", feature.getType()); jsonObject.put("value", feature.getValue()); - if (feature.getEntityId() != null) jsonObject.put("entityId", feature.getEntityId()); + if (feature.getEntityId() != null) jsonObject.put("entity_id", feature.getEntityId()); if (feature.getKey() != null) jsonObject.put("key", feature.getKey()); return jsonObject; diff --git a/core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderTest.java b/core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderTest.java index e88a90a64..b976bed29 100644 --- a/core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderTest.java +++ b/core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderTest.java @@ -1,6 +1,8 @@ package com.optimizely.ab.event.internal; +import com.google.gson.FieldNamingPolicy; import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import com.optimizely.ab.bucketing.Bucketer; import com.optimizely.ab.bucketing.DecisionService; import com.optimizely.ab.bucketing.UserProfileService; @@ -68,7 +70,9 @@ public static Collection data() throws IOException { }); } - private Gson gson = new Gson(); + private Gson gson = new GsonBuilder() + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .create(); private EventBuilder builder = new EventBuilder(); private static String userId = "userId"; diff --git a/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/GsonSerializerTest.java b/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/GsonSerializerTest.java index 6c2f1253e..4581225a8 100644 --- a/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/GsonSerializerTest.java +++ b/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/GsonSerializerTest.java @@ -16,8 +16,10 @@ */ package com.optimizely.ab.event.internal.serializer; +import com.google.gson.FieldNamingPolicy; import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import com.optimizely.ab.event.internal.payload.EventBatch; import org.junit.Test; @@ -39,7 +41,9 @@ public class GsonSerializerTest { private GsonSerializer serializer = new GsonSerializer(); - private Gson gson = new Gson(); + private Gson gson = new GsonBuilder() + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .create(); @Test public void serializeImpression() throws IOException { diff --git a/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JacksonSerializerTest.java b/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JacksonSerializerTest.java index 41b1eae95..76776aacd 100644 --- a/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JacksonSerializerTest.java +++ b/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JacksonSerializerTest.java @@ -18,6 +18,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.optimizely.ab.event.internal.payload.EventBatch; import org.junit.Test; @@ -39,7 +40,10 @@ public class JacksonSerializerTest { private JacksonSerializer serializer = new JacksonSerializer(); - private ObjectMapper mapper = new ObjectMapper(); + private ObjectMapper mapper = + new ObjectMapper().setPropertyNamingStrategy( + PropertyNamingStrategy.SNAKE_CASE); + @Test public void serializeImpression() throws IOException { diff --git a/core-api/src/test/resources/serializer/conversion-session-id.json b/core-api/src/test/resources/serializer/conversion-session-id.json index a2480716d..2c1671da1 100644 --- a/core-api/src/test/resources/serializer/conversion-session-id.json +++ b/core-api/src/test/resources/serializer/conversion-session-id.json @@ -1,21 +1,21 @@ { - "accountId": "3", + "account_id": "3", "visitors": [ { "snapshots": [ { "decisions": [ { - "variationId": "4", - "campaignId": "2", - "experimentId": "5", - "isCampaignHoldback": false + "variation_id": "4", + "campaign_id": "2", + "experiment_id": "5", + "is_campaign_holdback": false } ], "events": [ { "revenue": 5000, - "entityId": "7", + "entity_id": "7", "type": "testevent", "uuid": "uuid", "key": "testevent", @@ -26,19 +26,19 @@ ], "attributes": [ { - "entityId": "6", + "entity_id": "6", "type": "custom", "value": "testfeaturevalue", "key": "testfeature" } ], - "visitorId": "testvisitor", - "sessionId": "sessionid" + "visitor_id": "testvisitor", + "session_id": "sessionid" } ], - "clientName": "java-sdk", - "clientVersion": "0.1.1", - "anonymizeIp": true, - "projectId": "1", + "client_name": "java-sdk", + "client_version": "0.1.1", + "anonymize_ip": true, + "project_id": "1", "revision": "1" } \ No newline at end of file diff --git a/core-api/src/test/resources/serializer/conversion.json b/core-api/src/test/resources/serializer/conversion.json index 006ae0a63..2203295dd 100644 --- a/core-api/src/test/resources/serializer/conversion.json +++ b/core-api/src/test/resources/serializer/conversion.json @@ -1,21 +1,21 @@ { - "accountId": "3", + "account_id": "3", "visitors": [ { "snapshots": [ { "decisions": [ { - "variationId": "4", - "campaignId": "2", - "experimentId": "5", - "isCampaignHoldback": false + "variation_id": "4", + "campaign_id": "2", + "experiment_id": "5", + "is_campaign_holdback": false } ], "events": [ { "revenue": 5000, - "entityId": "7", + "entity_id": "7", "type": "testevent", "uuid": "uuid", "key": "testevent", @@ -26,18 +26,18 @@ ], "attributes": [ { - "entityId": "6", + "entity_id": "6", "type": "custom", "value": "testfeaturevalue", "key": "testfeature" } ], - "visitorId": "testvisitor" + "visitor_id": "testvisitor" } ], - "clientName": "java-sdk", - "clientVersion": "0.1.1", - "anonymizeIp": true, - "projectId": "1", + "client_name": "java-sdk", + "client_version": "0.1.1", + "anonymize_ip": true, + "project_id": "1", "revision": "1" } \ No newline at end of file diff --git a/core-api/src/test/resources/serializer/impression-session-id.json b/core-api/src/test/resources/serializer/impression-session-id.json index a2480716d..2c1671da1 100644 --- a/core-api/src/test/resources/serializer/impression-session-id.json +++ b/core-api/src/test/resources/serializer/impression-session-id.json @@ -1,21 +1,21 @@ { - "accountId": "3", + "account_id": "3", "visitors": [ { "snapshots": [ { "decisions": [ { - "variationId": "4", - "campaignId": "2", - "experimentId": "5", - "isCampaignHoldback": false + "variation_id": "4", + "campaign_id": "2", + "experiment_id": "5", + "is_campaign_holdback": false } ], "events": [ { "revenue": 5000, - "entityId": "7", + "entity_id": "7", "type": "testevent", "uuid": "uuid", "key": "testevent", @@ -26,19 +26,19 @@ ], "attributes": [ { - "entityId": "6", + "entity_id": "6", "type": "custom", "value": "testfeaturevalue", "key": "testfeature" } ], - "visitorId": "testvisitor", - "sessionId": "sessionid" + "visitor_id": "testvisitor", + "session_id": "sessionid" } ], - "clientName": "java-sdk", - "clientVersion": "0.1.1", - "anonymizeIp": true, - "projectId": "1", + "client_name": "java-sdk", + "client_version": "0.1.1", + "anonymize_ip": true, + "project_id": "1", "revision": "1" } \ No newline at end of file diff --git a/core-api/src/test/resources/serializer/impression.json b/core-api/src/test/resources/serializer/impression.json index 006ae0a63..2203295dd 100644 --- a/core-api/src/test/resources/serializer/impression.json +++ b/core-api/src/test/resources/serializer/impression.json @@ -1,21 +1,21 @@ { - "accountId": "3", + "account_id": "3", "visitors": [ { "snapshots": [ { "decisions": [ { - "variationId": "4", - "campaignId": "2", - "experimentId": "5", - "isCampaignHoldback": false + "variation_id": "4", + "campaign_id": "2", + "experiment_id": "5", + "is_campaign_holdback": false } ], "events": [ { "revenue": 5000, - "entityId": "7", + "entity_id": "7", "type": "testevent", "uuid": "uuid", "key": "testevent", @@ -26,18 +26,18 @@ ], "attributes": [ { - "entityId": "6", + "entity_id": "6", "type": "custom", "value": "testfeaturevalue", "key": "testfeature" } ], - "visitorId": "testvisitor" + "visitor_id": "testvisitor" } ], - "clientName": "java-sdk", - "clientVersion": "0.1.1", - "anonymizeIp": true, - "projectId": "1", + "client_name": "java-sdk", + "client_version": "0.1.1", + "anonymize_ip": true, + "project_id": "1", "revision": "1" } \ No newline at end of file