diff --git a/CHANGES.txt b/CHANGES.txt index 5a060d81e..36b9b2662 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -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. diff --git a/client/pom.xml b/client/pom.xml index 131bfe230..c3882baba 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -5,7 +5,7 @@ io.split.client java-client-parent - 4.10.1 + 4.10.2 java-client jar diff --git a/client/src/main/java/io/split/client/SplitClient.java b/client/src/main/java/io/split/client/SplitClient.java index e997be448..a07718b1f 100644 --- a/client/src/main/java/io/split/client/SplitClient.java +++ b/client/src/main/java/io/split/client/SplitClient.java @@ -256,7 +256,7 @@ public interface SplitClient { Map getTreatmentsWithConfig(String key, List featureFlagNames, Map attributes); /** - * Same as {@link #getTreatments(Key, List, 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. @@ -268,6 +268,21 @@ public interface SplitClient { */ Map getTreatmentsWithConfig(Key key, List featureFlagNames, Map attributes); + /** + * Same as {@link #getTreatments(String, 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. + *

+ *

+ * 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 getTreatmentsByFlagSet(String key, String flagSet); + /** * Same as {@link #getTreatments(String, 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. @@ -300,6 +315,21 @@ public interface SplitClient { */ Map getTreatmentsByFlagSet(Key key, String flagSet, Map attributes); + /** + * Same as {@link #getTreatments(String, 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. + *

+ *

+ * 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 getTreatmentsByFlagSets(String key, List flagSets); + /** * Same as {@link #getTreatments(String, 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. @@ -332,6 +362,22 @@ public interface SplitClient { */ Map getTreatmentsByFlagSets(Key key, List flagSets, Map attributes); + /** + * Same as {@link #getTreatments(String, 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. + *

+ *

+ * 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 getTreatmentsWithConfigByFlagSet(String key, String flagSet); + /** * Same as {@link #getTreatments(String, 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. @@ -366,6 +412,22 @@ public interface SplitClient { */ Map getTreatmentsWithConfigByFlagSet(Key key, String flagSet, Map attributes); + /** + * Same as {@link #getTreatments(String, 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. + *

+ *

+ * 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 getTreatmentsWithConfigByFlagSets(String key, List flagSets); + /** * Same as {@link #getTreatments(String, 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. @@ -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 */ diff --git a/client/src/main/java/io/split/client/SplitClientImpl.java b/client/src/main/java/io/split/client/SplitClientImpl.java index 5ac3f934c..e876871e3 100644 --- a/client/src/main/java/io/split/client/SplitClientImpl.java +++ b/client/src/main/java/io/split/client/SplitClientImpl.java @@ -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); @@ -150,6 +151,13 @@ public Map getTreatmentsWithConfig(Key key, List fe MethodEnum.TREATMENTS_WITH_CONFIG); } + @Override + public Map 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 getTreatmentsByFlagSet(String key, String flagSet, Map attributes) { return getTreatmentsBySetsWithConfigInternal(key, null, new ArrayList<>(Arrays.asList(flagSet)), @@ -164,6 +172,13 @@ public Map getTreatmentsByFlagSet(Key key, String flagSet, Map e.getValue().treatment())); } + @Override + public Map getTreatmentsByFlagSets(String key, List 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 getTreatmentsByFlagSets(String key, List flagSets, Map attributes) { return getTreatmentsBySetsWithConfigInternal(key, null, flagSets, @@ -178,6 +193,12 @@ public Map getTreatmentsByFlagSets(Key key, List flagSet .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment())); } + @Override + public Map 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 getTreatmentsWithConfigByFlagSet(String key, String flagSet, Map attributes) { return getTreatmentsBySetsWithConfigInternal(key, null, new ArrayList<>(Arrays.asList(flagSet)), @@ -190,6 +211,12 @@ public Map getTreatmentsWithConfigByFlagSet(Key key, String attributes, MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SET); } + @Override + public Map getTreatmentsWithConfigByFlagSets(String key, List flagSets) { + return getTreatmentsBySetsWithConfigInternal(key, null, flagSets, + null, MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS); + } + @Override public Map getTreatmentsWithConfigByFlagSets(String key, List flagSets, Map attributes) { return getTreatmentsBySetsWithConfigInternal(key, null, flagSets, @@ -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; } @@ -381,14 +408,14 @@ private Map 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 featureFlagNames = new ArrayList<>(); try { checkSDKReady(methodEnum); - featureFlagNames = getAllFlags(cleanFlagSets); - Map result = validateBeforeEvaluate(featureFlagNames, matchingKey, methodEnum,bucketingKey); + Map result = validateBeforeEvaluateByFlagSets(matchingKey, methodEnum,bucketingKey); if(result != null) { return result; } @@ -405,7 +432,6 @@ private Map getTreatmentsBySetsWithConfigInternal(String ma return createMapControl(featureFlagNames); } } - private Map processEvaluatorResult(Map evaluatorResult, MethodEnum methodEnum, String matchingKey, String bucketingKey, Map attributes, long initTime){ @@ -430,13 +456,28 @@ private Map processEvaluatorResult(Map 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 validateBeforeEvaluate(List 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())) { @@ -447,8 +488,8 @@ private Map validateBeforeEvaluate(List featureFlag } return null; } - private List filterSetsAreInConfig(Set sets, MethodEnum methodEnum) { - List setsToReturn = new ArrayList<>(); + private Set filterSetsAreInConfig(Set sets, MethodEnum methodEnum) { + Set 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, " + @@ -459,16 +500,6 @@ private List filterSetsAreInConfig(Set sets, MethodEnum methodEn } return setsToReturn; } - - private List getAllFlags(Set sets) { - Map> namesBySets = _splitCacheConsumer.getNamesByFlagSets(new ArrayList<>(sets)); - HashSet 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 attributes) { try { diff --git a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java index db4da8637..eb56009f9 100644 --- a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java +++ b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java @@ -63,7 +63,7 @@ private List getFeatureFlagNamesByFlagSets(List flagSets) { Map> namesByFlagSets = _splitCacheConsumer.getNamesByFlagSets(flagSets); for (String set: flagSets) { HashSet 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; diff --git a/client/src/main/java/io/split/inputValidation/FlagSetsValidator.java b/client/src/main/java/io/split/inputValidation/FlagSetsValidator.java index 4efccb0b8..6b6df2d88 100644 --- a/client/src/main/java/io/split/inputValidation/FlagSetsValidator.java +++ b/client/src/main/java/io/split/inputValidation/FlagSetsValidator.java @@ -33,7 +33,7 @@ public static Set cleanup(List 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; } diff --git a/client/src/main/java/io/split/storages/memory/InMemoryCacheImp.java b/client/src/main/java/io/split/storages/memory/InMemoryCacheImp.java index 7920a6af0..62baaf44b 100644 --- a/client/src/main/java/io/split/storages/memory/InMemoryCacheImp.java +++ b/client/src/main/java/io/split/storages/memory/InMemoryCacheImp.java @@ -110,9 +110,7 @@ public Map> getNamesByFlagSets(List flagSets) { Map> toReturn = new HashMap<>(); for (String set: flagSets) { HashSet keys = _flagSets.get(set); - if(keys != null){ - toReturn.put(set, keys); - } + toReturn.put(set, keys); } return toReturn; } diff --git a/client/src/main/java/io/split/storages/pluggable/synchronizer/TelemetryConsumerSubmitter.java b/client/src/main/java/io/split/storages/pluggable/synchronizer/TelemetryConsumerSubmitter.java index 61d74bc0a..2e80abdfc 100644 --- a/client/src/main/java/io/split/storages/pluggable/synchronizer/TelemetryConsumerSubmitter.java +++ b/client/src/main/java/io/split/storages/pluggable/synchronizer/TelemetryConsumerSubmitter.java @@ -64,9 +64,6 @@ ConfigConsumer generateConfig(SplitClientConfig splitClientConfig, Map(Arrays.asList(test2, test)))).thenReturn(fetchManyResult); - List sets = new ArrayList<>(Arrays.asList("set1", "set3")); + List sets = new ArrayList<>(Arrays.asList("set3", "set1")); Map> flagsBySets = new HashMap<>(); flagsBySets.put("set1", new HashSet<>(Arrays.asList(test))); flagsBySets.put("set3", new HashSet<>(Arrays.asList(test2))); diff --git a/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java b/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java index 272b5aa5b..e6598071b 100644 --- a/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java +++ b/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java @@ -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 sets = new ArrayList<>(Arrays.asList("set1")); + List sets = new ArrayList<>(Arrays.asList("set1", "empty_set")); Map> 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 parsedSplits = new HashMap<>(); parsedSplits.put(SPLIT_NAME, split); diff --git a/client/src/test/java/io/split/storages/memory/InMemoryCacheTest.java b/client/src/test/java/io/split/storages/memory/InMemoryCacheTest.java index 5a42d2915..374f734c9 100644 --- a/client/src/test/java/io/split/storages/memory/InMemoryCacheTest.java +++ b/client/src/test/java/io/split/storages/memory/InMemoryCacheTest.java @@ -23,7 +23,9 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; public class InMemoryCacheTest { @@ -229,10 +231,15 @@ public void testGetNamesByFlagSets() { assertTrue(namesByFlagSets.get("set1").contains("splitName_2")); assertFalse(namesByFlagSets.get("set1").contains("splitName_3")); assertFalse(namesByFlagSets.get("set1").contains("splitName_4")); - assertFalse(namesByFlagSets.keySet().contains("set3")); + assertTrue(namesByFlagSets.keySet().contains("set3")); + assertNull(namesByFlagSets.get("set3")); _cache.remove("splitName_2"); namesByFlagSets = _cache.getNamesByFlagSets(new ArrayList<>(Arrays.asList("set1", "set2", "set3"))); assertFalse(namesByFlagSets.get("set1").contains("splitName_2")); + _cache.remove("splitName_1"); + namesByFlagSets = _cache.getNamesByFlagSets(new ArrayList<>(Arrays.asList("set1", "set2", "set3"))); + assertFalse(namesByFlagSets.get("set1").contains("splitName_1")); + assertTrue(namesByFlagSets.get("set1").isEmpty()); } } \ No newline at end of file diff --git a/client/src/test/java/io/split/storages/pluggable/synchronizer/TelemetryConsumerSubmitterTest.java b/client/src/test/java/io/split/storages/pluggable/synchronizer/TelemetryConsumerSubmitterTest.java index c73a7ba3f..77dba1aa4 100644 --- a/client/src/test/java/io/split/storages/pluggable/synchronizer/TelemetryConsumerSubmitterTest.java +++ b/client/src/test/java/io/split/storages/pluggable/synchronizer/TelemetryConsumerSubmitterTest.java @@ -38,8 +38,6 @@ public void testSynchronizeConfig() { ConfigConsumer config = telemetrySynchronizer.generateConfig(splitClientConfig, ApiKeyCounter.getApiKeyCounterInstance().getFactoryInstances(), Stream.of("tag1", "tag2").collect(Collectors.toList())); Assert.assertEquals(3, config.getRedundantFactories()); Assert.assertEquals(2, config.getTags().size()); - Assert.assertEquals(3, config.getFlagSetsTotal()); - Assert.assertEquals(1, config.getFlagSetsInvalid()); } @Test diff --git a/pluggable-storage/pom.xml b/pluggable-storage/pom.xml index 693016b51..6d8a9728e 100644 --- a/pluggable-storage/pom.xml +++ b/pluggable-storage/pom.xml @@ -6,7 +6,7 @@ java-client-parent io.split.client - 4.10.1 + 4.10.2 2.1.0 diff --git a/pom.xml b/pom.xml index d3d441c0e..4cb3c90ce 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 io.split.client java-client-parent - 4.10.1 + 4.10.2 diff --git a/redis-wrapper/pom.xml b/redis-wrapper/pom.xml index fb354ff47..2c089f60f 100644 --- a/redis-wrapper/pom.xml +++ b/redis-wrapper/pom.xml @@ -6,7 +6,7 @@ java-client-parent io.split.client - 4.10.1 + 4.10.2 redis-wrapper 3.1.0 @@ -35,12 +35,6 @@ junit test - - io.split.client - pluggable-storage - 2.1.0 - compile - diff --git a/testing/pom.xml b/testing/pom.xml index 704c77320..303309ef8 100644 --- a/testing/pom.xml +++ b/testing/pom.xml @@ -5,7 +5,7 @@ io.split.client java-client-parent - 4.10.1 + 4.10.2 java-client-testing jar diff --git a/testing/src/main/java/io/split/client/testing/SplitClientForTest.java b/testing/src/main/java/io/split/client/testing/SplitClientForTest.java index 7fee29996..0c9173457 100644 --- a/testing/src/main/java/io/split/client/testing/SplitClientForTest.java +++ b/testing/src/main/java/io/split/client/testing/SplitClientForTest.java @@ -132,6 +132,11 @@ public Map getTreatmentsWithConfig(Key key, List fe return treatments; } + @Override + public Map getTreatmentsByFlagSet(String key, String flagSet) { + return null; + } + @Override public Map getTreatmentsByFlagSet(String key, String flagSet, Map attributes) { return new HashMap<>(); @@ -142,6 +147,11 @@ public Map getTreatmentsByFlagSet(Key key, String flagSet, Map(); } + @Override + public Map getTreatmentsByFlagSets(String key, List flagSets) { + return null; + } + @Override public Map getTreatmentsByFlagSets(String key, List flagSets, Map attributes) { return new HashMap<>(); @@ -152,6 +162,11 @@ public Map getTreatmentsByFlagSets(Key key, List flagSet return new HashMap<>(); } + @Override + public Map getTreatmentsWithConfigByFlagSet(String key, String flagSet) { + return null; + } + @Override public Map getTreatmentsWithConfigByFlagSet(String key, String flagSet, Map attributes) { return new HashMap<>(); @@ -162,6 +177,11 @@ public Map getTreatmentsWithConfigByFlagSet(Key key, String return new HashMap<>(); } + @Override + public Map getTreatmentsWithConfigByFlagSets(String key, List flagSets) { + return null; + } + @Override public Map getTreatmentsWithConfigByFlagSets(String key, List flagSets, Map attributes) { return new HashMap<>();