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

[SDKS-7541] Add getTreatments for sets #443

Merged
merged 3 commits into from
Oct 2, 2023
Merged
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
66 changes: 66 additions & 0 deletions client/src/main/java/io/split/client/SplitClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,72 @@ public interface SplitClient {
*/
Map<String, SplitResult> getTreatmentsWithConfig(Key key, List<String> featureFlagNames, Map<String, Object> attributes);

/**
* Same as {@link #getTreatments(String, List<String>, Map)} but it returns for each feature flag the configuration associated to the
* matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
* <p/>
* <p/>
* Examples include showing a different treatment to users on trial plan
* vs. premium plan. Another example is to show a different treatment
* to users created after a certain date.
*
* @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null or empty.
* @param flagSet the Flag Set name that you want to evaluate. MUST not be null or empty.
* @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty.
* @return for each feature flag the evaluated treatment, the default treatment of this feature flag, or 'control'.
*/
Map<String, String> getTreatmentsByFlagSet(String key, String flagSet, Map<String, Object> attributes);

/**
* Same as {@link #getTreatments(String, List<String>, Map)} but it returns for each feature flag the configuration associated to the
* matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
* <p/>
* <p/>
* Examples include showing a different treatment to users on trial plan
* vs. premium plan. Another example is to show a different treatment
* to users created after a certain date.
*
* @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null or empty.
* @param flagSets the names of Flag Sets that you want to evaluate. MUST not be null or empty.
* @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty.
* @return for each feature flag the evaluated treatment, the default treatment of this feature flag, or 'control'.
*/
Map<String, String> getTreatmentsByFlagSets(String key, List<String> flagSets, Map<String, Object> attributes);

/**
* Same as {@link #getTreatments(String, List<String>, Map)} but it returns for each feature flag the configuration associated to the
* matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
* <p/>
* <p/>
* Examples include showing a different treatment to users on trial plan
* vs. premium plan. Another example is to show a different treatment
* to users created after a certain date.
*
* @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null or empty.
* @param flagSet the Flag Set name that you want to evaluate. MUST not be null or empty.
* @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty.
* @return for each feature flag the evaluated treatment (the default treatment of this feature flag, or 'control') and a configuration
* associated to this treatment if set.
*/
Map<String, SplitResult> getTreatmentsWithConfigByFlagSet(String key, String flagSet, Map<String, Object> attributes);

/**
* Same as {@link #getTreatments(String, List<String>, Map)} but it returns for each feature flag the configuration associated to the
* matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
* <p/>
* <p/>
* Examples include showing a different treatment to users on trial plan
* vs. premium plan. Another example is to show a different treatment
* to users created after a certain date.
*
* @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null or empty.
* @param flagSets the names of Flag Sets that you want to evaluate. MUST not be null or empty.
* @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty.
* @return for each feature flag the evaluated treatment (the default treatment of this feature flag, or 'control') and a configuration
* associated to this treatment if set.
*/
Map<String, SplitResult> getTreatmentsWithConfigByFlagSets(String key, List<String> flagSets, Map<String, Object> attributes);

/**
* Destroys the background processes and clears the cache, releasing the resources used by
* the any instances of SplitClient or SplitManager generated by the client's parent SplitFactory
Expand Down
101 changes: 87 additions & 14 deletions client/src/main/java/io/split/client/SplitClientImpl.java
gthea marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import io.split.engine.evaluator.Labels;
import io.split.grammar.Treatments;
import io.split.inputValidation.EventsValidator;
import io.split.inputValidation.FlagSetsValidResult;
import io.split.inputValidation.FlagSetsValidator;
import io.split.inputValidation.KeyValidator;
import io.split.inputValidation.SplitNameValidator;
import io.split.inputValidation.TrafficTypeValidator;
Expand All @@ -23,8 +25,10 @@
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand Down Expand Up @@ -111,35 +115,61 @@ public Map<String, String> getTreatments(String key, List<String> featureFlagNam

@Override
public Map<String, String> getTreatments(String key, List<String> featureFlagNames, Map<String, Object> attributes) {
return getTreatmentsWithConfigInternal(key, null, featureFlagNames, attributes, MethodEnum.TREATMENTS)
return getTreatmentsWithConfigInternal(key, null, featureFlagNames, null, attributes, MethodEnum.TREATMENTS)
.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment()));
}

@Override
public Map<String, String> getTreatments(Key key, List<String> featureFlagNames, Map<String, Object> attributes) {
return getTreatmentsWithConfigInternal(key.matchingKey(), key.bucketingKey(), featureFlagNames, attributes, MethodEnum.TREATMENTS)
return getTreatmentsWithConfigInternal(key.matchingKey(), key.bucketingKey(), featureFlagNames, null, attributes, MethodEnum.TREATMENTS)
.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment()));
}

@Override
public Map<String, SplitResult> getTreatmentsWithConfig(String key, List<String> featureFlagNames) {
return getTreatmentsWithConfigInternal(key, null, featureFlagNames, Collections.<String, Object>emptyMap(),
return getTreatmentsWithConfigInternal(key, null, featureFlagNames, null, Collections.<String, Object>emptyMap(),
MethodEnum.TREATMENTS_WITH_CONFIG);
}

@Override
public Map<String, SplitResult> getTreatmentsWithConfig(String key, List<String> featureFlagNames, Map<String, Object> attributes) {
return getTreatmentsWithConfigInternal(key, null, featureFlagNames, attributes, MethodEnum.TREATMENTS_WITH_CONFIG);
return getTreatmentsWithConfigInternal(key, null, featureFlagNames, null, attributes, MethodEnum.TREATMENTS_WITH_CONFIG);
}

@Override
public Map<String, SplitResult> getTreatmentsWithConfig(Key key, List<String> featureFlagNames, Map<String, Object> attributes) {
return getTreatmentsWithConfigInternal(key.matchingKey(), key.bucketingKey(), featureFlagNames, attributes,
return getTreatmentsWithConfigInternal(key.matchingKey(), key.bucketingKey(), featureFlagNames, null, attributes,
MethodEnum.TREATMENTS_WITH_CONFIG);
}

@Override
public Map<String, String> getTreatmentsByFlagSet(String key, String flagSet, Map<String, Object> attributes) {
return getTreatmentsWithConfigInternal(key, null, null, new ArrayList<>(Arrays.asList(flagSet)),
attributes, MethodEnum.TREATMENTS_BY_FLAG_SET).entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment()));
}

@Override
public Map<String, String> getTreatmentsByFlagSets(String key, List<String> flagSets, Map<String, Object> attributes) {
return getTreatmentsWithConfigInternal(key, null, null, flagSets,
attributes, MethodEnum.TREATMENTS_BY_FLAG_SETS).entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment()));
}

@Override
public Map<String, SplitResult> getTreatmentsWithConfigByFlagSet(String key, String flagSet, Map<String, Object> attributes) {
return getTreatmentsWithConfigInternal(key, null, null, new ArrayList<>(Arrays.asList(flagSet)),
attributes, MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SET);
}

@Override
public Map<String, SplitResult> getTreatmentsWithConfigByFlagSets(String key, List<String> flagSets, Map<String, Object> attributes) {
return getTreatmentsWithConfigInternal(key, null, null, flagSets,
attributes, MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS);
}

@Override
public boolean track(String key, String trafficType, String eventType) {
Event event = createEvent(key, trafficType, eventType);
Expand Down Expand Up @@ -283,9 +313,27 @@ private SplitResult getTreatmentWithConfigInternal(String matchingKey, String bu
}

private Map<String, SplitResult> getTreatmentsWithConfigInternal(String matchingKey, String bucketingKey, List<String> featureFlagNames,
Map<String, Object> attributes, MethodEnum methodEnum) {
List<String> sets, Map<String, Object> attributes, MethodEnum methodEnum) {

long initTime = System.currentTimeMillis();
if (featureFlagNames == null) {
FlagSetsValidResult flagSetsValidResult = null;
if (methodEnum == MethodEnum.TREATMENTS_BY_FLAG_SET || methodEnum == MethodEnum.TREATMENTS_BY_FLAG_SETS ||
methodEnum == MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SET || methodEnum == MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS) {
if (sets == null || sets.isEmpty()) {
_log.warn(String.format("%s: sets must be a non-empty array", methodEnum.getMethod()));
return new HashMap<>();
}
flagSetsValidResult = FlagSetsValidator.areValid(sets);
if (!flagSetsValidResult.getValid()) {
_log.warn("The sets are invalid");
return new HashMap<>();
}
if (filterSetsAreInConfig(flagSetsValidResult.getFlagSets()).isEmpty()) {
_log.warn("The sets are not in flagSetsFilter config");
return new HashMap<>();
}
featureFlagNames = getAllFlags(flagSetsValidResult.getFlagSets());
} else if (featureFlagNames == null) {
_log.error(String.format("%s: featureFlagNames must be a non-empty array", methodEnum.getMethod()));
return new HashMap<>();
}
Expand All @@ -295,20 +343,24 @@ private Map<String, SplitResult> getTreatmentsWithConfigInternal(String matching
_log.error("Client has already been destroyed - no calls possible");
return createMapControl(featureFlagNames);
}

if (!KeyValidator.isValid(matchingKey, "matchingKey", _config.maxStringLength(), methodEnum.getMethod())) {
return createMapControl(featureFlagNames);
}

if (!KeyValidator.bucketingKeyIsValid(bucketingKey, _config.maxStringLength(), methodEnum.getMethod())) {
return createMapControl(featureFlagNames);
} else if (featureFlagNames.isEmpty()) {
_log.error(String.format("%s: featureFlagNames must be a non-empty array", methodEnum.getMethod()));
return new HashMap<>();
}
featureFlagNames = SplitNameValidator.areValid(featureFlagNames, methodEnum.getMethod());
Map<String, EvaluatorImp.TreatmentLabelAndChangeNumber> evaluatorResult = _evaluator.evaluateFeatures(matchingKey,
bucketingKey, featureFlagNames, attributes);
Map<String, EvaluatorImp.TreatmentLabelAndChangeNumber> evaluatorResult;
if (flagSetsValidResult != null) {
evaluatorResult = _evaluator.evaluateFeaturesByFlagSets(matchingKey, bucketingKey, new ArrayList<>(flagSetsValidResult.
getFlagSets()));
} else {
featureFlagNames = SplitNameValidator.areValid(featureFlagNames, methodEnum.getMethod());
evaluatorResult = _evaluator.evaluateFeatures(matchingKey, bucketingKey, featureFlagNames, attributes);
}

List<Impression> impressions = new ArrayList<>();
Map<String, SplitResult> result = new HashMap<>();
evaluatorResult.keySet().forEach(t -> {
Expand All @@ -323,9 +375,7 @@ private Map<String, SplitResult> getTreatmentsWithConfigInternal(String matching
evaluatorResult.get(t).label, evaluatorResult.get(t).changeNumber, attributes));
}
});

_telemetryEvaluationProducer.recordLatency(methodEnum, System.currentTimeMillis() - initTime);
//Track of impressions
if (impressions.size() > 0) {
_impressionManager.track(impressions);
}
Expand All @@ -341,6 +391,29 @@ private Map<String, SplitResult> getTreatmentsWithConfigInternal(String matching
}
}

private List<String> filterSetsAreInConfig(HashSet<String> sets) {
HashSet<String> configSets = _config.getSetsFilter();
List<String> setsToReturn = new ArrayList<>();
for (String set : sets) {
if (!configSets.contains(set)) {
_log.warn(String.format("GetTreatmentsByFlagSets: you passed %s which is not part of the configured FlagSetsFilter, " +
"ignoring Flag Set.", set));
continue;
}
setsToReturn.add(set);
}
return setsToReturn;
}

private List<String> getAllFlags(HashSet<String> sets) {
Map<String, HashSet<String>> namesBySets = _splitCacheConsumer.getNamesByFlagSets(new ArrayList<>(sets));
HashSet<String> flags = new HashSet<>();
for (String set: namesBySets.keySet()) {
flags.addAll(namesBySets.get(set));
}
return new ArrayList<>(flags);
}

private void recordStats(String matchingKey, String bucketingKey, String featureFlagName, long start, String result,
String operation, String label, Long changeNumber, Map<String, Object> attributes) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ EvaluatorImp.TreatmentLabelAndChangeNumber evaluateFeature(String matchingKey, S
Map<String, Object> attributes);
Map<String, EvaluatorImp.TreatmentLabelAndChangeNumber> evaluateFeatures(String matchingKey, String bucketingKey,
List<String> featureFlags, Map<String, Object> attributes);
EvaluatorImp.ByFlagSetsResult evaluateFeaturesByFlagSets(String key, String bucketingKey, List<String> flagSets);
Map<String, EvaluatorImp.TreatmentLabelAndChangeNumber> evaluateFeaturesByFlagSets(String key, String bucketingKey, List<String> flagSets);
}
18 changes: 3 additions & 15 deletions client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,11 @@ public Map<String, TreatmentLabelAndChangeNumber> evaluateFeatures(String matchi
}

@Override
public ByFlagSetsResult evaluateFeaturesByFlagSets(String key, String bucketingKey, List<String> flagSets) {
Stopwatch stopwatch = Stopwatch.createStarted();
public Map<String, EvaluatorImp.TreatmentLabelAndChangeNumber> evaluateFeaturesByFlagSets(String key, String bucketingKey,
List<String> flagSets) {
List<String> flagSetsWithNames = getFeatureFlagNamesByFlagSets(flagSets);
Map<String, TreatmentLabelAndChangeNumber> evaluations = evaluateFeatures(key, bucketingKey, flagSetsWithNames, null);
stopwatch.stop();
long millis = stopwatch.elapsed(TimeUnit.MILLISECONDS);
return new ByFlagSetsResult(evaluations, millis);
return evaluations;
}

private List<String> getFeatureFlagNamesByFlagSets(List<String> flagSets) {
Expand Down Expand Up @@ -155,16 +153,6 @@ private TreatmentLabelAndChangeNumber evaluateParsedSplit(String matchingKey, St
}
}

public static class ByFlagSetsResult {
public final Map<String, TreatmentLabelAndChangeNumber> evaluations;
public final long elapsedMilliseconds;

public ByFlagSetsResult(Map<String, TreatmentLabelAndChangeNumber> evaluations, long elapsed) {
this.evaluations = evaluations;
this.elapsedMilliseconds = elapsed;
}
}

public static final class TreatmentLabelAndChangeNumber {
public final String treatment;
public final String label;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ public enum MethodEnum {
TREATMENTS("getTreatments"),
TREATMENT_WITH_CONFIG("getTreatmentWithConfig"),
TREATMENTS_WITH_CONFIG("getTreatmentsWithConfig"),
TREATMENTS_BY_FLAG_SET("getTreatmentsByFlagSet"),
TREATMENTS_BY_FLAG_SETS("getTreatmentsByFlagSets"),
TREATMENTS_WITH_CONFIG_BY_FLAG_SET("getTreatmentsWithConfigByFlagSet"),
TREATMENTS_WITH_CONFIG_BY_FLAG_SETS("getTreatmentsWithConfigByFlagSets"),
TRACK("track");

private String _method;
Expand Down
Loading