Skip to content
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
4 changes: 4 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
4.10.2 (Dec 1, 2023)
- Added getTreatmentsByFlagSets without attributes.
- Fixed some issues for flag sets: Not logging a warning when using flag sets that don't contain cached feature flags.

4.10.1 (Nov 9, 2023)
- Fixed handler for response http headers.

Expand Down
2 changes: 1 addition & 1 deletion client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>io.split.client</groupId>
<artifactId>java-client-parent</artifactId>
<version>4.10.1</version>
<version>4.10.2</version>
</parent>
<artifactId>java-client</artifactId>
<packaging>jar</packaging>
Expand Down
65 changes: 63 additions & 2 deletions client/src/main/java/io/split/client/SplitClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ public interface SplitClient {
Map<String, SplitResult> getTreatmentsWithConfig(String key, List<String> featureFlagNames, Map<String, Object> attributes);

/**
* Same as {@link #getTreatments(Key, List<String>, Map)} but it returns for each feature flag the configuration associated to the
* Same as {@link #getTreatments(Key, List, Map)} but it returns for each feature flag the configuration associated to the
* matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
*
* @param key the matching and bucketing keys. MUST NOT be null.
Expand All @@ -268,6 +268,21 @@ 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.
* @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);

/**
* 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.
Expand Down Expand Up @@ -300,6 +315,21 @@ public interface SplitClient {
*/
Map<String, String> getTreatmentsByFlagSet(Key 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.
* @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);

/**
* 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.
Expand Down Expand Up @@ -332,6 +362,22 @@ public interface SplitClient {
*/
Map<String, String> getTreatmentsByFlagSets(Key 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.
* @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);

/**
* 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.
Expand Down Expand Up @@ -366,6 +412,22 @@ public interface SplitClient {
*/
Map<String, SplitResult> getTreatmentsWithConfigByFlagSet(Key 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.
* @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);

/**
* 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.
Expand Down Expand Up @@ -444,7 +506,6 @@ public interface SplitClient {
* @param key the identifier of the entity
* @param trafficType the type of the event
* @param eventType the type of the event
* @param value the value of the event
*
* @return true if the track was successful, false otherwise
*/
Expand Down
67 changes: 49 additions & 18 deletions client/src/main/java/io/split/client/SplitClientImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public final class SplitClientImpl implements SplitClient {
public static final SplitResult SPLIT_RESULT_CONTROL = new SplitResult(Treatments.CONTROL, null);
private static final String CLIENT_DESTROY = "Client has already been destroyed - no calls possible";
private static final String CATCHALL_EXCEPTION = "CatchAll Exception";
private static final String MATCHING_KEY = "matchingKey";

private static final Logger _log = LoggerFactory.getLogger(SplitClientImpl.class);

Expand Down Expand Up @@ -150,6 +151,13 @@ public Map<String, SplitResult> getTreatmentsWithConfig(Key key, List<String> fe
MethodEnum.TREATMENTS_WITH_CONFIG);
}

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

@Override
public Map<String, String> getTreatmentsByFlagSet(String key, String flagSet, Map<String, Object> attributes) {
return getTreatmentsBySetsWithConfigInternal(key, null, new ArrayList<>(Arrays.asList(flagSet)),
Expand All @@ -164,6 +172,13 @@ public Map<String, String> getTreatmentsByFlagSet(Key key, String flagSet, Map<S
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment()));
}

@Override
public Map<String, String> getTreatmentsByFlagSets(String key, List<String> flagSets) {
return getTreatmentsBySetsWithConfigInternal(key, null, flagSets,
null, MethodEnum.TREATMENTS_BY_FLAG_SETS).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 getTreatmentsBySetsWithConfigInternal(key, null, flagSets,
Expand All @@ -178,6 +193,12 @@ public Map<String, String> getTreatmentsByFlagSets(Key key, List<String> flagSet
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment()));
}

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

@Override
public Map<String, SplitResult> getTreatmentsWithConfigByFlagSet(String key, String flagSet, Map<String, Object> attributes) {
return getTreatmentsBySetsWithConfigInternal(key, null, new ArrayList<>(Arrays.asList(flagSet)),
Expand All @@ -190,6 +211,12 @@ public Map<String, SplitResult> getTreatmentsWithConfigByFlagSet(Key key, String
attributes, MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SET);
}

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

@Override
public Map<String, SplitResult> getTreatmentsWithConfigByFlagSets(String key, List<String> flagSets, Map<String, Object> attributes) {
return getTreatmentsBySetsWithConfigInternal(key, null, flagSets,
Expand Down Expand Up @@ -295,7 +322,7 @@ private SplitResult getTreatmentWithConfigInternal(String matchingKey, String bu
return SPLIT_RESULT_CONTROL;
}

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

Expand Down Expand Up @@ -381,14 +408,14 @@ private Map<String, SplitResult> getTreatmentsBySetsWithConfigInternal(String ma
return new HashMap<>();
}
Set cleanFlagSets = cleanup(sets);
if (filterSetsAreInConfig(cleanFlagSets, methodEnum).isEmpty()) {
cleanFlagSets = filterSetsAreInConfig(cleanFlagSets, methodEnum);
if (cleanFlagSets.isEmpty()) {
return new HashMap<>();
}
List<String> featureFlagNames = new ArrayList<>();
try {
checkSDKReady(methodEnum);
featureFlagNames = getAllFlags(cleanFlagSets);
Map<String, SplitResult> result = validateBeforeEvaluate(featureFlagNames, matchingKey, methodEnum,bucketingKey);
Map<String, SplitResult> result = validateBeforeEvaluateByFlagSets(matchingKey, methodEnum,bucketingKey);
if(result != null) {
return result;
}
Expand All @@ -405,7 +432,6 @@ private Map<String, SplitResult> getTreatmentsBySetsWithConfigInternal(String ma
return createMapControl(featureFlagNames);
}
}

private Map<String, SplitResult> processEvaluatorResult(Map<String, EvaluatorImp.TreatmentLabelAndChangeNumber> evaluatorResult,
MethodEnum methodEnum, String matchingKey, String bucketingKey, Map<String,
Object> attributes, long initTime){
Expand All @@ -430,13 +456,28 @@ private Map<String, SplitResult> processEvaluatorResult(Map<String, EvaluatorImp
return result;
}

private Map<String, SplitResult> validateBeforeEvaluateByFlagSets(String matchingKey, MethodEnum methodEnum,
String bucketingKey) {
if (_container.isDestroyed()) {
_log.error(CLIENT_DESTROY);
return new HashMap<>();
}
if (!KeyValidator.isValid(matchingKey, MATCHING_KEY, _config.maxStringLength(), methodEnum.getMethod())) {
return new HashMap<>();
}
if (!KeyValidator.bucketingKeyIsValid(bucketingKey, _config.maxStringLength(), methodEnum.getMethod())) {
return new HashMap<>();
}
return null;
}

private Map<String, SplitResult> validateBeforeEvaluate(List<String> featureFlagNames, String matchingKey, MethodEnum methodEnum,
String bucketingKey) {
if (_container.isDestroyed()) {
_log.error(CLIENT_DESTROY);
return createMapControl(featureFlagNames);
}
if (!KeyValidator.isValid(matchingKey, "matchingKey", _config.maxStringLength(), methodEnum.getMethod())) {
if (!KeyValidator.isValid(matchingKey, MATCHING_KEY, _config.maxStringLength(), methodEnum.getMethod())) {
return createMapControl(featureFlagNames);
}
if (!KeyValidator.bucketingKeyIsValid(bucketingKey, _config.maxStringLength(), methodEnum.getMethod())) {
Expand All @@ -447,8 +488,8 @@ private Map<String, SplitResult> validateBeforeEvaluate(List<String> featureFlag
}
return null;
}
private List<String> filterSetsAreInConfig(Set<String> sets, MethodEnum methodEnum) {
List<String> setsToReturn = new ArrayList<>();
private Set<String> filterSetsAreInConfig(Set<String> sets, MethodEnum methodEnum) {
Set<String> setsToReturn = new HashSet<>();
for (String set : sets) {
if (!_flagSetsFilter.intersect(set)) {
_log.warn(String.format("%s: you passed %s which is not part of the configured FlagSetsFilter, " +
Expand All @@ -459,16 +500,6 @@ private List<String> filterSetsAreInConfig(Set<String> sets, MethodEnum methodEn
}
return setsToReturn;
}

private List<String> getAllFlags(Set<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 @@ -63,7 +63,7 @@ private List<String> getFeatureFlagNamesByFlagSets(List<String> flagSets) {
Map<String, HashSet<String>> namesByFlagSets = _splitCacheConsumer.getNamesByFlagSets(flagSets);
for (String set: flagSets) {
HashSet<String> flags = namesByFlagSets.get(set);
if (flags == null) {
if (flags == null || flags.isEmpty()) {
_log.warn(String.format("You passed %s Flag Set that does not contain cached feature flag names, please double check " +
"what Flag Sets are in use in the Split user interface.", set));
continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public static Set<String> cleanup(List<String> flagSets) {
}
if (!Pattern.matches(FLAG_SET_REGEX, flagSet)) {
_log.warn(String.format("you passed %s, Flag Set must adhere to the regular expressions %s. This means a Flag Set must be " +
"start with a letter, be in lowercase, alphanumeric and have a max length of 50 characters. %s was discarded.",
"start with a letter or number, be in lowercase, alphanumeric and have a max length of 50 characters. %s was discarded.",
flagSet, FLAG_SET_REGEX, flagSet));
continue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,7 @@ public Map<String, HashSet<String>> getNamesByFlagSets(List<String> flagSets) {
Map<String, HashSet<String>> toReturn = new HashMap<>();
for (String set: flagSets) {
HashSet<String> keys = _flagSets.get(set);
if(keys != null){
toReturn.put(set, keys);
}
toReturn.put(set, keys);
}
return toReturn;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,6 @@ ConfigConsumer generateConfig(SplitClientConfig splitClientConfig, Map<String, L
config.setActiveFactories(factoryInstances.size());
config.setRedundantFactories(getRedundantFactories(factoryInstances));
config.setTags(tags.size() < 10 ? tags : tags.subList(0, 10));
int invalidSets = splitClientConfig.getInvalidSets();
config.setFlagSetsTotal(splitClientConfig.getSetsFilter().size() + invalidSets);
config.setFlagSetsInvalid(invalidSets);
return config;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import com.google.common.annotations.VisibleForTesting;
import io.split.client.dtos.UniqueKeys;
import io.split.client.utils.Utils;
import io.split.engine.segments.SegmentFetcherImp;
import io.split.service.HttpPostImp;
import io.split.telemetry.domain.Config;
import io.split.telemetry.domain.Stats;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.split.telemetry.synchronizer;

import io.split.client.SplitClientConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1955,7 +1955,7 @@ public void testTreatmentsByFlagSets() {
fetchManyResult.put(test2, parsedSplit2);
when(splitCacheConsumer.fetchMany(new ArrayList<>(Arrays.asList(test2, test)))).thenReturn(fetchManyResult);

List<String> sets = new ArrayList<>(Arrays.asList("set1", "set3"));
List<String> sets = new ArrayList<>(Arrays.asList("set3", "set1"));
Map<String, HashSet<String>> flagsBySets = new HashMap<>();
flagsBySets.put("set1", new HashSet<>(Arrays.asList(test)));
flagsBySets.put("set3", new HashSet<>(Arrays.asList(test2)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,10 @@ public void evaluateWithWhitelistConditionReturnTreatment() {
@Test
public void evaluateWithSets() {
ParsedSplit split = ParsedSplit.createParsedSplitForTests(SPLIT_NAME, 0, false, DEFAULT_TREATMENT_VALUE, _conditions, TRAFFIC_TYPE_VALUE, CHANGE_NUMBER, 2, new HashSet<>(Arrays.asList("set1", "set2")));
List<String> sets = new ArrayList<>(Arrays.asList("set1"));
List<String> sets = new ArrayList<>(Arrays.asList("set1", "empty_set"));
Map<String, HashSet<String>> flagSets = new HashMap<>();
flagSets.put("set1", new HashSet<>(Arrays.asList(SPLIT_NAME)));
flagSets.put("empty_set", null);
Mockito.when(_splitCacheConsumer.getNamesByFlagSets(sets)).thenReturn(flagSets);
Map<String, ParsedSplit> parsedSplits = new HashMap<>();
parsedSplits.put(SPLIT_NAME, split);
Expand Down
Loading