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 62 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
101 changes: 98 additions & 3 deletions core-api/src/main/java/com/optimizely/ab/config/Experiment.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
*
* Copyright 2016-2019, Optimizely and contributors
* Copyright 2016-2019, 2021, Optimizely and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -18,8 +18,7 @@

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;
Expand All @@ -43,6 +42,10 @@ public class Experiment implements IdKeyMapped {
private final String layerId;
private final String groupId;

private final String AND = "AND";
private final String OR = "OR";
private final String NOT = "NOT";

private final List<String> audienceIds;
private final Condition<AudienceIdCondition> audienceConditions;
private final List<Variation> variations;
Expand Down Expand Up @@ -173,6 +176,98 @@ public boolean isLaunched() {
return status.equals(ExperimentStatus.LAUNCHED.toString());
}

public String serializeConditions(Map<String, String> audiencesMap) {
Condition condition = this.audienceConditions;
return condition instanceof EmptyCondition ? "" : this.serialize(condition, audiencesMap);
}

private String getNameFromAudienceId(String audienceId, Map<String, String> audiencesMap) {
StringBuilder audienceName = new StringBuilder();
if (audiencesMap != null && audiencesMap.get(audienceId) != null) {
audienceName.append("\"" + audiencesMap.get(audienceId) + "\"");
} else {
audienceName.append("\"" + audienceId + "\"");
}
return audienceName.toString();
}

private String getOperandOrAudienceId(Condition condition, Map<String, String> audiencesMap) {
if (condition != null) {
if (condition instanceof AudienceIdCondition) {
return this.getNameFromAudienceId(condition.getOperandOrId(), audiencesMap);
} else {
return condition.getOperandOrId();
}
} else {
return "";
}
}

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();
List<Condition> conditions;

String operand = this.getOperandOrAudienceId(condition, audiencesMap);
switch (operand){
case (AND):
conditions = ((AndCondition<?>) condition).getConditions();
stringBuilder.append(this.getNameOrNextCondition(operand, conditions, audiencesMap));
break;
case (OR):
conditions = ((OrCondition<?>) condition).getConditions();
stringBuilder.append(this.getNameOrNextCondition(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) + ")");
}
break;
default:
stringBuilder.append(operand);
break;
}

return stringBuilder.toString();
}

