Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Audiences, Attributes and Events implementation #438

Merged
merged 65 commits into from
Aug 12, 2021
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
ed55d43
[OASIS-7798] - Add support for attributes and events
The-inside-man Jun 25, 2021
ebf3a7b
Add copyright header to OptimizelyEventTest
The-inside-man Jun 25, 2021
a5eb5c9
Changes made to remove getAttributesMap and getEventsMap as well as c…
The-inside-man Jun 29, 2021
13c5a96
Clean up serializers for default case in
The-inside-man Jul 19, 2021
5b83801
Added spaces before else statement, Changed compare of strings to use…
The-inside-man Jul 19, 2021
0aa4ad4
Correct logic in retrieving name from audienceId
The-inside-man Jul 19, 2021
4fa26dd
Remove uneeded import and exceptions from jsonConfigParser.
The-inside-man Jul 19, 2021
d064f8d
Fix Format fo brackets.
The-inside-man Jul 19, 2021
346b458
Formatting
The-inside-man Jul 19, 2021
3c71919
Simplify method to create audiences list and reduce time complexity.
The-inside-man Jul 19, 2021
5413845
Added null chaeck and spaces where required to follow formatting.:
The-inside-man Jul 20, 2021
0c84679
Updated Null checks as per spotbugsMain
The-inside-man Jul 20, 2021
6ff39a4
Added more null checks to optimizelyConfigService.
The-inside-man Jul 20, 2021
83c32e1
Update experimentsMap with null check on audiences.
The-inside-man Jul 20, 2021
725e8a7
Change Logic for empty list in DeliverRules.
The-inside-man Jul 20, 2021
5f0de11
Added testcases to check all scenarios of audience conditions.
The-inside-man Jul 20, 2021
48a1b42
Update license information.
The-inside-man Jul 20, 2021
b1fd2b5
Remove key from OptimizelyAudience
The-inside-man Jul 21, 2021
f3b3d25
Some cleanup to suggested constructors and multiple methods.
The-inside-man Jul 22, 2021
707a348
Fixed feature constructor in config test.
The-inside-man Jul 22, 2021
1a48ad7
Add scenario 13 to serialize test cases for [and, and] by using an in…
The-inside-man Jul 22, 2021
d0bf1c6
Update Scenario13 comment.
The-inside-man Jul 22, 2021
dddcedf
Added Null check to account for constructor change and no audiences set.
The-inside-man Jul 22, 2021
4b50774
Fix test for featuresMap.
The-inside-man Jul 22, 2021
1da6305
Remove lines for formatting.
The-inside-man Jul 22, 2021
f09fe7a
Fix testcase for featuresMap.
The-inside-man Jul 22, 2021
a8c4aab
Spacing before operator
The-inside-man Jul 23, 2021
7baef32
Move audiencesMap to global to be reused.
The-inside-man Jul 23, 2021
5df424e
Remove unused audiencesMap from getExperimentsMap method.
The-inside-man Jul 29, 2021
011a6d4
Update sdkKey and environmentKey to default to a blank string
The-inside-man Aug 6, 2021
974ea93
remove redundant null check.
The-inside-man Aug 6, 2021
272bbe1
default string for sdkKey and EnvironmentKey testing.
The-inside-man Aug 6, 2021
ec15d25
revert to test FSC
The-inside-man Aug 6, 2021
5fccddf
default sdkKey and environmentKey to empty string
The-inside-man Aug 6, 2021
ff0e4ad
Add double quotes to to_string method for conditions.
The-inside-man Aug 6, 2021
d382c3d
Update check for dummy audience
The-inside-man Aug 6, 2021
d63441c
Update Tests
The-inside-man Aug 6, 2021
ae7b43c
Test using toJson for audience conditions.
The-inside-man Aug 6, 2021
ed83db4
Remove quotes around value in userAttribute.
The-inside-man Aug 6, 2021
9830de1
Null check on attributes in UserAttributes before creating conditions…
The-inside-man Aug 9, 2021
c9d9dca
remove quotes around valueStr for attributes.
The-inside-man Aug 9, 2021
7613fde
Add in quotes and feature Id to Delivery Rules. More to add still fo…
The-inside-man Aug 9, 2021
5bbe711
Remove featureId from delivery rules.
The-inside-man Aug 9, 2021
a7766d0
test
The-inside-man Aug 9, 2021
8e737dd
Add test cases for toJson method
The-inside-man Aug 9, 2021
fe32f57
Initial commit for variationsMap fix.
The-inside-man Aug 9, 2021
c7a9fcb
Test for feature_enabled not displaying
The-inside-man Aug 9, 2021
52220b7
Remove unused isFeatureExperiment.
The-inside-man Aug 9, 2021
3146e9d
Update condition in toJson for userAttribute to check instance for qu…
The-inside-man Aug 9, 2021
fc7fab5
Test for featureEnabled defaulting to Null instead of false.
The-inside-man Aug 9, 2021
4e9c2fa
Revert changes for featureEnabled to null.
The-inside-man Aug 9, 2021
035cca9
Update experiments map to use experimentID rather than Key as experim…
The-inside-man Aug 9, 2021
c2efd87
Update Experiment rules to use Experiment ID.
The-inside-man Aug 9, 2021
39943ed
Test ordering experimentRules.
The-inside-man Aug 9, 2021
3993335
Revert delivery rules.
The-inside-man Aug 9, 2021
c439a2f
Change lookup of feature experiments Map to use experimentsMapByExper…
The-inside-man Aug 9, 2021
48dda4f
Updated getExperimentsMapForFeature to use new id mapping.
The-inside-man Aug 9, 2021
efd1383
trigger CI
The-inside-man Aug 11, 2021
20683c7
Updated change requests. and deprecated warning for OptimizelyFeature…
The-inside-man Aug 12, 2021
d5d5383
Spelling correction.
The-inside-man Aug 12, 2021
41e8e2b
Add warning comment about OptimizelyConfig experimentsMap.
The-inside-man Aug 12, 2021
a877930
Remove deprecated line saying when will be removed.
The-inside-man Aug 12, 2021
f79f199
Update deprecated to follow suite of Android for compatibility.
The-inside-man Aug 12, 2021
04d1fb9
Update tests case to use proper order of assert.
The-inside-man Aug 12, 2021
89722b7
Update deprecated format.
The-inside-man Aug 12, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 100 additions & 3 deletions core-api/src/main/java/com/optimizely/ab/config/Experiment.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@

