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

feat: Add ODP Segments support in Audience Evaluation #474

Merged
merged 28 commits into from
Jul 21, 2022
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
2ba5621
OASIS-8309 Added qualified segments to User Context, updated evaluate…
genemitelmanopti May 23, 2022
6d1924a
OASIS-8309 Updated UserAttribute evaluate to check qualified segments.
genemitelmanopti May 24, 2022
0d9d3b8
Added unit tests and fixed issues
NomanShoaib Jun 16, 2022
a8d0c3f
Updated header
NomanShoaib Jun 16, 2022
b461f82
resolved comments
NomanShoaib Jun 21, 2022
8ccd995
Resolved spotbugs error
NomanShoaib Jun 21, 2022
f449dda
removed unnecessary null check
NomanShoaib Jun 21, 2022
a9c8d05
Merge branch 'master' into gene/ats
mnoman09 Jun 27, 2022
315a9c9
Comment added
NomanShoaib Jun 28, 2022
0469623
1. Implemented parsing of integrations in all four Datafile Parsers.
zashraf1985 Jul 16, 2022
15bfe93
Update core-api/src/main/java/com/optimizely/ab/OptimizelyUserContext…
mnoman09 Jul 18, 2022
b8eaae1
Resolved comment and fixed
NomanShoaib Jul 18, 2022
eb2e8c8
trying to fix a test
zashraf1985 Jul 18, 2022
1ba1b3e
trying ignoing some tests
zashraf1985 Jul 18, 2022
9910be8
IGNORED the correct test
zashraf1985 Jul 18, 2022
6af28aa
Merge branch 'master' into gene/ats
msohailhussain Jul 18, 2022
226d3ae
Enabled ignored tests
zashraf1985 Jul 19, 2022
24396e9
test
mnoman09 Jul 19, 2022
12d92ee
test
mnoman09 Jul 19, 2022
889e405
Reverting assertEqual to assertThat
mnoman09 Jul 19, 2022
29750c4
test
mnoman09 Jul 19, 2022
cc4ee31
test
mnoman09 Jul 19, 2022
4f61e54
using macos as in git action there is some problem with ubuntu 18.04
mnoman09 Jul 19, 2022
99a7f0a
change os version to macos latest for unit test
mnoman09 Jul 19, 2022
aad5d0b
added integrations to parsing tests
zashraf1985 Jul 19, 2022
c0f0284
Incorporate review feedback
zashraf1985 Jul 20, 2022
d6ec898
Added unit tests for integrations parsing
zashraf1985 Jul 21, 2022
5df10d4
Added some more unit tests
zashraf1985 Jul 21, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ public class OptimizelyUserContext {
@Nonnull
private final Map<String, Object> attributes;

private List<String> qualifiedSegments;

@Nonnull
private final Optimizely optimizely;

Expand All @@ -44,13 +46,7 @@ public class OptimizelyUserContext {
public OptimizelyUserContext(@Nonnull Optimizely optimizely,
@Nonnull String userId,
@Nonnull Map<String, ?> attributes) {
this.optimizely = optimizely;
this.userId = userId;
if (attributes != null) {
this.attributes = Collections.synchronizedMap(new HashMap<>(attributes));
} else {
this.attributes = Collections.synchronizedMap(new HashMap<>());
}
this(optimizely, userId, attributes, Collections.EMPTY_MAP);
}

public OptimizelyUserContext(@Nonnull Optimizely optimizely,
Expand All @@ -67,6 +63,7 @@ public OptimizelyUserContext(@Nonnull Optimizely optimizely,
if (forcedDecisionsMap != null) {
this.forcedDecisionsMap = new ConcurrentHashMap<>(forcedDecisionsMap);
}
this.qualifiedSegments = Collections.synchronizedList(new LinkedList<>());
}

public OptimizelyUserContext(@Nonnull Optimizely optimizely, @Nonnull String userId) {
Expand All @@ -89,6 +86,15 @@ public OptimizelyUserContext copy() {
return new OptimizelyUserContext(optimizely, userId, attributes, forcedDecisionsMap);
}

/**
* Returns true if the user is qualified for the given segment name
* @param segment A String segment key which will be check in qualified segments list that if it exist then user is qualified.
mnoman09 marked this conversation as resolved.
Show resolved Hide resolved
* @return boolean Is user qualified for a segment.
*/
public boolean isQualifiedFor(@Nonnull String segment) {
return qualifiedSegments.contains(segment);
}

/**
* Set an attribute for a given key.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ public DecisionResponse<Variation> getVariation(@Nonnull Experiment experiment,
}
}

DecisionResponse<Boolean> decisionMeetAudience = ExperimentUtils.doesUserMeetAudienceConditions(projectConfig, experiment, user.getAttributes(), EXPERIMENT, experiment.getKey());
DecisionResponse<Boolean> decisionMeetAudience = ExperimentUtils.doesUserMeetAudienceConditions(projectConfig, experiment, user, EXPERIMENT, experiment.getKey());
reasons.merge(decisionMeetAudience.getReasons());
if (decisionMeetAudience.getResult()) {
String bucketingId = getBucketingId(user.getUserId(), user.getAttributes());
Expand Down Expand Up @@ -693,7 +693,7 @@ DecisionResponse<AbstractMap.SimpleEntry> getVariationFromDeliveryRule(@Nonnull
DecisionResponse<Boolean> audienceDecisionResponse = ExperimentUtils.doesUserMeetAudienceConditions(
projectConfig,
rule,
user.getAttributes(),
user,
RULE,
String.valueOf(ruleIndex + 1)
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
*
* Copyright 2016-2019, Optimizely and contributors
* Copyright 2016-2019, 2022, 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 @@ -16,6 +16,7 @@
*/
package com.optimizely.ab.config.audience;

import com.optimizely.ab.OptimizelyUserContext;
import com.optimizely.ab.config.ProjectConfig;

import javax.annotation.Nonnull;
Expand All @@ -42,7 +43,7 @@ public List<Condition> getConditions() {
}

@Nullable
public Boolean evaluate(ProjectConfig config, Map<String, ?> attributes) {
public Boolean evaluate(ProjectConfig config, OptimizelyUserContext user) {
if (conditions == null) return null;
boolean foundNull = false;
// According to the matrix where:
Expand All @@ -53,7 +54,7 @@ public Boolean evaluate(ProjectConfig config, Map<String, ?> attributes) {
// true and true is true
// null and null is null
for (Condition condition : conditions) {
Boolean conditionEval = condition.evaluate(config, attributes);
Boolean conditionEval = condition.evaluate(config, user);
if (conditionEval == null) {
foundNull = true;
} else if (!conditionEval) { // false with nulls or trues is false.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
*
* Copyright 2022, 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.config.audience;

public enum AttributeType {
CUSTOM_ATTRIBUTE("custom_attribute"),
THIRD_PARTY_DIMENSION("third_party_dimension");

private final String key;

AttributeType(String key) {
this.key = key;
}

@Override
public String toString() {
return key;
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/**
*
* Copyright 2018-2021, Optimizely and contributors
* Copyright 2018-2022, 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
* https://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,
Expand All @@ -19,6 +19,7 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.optimizely.ab.OptimizelyUserContext;
import com.optimizely.ab.config.ProjectConfig;
import com.optimizely.ab.internal.InvalidAudienceCondition;
import org.slf4j.Logger;
Expand Down Expand Up @@ -71,7 +72,7 @@ public String getOperandOrId() {

@Nullable
@Override
public Boolean evaluate(ProjectConfig config, Map<String, ?> attributes) {
public Boolean evaluate(ProjectConfig config, OptimizelyUserContext user) {
if (config != null) {
audience = config.getAudienceIdMapping().get(audienceId);
}
Expand All @@ -80,7 +81,7 @@ public Boolean evaluate(ProjectConfig config, Map<String, ?> attributes) {
return null;
}
logger.debug("Starting to evaluate audience \"{}\" with conditions: {}.", audience.getId(), audience.getConditions());
Boolean result = audience.getConditions().evaluate(config, attributes);
Boolean result = audience.getConditions().evaluate(config, user);
logger.debug("Audience \"{}\" evaluated to {}.", audience.getId(), result);
return result;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
*
* Copyright 2016-2018, Optimizely and contributors
* Copyright 2016-2018, 2022, 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 @@ -16,6 +16,7 @@
*/
package com.optimizely.ab.config.audience;

import com.optimizely.ab.OptimizelyUserContext;
import com.optimizely.ab.config.ProjectConfig;

import javax.annotation.Nullable;
Expand All @@ -27,7 +28,7 @@
public interface Condition<T> {

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

String toJson();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2019, Optimizely Inc. and contributors
* Copyright 2019, 2022, Optimizely Inc. and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,6 +15,7 @@
*/
package com.optimizely.ab.config.audience;

import com.optimizely.ab.OptimizelyUserContext;
import com.optimizely.ab.config.ProjectConfig;

import javax.annotation.Nullable;
Expand All @@ -23,7 +24,7 @@
public class EmptyCondition<T> implements Condition<T> {
@Nullable
@Override
public Boolean evaluate(ProjectConfig config, Map<String, ?> attributes) {
public Boolean evaluate(ProjectConfig config, OptimizelyUserContext user) {
return true;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
*
* Copyright 2016-2019, Optimizely and contributors
* Copyright 2016-2019, 2022, 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 @@ -16,14 +16,13 @@
*/
package com.optimizely.ab.config.audience;

import com.optimizely.ab.OptimizelyUserContext;
import com.optimizely.ab.config.ProjectConfig;

import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.annotation.Nonnull;

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

/**
* Represents a 'Not' conditions condition operation.
Expand All @@ -43,9 +42,8 @@ public Condition getCondition() {
}

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

Boolean conditionEval = condition == null ? null : condition.evaluate(config, attributes);
public Boolean evaluate(ProjectConfig config, OptimizelyUserContext user) {
Boolean conditionEval = condition == null ? null : condition.evaluate(config, user);
return (conditionEval == null ? null : !conditionEval);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2019, Optimizely Inc. and contributors
* Copyright 2019, 2022, Optimizely Inc. and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,6 +15,7 @@
*/
package com.optimizely.ab.config.audience;

import com.optimizely.ab.OptimizelyUserContext;
import com.optimizely.ab.config.ProjectConfig;

import javax.annotation.Nullable;
Expand All @@ -23,7 +24,7 @@
public class NullCondition<T> implements Condition<T> {
@Nullable
@Override
public Boolean evaluate(ProjectConfig config, Map<String, ?> attributes) {
public Boolean evaluate(ProjectConfig config, OptimizelyUserContext user) {
return null;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
*
* Copyright 2016-2019, Optimizely and contributors
* Copyright 2016-2019, 2022, 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 @@ -16,6 +16,7 @@
*/
package com.optimizely.ab.config.audience;

import com.optimizely.ab.OptimizelyUserContext;
import com.optimizely.ab.config.ProjectConfig;

import javax.annotation.Nonnull;
Expand Down Expand Up @@ -47,11 +48,11 @@ public List<Condition> getConditions() {
// false or false is false
// null or null is null
@Nullable
public Boolean evaluate(ProjectConfig config, Map<String, ?> attributes) {
public Boolean evaluate(ProjectConfig config, OptimizelyUserContext user) {
if (conditions == null) return null;
boolean foundNull = false;
for (Condition condition : conditions) {
Boolean conditionEval = condition.evaluate(config, attributes);
Boolean conditionEval = condition.evaluate(config, user);
if (conditionEval == null) { // true with falses and nulls is still true
foundNull = true;
} else if (conditionEval) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
*
* Copyright 2016-2020, Optimizely and contributors
* Copyright 2016-2020, 2022, 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 @@ -19,6 +19,7 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.optimizely.ab.OptimizelyUserContext;
import com.optimizely.ab.config.ProjectConfig;
import com.optimizely.ab.config.audience.match.*;
import org.slf4j.Logger;
Expand All @@ -27,8 +28,11 @@
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import java.util.Collections;
import java.util.Map;
import java.util.*;

import static com.optimizely.ab.config.audience.AttributeType.CUSTOM_ATTRIBUTE;
import static com.optimizely.ab.config.audience.AttributeType.THIRD_PARTY_DIMENSION;
import static com.optimizely.ab.config.audience.match.MatchRegistry.QUALIFIED;

/**
* Represents a user attribute instance within an audience's conditions.
Expand All @@ -42,7 +46,7 @@ public class UserAttribute<T> implements Condition<T> {
private final String type;
private final String match;
private final Object value;

private final static List ATTRIBUTE_TYPE = Arrays.asList(new String[]{CUSTOM_ATTRIBUTE.toString(), THIRD_PARTY_DIMENSION.toString()});
@JsonCreator
public UserAttribute(@JsonProperty("name") @Nonnull String name,
@JsonProperty("type") @Nonnull String type,
Expand Down Expand Up @@ -71,19 +75,26 @@ public Object getValue() {
}

@Nullable
public Boolean evaluate(ProjectConfig config, Map<String, ?> attributes) {
if (attributes == null) {
attributes = Collections.emptyMap();
}
public Boolean evaluate(ProjectConfig config, OptimizelyUserContext user) {
Map<String,Object> attributes = user.getAttributes();
// Valid for primitive types, but needs to change when a value is an object or an array
Object userAttributeValue = attributes.get(name);

if (!"custom_attribute".equals(type)) {
if (!isValidType(type)) {
logger.warn("Audience condition \"{}\" uses an unknown condition type. You may need to upgrade to a newer release of the Optimizely SDK.", this);
return null; // unknown type
}
// check user attribute value is equal
try {
// Handle qualified segments
if (QUALIFIED.equals(match)) {
if (value instanceof String) {
return user.isQualifiedFor(value.toString());
} else {
throw new UnknownValueTypeException();
}
}
// Handle other conditions
Match matcher = MatchRegistry.getMatch(match);
Boolean result = matcher.eval(value, userAttributeValue);
if (result == null) {
Expand Down Expand Up @@ -118,6 +129,13 @@ public Boolean evaluate(ProjectConfig config, Map<String, ?> attributes) {
return null;
}

private boolean isValidType(String type) {
if (ATTRIBUTE_TYPE.contains(type)) {
return true;
}
return false;
}

@Override
public String getOperandOrId() {
return null;
Expand Down
Loading