Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
483 changes: 483 additions & 0 deletions client/src/main/java/io/split/client/SplitClient.java

Large diffs are not rendered by default.

258 changes: 225 additions & 33 deletions client/src/main/java/io/split/client/SplitClientImpl.java

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions client/src/main/java/io/split/client/dtos/EvaluationOptions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.split.client.dtos;

import java.util.Map;

public class EvaluationOptions {
private Map<String, Object> _properties;

public EvaluationOptions(Map<String, Object> properties) {
_properties = properties;
}
public Map<String, Object> getProperties() {
return _properties;
};
}
8 changes: 8 additions & 0 deletions client/src/main/java/io/split/client/dtos/KeyImpression.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ public class KeyImpression {
/* package private */ static final String FIELD_TIME = "m";
/* package private */ static final String FIELD_CHANGE_NUMBER = "c";
/* package private */ static final String FIELD_PREVIOUS_TIME = "pt";
/* package private */ static final String FIELD_PROPERTIES = "properties";

public static int MAX_PROPERTIES_LENGTH_BYTES = 32 * 1024;

public transient String feature; // Non-serializable

Expand All @@ -39,6 +42,9 @@ public class KeyImpression {
@SerializedName(FIELD_PREVIOUS_TIME)
public Long previousTime;

@SerializedName(FIELD_PROPERTIES)
public String properties;

@Override
public boolean equals(Object o) {
if (this == o) return true;
Expand All @@ -50,6 +56,7 @@ public boolean equals(Object o) {
if (!Objects.equals(feature, that.feature)) return false;
if (!keyName.equals(that.keyName)) return false;
if (!treatment.equals(that.treatment)) return false;
if (properties != null && !properties.equals(that.properties)) return false;

if (bucketingKey == null) {
return that.bucketingKey == null;
Expand Down Expand Up @@ -78,6 +85,7 @@ public static KeyImpression fromImpression(Impression i) {
ki.treatment = i.treatment();
ki.label = i.appliedRule();
ki.previousTime = i.pt();
ki.properties = i.properties();
return ki;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ public class Impression {
private final Long _changeNumber;
private Long _pt;
private final Map<String, Object> _attributes;
private final String _properties;


public Impression(String key, String bucketingKey, String featureFlag, String treatment, long time, String appliedRule,
Long changeNumber, Map<String, Object> atributes) {
Long changeNumber, Map<String, Object> atributes, String properties) {
_key = key;
_bucketingKey = bucketingKey;
_split = featureFlag;
Expand All @@ -28,6 +29,7 @@ public Impression(String key, String bucketingKey, String featureFlag, String tr
_appliedRule = appliedRule;
_changeNumber = changeNumber;
_attributes = atributes;
_properties = properties;
}

public String key() {
Expand Down Expand Up @@ -67,4 +69,8 @@ public Long pt() {
}

public Impression withPreviousTime(Long pt) { _pt = pt; return this; }

public String properties() {
return _properties;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ public ProcessImpressionDebug(boolean listenerEnabled, ImpressionObserver impres
@Override
public ImpressionsResult process(List<Impression> impressions) {
for(Impression impression : impressions) {
if (impression.properties() != null) {
continue;
}
impression.withPreviousTime(_impressionObserver.testAndSet(impression));
}
List<Impression> impressionForListener = this._listenerEnabled ? impressions : null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ public ProcessImpressionOptimized(boolean listenerEnabled, ImpressionObserver im
public ImpressionsResult process(List<Impression> impressions) {
List<Impression> impressionsToQueue = new ArrayList<>();
for(Impression impression : impressions) {
if (impression.properties() != null) {
impressionsToQueue.add(impression);
continue;
}
impression = impression.withPreviousTime(_impressionObserver.testAndSet(impression));
if(!Objects.isNull(impression.pt()) && impression.pt() != 0){
_impressionCounter.inc(impression.split(), impression.time(), 1);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package io.split.inputValidation;

import io.split.client.dtos.KeyImpression;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.Map;

public class ImpressionPropertiesValidator {
private static final Logger _log = LoggerFactory.getLogger(ImpressionPropertiesValidator.class);

public static ImpressionPropertiesValidatorResult propertiesAreValid(Map<String, Object> properties) {
int size = 1024; // We assume 1kb events without properties (750 bytes avg measured)

if (properties == null) {
return new ImpressionPropertiesValidatorResult(true);
}
if (properties.size() > 300) {
_log.warn("Impression properties has more than 300 properties. Some of them will be trimmed when processed");
}

Map<String, Object> result = new HashMap<>();
for (Map.Entry<String, Object> entry : properties.entrySet()) {
if (entry.getKey() == null || entry.getKey().isEmpty()) {
continue;
}

size += entry.getKey().length();
Object value = entry.getValue();

if (!(value instanceof Number) && !(value instanceof Boolean) && !(value instanceof String)) {
_log.warn(String.format("Property %s is of invalid type. Setting value to null", entry.getKey()));
value = null;
}

if (value instanceof String) {
size += ((String) value).length();
}

if (size > KeyImpression.MAX_PROPERTIES_LENGTH_BYTES) {
_log.error(String.format("The maximum size allowed for the properties is 32768 bytes. "
+ "Current one is %s bytes. Properties field is ignored", size));

return new ImpressionPropertiesValidatorResult(false);
}

result.put(entry.getKey(), value);
}

return new ImpressionPropertiesValidatorResult(true, size, result);
}

public static class ImpressionPropertiesValidatorResult {
private final boolean _success;
private final int _propertySize;
private final Map<String, Object> _value;

public ImpressionPropertiesValidatorResult(boolean success, int propertySize, Map<String, Object> value) {
_success = success;
_propertySize = propertySize;
_value = value;
}

public ImpressionPropertiesValidatorResult(boolean success) {
_success = success;
_propertySize = 0;
_value = null;
}

public boolean getSuccess() {
return _success;
}

public int getSize() {
return _propertySize;
}

public Map<String, Object> getValue() {
return _value;
}
}
}
91 changes: 84 additions & 7 deletions client/src/test/java/io/split/client/SplitClientImplTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,7 @@ public void attributesWork() {
);

assertEquals("on", client.getTreatment("[email protected]", test));
assertEquals("on", client.getTreatment("[email protected]", test, null));
assertEquals("on", client.getTreatment("[email protected]", test, new HashMap<>()));
assertEquals("on", client.getTreatment("[email protected]", test, ImmutableMap.<String, Object>of()));
assertEquals("on", client.getTreatment("[email protected]", test, ImmutableMap.<String, Object>of("age", 10)));
assertEquals("off", client.getTreatment("[email protected]", test, ImmutableMap.<String, Object>of("age", 9)));
Expand Down Expand Up @@ -599,7 +599,7 @@ public void attributesWork2() {
);

assertEquals("off", client.getTreatment("[email protected]", test));
assertEquals("off", client.getTreatment("[email protected]", test, null));
assertEquals("off", client.getTreatment("[email protected]", test, new HashMap<>()));
assertEquals("off", client.getTreatment("[email protected]", test, ImmutableMap.<String, Object>of()));

assertEquals("off", client.getTreatment("[email protected]", test, ImmutableMap.<String, Object>of("age", 10)));
Expand Down Expand Up @@ -634,7 +634,7 @@ public void attributesGreaterThanNegativeNumber() {
);

assertEquals("off", client.getTreatment("[email protected]", test));
assertEquals("off", client.getTreatment("[email protected]", test, null));
assertEquals("off", client.getTreatment("[email protected]", test, new HashMap<>()));
assertEquals("off", client.getTreatment("[email protected]", test, ImmutableMap.<String, Object>of()));
assertEquals("off", client.getTreatment("[email protected]", test, ImmutableMap.<String, Object>of("age", 10)));
assertEquals("on", client.getTreatment("[email protected]", test, ImmutableMap.<String, Object>of("age", -20)));
Expand Down Expand Up @@ -671,7 +671,7 @@ public void attributesForSets() {
);

assertEquals("off", client.getTreatment("[email protected]", test));
assertEquals("off", client.getTreatment("[email protected]", test, null));
assertEquals("off", client.getTreatment("[email protected]", test, new HashMap<>()));

assertEquals("off", client.getTreatment("[email protected]", test, ImmutableMap.<String, Object>of()));
assertEquals("off", client.getTreatment("[email protected]", test, ImmutableMap.<String, Object>of("products", Lists.newArrayList())));
Expand Down Expand Up @@ -1894,7 +1894,7 @@ public void testTreatmentsByFlagSet() {
Map<String, String> getTreatmentResult;
for (int i = 0; i < numKeys; i++) {
String randomKey = RandomStringUtils.random(10);
getTreatmentResult = client.getTreatmentsByFlagSet(randomKey, "set1", null);
getTreatmentResult = client.getTreatmentsByFlagSet(randomKey, "set1", new HashMap<>());
assertEquals("on", getTreatmentResult.get(test));
}
verify(splitCacheConsumer, times(numKeys)).fetchMany(new ArrayList<>(Arrays.asList(test)));
Expand Down Expand Up @@ -1927,7 +1927,7 @@ public void testTreatmentsByFlagSetInvalid() {
new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE,
flagSetsFilter
);
assertTrue(client.getTreatmentsByFlagSet(RandomStringUtils.random(10), "", null).isEmpty());
assertTrue(client.getTreatmentsByFlagSet(RandomStringUtils.random(10), "", new HashMap<>()).isEmpty());
}

@Test
Expand Down Expand Up @@ -1974,7 +1974,7 @@ public void testTreatmentsByFlagSets() {
Map<String, String> getTreatmentResult;
for (int i = 0; i < numKeys; i++) {
String randomKey = RandomStringUtils.random(10);
getTreatmentResult = client.getTreatmentsByFlagSets(randomKey, Arrays.asList("set1", "set3"), null);
getTreatmentResult = client.getTreatmentsByFlagSets(randomKey, Arrays.asList("set1", "set3"), new HashMap<>());
assertEquals("on", getTreatmentResult.get(test));
assertEquals("on", getTreatmentResult.get(test2));
}
Expand Down Expand Up @@ -2081,4 +2081,81 @@ public void treatmentsWorksAndHasConfigFlagSets() {

verify(splitCacheConsumer, times(1)).fetchMany(anyList());
}

@Test
public void impressionPropertiesTest() {
String test = "test1";

ParsedCondition age_equal_to_0_should_be_on = new ParsedCondition(ConditionType.ROLLOUT,
CombiningMatcher.of("age", new EqualToMatcher(-20, DataType.NUMBER)),
Lists.newArrayList(partition("on", 100)),
"foolabel"
);

List<ParsedCondition> conditions = Lists.newArrayList(age_equal_to_0_should_be_on);
ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1, new HashSet<>(Arrays.asList("set")), true);
Map<String, ParsedSplit> parsedSplits = new HashMap<>();
parsedSplits.put(test, parsedSplit);

SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class);
SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class);
when(splitCacheConsumer.get(test)).thenReturn(parsedSplit);
when(splitCacheConsumer.fetchMany(Arrays.asList(test))).thenReturn(parsedSplits);
Map<String, HashSet<String>> splits = new HashMap<>();
splits.put("set", new HashSet<>(Arrays.asList(test)));
when(splitCacheConsumer.getNamesByFlagSets(Arrays.asList("set"))).thenReturn(splits);

SDKReadinessGates gates = mock(SDKReadinessGates.class);
ImpressionsManager impressionsManager = mock(ImpressionsManager.class);
SplitClientImpl client = new SplitClientImpl(
mock(SplitFactory.class),
splitCacheConsumer,
impressionsManager,
NoopEventsStorageImp.create(),
config,
gates,
new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE,
new FlagSetsFilterImpl(new HashSet<>())
);
Map<String, Object> attributes = ImmutableMap.<String, Object>of("age", -20, "acv", "1000000");
EvaluationOptions properties = new EvaluationOptions(new HashMap<String, Object>()
{{
put("prop2", "val2");
put("prop1", "val1");
}});
Map<String, String> result = new HashMap<>();
result.put(test, Treatments.ON);
List<String> split_names = Arrays.asList(test);

assertEquals("on", client.getTreatment("[email protected]", test, attributes, properties));
assertEquals("on", client.getTreatmentWithConfig("[email protected]", test, attributes, properties).treatment());
assertEquals("on", client.getTreatments("[email protected]", Arrays.asList(test), attributes, properties).get(test));
assertEquals("on", client.getTreatmentsWithConfig("[email protected]", Arrays.asList(test), attributes, properties).get(test).treatment());
assertEquals("on", client.getTreatmentsByFlagSet("[email protected]", "set", attributes, properties).get(test));
assertEquals("on", client.getTreatmentsByFlagSets("[email protected]", Arrays.asList("set"), attributes, properties).get(test));
assertEquals("on", client.getTreatmentsWithConfigByFlagSet("[email protected]", "set", attributes, properties).get(test).treatment());
assertEquals("on", client.getTreatmentsWithConfigByFlagSets("[email protected]", Arrays.asList("set"), attributes, properties).get(test).treatment());
assertEquals("on", client.getTreatment(new Key("[email protected]", "[email protected]"), test, attributes, properties));
assertEquals("on", client.getTreatmentWithConfig(new Key("[email protected]", "[email protected]"), test, attributes, properties).treatment());
assertEquals("on", client.getTreatments(new Key("[email protected]", "[email protected]"), Arrays.asList(test), attributes, properties).get(test));
assertEquals("on", client.getTreatmentsWithConfig(new Key("[email protected]", "[email protected]"), Arrays.asList(test), attributes, properties).get(test).treatment());
assertEquals("on", client.getTreatmentsByFlagSet(new Key("[email protected]", "[email protected]"), "set", attributes, properties).get(test));
assertEquals("on", client.getTreatmentsByFlagSets(new Key("[email protected]", "[email protected]"), Arrays.asList("set"), attributes, properties).get(test));
assertEquals("on", client.getTreatmentsWithConfigByFlagSet(new Key("[email protected]", "[email protected]"), "set", attributes, properties).get(test).treatment());
assertEquals("on", client.getTreatmentsWithConfigByFlagSets(new Key("[email protected]", "[email protected]"), Arrays.asList("set"), attributes, properties).get(test).treatment());

ArgumentCaptor<List> impressionCaptor = ArgumentCaptor.forClass(List.class);
verify(impressionsManager, times(16)).track(impressionCaptor.capture());
assertNotNull(impressionCaptor.getValue());

DecoratedImpression impression = (DecoratedImpression) impressionCaptor.getAllValues().get(0).get(0);
assertEquals("[email protected]", impression.impression().key());
assertEquals("{\"prop2\":\"val2\",\"prop1\":\"val1\"}", impression.impression().properties());

for (int i=1; i<=15; i++) {
impression = (DecoratedImpression) impressionCaptor.getAllValues().get(i).get(0);
assertEquals("bilal" + i + "@codigo.com", impression.impression().key());
assertEquals("{\"prop2\":\"val2\",\"prop1\":\"val1\"}", impression.impression().properties());
}
}
}
Loading