From 44965b69c3c79837f77741e2785e6285d1d89a41 Mon Sep 17 00:00:00 2001 From: Mansi Pandya Date: Wed, 10 Jul 2024 19:11:12 -0400 Subject: [PATCH 1/6] fix: Add enum value to hash ecommerce event attributes --- .../kotlin/com/mparticle/kits/AppboyKit.kt | 12 +- src/test/kotlin/com/braze/Braze.kt | 4 + .../com/mparticle/kits/AppboyKitTest.kt | 205 ++++++++++++++++++ 3 files changed, 219 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/mparticle/kits/AppboyKit.kt b/src/main/kotlin/com/mparticle/kits/AppboyKit.kt index 4616e27..6b67331 100644 --- a/src/main/kotlin/com/mparticle/kits/AppboyKit.kt +++ b/src/main/kotlin/com/mparticle/kits/AppboyKit.kt @@ -142,7 +142,11 @@ open class AppboyKit : KitIntegration(), AttributeListener, CommerceListener, val brazePropertiesSetter = BrazePropertiesSetter(properties, enableTypeDetection) event.customAttributeStrings?.let { it -> for ((key, value) in it) { - newAttributes[key] = brazePropertiesSetter.parseValue(key, value) + try { + newAttributes[key] = brazePropertiesSetter.parseValue(key, value) + } catch (e: Exception) { + Logger.warning("Exception while parsing custom attributes $e") + } } } Braze.getInstance(context).logCustomEvent(event.eventName, properties) @@ -152,7 +156,11 @@ open class AppboyKit : KitIntegration(), AttributeListener, CommerceListener, event.customAttributeStrings?.let { it -> for ((key, attributeValue) in it) { val hashedKey = - KitUtils.hashForFiltering(event.eventType.value.toString() + event.eventName + key) + if (event.eventName.contains("eCommerce")) { + KitUtils.hashForFiltering(event.eventType.value.toString() + key.trim()) + } else { + KitUtils.hashForFiltering(event.eventType.value.toString() + event.eventName.trim() + key.trim()) + } configuration.eventAttributesAddToUser?.get(hashedKey)?.let { value.addToCustomAttributeArray(it, attributeValue) diff --git a/src/test/kotlin/com/braze/Braze.kt b/src/test/kotlin/com/braze/Braze.kt index e6fc4ed..33ce6bf 100644 --- a/src/test/kotlin/com/braze/Braze.kt +++ b/src/test/kotlin/com/braze/Braze.kt @@ -51,6 +51,10 @@ class Braze { events.clear() } + fun clearBrazeUser(){ + currentUser.customUserAttributes.clear() + currentUser.customAttributeArray.clear() + } val currentUser = BrazeUser() @JvmStatic diff --git a/src/test/kotlin/com/mparticle/kits/AppboyKitTest.kt b/src/test/kotlin/com/mparticle/kits/AppboyKitTest.kt index 1ac3cea..ff2e48b 100644 --- a/src/test/kotlin/com/mparticle/kits/AppboyKitTest.kt +++ b/src/test/kotlin/com/mparticle/kits/AppboyKitTest.kt @@ -24,6 +24,8 @@ import org.junit.Before import org.junit.Test import org.mockito.Mock import org.mockito.Mockito +import org.mockito.Mockito.mock +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations import java.math.BigDecimal import java.util.Calendar @@ -46,6 +48,7 @@ class AppboyKitTests { MockitoAnnotations.initMocks(this) Braze.clearPurchases() Braze.clearEvents() + Braze.clearBrazeUser() MParticle.setInstance(Mockito.mock(MParticle::class.java)) Mockito.`when`(MParticle.getInstance()!!.Identity()).thenReturn( Mockito.mock( @@ -567,6 +570,208 @@ class AppboyKitTests { Assert.assertEquals(emptyAttributes, properties) } + @Test + fun testCustomAttributes() { + val kit = MockAppboyKit() + val currentUser = braze.currentUser + + val product: Product = Product.Builder("La Enchilada", "13061043670", 12.5) + .quantity(1.0) + .build() + + val txAttributes = TransactionAttributes() + .setRevenue(product.totalAmount) + + kit.configuration = MockKitConfiguration() + val customAttributes: MutableMap = HashMap() + customAttributes["currentLocationLongitude"] = "2.1811267" + customAttributes["country"] = "ES" + customAttributes["deliveryLocationLatitude"] = "41.4035798" + customAttributes["appVersion"] = "5.201.0" + customAttributes["city"] = "BCN" + customAttributes["deviceId"] = "1104442582" + customAttributes["platform"] = "android" + customAttributes["isAuthorized"] = "true" + val commerceEvent: CommerceEvent = CommerceEvent.Builder(Product.ADD_TO_CART, product) + .currency("EUR") + .customAttributes(customAttributes) + .transactionAttributes(txAttributes) + .build() + + val jsonObject = JSONObject() + val mapValue = JSONObject() + mapValue.put("-844012960", "test") + val eassObject = JSONObject() + eassObject.put("eaa", mapValue) + + jsonObject.put("hs", eassObject) + val mockSparseBooleanArray = mock(SparseBooleanArray::class.java) + + `when`(mockSparseBooleanArray.size()).thenReturn(0) + `when`(mTypeFilters!!.size()).thenReturn(0) // Example mock behavior + + var kitConfiguration = MockKitConfiguration.createKitConfiguration(jsonObject) + kit.configuration = kitConfiguration + + kit.logEvent(commerceEvent) + Assert.assertEquals(1, braze.events.size.toLong()) + Assert.assertEquals(1, currentUser.getCustomAttribute().size.toLong()) + var outputKey = "" + for (keys in currentUser.getCustomAttribute().keys) { + outputKey = keys + break + } + Assert.assertEquals("test", outputKey) + + } + + @Test + fun testCustomAttributes_log_event() { + val kit = MockAppboyKit() + val currentUser = braze.currentUser + + kit.configuration = MockKitConfiguration() + + val jsonObject = JSONObject() + val mapValue = JSONObject() + //this is hash for event attribute i.e combination of eventType + eventName + attribute key + mapValue.put("888169310", "testEvent") + val eassObject = JSONObject() + eassObject.put("eaa", mapValue) + + jsonObject.put("hs", eassObject) + val mockSparseBooleanArray = mock(SparseBooleanArray::class.java) + + `when`(mockSparseBooleanArray.size()).thenReturn(0) + `when`(mTypeFilters!!.size()).thenReturn(0) // Example mock behavior + + var kitConfiguration = MockKitConfiguration.createKitConfiguration(jsonObject) + kit.configuration = kitConfiguration + val customAttributes: MutableMap = HashMap() + customAttributes["destination"] = "Shop" + val event = MPEvent.Builder("AndroidTEST", MParticle.EventType.Navigation) + .customAttributes(customAttributes) + .build() + val instance = MParticle.getInstance() + instance?.logEvent(event) + kit.logEvent(event) + Assert.assertEquals(1, braze.events.size.toLong()) + Assert.assertEquals(1, currentUser.getCustomAttribute().size.toLong()) + var outputKey = "" + for (keys in currentUser.getCustomAttribute().keys) { + outputKey = keys + break + } + Assert.assertEquals("testEvent", outputKey) + } + + @Test + fun testCustomAttributes_remove_attributes() { + val kit = MockAppboyKit() + val currentUser = braze.currentUser + kit.configuration = MockKitConfiguration() + val product: Product = Product.Builder("La Enchilada", "13061043670", 12.5) + .quantity(1.0) + .build() + + val txAttributes = TransactionAttributes() + .setRevenue(product.totalAmount) + + + val customAttributes: MutableMap = HashMap() + customAttributes["currentLocationLongitude"] = "2.1811267" + customAttributes["country"] = "ES" + customAttributes["deliveryLocationLatitude"] = "41.4035798" + customAttributes["appVersion"] = "5.201.0" + customAttributes["city"] = "BCN" + customAttributes["deviceId"] = "1104442582" + customAttributes["platform"] = "android" + customAttributes["isAuthorized"] = "true" + val commerceEvent: CommerceEvent = CommerceEvent.Builder(Product.ADD_TO_CART, product) + .currency("EUR") + .customAttributes(customAttributes) + .transactionAttributes(txAttributes) + .build() + + + val jsonObject = JSONObject() + val mapValue = JSONObject() + //this is hash for event attribute i.e combination of eventType + eventName + attribute key + mapValue.put("-844012960", "test") + val eassObject = JSONObject() + eassObject.put("eaa", mapValue) + eassObject.put("ear", mapValue) + jsonObject.put("hs", eassObject) + val mockSparseBooleanArray = mock(SparseBooleanArray::class.java) + + `when`(mockSparseBooleanArray.size()).thenReturn(0) + `when`(mTypeFilters!!.size()).thenReturn(0) // Example mock behavior + + var kitConfiguration = MockKitConfiguration.createKitConfiguration(jsonObject) + kit.configuration = kitConfiguration + + kit.logEvent(commerceEvent) + + Assert.assertEquals(1, braze.events.size.toLong()) + Assert.assertEquals(0, currentUser.getCustomAttribute().size.toLong()) + + } + + + + @Test + fun testCustomAttributes_add_customUserAttribute() { + val kit = MockAppboyKit() + val currentUser = braze.currentUser + kit.configuration = MockKitConfiguration() + val product: Product = Product.Builder("La Enchilada", "13061043670", 12.5) + .quantity(1.0) + .build() + + val txAttributes = TransactionAttributes() + .setRevenue(product.totalAmount) + + + val customAttributes: MutableMap = HashMap() + customAttributes["currentLocationLongitude"] = "2.1811267" + customAttributes["country"] = "ES" + customAttributes["deliveryLocationLatitude"] = "41.4035798" + customAttributes["appVersion"] = "5.201.0" + customAttributes["city"] = "BCN" + customAttributes["deviceId"] = "1104442582" + customAttributes["platform"] = "android" + customAttributes["isAuthorized"] = "true" + val commerceEvent: CommerceEvent = CommerceEvent.Builder(Product.ADD_TO_CART, product) + .currency("EUR") + .customAttributes(customAttributes) + .transactionAttributes(txAttributes) + .build() + + + val jsonObject = JSONObject() + val mapValue = JSONObject() + //this is hash for event attribute i.e combination of eventType + eventName + attribute key + mapValue.put("-844012960", "test") + val eassObject = JSONObject() + eassObject.put("eaa", mapValue) + eassObject.put("eas", mapValue) + + jsonObject.put("hs", eassObject) + val mockSparseBooleanArray = mock(SparseBooleanArray::class.java) + + `when`(mockSparseBooleanArray.size()).thenReturn(0) + `when`(mTypeFilters!!.size()).thenReturn(0) // Example mock behavior + + var kitConfiguration = MockKitConfiguration.createKitConfiguration(jsonObject) + kit.configuration = kitConfiguration + + kit.logEvent(commerceEvent) + + Assert.assertEquals(1, braze.events.size.toLong()) + Assert.assertEquals(1, currentUser.getCustomUserAttribute().size) + + } + // @Test // fun testPromotion() { // val emptyAttributes = HashMap() From edf1408b5ca573ea06f5db07760078cd2408b69a Mon Sep 17 00:00:00 2001 From: Mansi Pandya Date: Thu, 18 Jul 2024 13:27:02 -0400 Subject: [PATCH 2/6] Update the logic based on feedback --- .../kotlin/com/mparticle/kits/AppboyKit.kt | 82 +++++--- .../com/mparticle/kits/AppboyKitTest.kt | 187 +++++++++++++++++- 2 files changed, 238 insertions(+), 31 deletions(-) diff --git a/src/main/kotlin/com/mparticle/kits/AppboyKit.kt b/src/main/kotlin/com/mparticle/kits/AppboyKit.kt index 6b67331..a58240a 100644 --- a/src/main/kotlin/com/mparticle/kits/AppboyKit.kt +++ b/src/main/kotlin/com/mparticle/kits/AppboyKit.kt @@ -134,6 +134,19 @@ open class AppboyKit : KitIntegration(), AttributeListener, CommerceListener, ): List = emptyList() override fun logEvent(event: MPEvent): List { + if (event.customAttributes != null) { + event.customAttributeStrings?.let { + changeUserArray( + it, + event.eventType.value, + event.eventName, false + ) + } + } + return logBrazeEvent(event) + } + + private fun logBrazeEvent(event: MPEvent): List { val newAttributes: MutableMap = HashMap() if (event.customAttributes == null) { Braze.getInstance(context).logCustomEvent(event.eventName) @@ -150,40 +163,47 @@ open class AppboyKit : KitIntegration(), AttributeListener, CommerceListener, } } Braze.getInstance(context).logCustomEvent(event.eventName, properties) - Braze.getInstance(context).getCurrentUser(object : IValueCallback { - override fun onSuccess(value: BrazeUser) { - val userAttributeSetter = UserAttributeSetter(value, enableTypeDetection) - event.customAttributeStrings?.let { it -> - for ((key, attributeValue) in it) { - val hashedKey = - if (event.eventName.contains("eCommerce")) { - KitUtils.hashForFiltering(event.eventType.value.toString() + key.trim()) - } else { - KitUtils.hashForFiltering(event.eventType.value.toString() + event.eventName.trim() + key.trim()) - } - - configuration.eventAttributesAddToUser?.get(hashedKey)?.let { - value.addToCustomAttributeArray(it, attributeValue) - } - configuration.eventAttributesRemoveFromUser?.get(hashedKey)?.let { - value.removeFromCustomAttributeArray(it, attributeValue) - } - configuration.eventAttributesSingleItemUser?.get(hashedKey)?.let { - userAttributeSetter.parseValue(it, attributeValue) + } + queueDataFlush() + return listOf(ReportingMessage.fromEvent(this, event).setAttributes(newAttributes)) + } + + private fun changeUserArray( + customAttributes: Map, + eventType: Int, + eventName: String?, + isCommerceEvent: Boolean + ) { + Braze.getInstance(context).getCurrentUser(object : IValueCallback { + override fun onSuccess(value: BrazeUser) { + val userAttributeSetter = UserAttributeSetter(value, enableTypeDetection) + customAttributes?.let { it -> + for ((key, attributeValue) in it) { + //for commerce event, event name is not required for generate hash + val hashedKey = + if (isCommerceEvent) { + KitUtils.hashForFiltering(eventType.toString() + key) + } else { + KitUtils.hashForFiltering(eventType.toString() + eventName + key) } + configuration.eventAttributesAddToUser?.get(hashedKey)?.let { + value.addToCustomAttributeArray(it, attributeValue) + } + configuration.eventAttributesRemoveFromUser?.get(hashedKey)?.let { + value.removeFromCustomAttributeArray(it, attributeValue) + } + configuration.eventAttributesSingleItemUser?.get(hashedKey)?.let { + userAttributeSetter.parseValue(it, attributeValue) } } } + } - override fun onError() { - Logger.warning("unable to acquire user to add or remove custom user attributes from events") - } - }) - } - queueDataFlush() - return listOf(ReportingMessage.fromEvent(this, event).setAttributes(newAttributes)) + override fun onError() { + Logger.warning("unable to acquire user to add or remove custom user attributes from events") + } + }) } - override fun logScreen( screenName: String, screenAttributes: Map? @@ -224,6 +244,10 @@ open class AppboyKit : KitIntegration(), AttributeListener, CommerceListener, override fun logEvent(event: CommerceEvent): List { val messages: MutableList = LinkedList() + //For CommerceEvent, Event Name is not required to generate hash. So, it will be always null. + event.products?.get(0)?.customAttributes?.let { + changeUserArray(it, CommerceEventUtils.getEventType(event), null, true) + } if (!KitUtils.isEmpty(event.productAction) && event.productAction.equals( Product.PURCHASE, @@ -257,7 +281,7 @@ open class AppboyKit : KitIntegration(), AttributeListener, CommerceListener, for (pair in map) { e.customAttributes?.put(pair.key, pair.value) } - logEvent(e) + logBrazeEvent(e) messages.add(ReportingMessage.fromEvent(this, event)) } catch (e: Exception) { Logger.warning("Failed to call logCustomEvent to Appboy kit: $e") diff --git a/src/test/kotlin/com/mparticle/kits/AppboyKitTest.kt b/src/test/kotlin/com/mparticle/kits/AppboyKitTest.kt index ff2e48b..15d93b6 100644 --- a/src/test/kotlin/com/mparticle/kits/AppboyKitTest.kt +++ b/src/test/kotlin/com/mparticle/kits/AppboyKitTest.kt @@ -27,6 +27,7 @@ import org.mockito.Mockito import org.mockito.Mockito.mock import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations +import java.lang.reflect.Method import java.math.BigDecimal import java.util.Calendar import java.util.Locale @@ -577,6 +578,13 @@ class AppboyKitTests { val product: Product = Product.Builder("La Enchilada", "13061043670", 12.5) .quantity(1.0) + .customAttributes( + mapOf( + "size" to "5", + "color" to "Black", + "Total Amount" to "120.22" + ) + ) .build() val txAttributes = TransactionAttributes() @@ -600,7 +608,7 @@ class AppboyKitTests { val jsonObject = JSONObject() val mapValue = JSONObject() - mapValue.put("-844012960", "test") + mapValue.put("-94160813", "test") val eassObject = JSONObject() eassObject.put("eaa", mapValue) @@ -726,6 +734,13 @@ class AppboyKitTests { kit.configuration = MockKitConfiguration() val product: Product = Product.Builder("La Enchilada", "13061043670", 12.5) .quantity(1.0) + .customAttributes( + mapOf( + "size" to "5", + "color" to "Black", + "Total Amount" to "120.22" + ) + ) .build() val txAttributes = TransactionAttributes() @@ -741,6 +756,9 @@ class AppboyKitTests { customAttributes["deviceId"] = "1104442582" customAttributes["platform"] = "android" customAttributes["isAuthorized"] = "true" + + + val commerceEvent: CommerceEvent = CommerceEvent.Builder(Product.ADD_TO_CART, product) .currency("EUR") .customAttributes(customAttributes) @@ -751,7 +769,7 @@ class AppboyKitTests { val jsonObject = JSONObject() val mapValue = JSONObject() //this is hash for event attribute i.e combination of eventType + eventName + attribute key - mapValue.put("-844012960", "test") + mapValue.put("-94160813", "test") val eassObject = JSONObject() eassObject.put("eaa", mapValue) eassObject.put("eas", mapValue) @@ -1247,4 +1265,169 @@ class AppboyKitTests { } Assert.assertEquals("testEvent", outputKey) } + + @Test + fun testChangeUserArray() { + val kit = MockAppboyKit() + val currentUser = braze.currentUser + + kit.configuration = MockKitConfiguration() + + val jsonObject = JSONObject() + val mapValue = JSONObject() + //this is hash for event attribute i.e combination of eventType + eventName + attribute key + mapValue.put("888169310", "testEvent") + val eaaObject = JSONObject() + eaaObject.put("eaa", mapValue) + jsonObject.put("hs", eaaObject) + + Mockito.`when`(mTypeFilters!!.size()).thenReturn(0) + + var kitConfiguration = MockKitConfiguration.createKitConfiguration(jsonObject) + kit.configuration = kitConfiguration + val customAttributes: MutableMap = HashMap() + customAttributes["destination"] = "Shop" + + val method: Method = AppboyKit::class.java.getDeclaredMethod( + "changeUserArray", + Map::class.java, + Int::class.java, + String::class.java, + Boolean::class.java + ) + method.isAccessible = true + method.invoke(kit, customAttributes, 1, "AndroidTEST", false) + Assert.assertEquals(1, currentUser.getCustomAttribute().size.toLong()) + var outputKey = "" + for (keys in currentUser.getCustomAttribute().keys) { + outputKey = keys + break + } + Assert.assertEquals("testEvent", outputKey) + } + + @Test + fun testChangeUserArray_for_commerceEvent() { + val kit = MockAppboyKit() + val currentUser = braze.currentUser + + val product: Product = Product.Builder("La Enchilada", "13061043670", 12.5) + .quantity(1.0) + .build() + + val txAttributes = TransactionAttributes() + .setRevenue(product.totalAmount) + + kit.configuration = MockKitConfiguration() + val customAttributes: MutableMap = HashMap() + customAttributes["currentLocationLongitude"] = "2.1811267" + customAttributes["country"] = "ES" + customAttributes["deliveryLocationLatitude"] = "41.4035798" + customAttributes["appVersion"] = "5.201.0" + customAttributes["city"] = "BCN" + customAttributes["deviceId"] = "1104442582" + customAttributes["platform"] = "android" + customAttributes["isAuthorized"] = "true" + + + val jsonObject = JSONObject() + val mapValue = JSONObject() + mapValue.put("-844012960", "test") + val eassObject = JSONObject() + eassObject.put("eaa", mapValue) + + jsonObject.put("hs", eassObject) + val mockSparseBooleanArray = Mockito.mock(SparseBooleanArray::class.java) + + Mockito.`when`(mockSparseBooleanArray.size()).thenReturn(0) + Mockito.`when`(mTypeFilters!!.size()).thenReturn(0) // Example mock behavior + + var kitConfiguration = MockKitConfiguration.createKitConfiguration(jsonObject) + kit.configuration = kitConfiguration + + val method: Method = AppboyKit::class.java.getDeclaredMethod( + "changeUserArray", + Map::class.java, + Int::class.java, + String::class.java, + Boolean::class.java + ) + method.isAccessible = true + method.invoke(kit, customAttributes, 10, null, true) + Assert.assertEquals(1, currentUser.getCustomAttribute().size.toLong()) + var outputKey = "" + for (keys in currentUser.getCustomAttribute().keys) { + outputKey = keys + break + } + Assert.assertEquals("test", outputKey) + } + + @Test + fun testChangeUserArray_When_customAttribute_null() { + val kit = MockAppboyKit() + val currentUser = braze.currentUser + + kit.configuration = MockKitConfiguration() + + val jsonObject = JSONObject() + val mapValue = JSONObject() + //this is hash for event attribute i.e combination of eventType + eventName + attribute key + mapValue.put("888169310", "testEvent") + val eaaObject = JSONObject() + eaaObject.put("eaa", mapValue) + jsonObject.put("hs", eaaObject) + + Mockito.`when`(mTypeFilters!!.size()).thenReturn(0) + + var kitConfiguration = MockKitConfiguration.createKitConfiguration(jsonObject) + kit.configuration = kitConfiguration + val customAttributes: MutableMap = HashMap() + customAttributes["destination"] = "Shop" + + val method: Method = AppboyKit::class.java.getDeclaredMethod( + "changeUserArray", + Map::class.java, + Int::class.java, + String::class.java, + Boolean::class.java + ) + method.isAccessible = true + method.invoke(kit, null, 1, "AndroidTEST", false) + Assert.assertEquals(0, currentUser.getCustomAttribute().size.toLong()) + } + + @Test + fun testChangeUserArray_When_EventType_Wrong() { + val kit = MockAppboyKit() + val currentUser = braze.currentUser + + kit.configuration = MockKitConfiguration() + + val jsonObject = JSONObject() + val mapValue = JSONObject() + //this is hash for event attribute i.e combination of eventType + eventName + attribute key + mapValue.put("888169310", "testEvent") + val eaaObject = JSONObject() + eaaObject.put("eaa", mapValue) + jsonObject.put("hs", eaaObject) + + Mockito.`when`(mTypeFilters!!.size()).thenReturn(0) + + var kitConfiguration = MockKitConfiguration.createKitConfiguration(jsonObject) + kit.configuration = kitConfiguration + val customAttributes: MutableMap = HashMap() + customAttributes["destination"] = "Shop" + + val method: Method = AppboyKit::class.java.getDeclaredMethod( + "changeUserArray", + Map::class.java, + Int::class.java, + String::class.java, + Boolean::class.java + ) + method.isAccessible = true + method.invoke(kit, customAttributes, 5, "AndroidTEST", false) + Assert.assertEquals(0, currentUser.getCustomAttribute().size.toLong()) + } } From f932c0334c25edd0079b245665bd513648aa333d Mon Sep 17 00:00:00 2001 From: Mansi Pandya Date: Fri, 19 Jul 2024 09:53:44 -0400 Subject: [PATCH 3/6] Address review comment --- .../kotlin/com/mparticle/kits/AppboyKit.kt | 51 ++++- .../com/mparticle/kits/AppboyKitTest.kt | 180 ++++++++++++++++++ 2 files changed, 227 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/com/mparticle/kits/AppboyKit.kt b/src/main/kotlin/com/mparticle/kits/AppboyKit.kt index a58240a..b97a4f2 100644 --- a/src/main/kotlin/com/mparticle/kits/AppboyKit.kt +++ b/src/main/kotlin/com/mparticle/kits/AppboyKit.kt @@ -9,7 +9,11 @@ import com.braze.Braze import com.braze.BrazeActivityLifecycleCallbackListener import com.braze.BrazeUser import com.braze.configuration.BrazeConfig -import com.braze.enums.* +import com.braze.enums.BrazeSdkMetadata +import com.braze.enums.Gender +import com.braze.enums.Month +import com.braze.enums.NotificationSubscriptionType +import com.braze.enums.SdkFlavor import com.braze.events.IValueCallback import com.braze.models.outgoing.BrazeProperties import com.braze.push.BrazeFirebaseMessagingService @@ -26,10 +30,16 @@ import com.mparticle.commerce.Promotion import com.mparticle.identity.MParticleUser import com.mparticle.internal.Logger import com.mparticle.kits.CommerceEventUtils.OnAttributeExtracted -import com.mparticle.kits.KitIntegration.* +import com.mparticle.kits.KitIntegration.AttributeListener +import com.mparticle.kits.KitIntegration.CommerceListener +import com.mparticle.kits.KitIntegration.IdentityListener +import com.mparticle.kits.KitIntegration.PushListener import java.math.BigDecimal import java.text.SimpleDateFormat -import java.util.* +import java.util.Calendar +import java.util.Date +import java.util.EnumSet +import java.util.LinkedList /** * mParticle client-side Appboy integration @@ -244,10 +254,42 @@ open class AppboyKit : KitIntegration(), AttributeListener, CommerceListener, override fun logEvent(event: CommerceEvent): List { val messages: MutableList = LinkedList() + //For CommerceEvent, Event Name is not required to generate hash. So, it will be always null. - event.products?.get(0)?.customAttributes?.let { + event.products?.forEach { + it.customAttributes?.let { + changeUserArray(it, CommerceEventUtils.getEventType(event), null, true) + } + } + event.transactionAttributes?.let { + val map = mapOf( + "Affiliation" to it.affiliation, + "Revenue" to it.revenue?.toString(), + "Shipping" to it.shipping?.toString(), + "Tax" to it.tax?.toString(), + "CouponCode" to it.couponCode?.toString(), + "Id" to it.id.toString() + ).mapValues { it.value.toString() } + changeUserArray(map, CommerceEventUtils.getEventType(event), null, true) + } + event.customAttributeStrings?.let { changeUserArray(it, CommerceEventUtils.getEventType(event), null, true) } + event.impressions?.forEach { impression -> + impression.products.forEach { product -> + product.customAttributes?.let { + changeUserArray(it, CommerceEventUtils.getEventType(event), null, true) + } + } + } + event.promotions?.forEach { + val attributes: MutableMap = HashMap() + if (event.customAttributeStrings != null) { + attributes.putAll(event.customAttributeStrings!!) + } + CommerceEventUtils.extractPromotionAttributes(it, attributes) + changeUserArray(attributes, CommerceEventUtils.getEventType(event), null, true) + } if (!KitUtils.isEmpty(event.productAction) && event.productAction.equals( Product.PURCHASE, @@ -856,6 +898,7 @@ open class AppboyKit : KitIntegration(), AttributeListener, CommerceListener, return impressionArray } + internal abstract class StringTypeParser(var enableTypeDetection: Boolean) { fun parseValue(key: String, value: String): Any { if (!enableTypeDetection) { diff --git a/src/test/kotlin/com/mparticle/kits/AppboyKitTest.kt b/src/test/kotlin/com/mparticle/kits/AppboyKitTest.kt index 15d93b6..2330fda 100644 --- a/src/test/kotlin/com/mparticle/kits/AppboyKitTest.kt +++ b/src/test/kotlin/com/mparticle/kits/AppboyKitTest.kt @@ -1430,4 +1430,184 @@ class AppboyKitTests { method.invoke(kit, customAttributes, 5, "AndroidTEST", false) Assert.assertEquals(0, currentUser.getCustomAttribute().size.toLong()) } + + @Test + fun testChangeUserArray_for_TransactionAttributes() { + val kit = MockAppboyKit() + val currentUser = braze.currentUser + + val product: Product = Product.Builder("La Enchilada", "13061043670", 12.5) + .quantity(1.0) + .customAttributes( + mapOf( + "size" to "5", + "color" to "Black", + "Total Amount" to "120.22" + ) + ) + .build() + + val txAttributes = TransactionAttributes() + .setRevenue(product.totalAmount) + + kit.configuration = MockKitConfiguration() + val commerceEvent: CommerceEvent = CommerceEvent.Builder(Product.ADD_TO_CART, product) + .currency("EUR") + .transactionAttributes(txAttributes) + .build() + + val jsonObject = JSONObject() + val mapValue = JSONObject() + mapValue.put("484437277", "transactionAttributes") + val eassObject = JSONObject() + eassObject.put("eaa", mapValue) + + jsonObject.put("hs", eassObject) + val mockSparseBooleanArray = mock(SparseBooleanArray::class.java) + + `when`(mockSparseBooleanArray.size()).thenReturn(0) + `when`(mTypeFilters!!.size()).thenReturn(0) // Example mock behavior + + var kitConfiguration = MockKitConfiguration.createKitConfiguration(jsonObject) + kit.configuration = kitConfiguration + + kit.logEvent(commerceEvent) + Assert.assertEquals(1, braze.events.size.toLong()) + Assert.assertEquals(1, currentUser.getCustomAttribute().size.toLong()) + var outputKey = "" + for (keys in currentUser.getCustomAttribute().keys) { + outputKey = keys + break + } + Assert.assertEquals("transactionAttributes", outputKey) + } + + @Test + fun testChangeUserArray_for_Impressions() { + val kit = MockAppboyKit() + val currentUser = braze.currentUser + + val product: Product = Product.Builder("La Enchilada", "13061043670", 12.5) + .quantity(1.0) + .customAttributes( + mapOf( + "size" to "5", + "color" to "Black", + "Total Amount" to "120.22" + ) + ) + .build() + + kit.configuration = MockKitConfiguration() + val jsonObject = JSONObject() + val mapValue = JSONObject() + mapValue.put("-1186525452", "TestImpressions") + val eassObject = JSONObject() + eassObject.put("eaa", mapValue) + + jsonObject.put("hs", eassObject) + val mockSparseBooleanArray = mock(SparseBooleanArray::class.java) + + `when`(mockSparseBooleanArray.size()).thenReturn(0) + `when`(mTypeFilters!!.size()).thenReturn(0) // Example mock behavior + + var kitConfiguration = MockKitConfiguration.createKitConfiguration(jsonObject) + kit.configuration = kitConfiguration + Impression("Suggested Products List", product).let { + CommerceEvent.Builder(it).build() + }.let { + kit.logEvent(it) + } + Assert.assertEquals(1, braze.events.size.toLong()) + Assert.assertEquals(1, currentUser.getCustomAttribute().size.toLong()) + var outputKey = "" + for (keys in currentUser.getCustomAttribute().keys) { + outputKey = keys + break + } + Assert.assertEquals("TestImpressions", outputKey) + } + + @Test + fun testChangeUserArray_for_Promotions() { + val kit = MockAppboyKit() + val currentUser = braze.currentUser + kit.configuration = MockKitConfiguration() + val jsonObject = JSONObject() + val mapValue = JSONObject() + mapValue.put("1458842803", "TestImpressions") + val eassObject = JSONObject() + eassObject.put("eaa", mapValue) + + jsonObject.put("hs", eassObject) + val mockSparseBooleanArray = mock(SparseBooleanArray::class.java) + + `when`(mockSparseBooleanArray.size()).thenReturn(0) + `when`(mTypeFilters!!.size()).thenReturn(0) // Example mock behavior + + var kitConfiguration = MockKitConfiguration.createKitConfiguration(jsonObject) + kit.configuration = kitConfiguration + Promotion().apply { + id = "test_1" + creative = "test_button_1" + name = "10% off Sale" + position ="button_center" + }.let { + CommerceEvent.Builder(Promotion.CLICK, it).build() + }.let { + kit.logEvent(it) + } + Assert.assertEquals(1, braze.events.size.toLong()) + Assert.assertEquals(1, currentUser.getCustomAttribute().size.toLong()) + var outputKey = "" + for (keys in currentUser.getCustomAttribute().keys) { + outputKey = keys + break + } + Assert.assertEquals("TestImpressions", outputKey) + } + + @Test + fun testChangeUserArray_for_Promotions_with_custom_attribute() { + val kit = MockAppboyKit() + val currentUser = braze.currentUser + kit.configuration = MockKitConfiguration() + val jsonObject = JSONObject() + val mapValue = JSONObject() + mapValue.put("1560179420", "TestImpressions") + val eassObject = JSONObject() + eassObject.put("eaa", mapValue) + + jsonObject.put("hs", eassObject) + val mockSparseBooleanArray = mock(SparseBooleanArray::class.java) + + `when`(mockSparseBooleanArray.size()).thenReturn(0) + `when`(mTypeFilters!!.size()).thenReturn(0) // Example mock behavior + + var kitConfiguration = MockKitConfiguration.createKitConfiguration(jsonObject) + kit.configuration = kitConfiguration + Promotion().apply { + id = "test_1" + creative = "test_button_1" + name = "10% off Sale" + position ="button_center" + }.let { + CommerceEvent.Builder(Promotion.CLICK, it).customAttributes( + mapOf( + "color" to "Red", + "Total Amount" to "150.00" + )).build() + }.let { + kit.logEvent(it) + } + Assert.assertEquals(1, braze.events.size.toLong()) + Assert.assertEquals(1, currentUser.getCustomAttribute().size.toLong()) + var outputKey = "" + for (keys in currentUser.getCustomAttribute().keys) { + outputKey = keys + break + } + Assert.assertEquals("TestImpressions", outputKey) + } + } From 50902e27ecfa6a86a7c4b46249c1e8c1cde13f12 Mon Sep 17 00:00:00 2001 From: Mansi Pandya Date: Fri, 19 Jul 2024 09:55:29 -0400 Subject: [PATCH 4/6] change variable name --- src/main/kotlin/com/mparticle/kits/AppboyKit.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/com/mparticle/kits/AppboyKit.kt b/src/main/kotlin/com/mparticle/kits/AppboyKit.kt index b97a4f2..c074337 100644 --- a/src/main/kotlin/com/mparticle/kits/AppboyKit.kt +++ b/src/main/kotlin/com/mparticle/kits/AppboyKit.kt @@ -185,8 +185,8 @@ open class AppboyKit : KitIntegration(), AttributeListener, CommerceListener, isCommerceEvent: Boolean ) { Braze.getInstance(context).getCurrentUser(object : IValueCallback { - override fun onSuccess(value: BrazeUser) { - val userAttributeSetter = UserAttributeSetter(value, enableTypeDetection) + override fun onSuccess(brazeUser: BrazeUser) { + val userAttributeSetter = UserAttributeSetter(brazeUser, enableTypeDetection) customAttributes?.let { it -> for ((key, attributeValue) in it) { //for commerce event, event name is not required for generate hash @@ -197,10 +197,10 @@ open class AppboyKit : KitIntegration(), AttributeListener, CommerceListener, KitUtils.hashForFiltering(eventType.toString() + eventName + key) } configuration.eventAttributesAddToUser?.get(hashedKey)?.let { - value.addToCustomAttributeArray(it, attributeValue) + brazeUser.addToCustomAttributeArray(it, attributeValue) } configuration.eventAttributesRemoveFromUser?.get(hashedKey)?.let { - value.removeFromCustomAttributeArray(it, attributeValue) + brazeUser.removeFromCustomAttributeArray(it, attributeValue) } configuration.eventAttributesSingleItemUser?.get(hashedKey)?.let { userAttributeSetter.parseValue(it, attributeValue) From fd439ba3165e4b300e50a6ba0ceb0b9d11e29356 Mon Sep 17 00:00:00 2001 From: Mansi Pandya Date: Fri, 19 Jul 2024 10:09:39 -0400 Subject: [PATCH 5/6] fix unit test case --- src/main/kotlin/com/mparticle/kits/AppboyKit.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/mparticle/kits/AppboyKit.kt b/src/main/kotlin/com/mparticle/kits/AppboyKit.kt index c074337..3bfd62c 100644 --- a/src/main/kotlin/com/mparticle/kits/AppboyKit.kt +++ b/src/main/kotlin/com/mparticle/kits/AppboyKit.kt @@ -268,7 +268,7 @@ open class AppboyKit : KitIntegration(), AttributeListener, CommerceListener, "Shipping" to it.shipping?.toString(), "Tax" to it.tax?.toString(), "CouponCode" to it.couponCode?.toString(), - "Id" to it.id.toString() + "Id" to it.id?.toString() ).mapValues { it.value.toString() } changeUserArray(map, CommerceEventUtils.getEventType(event), null, true) } From c162935f2166aa4bb7e7fa1cdfe4ac6480c48104 Mon Sep 17 00:00:00 2001 From: Mansi Pandya Date: Fri, 19 Jul 2024 16:37:37 -0400 Subject: [PATCH 6/6] add custom attributes --- .../kotlin/com/mparticle/kits/AppboyKit.kt | 33 ++----------------- .../com/mparticle/kits/AppboyKitTest.kt | 2 +- 2 files changed, 4 insertions(+), 31 deletions(-) diff --git a/src/main/kotlin/com/mparticle/kits/AppboyKit.kt b/src/main/kotlin/com/mparticle/kits/AppboyKit.kt index 3bfd62c..b307d9d 100644 --- a/src/main/kotlin/com/mparticle/kits/AppboyKit.kt +++ b/src/main/kotlin/com/mparticle/kits/AppboyKit.kt @@ -256,40 +256,13 @@ open class AppboyKit : KitIntegration(), AttributeListener, CommerceListener, val messages: MutableList = LinkedList() //For CommerceEvent, Event Name is not required to generate hash. So, it will be always null. - event.products?.forEach { - it.customAttributes?.let { - changeUserArray(it, CommerceEventUtils.getEventType(event), null, true) - } - } - event.transactionAttributes?.let { - val map = mapOf( - "Affiliation" to it.affiliation, - "Revenue" to it.revenue?.toString(), - "Shipping" to it.shipping?.toString(), - "Tax" to it.tax?.toString(), - "CouponCode" to it.couponCode?.toString(), - "Id" to it.id?.toString() - ).mapValues { it.value.toString() } - changeUserArray(map, CommerceEventUtils.getEventType(event), null, true) + val eventAllAttributes=CommerceEventUtils.convertCommerceEventToMap(event) + eventAllAttributes?.let { + changeUserArray(it, CommerceEventUtils.getEventType(event), null, true) } event.customAttributeStrings?.let { changeUserArray(it, CommerceEventUtils.getEventType(event), null, true) } - event.impressions?.forEach { impression -> - impression.products.forEach { product -> - product.customAttributes?.let { - changeUserArray(it, CommerceEventUtils.getEventType(event), null, true) - } - } - } - event.promotions?.forEach { - val attributes: MutableMap = HashMap() - if (event.customAttributeStrings != null) { - attributes.putAll(event.customAttributeStrings!!) - } - CommerceEventUtils.extractPromotionAttributes(it, attributes) - changeUserArray(attributes, CommerceEventUtils.getEventType(event), null, true) - } if (!KitUtils.isEmpty(event.productAction) && event.productAction.equals( Product.PURCHASE, diff --git a/src/test/kotlin/com/mparticle/kits/AppboyKitTest.kt b/src/test/kotlin/com/mparticle/kits/AppboyKitTest.kt index 2330fda..7ec361a 100644 --- a/src/test/kotlin/com/mparticle/kits/AppboyKitTest.kt +++ b/src/test/kotlin/com/mparticle/kits/AppboyKitTest.kt @@ -1458,7 +1458,7 @@ class AppboyKitTests { val jsonObject = JSONObject() val mapValue = JSONObject() - mapValue.put("484437277", "transactionAttributes") + mapValue.put("-94160813", "transactionAttributes") val eassObject = JSONObject() eassObject.put("eaa", mapValue)