public String getNameOrNextCondition(String operand, List<Condition> conditions, Map<String, String> audiencesMap) {
StringBuilder stringBuilder = new StringBuilder();
int index = 0;
if (conditions.isEmpty()) {
return "";
} else if (conditions.size() == 1) {
return serialize(conditions.get(0), audiencesMap);
} else {
for (Condition con : conditions) {
index++;
if (index + 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 @@ -23,13 +23,15 @@
import javax.annotation.concurrent.Immutable;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;

/**
* Represents an 'And' conditions condition operation.
*/
public class AndCondition<T> implements Condition<T> {

private final List<Condition> conditions;
private static final String OPERAND = "AND";

public AndCondition(@Nonnull List<Condition> conditions) {
this.conditions = conditions;
Expand Down Expand Up @@ -67,6 +69,21 @@ public Boolean evaluate(ProjectConfig config, Map<String, ?> attributes) {
return true; // otherwise, return true
}

@Override
public String getOperandOrId() {
return OPERAND;
}

@Override
public String toJson() {
StringJoiner s = new StringJoiner(", ", "[", "]");
s.add("\"and\"");
for (int i = 0; i < conditions.size(); i++) {
s.add(conditions.get(i).toJson());
}
return s.toString();
}

@Override
public String toString() {
StringBuilder s = new StringBuilder();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ public String getAudienceId() {
return audienceId;
}

@Override
public String getOperandOrId() {
return audienceId;
}

@Nullable
@Override
public Boolean evaluate(ProjectConfig config, Map<String, ?> attributes) {
Expand Down Expand Up @@ -101,4 +106,7 @@ public int hashCode() {
public String toString() {
return audienceId;
}

@Override
public String toJson() { return null; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,8 @@ public interface Condition<T> {

@Nullable
Boolean evaluate(ProjectConfig config, Map<String, ?> attributes);

String toJson();

String getOperandOrId();
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,11 @@ public Boolean evaluate(ProjectConfig config, Map<String, ?> attributes) {
return true;
}

@Override
public String toJson() { return null; }

@Override
public String getOperandOrId() {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import javax.annotation.Nonnull;

import java.util.Map;
import java.util.StringJoiner;

/**
* Represents a 'Not' conditions condition operation.
Expand All @@ -31,6 +32,7 @@
public class NotCondition<T> implements Condition<T> {

private final Condition condition;
private static final String OPERAND = "NOT";

public NotCondition(@Nonnull Condition condition) {
this.condition = condition;
Expand All @@ -47,6 +49,19 @@ public Boolean evaluate(ProjectConfig config, Map<String, ?> attributes) {
return (conditionEval == null ? null : !conditionEval);
}

@Override
public String getOperandOrId() {
return OPERAND;
}

@Override
public String toJson() {
StringJoiner s = new StringJoiner(", ","[","]");
s.add("\"not\"");
s.add(condition.toJson());
return s.toString();
}

@Override
public String toString() {
StringBuilder s = new StringBuilder();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,12 @@ public class NullCondition<T> implements Condition<T> {
public Boolean evaluate(ProjectConfig config, Map<String, ?> attributes) {
return null;
}

@Override
public String toJson() { return null; }

@Override
public String getOperandOrId() {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@
import javax.annotation.concurrent.Immutable;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;

/**
* Represents an 'Or' conditions condition operation.
*/
@Immutable
public class OrCondition<T> implements Condition<T> {
private final List<Condition> conditions;
private static final String OPERAND = "OR";

public OrCondition(@Nonnull List<Condition> conditions) {
this.conditions = conditions;
Expand Down Expand Up @@ -65,6 +67,21 @@ public Boolean evaluate(ProjectConfig config, Map<String, ?> attributes) {
return false;
}

@Override
public String getOperandOrId() {
return OPERAND;
}

@Override
public String toJson() {
StringJoiner s = new StringJoiner(", ", "[", "]");
s.add("\"or\"");
for (int i = 0; i < conditions.size(); i++) {
s.add(conditions.get(i).toJson());
}
return s.toString();
}

@Override
public String toString() {
StringBuilder s = new StringBuilder();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,19 +119,39 @@ public Boolean evaluate(ProjectConfig config, Map<String, ?> attributes) {
}

@Override
public String toString() {
public String getOperandOrId() {
return null;
}

public String getValueStr() {
final String valueStr;
if (value == null) {
valueStr = "null";
} else if (value instanceof String) {
valueStr = String.format("'%s'", value);
valueStr = String.format("%s", value);
} else {
valueStr = value.toString();
}
return valueStr;
}

@Override
public String toJson() {
StringBuilder attributes = new StringBuilder();
if (name != null) attributes.append("{\"name\":\"" + name + "\"");
if (type != null) attributes.append(", \"type\":\"" + type + "\"");
if (match != null) attributes.append(", \"match\":\"" + match + "\"");
attributes.append(", \"value\":" + ((value instanceof String) ? ("\"" + getValueStr() + "\"") : getValueStr()) + "}");

return attributes.toString();
}

@Override
public String toString() {
return "{name='" + name + "\'" +
", type='" + type + "\'" +
", match='" + match + "\'" +
", value=" + valueStr +
", value=" + ((value instanceof String) ? ("'" + getValueStr() + "'") : getValueStr()) +
Copy link
Contributor

Choose a reason for hiding this comment

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

why single quote here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was part of the original logic that I didn't want to change (Line 127) but don't need for the function above and wanted to reuse the same code minus those quotes. We can chat offline if you like.

"}";
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
*
* Copyright 2018-2019, Optimizely and contributors
* Copyright 2018-2019, 2021, Optimizely and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -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
Loading