Skip to content

Commit

Permalink
By name filter in feature flag processor (#536)
Browse files Browse the repository at this point in the history
  • Loading branch information
gthea authored Sep 12, 2023
2 parents eafcc26 + d41b6ea commit 406463b
Show file tree
Hide file tree
Showing 11 changed files with 227 additions and 98 deletions.
4 changes: 2 additions & 2 deletions src/main/java/io/split/android/client/SplitFactoryHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -189,9 +189,9 @@ SplitApiFacade buildApiFacade(SplitClientConfig splitClientConfig,
}

WorkManagerWrapper buildWorkManagerWrapper(Context context, SplitClientConfig splitClientConfig,
String apiKey, String databaseName, Set<String> configuredFlagSets) {
String apiKey, String databaseName, List<SplitFilter> filters) {
return new WorkManagerWrapper(
WorkManager.getInstance(context), splitClientConfig, apiKey, databaseName, configuredFlagSets);
WorkManager.getInstance(context), splitClientConfig, apiKey, databaseName, filters);

}

Expand Down
4 changes: 2 additions & 2 deletions src/main/java/io/split/android/client/SplitFactoryImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,10 @@ public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) {

SplitTaskFactory splitTaskFactory = new SplitTaskFactoryImpl(
config, splitApiFacade, mStorageContainer, splitsFilterQueryStringFromConfig, mEventsManagerCoordinator,
filters, configuredFlagSets, testingConfig);
filters, testingConfig);

cleanUpDabase(splitTaskExecutor, splitTaskFactory);
WorkManagerWrapper workManagerWrapper = factoryHelper.buildWorkManagerWrapper(context, config, apiToken, databaseName, configuredFlagSets);
WorkManagerWrapper workManagerWrapper = factoryHelper.buildWorkManagerWrapper(context, config, apiToken, databaseName, filters);
SplitSingleThreadTaskExecutor splitSingleThreadTaskExecutor = new SplitSingleThreadTaskExecutor();

ImpressionManager impressionManager = new StrategyImpressionManager(factoryHelper.getImpressionStrategy(splitTaskExecutor, splitTaskFactory, mStorageContainer, config));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,7 @@ public Map<String, String> getTreatmentsByFlagSet(@NonNull String flagSet, @Null
} catch (Exception exception) {
Logger.e(exception);

Map<String, String> result = new HashMap<>();
Set<String> namesByFlagSets = mSplitsStorage.getNamesByFlagSets(Collections.singletonList(flagSet));
for (String featureFlagName : namesByFlagSets) {
result.put(featureFlagName, Treatments.CONTROL);
}

return result;
return buildExceptionResult(Collections.singletonList(flagSet));
}
}

Expand All @@ -166,13 +160,7 @@ public Map<String, String> getTreatmentsByFlagSets(@NonNull List<String> flagSet
} catch (Exception exception) {
Logger.e(exception);

Map<String, String> result = new HashMap<>();
Set<String> namesByFlagSets = mSplitsStorage.getNamesByFlagSets(flagSets);
for (String featureFlagName : namesByFlagSets) {
result.put(featureFlagName, Treatments.CONTROL);
}

return result;
return buildExceptionResult(flagSets);
}
}

Expand All @@ -183,13 +171,7 @@ public Map<String, SplitResult> getTreatmentsWithConfigByFlagSet(@NonNull String
} catch (Exception exception) {
Logger.e(exception);

Map<String, SplitResult> result = new HashMap<>();
Set<String> namesByFlagSets = mSplitsStorage.getNamesByFlagSets(Collections.singletonList(flagSet));
for (String featureFlagName : namesByFlagSets) {
result.put(featureFlagName, new SplitResult(Treatments.CONTROL));
}

return result;
return buildExceptionResultWithConfig(Collections.singletonList(flagSet));
}
}

Expand All @@ -200,13 +182,7 @@ public Map<String, SplitResult> getTreatmentsWithConfigByFlagSets(@NonNull List<
} catch (Exception exception) {
Logger.e(exception);

Map<String, SplitResult> result = new HashMap<>();
Set<String> namesByFlagSets = mSplitsStorage.getNamesByFlagSets(flagSets);
for (String featureFlagName : namesByFlagSets) {
result.put(featureFlagName, new SplitResult(Treatments.CONTROL));
}

return result;
return buildExceptionResultWithConfig(flagSets);
}
}

