diff --git a/CHANGELOG.md b/CHANGELOG.md index 35d79d6..793412a 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 10.0.0 + +##### Breaking +- Updated to [Braze Android SDK 15.0.0](https://github.com/Appboy/appboy-android-sdk/blob/master/CHANGELOG.md#1500). +- A track call with event name "Completed Order" will now be treated as a purchase event for backwards compatibility with Segment eCommerce v1 API. + ## 9.0.0 ##### Breaking diff --git a/appboy-segment-integration/build.gradle b/appboy-segment-integration/build.gradle index 1235f91..5558a43 100755 --- a/appboy-segment-integration/build.gradle +++ b/appboy-segment-integration/build.gradle @@ -69,7 +69,7 @@ dependencies { compileOnly 'com.segment.analytics.android:analytics:4.3.1' - api 'com.appboy:android-sdk-ui:14.0.0' + api 'com.appboy:android-sdk-ui:15.0.0' testImplementation 'junit:junit:4.12' diff --git a/appboy-segment-integration/src/main/java/com/segment/analytics/android/integrations/appboy/AppboyIntegration.java b/appboy-segment-integration/src/main/java/com/segment/analytics/android/integrations/appboy/AppboyIntegration.java index 6479379..868b863 100755 --- a/appboy-segment-integration/src/main/java/com/segment/analytics/android/integrations/appboy/AppboyIntegration.java +++ b/appboy-segment-integration/src/main/java/com/segment/analytics/android/integrations/appboy/AppboyIntegration.java @@ -8,7 +8,6 @@ import androidx.annotation.VisibleForTesting; import com.appboy.Appboy; -import com.appboy.AppboyUser; import com.appboy.IAppboy; import com.appboy.enums.Gender; import com.appboy.enums.Month; @@ -16,8 +15,9 @@ import com.appboy.models.outgoing.AppboyProperties; import com.appboy.models.outgoing.AttributionData; import com.appboy.support.StringUtils; -import com.appboy.ui.inappmessage.AppboyInAppMessageManager; +import com.braze.BrazeUser; import com.braze.configuration.BrazeConfig; +import com.braze.ui.inappmessage.BrazeInAppMessageManager; import com.segment.analytics.Analytics; import com.segment.analytics.Properties; import com.segment.analytics.Traits; @@ -30,8 +30,10 @@ import org.json.JSONObject; import java.math.BigDecimal; +import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; +import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.List; @@ -40,9 +42,9 @@ public class AppboyIntegration extends Integration { private static final String APPBOY_KEY = "Appboy"; - private static final Set MALE_TOKENS = new HashSet(Arrays.asList("M", + private static final Set MALE_TOKENS = new HashSet<>(Arrays.asList("M", "MALE")); - private static final Set FEMALE_TOKENS = new HashSet(Arrays.asList("F", + private static final Set FEMALE_TOKENS = new HashSet<>(Arrays.asList("F", "FEMALE")); private static final String DEFAULT_CURRENCY_CODE = "USD"; private static final String API_KEY_KEY = "apiKey"; @@ -64,7 +66,7 @@ public Integration create(ValueMap settings, Analytics analytics) { settings.getBoolean(AUTOMATIC_IN_APP_MESSAGE_REGISTRATION_ENABLED, true); if (StringUtils.isNullOrBlank(API_KEY_KEY)) { - logger.info("Appboy+Segment integration attempted to initialize without api key."); + logger.info("Braze+Segment integration attempted to initialize without api key."); return null; } String customEndpoint = settings.getString(CUSTOM_ENDPOINT_KEY); @@ -78,7 +80,7 @@ public Integration create(ValueMap settings, Analytics analytics) { final Context applicationContext = analytics.getApplication().getApplicationContext(); Appboy.configure(applicationContext, builder.build()); Appboy appboy = Appboy.getInstance(applicationContext); - logger.verbose("Configured Appboy+Segment integration and initialized Appboy."); + logger.verbose("Configured Braze+Segment integration and initialized Appboy."); return new AppboyIntegration(appboy, apiKey, logger, inAppMessageRegistrationEnabled); } @@ -134,7 +136,7 @@ public void identify(IdentifyPayload identify) { Traits traits = identify.traits(); - AppboyUser currentUser = mAppboy.getCurrentUser(); + BrazeUser currentUser = mAppboy.getCurrentUser(); if (currentUser == null) { mLogger.info("Appboy.getCurrentUser() was null, aborting identify"); return; @@ -213,9 +215,21 @@ public void identify(IdentifyPayload identify) { currentUser.setCustomUserAttribute(key, (String) value); } else if (value instanceof String[]) { currentUser.setCustomAttributeArray(key, (String[]) value); + } else if (value instanceof List) { + ArrayList valueArrayList = new ArrayList((Collection) value); + List stringValueList = new ArrayList<>(); + for (Object objectValue : valueArrayList) { + if (objectValue instanceof String) { + stringValueList.add((String) objectValue); + } + } + if (stringValueList.size() > 0) { + String[] arrayValue = new String[stringValueList.size()]; + currentUser.setCustomAttributeArray(key, stringValueList.toArray(arrayValue)); + } } else { - mLogger.info("Appboy can't map segment value for custom Appboy user " + mLogger.info("Braze can't map segment value for custom Braze user " + "attribute with key %s and value %s", key, value); } } @@ -239,7 +253,7 @@ public void track(TrackPayload track) { try { if (event.equals("Install Attributed")) { ValueMap campaignProps = (ValueMap) properties.get("campaign"); - AppboyUser currentUser = mAppboy.getCurrentUser(); + BrazeUser currentUser = mAppboy.getCurrentUser(); if (campaignProps != null && currentUser != null) { currentUser.setAttributionData(new AttributionData( campaignProps.getString("source"), @@ -255,7 +269,7 @@ public void track(TrackPayload track) { } JSONObject propertiesJson = properties.toJsonObject(); double revenue = properties.revenue(); - if (revenue != 0 || event.equals("Order Completed")) { + if (revenue != 0 || event.equals("Order Completed") || event.equals("Completed Order")) { String currencyCode = StringUtils.isNullOrBlank(properties.currency()) ? DEFAULT_CURRENCY_CODE : properties.currency(); propertiesJson.remove(REVENUE_KEY); @@ -296,7 +310,7 @@ public void onActivityStopped(Activity activity) { public void onActivityResumed(Activity activity) { super.onActivityResumed(activity); if (mAutomaticInAppMessageRegistrationEnabled) { - AppboyInAppMessageManager.getInstance().registerInAppMessageManager(activity); + BrazeInAppMessageManager.getInstance().registerInAppMessageManager(activity); } } @@ -304,7 +318,7 @@ public void onActivityResumed(Activity activity) { public void onActivityPaused(Activity activity) { super.onActivityPaused(activity); if (mAutomaticInAppMessageRegistrationEnabled) { - AppboyInAppMessageManager.getInstance().unregisterInAppMessageManager(activity); + BrazeInAppMessageManager.getInstance().unregisterInAppMessageManager(activity); } } diff --git a/appboy-segment-integration/src/test/java/com/segment/analytics/android/integrations/appboy/AppboyTest.java b/appboy-segment-integration/src/test/java/com/segment/analytics/android/integrations/appboy/AppboyTest.java index e906fc0..95c781b 100644 --- a/appboy-segment-integration/src/test/java/com/segment/analytics/android/integrations/appboy/AppboyTest.java +++ b/appboy-segment-integration/src/test/java/com/segment/analytics/android/integrations/appboy/AppboyTest.java @@ -6,10 +6,10 @@ import androidx.test.core.app.ApplicationProvider; -import com.appboy.AppboyUser; import com.appboy.IAppboy; import com.appboy.enums.Gender; import com.appboy.models.outgoing.AppboyProperties; +import com.braze.BrazeUser; import com.segment.analytics.Analytics; import com.segment.analytics.Properties; import com.segment.analytics.Traits; @@ -27,11 +27,14 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.LooperMode; +import java.util.ArrayList; import java.math.BigDecimal; import static com.segment.analytics.Utils.createTraits; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.mockito.AdditionalMatchers.*; +import static org.mockito.ArgumentMatchers.refEq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -42,7 +45,7 @@ @RunWith(RobolectricTestRunner.class) public class AppboyTest { private IAppboy mAppboy; - private AppboyUser mAppboyUser; + private BrazeUser mAppboyUser; private AppboyIntegration mIntegration; private Analytics mAnalytics; @@ -53,7 +56,7 @@ public void setUp() { Mockito.when(mockApplication.getApplicationContext()).thenReturn(getContext()); Mockito.when(mAnalytics.getApplication()).thenReturn(mockApplication); mAppboy = spy(new MockAppboy()); - mAppboyUser = mock(AppboyUser.class); + mAppboyUser = mock(BrazeUser.class); when(mAppboy.getCurrentUser()).thenReturn(mAppboyUser); Logger logger = Logger.with(Analytics.LogLevel.DEBUG); @@ -121,6 +124,8 @@ public void testIdentifyFields() { traits.put("float", 5.0f); traits.put("long", 15L); traits.put("string", "value"); + final String[] testStringArray = {"Test Value 1", "Test Value 2"}; + traits.put("array", testStringArray); traits.put("unknown", new Object()); traits.put("userId", "id1"); traits.put("anonymousId", "id2"); @@ -139,12 +144,43 @@ public void testIdentifyFields() { verify(mAppboyUser).setCustomUserAttribute("float", new Float(5.0)); verify(mAppboyUser).setCustomUserAttribute("long", 15L); verify(mAppboyUser).setCustomUserAttribute("string", "value"); + verify(mAppboyUser).setCustomAttributeArray("array", testStringArray); verify(mAppboyUser, Mockito.never()).setCustomUserAttribute("userId", "id1"); verify(mAppboyUser, Mockito.never()).setCustomUserAttribute("anonymousId", "id2"); verify(mAppboy, Mockito.times(1)).changeUser("userId"); verify(mAppboy, Mockito.times(1)).getCurrentUser(); } + @Test + public void testIdentifyArrayList() { + Traits traits = createTraits("userId"); + ArrayList testArrayList = new ArrayList<>(); + testArrayList.add("Test Value 1"); + testArrayList.add("Test Value 2"); + final String[] stringArray = {"Test Value 1", "Test Value 2"}; + final String testName = "testArrayList"; + traits.put(testName, testArrayList); + IdentifyPayload identifyPayload = getBasicIdentifyPayloadWithTraits(traits); + mIntegration.identify(identifyPayload); + verify(mAppboyUser).setCustomAttributeArray(refEq(testName), aryEq(stringArray)); + } + + @Test + public void testIdentifyArrayListWithListInt() { + Traits traits = createTraits("userId"); + ArrayList testArrayList = new ArrayList<>(); + testArrayList.add(1); + testArrayList.add(2); + final String[] stringArray = {"1", "2"}; + final String[] emptyArray = {}; + final String testName = "testArrayList"; + traits.put(testName, testArrayList); + IdentifyPayload identifyPayload = getBasicIdentifyPayloadWithTraits(traits); + mIntegration.identify(identifyPayload); + verify(mAppboyUser, Mockito.never()).setCustomAttributeArray(refEq(testName), aryEq(stringArray)); + verify(mAppboyUser, Mockito.never()).setCustomAttributeArray(refEq(testName), aryEq(emptyArray)); + } + @Test public void testIdentifyGender() { Traits traits = createTraits("userId"); @@ -216,6 +252,14 @@ public void testTrackLogsPurchaseForOrderCompletedEvent() { verifyNoMoreAppboyInteractions(); } + @Test + public void testTrackLogsPurchaseForCompletedOrderEvent() { + TrackPayload trackPayload = getBasicTrackPayloadWithEventAndProps("Completed Order", null); + mIntegration.track(trackPayload); + verify(mAppboy).logPurchase("Completed Order", "USD", new BigDecimal("0.0")); + verifyNoMoreAppboyInteractions(); + } + @Test public void testTrackLogsPurchaseForEventWithRevenue() { Properties purchaseProperties = new Properties(); @@ -237,6 +281,17 @@ public void testTrackLogsPurchasesForOrderCompletedEventWithProducts() { verifyNoMoreAppboyInteractions(); } + @Test + public void testTrackLogsPurchasesForCompletedOrderEventWithProducts() { + Properties purchaseProperties = new Properties(); + purchaseProperties.putProducts(new Properties.Product("id1", "sku1", 10), new Properties.Product("id2", "sku2", 12)); + TrackPayload trackPayload = getBasicTrackPayloadWithEventAndProps("Completed Order", purchaseProperties); + mIntegration.track(trackPayload); + verify(mAppboy).logPurchase(Mockito.eq("id1"), Mockito.eq("USD"), Mockito.eq(new BigDecimal("10.0")), Mockito.any(AppboyProperties.class)); + verify(mAppboy).logPurchase(Mockito.eq("id2"), Mockito.eq("USD"), Mockito.eq(new BigDecimal("12.0")), Mockito.any(AppboyProperties.class)); + verifyNoMoreAppboyInteractions(); + } + @Test public void testTrackLogsPurchasesForEventWithRevenueWithProducts() { Properties purchaseProperties = new Properties(); @@ -259,6 +314,16 @@ public void testTrackLogsPurchaseForOrderCompletedEventWithCustomCurrency() { verifyNoMoreAppboyInteractions(); } + @Test + public void testTrackLogsPurchaseForCompletedOrderEventWithCustomCurrency() { + Properties purchaseProperties = new Properties(); + purchaseProperties.putCurrency("JPY"); + TrackPayload trackPayload = getBasicTrackPayloadWithEventAndProps("Completed Order", purchaseProperties); + mIntegration.track(trackPayload); + verify(mAppboy).logPurchase("Completed Order", "JPY", new BigDecimal("0.0")); + verifyNoMoreAppboyInteractions(); + } + @Test public void testTrackLogsPurchaseForEventWithRevenueWithCustomCurrency() { Properties purchaseProperties = new Properties(); diff --git a/appboy-segment-integration/src/test/java/com/segment/analytics/android/integrations/appboy/MockAppboy.java b/appboy-segment-integration/src/test/java/com/segment/analytics/android/integrations/appboy/MockAppboy.java index d253372..9bc794a 100644 --- a/appboy-segment-integration/src/test/java/com/segment/analytics/android/integrations/appboy/MockAppboy.java +++ b/appboy-segment-integration/src/test/java/com/segment/analytics/android/integrations/appboy/MockAppboy.java @@ -6,19 +6,22 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.appboy.AppboyUser; import com.appboy.IAppboy; import com.appboy.IAppboyImageLoader; import com.appboy.events.BrazeNetworkFailureEvent; -import com.appboy.events.ContentCardsUpdatedEvent; +import com.appboy.events.BrazeSdkAuthenticationErrorEvent; import com.appboy.events.FeedUpdatedEvent; import com.appboy.events.IEventSubscriber; import com.appboy.events.IValueCallback; -import com.appboy.events.InAppMessageEvent; import com.appboy.events.SessionStateChangedEvent; -import com.appboy.models.IInAppMessage; import com.appboy.models.cards.Card; import com.appboy.models.outgoing.AppboyProperties; +import com.braze.BrazeUser; +import com.braze.events.ContentCardsUpdatedEvent; +import com.braze.events.InAppMessageEvent; +import com.braze.images.IBrazeImageLoader; +import com.braze.models.inappmessage.IInAppMessage; +import com.braze.models.outgoing.BrazeProperties; import org.json.JSONObject; @@ -46,11 +49,21 @@ public void logCustomEvent(String s, AppboyProperties appboyProperties) { } + @Override + public void logCustomEvent(String s, BrazeProperties brazeProperties) { + + } + @Override public void logPurchase(String s, String s1, BigDecimal bigDecimal) { } + @Override + public void logPurchase(String s, String s1, BigDecimal bigDecimal, BrazeProperties brazeProperties) { + + } + @Override public void logPurchase(String s, String s1, BigDecimal bigDecimal, AppboyProperties appboyProperties) { @@ -61,6 +74,11 @@ public void logPurchase(String s, String s1, BigDecimal bigDecimal, int i) { } + @Override + public void logPurchase(String s, String s1, BigDecimal bigDecimal, int i, BrazeProperties brazeProperties) { + + } + @Override public void logPurchase(String s, String s1, BigDecimal bigDecimal, int i, AppboyProperties appboyProperties) { @@ -141,6 +159,16 @@ public void subscribeToNetworkFailures(IEventSubscriber iEventSubscriber) { + + } + + @Override + public void addSingleSynchronousSubscription(IEventSubscriber iEventSubscriber, Class aClass) { + + } + @Override public void removeSingleSubscription(IEventSubscriber iEventSubscriber, Class aClass) { @@ -152,12 +180,17 @@ public void changeUser(String s) { } @Override - public AppboyUser getCurrentUser() { + public void changeUser(String s, String s1) { + + } + + @Override + public BrazeUser getCurrentUser() { return null; } @Override - public void getCurrentUser(IValueCallback iValueCallback) { + public void getCurrentUser(IValueCallback iValueCallback) { } @@ -166,11 +199,21 @@ public void registerAppboyPushMessages(String s) { } + @Override + public void registerPushToken(String s) { + + } + @Override public String getAppboyPushMessageRegistrationId() { return null; } + @Override + public String getRegisteredPushToken() { + return null; + } + @Override public String getInstallTrackingId() { return null; @@ -181,11 +224,21 @@ public IAppboyImageLoader getAppboyImageLoader() { return null; } + @Override + public IBrazeImageLoader getImageLoader() { + return null; + } + @Override public void setAppboyImageLoader(IAppboyImageLoader iAppboyImageLoader) { } + @Override + public void setImageLoader(IBrazeImageLoader iBrazeImageLoader) { + + } + @Override public int getContentCardCount() { return 0; @@ -227,11 +280,22 @@ public Card deserializeContentCard(@NonNull JSONObject jsonObject) { return null; } + @Override + public void requestLocationInitialization() { + + } + @Override public void requestGeofences(double v, double v1) { } + @NonNull + @Override + public String getDeviceId() { + return null; + } + @Override public void logFeedCardImpression(String s) { @@ -241,4 +305,9 @@ public void logFeedCardImpression(String s) { public void logFeedCardClick(String s) { } + + @Override + public void setSdkAuthenticationSignature(@NonNull String s) { + + } }