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

Treatments by set in treatment manager #532

Merged
merged 10 commits into from
Sep 6, 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
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