Skip to content

Commit

Permalink
Merge pull request #2 from AdevintaSpain/traits_cache
Browse files Browse the repository at this point in the history
[IntegrationOptions] Trait diffing
  • Loading branch information
ivngrz authored Oct 30, 2019
2 parents e18376a + 39ef25b commit 21ef253
Show file tree
Hide file tree
Showing 9 changed files with 306 additions and 17 deletions.
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ dependencies {

androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test:rules:1.0.2'
androidTestImplementation 'com.linkedin.dexmaker:dexmaker-mockito:2.25.0'

androidTestImplementation 'junit:junit:4.12'
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ public void testIdentifyCallsChangeUser() {
Traits traits = createTraits(testUserId);
IdentifyPayload identifyPayload = new IdentifyPayloadBuilder().traits(traits).build();
Logger logger = Logger.with(Analytics.LogLevel.DEBUG);
AppboyIntegration integration = new AppboyIntegration(Appboy.getInstance(getContext()), "foo", logger, true, new DefaultUserIdMapper());
AppboyIntegration integration = new AppboyIntegration(Appboy.getInstance(getContext()), "foo", logger, true,
new PreferencesTraitsCache(getContext()), new DefaultUserIdMapper());
integration.identify(identifyPayload);

assertEquals(testUserId, Appboy.getInstance(getContext()).getCurrentUser().getUserId());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,50 +2,172 @@

import android.support.test.runner.AndroidJUnit4;
import com.appboy.Appboy;
import com.appboy.AppboyUser;
import com.appboy.configuration.AppboyConfig;
import com.segment.analytics.Analytics;
import com.segment.analytics.Traits;
import com.segment.analytics.integrations.IdentifyPayload;
import com.segment.analytics.integrations.Logger;
import com.segment.analytics.test.IdentifyPayloadBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

import static android.support.test.InstrumentationRegistry.getContext;
import static com.segment.analytics.Utils.createTraits;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@RunWith(AndroidJUnit4.class)
public class AppboyIntegrationOptionsAndroidTest {

private static final String ORIGINAL_USER_ID = "testUser";
private static final String USER_ID = "testUser";
private static final String OTHER_USER_ID = "testUser2";
private static final String TRANSFORMED_USER_ID = "transformedUser";
private static final String TRAIT_EMAIL = "[email protected]";
private static final String TRAIT_EMAIL_UPDATED = "[email protected]";
private static final String TRAIT_CITY = "city";
private static final String TRAIT_COUNTRY = "country";
private static final String CUSTOM_TRAIT_KEY = "custom_trait_key";
private static final String CUSTOM_KEY_VALUE = "custom_key_value";

@Mock Appboy appboy;
@Mock AppboyUser appboyUser;

private AppboyIntegration appboyIntegration;
private PreferencesTraitsCache traitsCache;

@BeforeClass
public static void beforeClass() {
AppboyConfig appboyConfig = new AppboyConfig.Builder().setApiKey("testkey").build();
Appboy.configure(getContext(), appboyConfig);
}

@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);

when(appboy.getCurrentUser()).thenReturn(appboyUser);

Logger logger = Logger.with(Analytics.LogLevel.DEBUG);

traitsCache = new PreferencesTraitsCache(getContext());

appboyIntegration = new AppboyIntegration(
appboy, "foo", logger, true,
traitsCache, new ReplaceUserIdMapper());
}

@After
public void tearDown() throws Exception {
traitsCache.clear();
}

@Test
public void testUserIdMapperTransformsAppboyUserId() {
Traits traits = createTraits(ORIGINAL_USER_ID);
IdentifyPayload identifyPayload = new IdentifyPayloadBuilder().traits(traits).build();
Traits traits = createTraits(USER_ID);

Logger logger = Logger.with(Analytics.LogLevel.DEBUG);
callIdentifyWithTraits(traits);

verify(appboy).changeUser(TRANSFORMED_USER_ID);
}

@Test
public void testShouldNotTriggerAppboyUpdateIfTraitDoesntChange() {
Traits traits = createTraits(USER_ID);

Traits.Address address = new Traits.Address();
address.putCity(TRAIT_CITY);
address.putCountry(TRAIT_COUNTRY);

AppboyIntegration integration = new AppboyIntegration(Appboy.getInstance(getContext()), "foo", logger, true, new ReplaceUserIdMapper());
traits.putAddress(address);
traits.putEmail(TRAIT_EMAIL);
traits.put(CUSTOM_TRAIT_KEY, CUSTOM_KEY_VALUE);

callIdentifyWithTraits(traits);
callIdentifyWithTraits(traits);
callIdentifyWithTraits(traits);

verify(appboyUser, times(1)).setEmail(TRAIT_EMAIL);
verify(appboyUser, times(1)).setHomeCity(TRAIT_CITY);
verify(appboyUser, times(1)).setCountry(TRAIT_COUNTRY);
verify(appboyUser, times(1)).setCustomUserAttribute(CUSTOM_TRAIT_KEY, CUSTOM_KEY_VALUE);
}

@Test
public void testShouldTriggerUpdateIfTraitChanges() {
Traits traits = createTraits(USER_ID);
traits.putEmail(TRAIT_EMAIL);
callIdentifyWithTraits(traits);
callIdentifyWithTraits(traits);

integration.identify(identifyPayload);
Traits traitsUpdate = createTraits(USER_ID);
traitsUpdate.putEmail(TRAIT_EMAIL_UPDATED);
callIdentifyWithTraits(traitsUpdate);
callIdentifyWithTraits(traitsUpdate);

InOrder inOrder = Mockito.inOrder(appboyUser);
inOrder.verify(appboyUser, times(1)).setEmail(TRAIT_EMAIL);
inOrder.verify(appboyUser, times(1)).setEmail(TRAIT_EMAIL_UPDATED);
}

@Test
public void testAvoidTriggeringRepeatedUserIdUpdates() {
Traits traits = createTraits(USER_ID);
traits.putEmail(TRAIT_EMAIL);

callIdentifyWithTraits(traits);
callIdentifyWithTraits(traits);

verify(appboyUser, times(1)).setEmail(TRAIT_EMAIL);
verify(appboy, times(1)).changeUser(TRANSFORMED_USER_ID);
}

@Test
public void clearCacheIfUserIdChanges() {
Traits traits = createTraits(USER_ID);
traits.putEmail(TRAIT_EMAIL);

Traits traitsUpdate = createTraits(OTHER_USER_ID);
traitsUpdate.putEmail(TRAIT_EMAIL);

callIdentifyWithTraits(traits);
callIdentifyWithTraits(traitsUpdate);

verify(appboyUser, times(2)).setEmail(TRAIT_EMAIL);
}

@Test
public void clearCacheOnReset() {
Traits traits = createTraits(USER_ID);
traits.putEmail(TRAIT_EMAIL);

callIdentifyWithTraits(traits);

appboyIntegration.reset();

callIdentifyWithTraits(traits);

verify(appboyUser, times(2)).setEmail(TRAIT_EMAIL);
}

public void callIdentifyWithTraits(Traits traits) {
IdentifyPayload identifyPayload = new IdentifyPayloadBuilder().traits(traits).build();

assertEquals(TRANSFORMED_USER_ID, Appboy.getInstance(getContext()).getCurrentUser().getUserId());
appboyIntegration.identify(identifyPayload);
}

class ReplaceUserIdMapper implements UserIdMapper {
@Override
public String transformUserId(String segmentUserId) {
return segmentUserId.replaceFirst(ORIGINAL_USER_ID, TRANSFORMED_USER_ID);
return segmentUserId.replaceFirst(USER_ID, TRANSFORMED_USER_ID);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.segment.analytics.integrations.Logger;
import com.segment.analytics.integrations.TrackPayload;

import java.util.Map;
import org.json.JSONObject;

import java.math.BigDecimal;
Expand Down Expand Up @@ -77,10 +78,15 @@ public Integration<?> create(ValueMap settings, Analytics analytics) {
userIdMapper = new DefaultUserIdMapper();
}

TraitsCache traitsCache = null;
if (options.isTraitDiffingEnabled()) {
traitsCache = new PreferencesTraitsCache(analytics.getApplication());
}

Appboy.configure(analytics.getApplication().getApplicationContext(), builder.build());
Appboy appboy = Appboy.getInstance(analytics.getApplication());
logger.verbose("Configured Appboy+Segment integration and initialized Appboy.");
return new AppboyIntegration(appboy, apiKey, logger, inAppMessageRegistrationEnabled, userIdMapper);
return new AppboyIntegration(appboy, apiKey, logger, inAppMessageRegistrationEnabled, traitsCache, userIdMapper);
}

@Override
Expand All @@ -95,15 +101,18 @@ public String key() {
private final Logger mLogger;
private final boolean mAutomaticInAppMessageRegistrationEnabled;
private final UserIdMapper mUserIdMapper;
private final TraitsCache mTraitsCache;

public AppboyIntegration(Appboy appboy, String token, Logger logger,
boolean automaticInAppMessageRegistrationEnabled,
TraitsCache traitsCache,
UserIdMapper userIdMapper) {
mAppboy = appboy;
mToken = token;
mLogger = logger;
mAutomaticInAppMessageRegistrationEnabled = automaticInAppMessageRegistrationEnabled;
mUserIdMapper = userIdMapper;
mTraitsCache = traitsCache;
}

public String getToken() {
Expand All @@ -121,14 +130,32 @@ public void identify(IdentifyPayload identify) {

String userId = identify.userId();
if (!StringUtils.isNullOrBlank(userId)) {
mAppboy.changeUser(mUserIdMapper.transformUserId(userId));

String cachedUserId = mTraitsCache.load().userId();

if (!userId.equals(cachedUserId)) {
mAppboy.changeUser(mUserIdMapper.transformUserId(userId));

if (mTraitsCache != null) {
mTraitsCache.clear();
}
}
}

Traits traits = identify.traits();

if (traits == null) {
return;
}

if (mTraitsCache != null) {
Traits lastEmittedTraits = mTraitsCache.load();

mTraitsCache.save(traits);

traits = diffTraits(traits, lastEmittedTraits);
}

Date birthday = traits.birthday();
if (birthday != null) {
Calendar birthdayCal = Calendar.getInstance(Locale.US);
Expand Down Expand Up @@ -200,11 +227,27 @@ public void identify(IdentifyPayload identify) {
mAppboy.getCurrentUser().setCustomUserAttribute(key, (String) value);
} else {
mLogger.info("Appboy can't map segment value for custom Appboy user "
+ "attribute with key %s and value %s", key, value);
+ "attribute with key %s and value %s", key, value);
}
}
}

private Traits diffTraits(Traits traits, Traits lastEmittedTraits) {
if (lastEmittedTraits == null) return traits;

Traits diffed = new Traits();

for (Map.Entry<String, Object> trait : traits.entrySet()) {
Object storedValue = lastEmittedTraits.get(trait.getKey());

if (storedValue == null || !trait.getValue().equals(storedValue)) {
diffed.put(trait.getKey(), trait.getValue());
}
}

return diffed;
}

@Override
public void flush() {
super.flush();
Expand Down Expand Up @@ -268,6 +311,15 @@ public void track(TrackPayload track) {
}
}

@Override
public void reset() {
super.reset();

if (mTraitsCache != null) {
mTraitsCache.clear();
}
}

@Override
public void onActivityStarted(Activity activity) {
super.onActivityStarted(activity);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
public class AppboyIntegrationOptions {

private UserIdMapper userIdMapper;
private boolean enableTraitDiffing;

public static Builder builder() {
return new Builder();
Expand All @@ -12,20 +13,32 @@ UserIdMapper getUserIdMapper() {
return userIdMapper;
}

private AppboyIntegrationOptions(UserIdMapper userIdMapper) {
public boolean isTraitDiffingEnabled() {
return enableTraitDiffing;
}

private AppboyIntegrationOptions(UserIdMapper userIdMapper, boolean enableTraitDiffing) {
this.userIdMapper = userIdMapper;

this.enableTraitDiffing = enableTraitDiffing;
}

public static class Builder {
private UserIdMapper userIdMapper;
private boolean traitDiffingEnabled;

public Builder userIdMapper(UserIdMapper userIdMapper) {
this.userIdMapper = userIdMapper;
return this;
}

public Builder enableTraitDiffing(boolean enable) {
this.traitDiffingEnabled = enable;
return this;
}

public AppboyIntegrationOptions build() {
return new AppboyIntegrationOptions(userIdMapper);
return new AppboyIntegrationOptions(userIdMapper, traitDiffingEnabled);
}
}
}
Loading

0 comments on commit 21ef253

Please sign in to comment.