Skip to content

Commit

Permalink
Treatments by set in treatment manager (#532)
Browse files Browse the repository at this point in the history
  • Loading branch information
gthea authored Sep 6, 2023
2 parents 7e9453c + ca511ac commit d873f04
Show file tree
Hide file tree
Showing 13 changed files with 645 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import io.split.android.client.storage.common.SplitStorageContainer;
import io.split.android.client.storage.attributes.AttributesStorage;
import io.split.android.client.storage.attributes.PersistentAttributesStorage;
import io.split.android.client.storage.splits.SplitsStorage;
import io.split.android.client.telemetry.TelemetrySynchronizer;
import io.split.android.client.telemetry.storage.TelemetryInitProducer;
import io.split.android.client.validators.AttributesValidatorImpl;
Expand Down Expand Up @@ -75,15 +76,17 @@ public SplitClientFactoryImpl(@NonNull SplitFactory splitFactory,
mStorageContainer.getPersistentAttributesStorage());
mSplitParser = new SplitParser(mStorageContainer.getMySegmentsStorageContainer());
mSplitValidator = new SplitValidatorImpl();
SplitsStorage splitsStorage = mStorageContainer.getSplitsStorage();
mTreatmentManagerFactory = new TreatmentManagerFactoryImpl(
keyValidator,
mSplitValidator,
customerImpressionListener,
config.labelsEnabled(),
new AttributesMergerImpl(),
mStorageContainer.getTelemetryStorage(),
new EvaluatorImpl(mStorageContainer.getSplitsStorage(), mSplitParser),
checkNotNull(configuredFlagSets)
mSplitParser,
configuredFlagSets,
splitsStorage
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ public LocalhostSplitClient(@NonNull LocalhostSplitFactory container,
mTreatmentManager = new TreatmentManagerImpl(mKey.matchingKey(), mKey.bucketingKey(),
mEvaluator, new KeyValidatorImpl(),
new SplitValidatorImpl(), getImpressionsListener(splitClientConfig),
splitClientConfig.labelsEnabled(), eventsManager, attributesManager, attributesMerger, telemetryStorageProducer, configuredFlagSets);
splitClientConfig.labelsEnabled(), eventsManager, attributesManager, attributesMerger,
telemetryStorageProducer, configuredFlagSets, splitsStorage);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ private SplitTaskExecutionInfo sync(long till, boolean clearBeforeUpdate, boolea
mTelemetryRuntimeProducer.recordSyncError(OperationType.SPLITS, e.getHttpStatus());

if (HttpStatus.fromCode(e.getHttpStatus()) == HttpStatus.URI_TOO_LONG) {
Logger.e("SDK initialization: the amount of flag sets provided is big, causing URI length error");
return SplitTaskExecutionInfo.error(SplitTaskType.SPLITS_SYNC,
Collections.singletonMap(SplitTaskExecutionInfo.DO_NOT_RETRY, true));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ public enum Method {
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 final String _method;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,9 @@ public List<String> cleanup(List<String> values) {

return new ArrayList<>(cleanedUpSets);
}

@Override
public boolean isValid(String value) {
return value != null && value.trim().matches(FLAG_SET_REGEX);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@
public interface SplitFilterValidator {

List<String> cleanup(List<String> values);

boolean isValid(String value);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package io.split.android.client.validators;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.List;
import java.util.Map;
import io.split.android.client.SplitResult;
Expand All @@ -13,4 +16,12 @@ public interface TreatmentManager {
Map<String, String> getTreatments(List<String> splits, Map<String, Object> attributes, boolean isClientDestroyed);

Map<String, SplitResult> getTreatmentsWithConfig(List<String> splits, Map<String, Object> attributes, boolean isClientDestroyed);

Map<String, String> getTreatmentsByFlagSet(@NonNull String flagSet, @Nullable Map<String, Object> attributes, boolean isClientDestroyed);

Map<String, String> getTreatmentsByFlagSets(@NonNull List<String> flagSets, @Nullable Map<String, Object> attributes, boolean isClientDestroyed);

Map<String, SplitResult> getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map<String, Object> attributes, boolean isClientDestroyed);

Map<String, SplitResult> getTreatmentsWithConfigByFlagSets(@NonNull List<String> flagSets, @Nullable Map<String, Object> attributes, boolean isClientDestroyed);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@
import java.util.Set;

import io.split.android.client.Evaluator;
import io.split.android.client.EvaluatorImpl;
import io.split.android.client.api.Key;
import io.split.android.client.attributes.AttributesManager;
import io.split.android.client.attributes.AttributesMerger;
import io.split.android.client.events.ISplitEventsManager;
import io.split.android.client.events.ListenableEventsManager;
import io.split.android.client.impressions.ImpressionListener;
import io.split.android.client.storage.splits.SplitsStorage;
import io.split.android.client.telemetry.storage.TelemetryStorageProducer;
import io.split.android.engine.experiments.SplitParser;

public class TreatmentManagerFactoryImpl implements TreatmentManagerFactory {

Expand All @@ -24,23 +28,26 @@ public class TreatmentManagerFactoryImpl implements TreatmentManagerFactory {
private final TelemetryStorageProducer mTelemetryStorageProducer;
private final Evaluator mEvaluator;
private final Set<String> mConfiguredFlagSets;
private final SplitsStorage mSplitsStorage;

public TreatmentManagerFactoryImpl(@NonNull KeyValidator keyValidator,
@NonNull SplitValidator splitValidator,
@NonNull ImpressionListener customerImpressionListener,
boolean labelsEnabled,
@NonNull AttributesMerger attributesMerger,
@NonNull TelemetryStorageProducer telemetryStorageProducer,
@NonNull Evaluator evaluator,
@NonNull Set<String> configuredFlagSets) {
@NonNull SplitParser splitParser,
@NonNull Set<String> configuredFlagSets,
@NonNull SplitsStorage splitsStorage) {
mKeyValidator = checkNotNull(keyValidator);
mSplitValidator = checkNotNull(splitValidator);
mCustomerImpressionListener = checkNotNull(customerImpressionListener);
mLabelsEnabled = labelsEnabled;
mAttributesMerger = checkNotNull(attributesMerger);
mTelemetryStorageProducer = checkNotNull(telemetryStorageProducer);
mEvaluator = checkNotNull(evaluator);
mConfiguredFlagSets = configuredFlagSets;
mEvaluator = new EvaluatorImpl(splitsStorage, splitParser);
mConfiguredFlagSets = checkNotNull(configuredFlagSets);
mSplitsStorage = checkNotNull(splitsStorage);
}

@Override
Expand All @@ -57,7 +64,8 @@ public TreatmentManager getTreatmentManager(Key key, ListenableEventsManager eve
attributesManager,
mAttributesMerger,
mTelemetryStorageProducer,
mConfiguredFlagSets
mConfiguredFlagSets,
mSplitsStorage
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
import static com.google.common.base.Preconditions.checkNotNull;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
Expand All @@ -19,6 +23,7 @@
import io.split.android.client.events.SplitEvent;
import io.split.android.client.impressions.Impression;
import io.split.android.client.impressions.ImpressionListener;
import io.split.android.client.storage.splits.SplitsStorage;
import io.split.android.client.telemetry.model.Method;
import io.split.android.client.telemetry.storage.TelemetryStorageProducer;
import io.split.android.client.utils.logger.Logger;
Expand All @@ -31,6 +36,10 @@ private static class ValidationTag {
public static final String GET_TREATMENTS = "getTreatments";
public static final String GET_TREATMENT_WITH_CONFIG = "getTreatmentWithConfig";
public static final String GET_TREATMENTS_WITH_CONFIG = "getTreatmentsWithConfig";
public static final String GET_TREATMENTS_BY_FLAG_SET = "getTreatmentsByFlagSet";
public static final String GET_TREATMENTS_BY_FLAG_SETS = "getTreatmentsByFlagSets";
public static final String GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET = "getTreatmentsWithConfigByFlagSet";
public static final String GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS = "getTreatmentsWithConfigByFlagSets";
}

private final String CLIENT_DESTROYED_MESSAGE = "Client has already been destroyed - no calls possible";
Expand All @@ -50,6 +59,8 @@ private static class ValidationTag {
private final AttributesMerger mAttributesMerger;
private final TelemetryStorageProducer mTelemetryStorageProducer;
private final Set<String> mConfiguredFlagSets;
private final SplitsStorage mSplitsStorage;
private final SplitFilterValidator mFlagSetsValidator;

public TreatmentManagerImpl(String matchingKey,
String bucketingKey,
Expand All @@ -62,7 +73,8 @@ public TreatmentManagerImpl(String matchingKey,
@NonNull AttributesManager attributesManager,
@NonNull AttributesMerger attributesMerger,
@NonNull TelemetryStorageProducer telemetryStorageProducer,
@NonNull Set<String> configuredFlagSets) {
@NonNull Set<String> configuredFlagSets,
@NonNull SplitsStorage splitsStorage) {
mEvaluator = evaluator;
mKeyValidator = keyValidator;
mSplitValidator = splitValidator;
Expand All @@ -76,6 +88,8 @@ public TreatmentManagerImpl(String matchingKey,
mAttributesMerger = checkNotNull(attributesMerger);
mTelemetryStorageProducer = checkNotNull(telemetryStorageProducer);
mConfiguredFlagSets = checkNotNull(configuredFlagSets);
mSplitsStorage = checkNotNull(splitsStorage);
mFlagSetsValidator = new FlagSetsValidatorImpl();
}

@Override
Expand Down Expand Up @@ -166,6 +180,74 @@ public Map<String, SplitResult> getTreatmentsWithConfig(List<String> splits, Map
return result;
}

@Override
public Map<String, String> getTreatmentsByFlagSet(@NonNull String flagSet, @Nullable Map<String, Object> attributes, boolean isClientDestroyed) {
String validationTag = ValidationTag.GET_TREATMENTS_BY_FLAG_SET;
Set<String> names = getNamesFromSet(validationTag, Collections.singletonList(flagSet));
if (isClientDestroyed) {
mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag);
return controlTreatmentsForSplits(new ArrayList<>(names), validationTag);
}

long start = System.currentTimeMillis();
try {
return evaluateFeatures(names, attributes, validationTag, SplitResult::treatment);
} finally {
recordLatency(Method.TREATMENTS_BY_FLAG_SET, start);
}
}

@Override
public Map<String, String> getTreatmentsByFlagSets(@NonNull List<String> flagSets, @Nullable Map<String, Object> attributes, boolean isClientDestroyed) {
String validationTag = ValidationTag.GET_TREATMENTS_BY_FLAG_SETS;
Set<String> names = getNamesFromSet(validationTag, flagSets);
if (isClientDestroyed) {
mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag);
return controlTreatmentsForSplits(new ArrayList<>(names), validationTag);
}

long start = System.currentTimeMillis();
try {
return evaluateFeatures(names, attributes, validationTag, SplitResult::treatment);
} finally {
recordLatency(Method.TREATMENTS_BY_FLAG_SETS, start);
}
}

@Override
public Map<String, SplitResult> getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map<String, Object> attributes, boolean isClientDestroyed) {
String validationTag = ValidationTag.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET;
Set<String> names = getNamesFromSet(validationTag, Collections.singletonList(flagSet));
if (isClientDestroyed) {
mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag);
return controlTreatmentsForSplitsWithConfig(new ArrayList<>(names), validationTag);
}

long start = System.currentTimeMillis();
try {
return evaluateFeatures(names, attributes, validationTag, ResultTransformer::identity);
} finally {
recordLatency(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SET, start);
}
}

@Override
public Map<String, SplitResult> getTreatmentsWithConfigByFlagSets(@NonNull List<String> flagSets, @Nullable Map<String, Object> attributes, boolean isClientDestroyed) {
String validationTag = ValidationTag.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS;
Set<String> names = getNamesFromSet(validationTag, flagSets);
if (isClientDestroyed) {
mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag);
return controlTreatmentsForSplitsWithConfig(new ArrayList<>(names), validationTag);
}

long start = System.currentTimeMillis();
try {
return evaluateFeatures(names, attributes, validationTag, ResultTransformer::identity);
} finally {
recordLatency(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, start);
}
}

private SplitResult getTreatmentWithConfigWithoutMetrics(String split, Map<String, Object> attributes, String validationTag) {

ValidationErrorInfo errorInfo = mKeyValidator.validate(mMatchingKey, mBucketingKey);
Expand Down Expand Up @@ -285,4 +367,55 @@ private EvaluationResult evaluateIfReady(String splitName,
private void recordLatency(Method treatment, long startTime) {
mTelemetryStorageProducer.recordLatency(treatment, System.currentTimeMillis() - startTime);
}

@NonNull
private Set<String> getNamesFromSet(String validationTag,
@NonNull List<String> flagSets) {

if (flagSets == null) {
return new HashSet<>();
}

List<String> setsToEvaluate = new ArrayList<>();
for (String flagSet : flagSets) {
if (setsToEvaluate.contains(flagSet)) {
continue;
}

boolean isValid = mFlagSetsValidator.isValid(flagSet);
boolean isConfigured = mConfiguredFlagSets.isEmpty() || mConfiguredFlagSets.contains(flagSet);

if (!isValid) {
mValidationLogger.e("you passed " + flagSet + " which is not valid.", validationTag);
} else if (!isConfigured) {
mValidationLogger.e("you passed " + flagSet + " which is not defined in the configuration.", validationTag);
} else {
setsToEvaluate.add(flagSet);
}
}

if (setsToEvaluate.isEmpty()) {
return new HashSet<>();
}

return mSplitsStorage.getNamesByFlagSets(setsToEvaluate);
}

private <T> Map<String, T> evaluateFeatures(Set<String> names, @Nullable Map<String, Object> attributes, String validationTag, ResultTransformer<T> transformer) {
Map<String, T> result = new HashMap<>();
for (String featureFlagName : names) {
SplitResult splitResult = getTreatmentWithConfigWithoutMetrics(featureFlagName, attributes, validationTag);
result.put(featureFlagName, transformer.transform(splitResult));
}
return result;
}

private interface ResultTransformer<T> {

T transform(SplitResult splitResult);

static SplitResult identity(SplitResult splitResult) {
return splitResult;
}
}
}
Loading

0 comments on commit d873f04

Please sign in to comment.