Expand Down Expand Up @@ -324,4 +300,24 @@ public boolean removeAttribute(String attributeName) {
public boolean clearAttributes() {
return true;
}

private Map<String, String> buildExceptionResult(List<String> flagSets) {
Map<String, String> result = new HashMap<>();
Set<String> namesByFlagSets = mSplitsStorage.getNamesByFlagSets(flagSets);
for (String featureFlagName : namesByFlagSets) {
result.put(featureFlagName, Treatments.CONTROL);
}

return result;
}

private Map<String, SplitResult> buildExceptionResultWithConfig(List<String> flagSets) {
Map<String, SplitResult> result = new HashMap<>();
Set<String> namesByFlagSets = mSplitsStorage.getNamesByFlagSets(flagSets);
for (String featureFlagName : namesByFlagSets) {
result.put(featureFlagName, new SplitResult(Treatments.CONTROL));
}

return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ public class ServiceConstants {
public static final String WORKER_PARAM_UNIQUE_KEYS_PER_PUSH = "unique_keys_per_push";
public static final String WORKER_PARAM_UNIQUE_KEYS_ESTIMATED_SIZE_IN_BYTES = "unique_keys_estimated_size_in_bytes";
public static final String WORKER_PARAM_ENCRYPTION_ENABLED = "encryptionEnabled";
public static final String WORKER_PARAM_CONFIGURED_SETS = "configuredSets";
public static final String WORKER_PARAM_CONFIGURED_FILTER_VALUES = "configuredFilterValues";
public static final String WORKER_PARAM_CONFIGURED_FILTER_TYPE = "configuredFilterType";

public static final long LAST_SEEN_IMPRESSION_CACHE_SIZE = 500;
public static final int MY_SEGMENT_V2_DATA_SIZE = 1024 * 10;// bytes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,14 @@ public SplitTaskFactoryImpl(@NonNull SplitClientConfig splitClientConfig,
@Nullable String splitsFilterQueryString,
ISplitEventsManager eventsManager,
@Nullable List<SplitFilter> filters,
@NonNull Set<String> configuredFlagSets,
@Nullable TestingConfig testingConfig) {

mSplitClientConfig = checkNotNull(splitClientConfig);
mSplitApiFacade = checkNotNull(splitApiFacade);
mSplitsStorageContainer = checkNotNull(splitStorageContainer);
mSplitsFilterQueryStringFromConfig = splitsFilterQueryString;
mEventsManager = eventsManager;
mSplitChangeProcessor = new SplitChangeProcessor(configuredFlagSets);
mSplitChangeProcessor = new SplitChangeProcessor(filters);

TelemetryStorage telemetryStorage = mSplitsStorageContainer.getTelemetryStorage();
mTelemetryRuntimeProducer = telemetryStorage;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package io.split.android.client.service.splits;

import androidx.annotation.NonNull;

import java.util.List;

import io.split.android.client.dtos.Split;
import io.split.android.client.dtos.Status;

interface FeatureFlagProcessStrategy {

void process(List<Split> activeFeatureFlags, List<Split> archivedFeatureFlags, Split featureFlag);
}

class StatusProcessStrategy implements FeatureFlagProcessStrategy {

@Override
public void process(List<Split> activeFeatureFlags, List<Split> archivedFeatureFlags, Split featureFlag) {
if (featureFlag.status == Status.ACTIVE) {
activeFeatureFlags.add(featureFlag);
} else {
archivedFeatureFlags.add(featureFlag);
}
}
}

class NamesProcessStrategy implements FeatureFlagProcessStrategy {

private final List<String> mConfiguredValues;
private final StatusProcessStrategy mStatusProcessStrategy;

NamesProcessStrategy(@NonNull List<String> configuredValues, @NonNull StatusProcessStrategy statusProcessStrategy) {
mConfiguredValues = configuredValues;
mStatusProcessStrategy = statusProcessStrategy;
}

@Override
public void process(List<Split> activeFeatureFlags, List<Split> archivedFeatureFlags, Split featureFlag) {
// If the feature flag name is in the filter, we process it according to its status. Otherwise it is ignored
if (mConfiguredValues.contains(featureFlag.name)) {
mStatusProcessStrategy.process(activeFeatureFlags, archivedFeatureFlags, featureFlag);
}
}
}

class SetsProcessStrategy implements FeatureFlagProcessStrategy {

private final List<String> mConfiguredValues;
private final StatusProcessStrategy mStatusProcessStrategy;

SetsProcessStrategy(@NonNull List<String> configuredValues, @NonNull StatusProcessStrategy statusProcessStrategy) {
mConfiguredValues = configuredValues;
mStatusProcessStrategy = statusProcessStrategy;
}

@Override
public void process(List<Split> activeFeatureFlags, List<Split> archivedFeatureFlags, Split featureFlag) {
if (featureFlag.sets == null || featureFlag.sets.isEmpty()) {
archivedFeatureFlags.add(featureFlag);
return;
}

boolean shouldArchive = true;
for (String set : featureFlag.sets) {
if (mConfiguredValues.contains(set)) {
// If the feature flag has at least one set that matches the configured sets,
// we process it according to its status
mStatusProcessStrategy.process(activeFeatureFlags, archivedFeatureFlags, featureFlag);
shouldArchive = false;
break;
}
}

if (shouldArchive) {
archivedFeatureFlags.add(featureFlag);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,31 +1,44 @@
package io.split.android.client.service.splits;

import static com.google.common.base.Preconditions.checkNotNull;

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

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import io.split.android.client.SplitFilter;
import io.split.android.client.dtos.Split;
import io.split.android.client.dtos.SplitChange;
import io.split.android.client.dtos.Status;
import io.split.android.client.storage.splits.ProcessedSplitChange;

public class SplitChangeProcessor {

private final Set<String> mConfiguredSets;
private final SplitFilter mSplitFilter;

private final StatusProcessStrategy mStatusProcessStrategy;

@VisibleForTesting
SplitChangeProcessor() {
this(Collections.emptySet());
this((SplitFilter) null);
}

public SplitChangeProcessor(@NonNull Set<String> configuredSets) {
mConfiguredSets = checkNotNull(configuredSets);
public SplitChangeProcessor(@Nullable List<SplitFilter> filters) {
// We're only supporting one filter type
if (filters == null || filters.isEmpty()) {
mSplitFilter = null;
} else {
mSplitFilter = filters.get(0);
}

mStatusProcessStrategy = new StatusProcessStrategy();
}

public SplitChangeProcessor(@Nullable SplitFilter splitFilter) {
mSplitFilter = splitFilter;
mStatusProcessStrategy = new StatusProcessStrategy();
}

public ProcessedSplitChange process(SplitChange splitChange) {
Expand All @@ -44,54 +57,31 @@ public ProcessedSplitChange process(Split featureFlag, long changeNumber) {
private ProcessedSplitChange buildProcessedSplitChange(List<Split> featureFlags, long changeNumber) {
List<Split> activeFeatureFlags = new ArrayList<>();
List<Split> archivedFeatureFlags = new ArrayList<>();

FeatureFlagProcessStrategy processStrategy = getProcessStrategy(mSplitFilter);

for (Split featureFlag : featureFlags) {
if (featureFlag.name == null) {
continue;
}

if (mConfiguredSets.isEmpty()) {
processAccordingToStatus(activeFeatureFlags, archivedFeatureFlags, featureFlag);
} else {
processAccordingToSets(activeFeatureFlags, archivedFeatureFlags, featureFlag);
}
processStrategy.process(activeFeatureFlags, archivedFeatureFlags, featureFlag);
}

return new ProcessedSplitChange(activeFeatureFlags, archivedFeatureFlags, changeNumber, System.currentTimeMillis() / 100);
}

/**
* Process the feature flag according to its status
*/
private void processAccordingToStatus(List<Split> activeFeatureFlags, List<Split> archivedFeatureFlags, Split featureFlag) {
if (featureFlag.status == Status.ACTIVE) {
activeFeatureFlags.add(featureFlag);
} else {
archivedFeatureFlags.add(featureFlag);
}
}

/**
* Process the feature flag according to its sets
*/
private void processAccordingToSets(List<Split> activeFeatureFlags, List<Split> archivedFeatureFlags, Split featureFlag) {
if (featureFlag.sets == null || featureFlag.sets.isEmpty()) {
archivedFeatureFlags.add(featureFlag);
return;
private FeatureFlagProcessStrategy getProcessStrategy(SplitFilter splitFilter) {
if (splitFilter == null) {
return mStatusProcessStrategy;
}

boolean shouldArchive = true;
for (String set : featureFlag.sets) {
if (mConfiguredSets.contains(set)) {
// If the feature flag has at least one set that matches the configured sets,
// we process it according to its status
processAccordingToStatus(activeFeatureFlags, archivedFeatureFlags, featureFlag);
shouldArchive = false;
break;
}
}

if (shouldArchive) {
archivedFeatureFlags.add(featureFlag);
if (splitFilter.getType() == SplitFilter.Type.BY_SET) {
return new SetsProcessStrategy(splitFilter.getValues(), mStatusProcessStrategy);
} else if (splitFilter.getType() == SplitFilter.Type.BY_NAME) {
return new NamesProcessStrategy(splitFilter.getValues(), mStatusProcessStrategy);
} else {
return mStatusProcessStrategy;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ProcessLifecycleOwner;
import androidx.work.Constraints;
Expand All @@ -23,6 +22,7 @@
import java.util.concurrent.TimeUnit;

import io.split.android.client.SplitClientConfig;
import io.split.android.client.SplitFilter;
import io.split.android.client.service.ServiceConstants;
import io.split.android.client.service.executor.SplitTaskExecutionInfo;
import io.split.android.client.service.executor.SplitTaskExecutionListener;
Expand All @@ -46,20 +46,21 @@ public class WorkManagerWrapper implements MySegmentsWorkManagerWrapper {
// This variable is used to avoid loading data first time
// we receive enqueued event
private final Set<String> mShouldLoadFromLocal;
private final Set<String> mConfiguredFlagSets;
@Nullable
private final SplitFilter mFilter;

public WorkManagerWrapper(@NonNull WorkManager workManager,
@NonNull SplitClientConfig splitClientConfig,
@NonNull String apiKey,
@NonNull String databaseName,
@NonNull Set<String> configuredFlagSets) {
@Nullable List<SplitFilter> filters) {
mWorkManager = checkNotNull(workManager);
mDatabaseName = checkNotNull(databaseName);
mSplitClientConfig = checkNotNull(splitClientConfig);
mApiKey = checkNotNull(apiKey);
mShouldLoadFromLocal = new HashSet<>();
mConstraints = buildConstraints();
mConfiguredFlagSets = checkNotNull(configuredFlagSets);
mFilter = (filters != null && filters.size() == 1) ? filters.get(0) : null;
}

public void setFetcherExecutionListener(SplitTaskExecutionListener fetcherExecutionListener) {
Expand Down Expand Up @@ -187,7 +188,8 @@ private Data buildSplitSyncInputData() {
dataBuilder.putLong(ServiceConstants.WORKER_PARAM_SPLIT_CACHE_EXPIRATION, mSplitClientConfig.cacheExpirationInSeconds());
dataBuilder.putString(ServiceConstants.WORKER_PARAM_ENDPOINT, mSplitClientConfig.endpoint());
dataBuilder.putBoolean(ServiceConstants.SHOULD_RECORD_TELEMETRY, mSplitClientConfig.shouldRecordTelemetry());
dataBuilder.putStringArray(ServiceConstants.WORKER_PARAM_CONFIGURED_SETS, mConfiguredFlagSets.toArray(new String[0]));
dataBuilder.putString(ServiceConstants.WORKER_PARAM_CONFIGURED_FILTER_TYPE, (mFilter != null) ? mFilter.getType().queryStringField() : null);
dataBuilder.putStringArray(ServiceConstants.WORKER_PARAM_CONFIGURED_FILTER_VALUES, (mFilter != null) ? mFilter.getValues().toArray(new String[0]) : new String[0]);
return buildInputData(dataBuilder.build());
}

Expand Down
Loading

0 comments on commit 406463b

Please sign in to comment.