import com.fasterxml.jackson.annotation.*;
import com.optimizely.ab.annotations.VisibleForTesting;
import com.optimizely.ab.config.audience.AudienceIdCondition;
import com.optimizely.ab.config.audience.Condition;
import com.optimizely.ab.config.audience.*;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
Expand All @@ -44,7 +44,7 @@ public class Experiment implements IdKeyMapped {
private final String groupId;

private final List<String> audienceIds;
private final Condition<AudienceIdCondition> audienceConditions;
private final Condition audienceConditions;
The-inside-man marked this conversation as resolved.
Show resolved Hide resolved
private final List<Variation> variations;
private final List<TrafficAllocation> trafficAllocation;

Expand Down Expand Up @@ -173,6 +173,103 @@ public boolean isLaunched() {
return status.equals(ExperimentStatus.LAUNCHED.toString());
}

public String serializeConditions(Map<String, String> audiencesMap) {

The-inside-man marked this conversation as resolved.
Show resolved Hide resolved
Condition condition = this.audienceConditions;

return this.serialize(condition, audiencesMap);
}

private String getNameFromAudienceId(String audienceId, Map<String, String> audiencesMap) {
String audienceName = audiencesMap.get(audienceId);
return !audienceName.isEmpty() ? "\"" + audienceName + "\"" : "\"" + audienceId + "\"";
}

private String getOperandOrAudienceId(Condition condition, Map<String, String> audiencesMap){
The-inside-man marked this conversation as resolved.
Show resolved Hide resolved
String operand = "";
if(condition != null) {
if (condition instanceof AndCondition) {
operand = "AND";
} else if (condition instanceof NotCondition) {
operand = "NOT";
} else if (condition instanceof OrCondition) {
operand = "OR";
} else if (condition instanceof AudienceIdCondition) {
String audienceName = this.getNameFromAudienceId(((AudienceIdCondition<?>) condition).getAudienceId(),
audiencesMap);
operand = audienceName;
}
}
return operand;
}

public String serialize(Condition condition, Map<String, String> audiencesMap) {
The-inside-man marked this conversation as resolved.
Show resolved Hide resolved
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("");
The-inside-man marked this conversation as resolved.
Show resolved Hide resolved
List<Condition> conditions;

String operand = this.getOperandOrAudienceId(condition, audiencesMap);
switch(operand){
case("AND"):
conditions = ((AndCondition<?>) condition).getConditions();
stringBuilder.append(this.helper(operand, conditions, audiencesMap));
break;
case("OR"):
conditions = ((OrCondition<?>) condition).getConditions();
stringBuilder.append(this.helper(operand, conditions, audiencesMap));
break;
case("NOT"):
stringBuilder.append(operand + " ");
Condition notCondition = ((NotCondition<?>) condition).getCondition();
if(notCondition instanceof AudienceIdCondition) {
stringBuilder.append(serialize(notCondition, audiencesMap));
} else {
stringBuilder.append("(" + serialize(notCondition, audiencesMap)+ ")");
The-inside-man marked this conversation as resolved.
Show resolved Hide resolved
}
break;
default:
stringBuilder.append(operand);
break;
}

return stringBuilder.toString();
}

public String helper(String operand, List<Condition> conditions, Map<String, String> audiencesMap) {
The-inside-man marked this conversation as resolved.
Show resolved Hide resolved
StringBuilder stringBuilder = new StringBuilder();
int ctr = 0;
if(conditions.isEmpty()) {
return "";
}else if(conditions.size() == 1) {
The-inside-man marked this conversation as resolved.
Show resolved Hide resolved
return serialize(conditions.get(0), audiencesMap);
}
if(conditions.size() > 1) {
for (Condition con : conditions) {
ctr++;
The-inside-man marked this conversation as resolved.
Show resolved Hide resolved
if(ctr + 1 <= conditions.size()) {
if(con instanceof AudienceIdCondition) {
String audienceName = this.getNameFromAudienceId(((AudienceIdCondition<?>) con).getAudienceId(),
audiencesMap);
stringBuilder.append( audienceName + " ");
} else {
stringBuilder.append("(" + serialize(con, audiencesMap) + ") ");
}
stringBuilder.append(operand);
stringBuilder.append(" ");
} else {
if(con instanceof AudienceIdCondition) {
String audienceName = this.getNameFromAudienceId(((AudienceIdCondition<?>) con).getAudienceId(),
audiencesMap);
stringBuilder.append(audienceName);
} else {
stringBuilder.append("(" + serialize(con, audiencesMap) + ")");
}
}
}
}
return stringBuilder.toString();
}

@Override
public String toString() {
return "Experiment{" +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,6 @@ protected static <T> Condition parseConditions(Class<T> clazz, ObjectMapper obje
case "and":
condition = new AndCondition(conditions);
break;
case "or":
The-inside-man marked this conversation as resolved.
Show resolved Hide resolved
condition = new OrCondition(conditions);
break;
case "not":
condition = new NotCondition(conditions.isEmpty() ? new NullCondition() : conditions.get(0));
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ private List<Experiment> parseExperiments(JSONArray experimentJson, String group
}

Condition conditions = null;

The-inside-man marked this conversation as resolved.
Show resolved Hide resolved
if (experimentObject.has("audienceConditions")) {
Object jsonCondition = experimentObject.get("audienceConditions");
conditions = ConditionUtils.<AudienceIdCondition>parseConditions(AudienceIdCondition.class, jsonCondition);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,23 +147,7 @@ static public <T> Condition parseConditions(Class<T> clazz, List<Object> rawObje
conditions.add(parseConditions(clazz, obj));
}

Condition condition;
switch (operand) {
case "and":
condition = new AndCondition(conditions);
break;
case "or":
condition = new OrCondition(conditions);
break;
case "not":
condition = new NotCondition(conditions.isEmpty() ? new NullCondition() : conditions.get(0));
break;
default:
condition = new OrCondition(conditions);
break;
}

return condition;
return buildCondition(operand, conditions);
}

static public String operand(Object object) {
Expand Down Expand Up @@ -210,14 +194,15 @@ static public <T> Condition parseConditions(Class<T> clazz, org.json.JSONArray c
conditions.add(parseConditions(clazz, obj));
}

return buildCondition(operand, conditions);
}

private static Condition buildCondition(String operand, List<Condition> conditions) {
Condition condition;
switch (operand) {
case "and":
condition = new AndCondition(conditions);
break;
case "or":
condition = new OrCondition(conditions);
break;
case "not":
condition = new NotCondition(conditions.isEmpty() ? new NullCondition() : conditions.get(0));
break;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/****************************************************************************
* Copyright 2021, Optimizely, Inc. and contributors *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); *
* you may not use this file except in compliance with the License. *
* 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.optimizelyconfig;

import com.optimizely.ab.config.IdKeyMapped;

/**
* Represents the Attribute's map {@link OptimizelyConfig}
*/
public class OptimizelyAttribute implements IdKeyMapped {

private String id;
private String key;

public OptimizelyAttribute(String id,
String key) {
this.id = id;
this.key = key;
}

public String getId() { return id; }

public String getKey() { return key; }

@Override
public boolean equals(Object obj) {
if (obj == null || getClass() != obj.getClass()) return false;
if (obj == this) return true;
OptimizelyAttribute optimizelyAttribute = (OptimizelyAttribute) obj;
return id.equals(optimizelyAttribute.getId()) &&
key.equals(optimizelyAttribute.getKey());
}

@Override
public int hashCode() {
int hash = id.hashCode();
hash = 31 * hash + key.hashCode();
return hash;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/****************************************************************************
* Copyright 2021, Optimizely, Inc. and contributors *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); *
* you may not use this file except in compliance with the License. *
* 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.optimizelyconfig;

import com.optimizely.ab.config.IdKeyMapped;
import com.optimizely.ab.config.audience.Condition;

import java.util.List;

/**
* Represents the Audiences list {@link OptimizelyConfig}
*/
public class OptimizelyAudience implements IdKeyMapped{

private String id;
private String name;
private String conditions;

public OptimizelyAudience(String id,
String name,
String conditions) {
this.id = id;
this.name = name;
this.conditions = conditions;
}

public String getId() { return id; }

public String getName() { return name; }

public String getKey() { return name; }

The-inside-man marked this conversation as resolved.
Show resolved Hide resolved
public String getConditions() { return conditions; }

@Override
public boolean equals(Object obj) {
if (obj == null || getClass() != obj.getClass()) return false;
if (obj == this) return true;
OptimizelyAudience optimizelyAudience = (OptimizelyAudience) obj;
return id.equals(optimizelyAudience.getId()) &&
name.equals(optimizelyAudience.getKey()) &&
conditions.equals(optimizelyAudience.getConditions());
}

@Override
public int hashCode() {
int hash = id.hashCode();
hash = 31 * hash + conditions.hashCode();
return hash;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@


import com.fasterxml.jackson.annotation.JsonInclude;
import com.optimizely.ab.config.Attribute;
import com.optimizely.ab.config.EventType;

import java.util.*;

Expand All @@ -28,6 +30,9 @@ public class OptimizelyConfig {

private Map<String, OptimizelyExperiment> experimentsMap;
private Map<String, OptimizelyFeature> featuresMap;
private List<OptimizelyAttribute> attributes;
private List<OptimizelyEvent> events;
private List<OptimizelyAudience> audiences;
The-inside-man marked this conversation as resolved.
Show resolved Hide resolved
private String revision;
private String sdkKey;
private String environmentKey;
Expand All @@ -53,6 +58,35 @@ public OptimizelyConfig(Map<String, OptimizelyExperiment> experimentsMap,
this.datafile = datafile;
}

public OptimizelyConfig(Map<String, OptimizelyExperiment> experimentsMap,
Map<String, OptimizelyFeature> featuresMap,
String revision, String sdkKey, String environmentKey,
List<OptimizelyAttribute> attributes,
List<OptimizelyEvent> events,
List<OptimizelyAudience> audiences) {
this(experimentsMap, featuresMap, revision, sdkKey, environmentKey, attributes, events, audiences, null);
}

public OptimizelyConfig(Map<String, OptimizelyExperiment> experimentsMap,
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we remove all 3 other constructors above and keep this one only? It's not supposed to open to clients, and internal use only.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No problem, will remove and retest to make sure nothing gets broken.

Copy link
Contributor

Choose a reason for hiding this comment

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

in case we are supposed to use it internally, can we set access modifier accordingly?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Lets chat offline on this, just a couple questions.

Map<String, OptimizelyFeature> featuresMap,
String revision,
String sdkKey,
String environmentKey,
List<OptimizelyAttribute> attributes,
List<OptimizelyEvent> events,
List<OptimizelyAudience> audiences,
String datafile) {
this.experimentsMap = experimentsMap;
this.featuresMap = featuresMap;
this.revision = revision;
this.sdkKey = sdkKey;
this.environmentKey = environmentKey;
this.attributes = attributes;
this.events = events;
this.audiences = audiences;
this.datafile = datafile;
}

public Map<String, OptimizelyExperiment> getExperimentsMap() {
return experimentsMap;
}
Expand All @@ -61,6 +95,12 @@ public Map<String, OptimizelyFeature> getFeaturesMap() {
return featuresMap;
}

public List<OptimizelyAttribute> getAttributes() { return attributes; }

public List<OptimizelyEvent> getEvents() { return events; }

public List<OptimizelyAudience> getAudiences() { return audiences; }

public String getRevision() {
return revision;
}
Expand Down
Loading