8000 feat: Add ODP Segments support in Audience Evaluation by mnoman09 · Pull Request #472 · optimizely/java-sdk · GitHub
[go: up one dir, main page]

Skip to content

feat: Add ODP Segments support in Audience Evaluation #472

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

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -16,7 +16,6 @@
*/
package com.optimizely.ab;

import com.optimizely.ab.config.Variation;
import com.optimizely.ab.optimizelydecision.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -36,6 +35,9 @@ public class OptimizelyUserContext {
@Nonnull
private final Map<String, Object> attributes;

@Nonnull
private final 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 ArrayList<>());
}

public OptimizelyUserContext(@Nonnull Optimizely optimizely, @Nonnull String userId) {
Expand All @@ -85,6 +82,10 @@ public Optimizely getOptimizely() {
return optimizely;
}

public List<String> getQualifiedSegments() {
return qualifiedSegments;
}

public OptimizelyUserContext copy() {
return new OptimizelyUserContext(optimizely, userId, attributes, forcedDecisionsMap);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,12 @@ 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.getAttributes(),
EXPERIMENT,
experiment.getKey(),
user.getQualifiedSegments());
reasons.merge(decisionMeetAudience.getReasons());
if (decisionMeetAudience.getResult()) {
String bucketingId = getBucketingId(user.getUserId(), user.getAttributes());
Expand Down Expand Up @@ -695,7 +700,8 @@ DecisionResponse<AbstractMap.SimpleEntry> getVariationFromDeliveryRule(@Nonnull
rule,
user.getAttributes(),
RULE,
String.valueOf(ruleIndex + 1)
String.valueOf(ruleIndex + 1),
user.getQualifiedSegments()
);

reasons.merge(audienceDecisionResponse.getReasons());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public List<Condition> getConditions() {
}

@Nullable
public Boolean evaluate(ProjectConfig config, Map<String, ?> attributes) {
public Boolean evaluate(ProjectConfig config, Map<String, ?> attributes, List<String> qualifiedSegments) {
if (conditions == null) return null;
boolean foundNull = false;
// According to the matrix where:
Expand All @@ -53,7 +53,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, attributes, qualifiedSegments);
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,6 +1,6 @@
/**
*
* 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.
Expand All @@ -26,6 +26,7 @@

import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import java.util.List;
import java.util.Map;
import java.util.Objects;

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, Map<String, ?> attributes, List<String> qualifiedSegments) {
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, attributes, qualifiedSegments);
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 @@ -19,6 +19,7 @@
import com.optimizely.ab.config.ProjectConfig;

import javax.annotation.Nullable;
import java.util.List;
import java.util.Map;

/**
Expand All @@ -27,7 +28,7 @@
public interface Condition<T> {

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

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 @@ -18,12 +18,13 @@
import com.optimizely.ab.config.ProjectConfig;

import javax.annotation.Nullable;
import java.util.List;
import java.util.Map;

public class EmptyCondition<T> implements Condition<T> {
@Nullable
@Override
public Boolean evaluate(ProjectConfig config, Map<String, ?> attributes) {
public Boolean evaluate(ProjectConfig config, Map<String, ?> attributes, List<String> qualifiedSegments) {
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 @@ -22,6 +22,7 @@
import javax.annotation.concurrent.Immutable;
import javax.annotation.Nonnull;

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

Expand All @@ -43,9 +44,9 @@ public Condition getCondition() {
}

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

Boolean conditionEval = condition == null ? null : condition.evaluate(config, attributes);
Boolean conditionEval = condition == null ? null : condition.evaluate(config, attributes, qualifiedSegments);
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 @@ -18,12 +18,13 @@
import com.optimizely.ab.config.ProjectConfig;

import javax.annotation.Nullable;
import java.util.List;
import java.util.Map;

public class NullCondition<T> implements Condition<T> {
@Nullable
@Override
public Boolean evaluate(ProjectConfig config, Map<String, ?> attributes) {
public Boolean evaluate(ProjectConfig config, Map<String, ?> attributes, List<String> qualifiedSegments) {
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 Down Expand Up @@ -47,12 +47,12 @@ 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, Map<String, ?> attributes, List<String> qualifiedSegments) {
if (conditions == null) return null;
boolean foundNull = false;
for (Condition condition : conditions) {
Boolean conditionEval = condition.evaluate(config, attributes);
if (conditionEval == null) { // true with falses and nulls is still true
Boolean conditionEval = condition.evaluate(config, attributes, qualifiedSegments);
if (conditionEval == null) { // true with false and nulls is still true
foundNull = true;
} else if (conditionEval) {
return true;
Expand Down
< 57AE td class="blob-num blob-num-expandable" colspan="2"> Expand Down Expand Up
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.
@@ -28,8 +28,12 @@
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import java.util.Collections;
import java.util.List;
import java.util.Map;

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

/**
* Represents a user attribute instance within an audience's conditions.
*/
Expand Down Expand Up @@ -71,21 +75,27 @@ public Object getValue() {
}

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

// check user attribute value is equal
try {
Match matcher = MatchRegistry.getMatch(match);
Boolean result = matcher.eval(value, userAttributeValue);
Boolean result;
if ("qualified".equals(match)) {
result = matcher.eval(value, qualifiedSegments);
} else {
result = matcher.eval(value, userAttributeValue);
}
if (result == null) {
throw new UnknownValueTypeException();
}
Expand Down Expand Up @@ -118,6 +128,13 @@ public Boolean evaluate(ProjectConfig config, Map<String, ?> attributes) {
return null;
}

private boolean isValidType(String type) {
if (type.equals(CUSTOM_ATTRIBUTE.toString()) || type.equals(THIRD_PARTY_DIMENSION.toString())) {
return true;
}
return false;
}

@Override
public String getOperandOrId() {
return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
*
* Copyright 2020-2021, Optimizely and contributors
* Copyright 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 Down Expand Up @@ -41,6 +41,7 @@ public class MatchRegistry {
public static final String SEMVER_LE = "semver_le";
public static final String SEMVER_LT = "semver_lt";
public static final String SUBSTRING = "substring";
public static final String QUALIFIED = "qualified";

static {
register(EXACT, new ExactMatch());
Expand All @@ -56,6 +57,7 @@ public class MatchRegistry {
register(SEMVER_LE, new SemanticVersionLEMatch());
register(SEMVER_LT, new SemanticVersionLTMatch());
register(SUBSTRING, new SubstringMatch());
register(QUALIFIED, new QualifiedMatch());
}

// TODO rename Match to Matcher
Expand Down
Loading
0