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

By name filter in feature flag processor #536

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