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

feat(decide): add a new set of decide apis #352

Merged
merged 11 commits into from
Jan 25, 2021
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/****************************************************************************
* Copyright 2017-2020, Optimizely, Inc. and contributors *
* Copyright 2017-2021, Optimizely, Inc. and contributors *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); *
* you may not use this file except in compliance with the License. *
Expand All @@ -23,6 +23,7 @@
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.optimizely.ab.Optimizely;
import com.optimizely.ab.OptimizelyUserContext;
import com.optimizely.ab.android.event_handler.DefaultEventHandler;
import com.optimizely.ab.bucketing.Bucketer;
import com.optimizely.ab.bucketing.DecisionService;
Expand All @@ -42,6 +43,9 @@
import com.optimizely.ab.notification.TrackNotificationListener;
import com.optimizely.ab.notification.UpdateConfigNotification;
import com.optimizely.ab.optimizelyconfig.OptimizelyConfig;
import com.optimizely.ab.optimizelydecision.DecisionResponse;
import com.optimizely.ab.optimizelydecision.OptimizelyDecideOption;
import com.optimizely.ab.optimizelydecision.OptimizelyDecision;
import com.optimizely.ab.optimizelyjson.OptimizelyJSON;

import org.junit.Assert;
Expand All @@ -68,10 +72,13 @@
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.hasEntry;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assume.assumeTrue;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
Expand Down Expand Up @@ -122,10 +129,24 @@ public OptimizelyClientTest(int datafileVersion,String datafile){
this.datafileVersion = datafileVersion;
eventHandler = spy(DefaultEventHandler.getInstance(InstrumentationRegistry.getInstrumentation().getTargetContext()));
optimizely = Optimizely.builder(datafile, eventHandler).build();

// set to return DecisionResponse with null variation by default (instead of null DecisionResponse)
when(bucketer.bucket(anyObject(), anyObject(), anyObject())).thenReturn(DecisionResponse.nullNoReasons());

if(datafileVersion==3) {
when(bucketer.bucket(optimizely.getProjectConfig().getExperiments().get(0), GENERIC_USER_ID, optimizely.getProjectConfig())).thenReturn(optimizely.getProjectConfig().getExperiments().get(0).getVariations().get(0));
Variation variation = optimizely.getProjectConfig().getExperiments().get(0).getVariations().get(0);
when(bucketer.bucket(
optimizely.getProjectConfig().getExperiments().get(0),
GENERIC_USER_ID,
optimizely.getProjectConfig())
).thenReturn(DecisionResponse.responseNoReasons(variation));
} else {
when(bucketer.bucket(optimizely.getProjectConfig().getExperimentKeyMapping().get(FEATURE_MULTI_VARIATE_EXPERIMENT_KEY), GENERIC_USER_ID, optimizely.getProjectConfig())).thenReturn(optimizely.getProjectConfig().getExperimentKeyMapping().get(FEATURE_MULTI_VARIATE_EXPERIMENT_KEY).getVariations().get(1));
Variation variation = optimizely.getProjectConfig().getExperimentKeyMapping().get(FEATURE_MULTI_VARIATE_EXPERIMENT_KEY).getVariations().get(1);
when(bucketer.bucket(
optimizely.getProjectConfig().getExperimentKeyMapping().get(FEATURE_MULTI_VARIATE_EXPERIMENT_KEY),
GENERIC_USER_ID,
optimizely.getProjectConfig())
).thenReturn(DecisionResponse.responseNoReasons(variation));
}
spyOnConfig();
} catch (Exception configException) {
Expand Down Expand Up @@ -2151,6 +2172,88 @@ public void testAddLogEventNotificationHandlerWithInvalidOptimizely() {
.getNotificationManager(LogEvent.class).remove(notificationId));
}

// OptimizelyUserContext + Decide API

@Test
public void testCreateUserContext() {
OptimizelyClient optimizelyClient = new OptimizelyClient(optimizely, logger);
OptimizelyUserContext userContext = optimizelyClient.createUserContext(GENERIC_USER_ID);
assertEquals(userContext.getUserId(), GENERIC_USER_ID);
assert(userContext.getAttributes().isEmpty());
}

@Test
public void testCreateUserContext_withAttributes() {
Map<String, Object> attributes = Collections.singletonMap("house", "Gryffindor");

OptimizelyClient optimizelyClient = new OptimizelyClient(optimizely, logger);
OptimizelyUserContext userContext = optimizelyClient.createUserContext(GENERIC_USER_ID, attributes);
assertEquals(userContext.getUserId(), GENERIC_USER_ID);
assertEquals(userContext.getAttributes(), attributes);
}

@Test
// this should be enough to validate connection to the core java-sdk
public void testDecide() {
assumeTrue(datafileVersion == Integer.parseInt(ProjectConfig.Version.V4.toString()));

String flagKey = INTEGER_FEATURE_KEY;
Map<String, Object> attributes = Collections.singletonMap("house", "Gryffindor");

OptimizelyClient optimizelyClient = new OptimizelyClient(optimizely, logger);
OptimizelyUserContext userContext = optimizelyClient.createUserContext(GENERIC_USER_ID, attributes);
OptimizelyDecision decision = userContext.decide(flagKey);
OptimizelyJSON variablesExpected = new OptimizelyJSON(Collections.singletonMap("integer_variable", 2));

assertEquals(decision.getVariationKey(), "Feorge");
assertTrue(decision.getEnabled());
assertEquals(decision.getVariables().toMap(), variablesExpected.toMap());
assertEquals(decision.getRuleKey(), FEATURE_MULTI_VARIATE_EXPERIMENT_KEY);
assertEquals(decision.getFlagKey(), flagKey);
assertEquals(decision.getUserContext(), userContext);
assertTrue(decision.getReasons().isEmpty());
}

@Test
// this should be enough to validate connection to the core java-sdk
public void testDecide_withoutDefaultDecideOptions() throws IOException {
assumeTrue(datafileVersion == Integer.parseInt(ProjectConfig.Version.V4.toString()));

String datafile = loadRawResource(InstrumentationRegistry.getInstrumentation().getTargetContext(),R.raw.validprojectconfigv4);
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
OptimizelyManager optimizelyManager = OptimizelyManager.builder(testProjectId).build(context);
optimizelyManager.initialize(context, datafile);

OptimizelyClient optimizelyClient = optimizelyManager.getOptimizely();
OptimizelyUserContext userContext = optimizelyClient.createUserContext(GENERIC_USER_ID);
OptimizelyDecision decision = userContext.decide(INTEGER_FEATURE_KEY);

assertTrue(decision.getReasons().isEmpty());
}

@Test
// this should be enough to validate connection to the core java-sdk
public void testDecide_withDefaultDecideOptions() throws IOException {
assumeTrue(datafileVersion == Integer.parseInt(ProjectConfig.Version.V4.toString()));

List<OptimizelyDecideOption> defaultDecideOptions = Arrays.asList(OptimizelyDecideOption.INCLUDE_REASONS);

String datafile = loadRawResource(InstrumentationRegistry.getInstrumentation().getTargetContext(),R.raw.validprojectconfigv4);
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
OptimizelyManager optimizelyManager = OptimizelyManager.builder(testProjectId)
.withDefaultDecideOptions(defaultDecideOptions)
.build(context);
optimizelyManager.initialize(context, datafile);

OptimizelyClient optimizelyClient = optimizelyManager.getOptimizely();
OptimizelyUserContext userContext = optimizelyClient.createUserContext(GENERIC_USER_ID);
OptimizelyDecision decision = userContext.decide(INTEGER_FEATURE_KEY);

assertTrue(decision.getReasons().size() > 0);
}

// Utils

private boolean compareJsonStrings(String str1, String str2) {
JsonParser parser = new JsonParser();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/****************************************************************************
* Copyright 2017-2018, Optimizely, Inc. and contributors *
* Copyright 2017-2021, Optimizely, Inc. and contributors *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); *
* you may not use this file except in compliance with the License. *
Expand Down Expand Up @@ -166,7 +166,7 @@ public void initializeSyncWithEnvironment() {
EventHandler eventHandler = mock(DefaultEventHandler.class);
EventProcessor eventProcessor = mock(EventProcessor.class);
OptimizelyManager optimizelyManager = new OptimizelyManager(testProjectId, testSdkKey, null, logger, 3600L, datafileHandler, null, 3600L,
eventHandler, eventProcessor, null, null);
eventHandler, eventProcessor, null, null, null);
/*
* Scenario#1: when datafile is not Empty
* Scenario#2: when datafile is Empty
Expand Down Expand Up @@ -225,7 +225,7 @@ public void initializeAsyncWithEnvironment() {
EventHandler eventHandler = mock(DefaultEventHandler.class);
EventProcessor eventProcessor = mock(EventProcessor.class);
final OptimizelyManager optimizelyManager = new OptimizelyManager(testProjectId, testSdkKey, null, logger, 3600L, datafileHandler, null, 3600L,
eventHandler, eventProcessor, null, null);
eventHandler, eventProcessor, null, null, null);

/*
* Scenario#1: when datafile is not Empty
Expand Down Expand Up @@ -515,7 +515,7 @@ public void initializeSyncWithUpdateOnNewDatafileDisabled() {
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();

OptimizelyManager manager = new OptimizelyManager(testProjectId, testSdkKey, null, logger, pollingInterval, datafileHandler, null, 0,
null, null, null, null);
null, null, null, null, null);

doAnswer(
new Answer<Object>() {
Expand Down Expand Up @@ -548,7 +548,7 @@ public void initializeSyncWithUpdateOnNewDatafileEnabled() {
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();

OptimizelyManager manager = new OptimizelyManager(testProjectId, testSdkKey, null, logger, pollingInterval, datafileHandler, null, 0,
null, null, null, null);
null, null, null, null, null);

doAnswer(
new Answer<Object>() {
Expand Down Expand Up @@ -581,7 +581,7 @@ public void initializeSyncWithDownloadToCacheDisabled() {
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();

OptimizelyManager manager = new OptimizelyManager(testProjectId, testSdkKey, null, logger, pollingInterval, datafileHandler, null, 0,
null, null, null, null);
null, null, null, null, null);

doAnswer(
new Answer<Object>() {
Expand Down Expand Up @@ -614,7 +614,7 @@ public void initializeSyncWithUpdateOnNewDatafileDisabledWithPeriodicPollingEnab
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();

OptimizelyManager manager = new OptimizelyManager(testProjectId, testSdkKey, null, logger, pollingInterval, datafileHandler, null, 0,
null, null, null, null);
null, null, null, null, null);

doAnswer(
(Answer<Object>) invocation -> {
Expand Down Expand Up @@ -646,7 +646,7 @@ public void initializeSyncWithUpdateOnNewDatafileEnabledWithPeriodicPollingEnabl
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();

OptimizelyManager manager = new OptimizelyManager(testProjectId, testSdkKey, null, logger, pollingInterval, datafileHandler, null, 0,
null, null, null, null);
null, null, null, null, null);

doAnswer(
new Answer<Object>() {
Expand Down Expand Up @@ -679,7 +679,7 @@ public void initializeSyncWithUpdateOnNewDatafileDisabledWithPeriodicPollingDisa
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();

OptimizelyManager manager = new OptimizelyManager(testProjectId, testSdkKey, null, logger, pollingInterval, datafileHandler, null, 0,
null, null, null, null);
null, null, null, null, null);

doAnswer(
new Answer<Object>() {
Expand Down Expand Up @@ -713,7 +713,7 @@ public void initializeSyncWithUpdateOnNewDatafileEnabledWithPeriodicPollingDisab
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();

OptimizelyManager manager = new OptimizelyManager(testProjectId, testSdkKey, null, logger, pollingInterval, datafileHandler, null, 0,
null, null, null, null);
null, null, null, null, null);

doAnswer(
new Answer<Object>() {
Expand Down Expand Up @@ -746,7 +746,7 @@ public void initializeSyncWithResourceDatafileNoCache() {
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();

OptimizelyManager manager = spy(new OptimizelyManager(testProjectId, testSdkKey, null, logger, pollingInterval, datafileHandler, null, 0,
null, null, null, null));
null, null, null, null, null));

datafileHandler.removeSavedDatafile(context, manager.getDatafileConfig());
OptimizelyClient client = manager.initialize(context, R.raw.datafile, downloadToCache, updateConfigOnNewDatafile);
Expand All @@ -763,7 +763,7 @@ public void initializeSyncWithResourceDatafileNoCacheWithDefaultParams() {
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();

OptimizelyManager manager = spy(new OptimizelyManager(testProjectId, testSdkKey, null, logger, pollingInterval, datafileHandler, null, 0,
null, null, null, null));
null, null, null, null, null));

datafileHandler.removeSavedDatafile(context, manager.getDatafileConfig());
OptimizelyClient client = manager.initialize(context, R.raw.datafile);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/****************************************************************************
* Copyright 2017-2020, Optimizely, Inc. and contributors *
* Copyright 2017-2021, Optimizely, Inc. and contributors *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); *
* you may not use this file except in compliance with the License. *
Expand All @@ -21,6 +21,7 @@
import androidx.annotation.Nullable;

import com.optimizely.ab.Optimizely;
import com.optimizely.ab.OptimizelyUserContext;
import com.optimizely.ab.UnknownEventTypeException;
import com.optimizely.ab.config.ProjectConfig;
import com.optimizely.ab.config.Variation;
Expand Down Expand Up @@ -771,6 +772,7 @@ public OptimizelyJSON getAllFeatureVariables(@NonNull String featureKey,
*
* @return {@link OptimizelyConfig}
*/
@Nullable
public OptimizelyConfig getOptimizelyConfig() {
if (isValid()) {
return optimizely.getOptimizelyConfig();
Expand All @@ -780,6 +782,30 @@ public OptimizelyConfig getOptimizelyConfig() {
}
}

/**
* Create a context of the user for which decision APIs will be called.
*
* A user context will be created successfully even when the SDK is not fully configured yet.
*
* @param userId The user ID to be used for bucketing.
* @param attributes: A map of attribute names to current user attribute values.
* @return An OptimizelyUserContext associated with this OptimizelyClient.
*/
@Nullable
public OptimizelyUserContext createUserContext(@NonNull String userId,
@NonNull Map<String, Object> attributes) {
if (isValid()) {
return optimizely.createUserContext(userId, attributes);
} else {
logger.warn("Optimizely is not initialized, could not create a user context");
return null;
}
}

public OptimizelyUserContext createUserContext(@NonNull String userId) {
return createUserContext(userId, null);
}

//======== Notification APIs ========//

/**
Expand Down
Loading