diff --git a/app/src/main/kotlin/com/crobox/sdk/testapp/MainActivity.kt b/app/src/main/kotlin/com/crobox/sdk/testapp/MainActivity.kt index d9825f2..c9fd87f 100644 --- a/app/src/main/kotlin/com/crobox/sdk/testapp/MainActivity.kt +++ b/app/src/main/kotlin/com/crobox/sdk/testapp/MainActivity.kt @@ -8,9 +8,13 @@ import com.crobox.sdk.common.LocaleCode import com.crobox.sdk.config.CroboxConfig import com.crobox.sdk.core.Crobox import com.crobox.sdk.data.model.CartQueryParams +import com.crobox.sdk.data.model.CheckoutParams import com.crobox.sdk.data.model.ClickQueryParams import com.crobox.sdk.data.model.ErrorQueryParams import com.crobox.sdk.data.model.PageType +import com.crobox.sdk.data.model.PageViewParams +import com.crobox.sdk.data.model.ProductParams +import com.crobox.sdk.data.model.PurchaseParams import com.crobox.sdk.data.model.RequestQueryParams import com.crobox.sdk.domain.PromotionsResponse import com.crobox.sdk.presenter.PromotionCallback @@ -66,7 +70,25 @@ class MainActivity : AppCompatActivity() { ) // Sending Page View events - croboxInstance.pageViewEvent(indexPageParams) + croboxInstance.pageViewEvent( + indexPageParams, + PageViewParams( + pageTitle = "some page title", + product = ProductParams( + productId = "1", + price = 1.0, + quantity = 1, + otherProductIds = setOf("2", "3", "4") + ), + searchTerms = "some search terms", + impressions = setOf( + ProductParams(productId = "5"), + ProductParams(productId = "6"), + ProductParams(productId = "7") + ), + customProperties = mapOf(Pair("page-specific", "true")) + ) + ) val detailPageParams = RequestQueryParams( viewId = UUID.randomUUID(), @@ -110,6 +132,64 @@ class MainActivity : AppCompatActivity() { ) ) + // Sending Checkout events + val checkoutPage = RequestQueryParams( + viewId = UUID.randomUUID(), + pageType = PageType.PageCheckout + ) + // Sending Page View events + croboxInstance.checkoutEvent( + checkoutPage, + CheckoutParams( + products = setOf( + ProductParams( + productId = "1", + price = 1.0, + quantity = 1, + otherProductIds = setOf("3", "5", "7") + ), ProductParams( + productId = "2", + price = 2.0, + quantity = 2, + otherProductIds = setOf("4", "6", "8") + ) + ), + step = "1", + customProperties = mapOf(Pair("page-specific", "true")) + ) + ) + + // Sending Purchase Events + val pageComplete = RequestQueryParams( + viewId = UUID.randomUUID(), + pageType = PageType.PageComplete + ) + // Sending Page View events + croboxInstance.purchaseEvent( + pageComplete, + PurchaseParams( + products = setOf( + ProductParams( + productId = "1", + price = 1.0, + quantity = 1, + otherProductIds = setOf("3", "5", "7") + ), ProductParams( + productId = "2", + price = 2.0, + quantity = 2, + otherProductIds = setOf("4", "6", "8") + ) + ), + transactionId = "abc123", + affiliation = "google store", + coupon = "some coupon", + revenue = 5.0, + customProperties = mapOf(Pair("page-specific", "true")) + ) + ) + + // A stub implementation for promotion response handling val stubPromotionCallback = object : PromotionCallback { val TAG = "PromotionCallback" diff --git a/sdk/src/main/kotlin/com/crobox/sdk/core/Crobox.kt b/sdk/src/main/kotlin/com/crobox/sdk/core/Crobox.kt index d3e9376..f30961c 100644 --- a/sdk/src/main/kotlin/com/crobox/sdk/core/Crobox.kt +++ b/sdk/src/main/kotlin/com/crobox/sdk/core/Crobox.kt @@ -2,7 +2,16 @@ package com.crobox.sdk.core import com.crobox.sdk.common.CroboxDebug import com.crobox.sdk.config.CroboxConfig -import com.crobox.sdk.data.model.* +import com.crobox.sdk.data.model.CartQueryParams +import com.crobox.sdk.data.model.CheckoutParams +import com.crobox.sdk.data.model.ClickQueryParams +import com.crobox.sdk.data.model.CustomQueryParams +import com.crobox.sdk.data.model.ErrorQueryParams +import com.crobox.sdk.data.model.EventType +import com.crobox.sdk.data.model.PageType +import com.crobox.sdk.data.model.PageViewParams +import com.crobox.sdk.data.model.PurchaseParams +import com.crobox.sdk.data.model.RequestQueryParams import com.crobox.sdk.presenter.CroboxAPIPresenter import com.crobox.sdk.presenter.PromotionCallback @@ -135,10 +144,7 @@ class Crobox private constructor(config: CroboxConfig) { * @param queryParams Common query parameters, shared by the requests sent from the same page view * @param pageViewParams Event specific query parameters for Page View Events */ - fun pageViewEvent( - queryParams: RequestQueryParams, - pageViewParams: PageViewParams? = null - ) { + fun pageViewEvent(queryParams: RequestQueryParams, pageViewParams: PageViewParams? = null) { presenter.event( eventType = EventType.PageView, queryParams = queryParams, @@ -146,7 +152,35 @@ class Crobox private constructor(config: CroboxConfig) { ) } - /** + /** + * For reporting checkout events + * + * @param queryParams Common query parameters, shared by the requests sent from the same page view + * @param checkoutParams Event specific query parameters for Checkout Events + */ + fun checkoutEvent(queryParams: RequestQueryParams, checkoutParams: CheckoutParams) { + presenter.event( + eventType = EventType.Checkout, + queryParams = queryParams, + additionalParams = checkoutParams + ) + } + + /** + * For reporting purchase events + * + * @param queryParams Common query parameters, shared by the requests sent from the same page view + * @param purchaseParams Event specific query parameters for Purchase Events + */ + fun purchaseEvent(queryParams: RequestQueryParams, purchaseParams: PurchaseParams) { + presenter.event( + eventType = EventType.Purchase, + queryParams = queryParams.copy(pageType = PageType.PageComplete), + additionalParams = purchaseParams + ) + } + + /** * For reporting custom events * * @param queryParams Common query parameters, shared by the requests sent from the same page view diff --git a/sdk/src/main/kotlin/com/crobox/sdk/data/model/RequestQueryParams.kt b/sdk/src/main/kotlin/com/crobox/sdk/data/model/RequestQueryParams.kt index 86bd42e..9ac8da1 100644 --- a/sdk/src/main/kotlin/com/crobox/sdk/data/model/RequestQueryParams.kt +++ b/sdk/src/main/kotlin/com/crobox/sdk/data/model/RequestQueryParams.kt @@ -1,6 +1,6 @@ package com.crobox.sdk.data.model -import java.util.* +import java.util.UUID import java.util.concurrent.atomic.AtomicInteger /** @@ -32,11 +32,13 @@ enum class PageType(val value: Int) { PageSearch(7); } -enum class EventType(val type: String) { +enum class EventType(internal val type: String) { Click("click"), // Click AddCart("cart"), // Add to Shopping Cart RemoveCart("rmcart"), // Remove from Shopping Cart PageView("pageview"), // PageView + Checkout("pageview"), // PageView + Purchase("pageview"), // PageView Error("error"), // Error CustomEvent("event") // CustomEvent } @@ -103,11 +105,65 @@ data class CustomQueryParams( ) /** - * Type specific parameters for general-purpose Custom events - * - * @param name Event name + * Type specific parameters for Page View events * + * @param pageTitle Optional Page title + * @param product Optional product specific parameters + * @param searchTerms Optional search terms that led the viewer to this product + * @param impressions Optional list of products that are viewed in the same page + * @param customProperties Optional set of general purpose custom properties, for example to help identifying certain traits of the page */ data class PageViewParams( - val name: String? = null + val pageTitle: String? = null, + val product: ProductParams? = null, + val searchTerms: String? = null, + val impressions: Set? = null, + val customProperties: Map? = emptyMap() +) + +/** + * Type specific parameters for Purchase events + * + * @param products Optional set of products purchased + * @param transactionId Optional transaction identifier + * @param affiliation Optional store or affiliation from which this transaction occurred (e.g. Google Store) + * @param coupon Optional coupon + * @param revenue Optional the total associated with the transaction + * @param customProperties Optional set of general purpose custom properties, for example to help identifying certain traits of the page + */ +data class PurchaseParams( + val products: Set? = null, + val transactionId: String? = null, + val affiliation: String? = null, + val coupon: String? = null, + val revenue: Double? = null, + val customProperties: Map? = emptyMap() +) + +/** + * Type specific parameters for Checkout events + * + * @param products Optional set of products to be purchased + * @param step Optional number representing a step in the checkout process + * @param customProperties Optional set of general purpose custom properties, for example to help identifying certain traits of the page + */ +data class CheckoutParams( + val products: Set? = null, + val step: String? = null, + val customProperties: Map? = emptyMap() +) + +/** + * Product specific parameters for various events + * + * @param productId Product identifier + * @param price Optional Product price + * @param quantity Optional quantity + * @param otherProductIds Optional set of accompanying productIds which this particular product belongs to + */ +data class ProductParams( + val productId: String, + val price: Double? = null, + val quantity: Int? = null, + val otherProductIds: Set? = emptySet() ) diff --git a/sdk/src/main/kotlin/com/crobox/sdk/presenter/CroboxAPIPresenter.kt b/sdk/src/main/kotlin/com/crobox/sdk/presenter/CroboxAPIPresenter.kt index 0bbee01..816a87e 100644 --- a/sdk/src/main/kotlin/com/crobox/sdk/presenter/CroboxAPIPresenter.kt +++ b/sdk/src/main/kotlin/com/crobox/sdk/presenter/CroboxAPIPresenter.kt @@ -6,10 +6,14 @@ import com.crobox.sdk.config.CroboxConfig import com.crobox.sdk.data.api.CroboxAPI import com.crobox.sdk.data.api.CroboxAPIClient import com.crobox.sdk.data.model.CartQueryParams +import com.crobox.sdk.data.model.CheckoutParams import com.crobox.sdk.data.model.ClickQueryParams import com.crobox.sdk.data.model.CustomQueryParams import com.crobox.sdk.data.model.ErrorQueryParams import com.crobox.sdk.data.model.EventType +import com.crobox.sdk.data.model.PageViewParams +import com.crobox.sdk.data.model.ProductParams +import com.crobox.sdk.data.model.PurchaseParams import com.crobox.sdk.data.model.RequestQueryParams import com.crobox.sdk.domain.BaseResponse import com.crobox.sdk.domain.PromotionsResponse @@ -72,7 +76,7 @@ internal class CroboxAPIPresenter(private val config: CroboxConfig) { val parameters = eventQuery(queryParams, additionalParams, eventType) val stringParameters = parameters.mapValues { it.value.toString() } - apiInterface.event(stringParameters)?.enqueue(object : Callback { + apiInterface.event(stringParameters).enqueue(object : Callback { override fun onResponse( call: Call, response: Response ) { @@ -102,37 +106,35 @@ internal class CroboxAPIPresenter(private val config: CroboxConfig) { // Additional parameters based on event type when (eventType) { EventType.Error -> (additionalParams as? ErrorQueryParams)?.let { - errorEvent( - it, parameters - ) + errorEvent(it, parameters) } EventType.Click -> (additionalParams as? ClickQueryParams)?.let { - clickEvent( - it, parameters - ) + clickEvent(it, parameters) } EventType.AddCart -> (additionalParams as? CartQueryParams)?.let { - addToCartEvent( - it, parameters - ) + addToCartEvent(it, parameters) } EventType.RemoveCart -> (additionalParams as? CartQueryParams)?.let { - removeFromCartEvent( - it, parameters - ) + removeFromCartEvent(it, parameters) } EventType.CustomEvent -> (additionalParams as? CustomQueryParams)?.let { - customEvent( - it, parameters - ) + customEvent(it, parameters) } - else -> { - // do nothing + EventType.PageView -> (additionalParams as? PageViewParams)?.let { + pageViewEvent(it, parameters) + } + + EventType.Checkout -> (additionalParams as? CheckoutParams)?.let { + checkoutEvent(it, parameters) + } + + EventType.Purchase -> (additionalParams as? PurchaseParams)?.let { + purchaseEvent(it, parameters) } } @@ -140,9 +142,7 @@ internal class CroboxAPIPresenter(private val config: CroboxConfig) { } - private fun promotionRequestBody( - impressions: List - ): RequestBody { + private fun promotionRequestBody(impressions: List): RequestBody { val bodyStr = impressions.indices.zip(impressions).joinToString("&") { t -> "${t.first}=${t.second}" } @@ -186,8 +186,8 @@ internal class CroboxAPIPresenter(private val config: CroboxConfig) { return parameters } - /*** - The following arguments are applicable for error events( where t=error ). They are all optional. + /** + * The following arguments are applicable for error events( where t=error ). They are all optional. */ private fun errorEvent( errorQueryParams: ErrorQueryParams, parameters: MutableMap @@ -199,10 +199,9 @@ internal class CroboxAPIPresenter(private val config: CroboxConfig) { errorQueryParams.line?.let { parameters["l"] = it } } - /*** - - The following arguments are applicable for click events( where t=click ). They are all optional - + /** + * The following arguments are applicable for click events( where t=click ). They are all optional + * */ private fun clickEvent(clickParams: ClickQueryParams, parameters: MutableMap) { clickParams.productId?.let { parameters["pi"] = it } @@ -210,10 +209,8 @@ internal class CroboxAPIPresenter(private val config: CroboxConfig) { clickParams.quantity?.let { parameters["qty"] = it } } - /*** - - The following arguments are applicable for AddToCart events( where t=cart ). They are all optional - + /** + * The following arguments are applicable for AddToCart events( where t=cart ). They are all optional */ private fun addToCartEvent( addCartQueryParams: CartQueryParams, parameters: MutableMap @@ -223,10 +220,8 @@ internal class CroboxAPIPresenter(private val config: CroboxConfig) { addCartQueryParams.quantity?.let { parameters["qty"] = it } } - /*** - - The following arguments are applicable for RemoveFromCart events( where t=rmcart ). They are all optional - + /** + * The following arguments are applicable for RemoveFromCart events( where t=rmcart ). They are all optional */ private fun removeFromCartEvent( removeFromCartQueryParams: CartQueryParams, parameters: MutableMap @@ -236,10 +231,8 @@ internal class CroboxAPIPresenter(private val config: CroboxConfig) { removeFromCartQueryParams.quantity?.let { parameters["qty"] = it } } - /*** - - The following arguments are applicable for click events( where t=event ). They are all optional - + /** + * The following arguments are applicable for click events( where t=event ). They are all optional */ private fun customEvent( customQueryParams: CustomQueryParams, parameters: MutableMap @@ -250,4 +243,66 @@ internal class CroboxAPIPresenter(private val config: CroboxConfig) { customQueryParams.price?.let { parameters["price"] = it } customQueryParams.quantity?.let { parameters["qty"] = it } } + + /** + * The following arguments are applicable for pageView events + */ + private fun pageViewEvent(pageViewParams: PageViewParams, parameters: MutableMap) { + pageViewParams.pageTitle?.let { parameters["dt"] = it } + pageViewParams.product?.let { productParams(it, parameters) } + pageViewParams.searchTerms?.let { parameters["st"] = it } + pageViewParams.impressions?.let { impressions(it, parameters) } + pageViewParams.customProperties?.let { customProperties(it, parameters) } + } + + /** + * The following arguments are applicable for checkout events + */ + private fun checkoutEvent(checkoutParams: CheckoutParams, parameters: MutableMap) { + checkoutParams.products?.let { impressions(it, parameters) } + checkoutParams.step?.let { parameters["stp"] = it } + checkoutParams.customProperties?.let { customProperties(it, parameters) } + } + + /** + * The following arguments are applicable for purchase events + */ + private fun purchaseEvent(purchaseParams: PurchaseParams, parameters: MutableMap) { + purchaseParams.products?.let { impressions(it, parameters) } + purchaseParams.transactionId?.let { parameters["tid"] = it } + purchaseParams.affiliation?.let { parameters["aff"] = it } + purchaseParams.coupon?.let { parameters["cpn"] = it } + purchaseParams.revenue?.let { parameters["rev"] = it } + purchaseParams.customProperties?.let { customProperties(it, parameters) } + } + + private fun productParams( + productParams: ProductParams, + parameters: MutableMap + ) { + parameters["pi"] = productParams.productId + productParams.price?.let { parameters["price"] = it } + productParams.quantity?.let { parameters["qty"] = it } + productParams.otherProductIds?.let { parameters["lst"] = it.joinToString() } + } + + private fun impressions( + impressions:Set, + parameters: MutableMap + ) { + val impressionsList = impressions.map{ it.productId }.toList() + for (m in impressionsList.indices.zip(impressionsList)) { + parameters["imp[${m.first}]"] = m.second + } + } + + private fun customProperties( + customProperties: Map, + parameters: MutableMap + ) { + for ((key, value) in customProperties) { + parameters["cp.$key"] = value + } + } + } \ No newline at end of file diff --git a/sdk/src/test/kotlin/com/crobox/sdk/EventsIT.kt b/sdk/src/test/kotlin/com/crobox/sdk/EventsIT.kt index 8bc7899..44ed6b0 100644 --- a/sdk/src/test/kotlin/com/crobox/sdk/EventsIT.kt +++ b/sdk/src/test/kotlin/com/crobox/sdk/EventsIT.kt @@ -2,12 +2,16 @@ package com.crobox.sdk import com.crobox.sdk.common.CurrencyCode import com.crobox.sdk.common.LocaleCode -import com.crobox.sdk.core.Crobox import com.crobox.sdk.config.CroboxConfig +import com.crobox.sdk.core.Crobox import com.crobox.sdk.data.model.CartQueryParams +import com.crobox.sdk.data.model.CheckoutParams import com.crobox.sdk.data.model.ClickQueryParams import com.crobox.sdk.data.model.ErrorQueryParams import com.crobox.sdk.data.model.PageType +import com.crobox.sdk.data.model.PageViewParams +import com.crobox.sdk.data.model.ProductParams +import com.crobox.sdk.data.model.PurchaseParams import com.crobox.sdk.data.model.RequestQueryParams import org.junit.After import org.junit.Test @@ -68,7 +72,87 @@ class EventsIT { croboxInstance.enableLogging() // Sending Page View events - croboxInstance.pageViewEvent(indexPageParams) + croboxInstance.pageViewEvent( + indexPageParams, PageViewParams( + pageTitle = "some page title", + product = ProductParams( + productId = "1", + price = 1.0, + quantity = 1, + otherProductIds = setOf("2", "3", "4") + ), + searchTerms = "some search terms", + impressions = setOf( + ProductParams(productId = "5"), + ProductParams(productId = "6"), + ProductParams(productId = "7") + ), + customProperties = mapOf(Pair("page-specific", "true")) + ) + ) + } + + @Test + fun testCheckoutEvent() { + croboxInstance.enableLogging() + val checkoutPage = RequestQueryParams( + viewId = UUID.randomUUID(), + pageType = PageType.PageCheckout + ) + // Sending Page View events + croboxInstance.checkoutEvent( + checkoutPage, + CheckoutParams( + products = setOf( + ProductParams( + productId = "1", + price = 1.0, + quantity = 1, + otherProductIds = setOf("3", "5", "7") + ), ProductParams( + productId = "2", + price = 2.0, + quantity = 2, + otherProductIds = setOf("4", "6", "8") + ) + ), + step = "1", + customProperties = mapOf(Pair("page-specific", "true")) + ) + ) + } + + @Test + fun testPurchaseEvent() { + croboxInstance.enableLogging() + val pageComplete = RequestQueryParams( + viewId = UUID.randomUUID(), + pageType = PageType.PageComplete + ) + // Sending Page View events + croboxInstance.purchaseEvent( + pageComplete, + PurchaseParams( + products = setOf( + ProductParams( + productId = "1", + price = 1.0, + quantity = 1, + otherProductIds = setOf("3", "5", "7") + ), ProductParams( + productId = "2", + price = 2.0, + quantity = 2, + otherProductIds = setOf("4", "6", "8") + ) + ), + transactionId = "abc123", + affiliation = "google store", + coupon = "some coupon", + revenue = 5.0, + customProperties = mapOf(Pair("page-specific", "true")) + ) + ) } @Test @@ -118,4 +202,44 @@ class EventsIT { ) } -} \ No newline at end of file + + @Test + fun testCounter_e() { + croboxInstance.enableLogging() + + // Sending two Error events + croboxInstance.errorEvent( + cartPageParams, + errorQueryParams = ErrorQueryParams( + tag = "ParsingError", + name = "IllegalArgumentException", + message = "bad input", + file = "MainActivity", + line = 100 + ) + ) + + croboxInstance.errorEvent( + cartPageParams, + errorQueryParams = ErrorQueryParams( + tag = "ParsingError", + name = "IllegalArgumentException", + message = "bad input", + file = "MainActivity", + line = 100 + ) + ) + + croboxInstance.errorEvent( + cartPageParams, + errorQueryParams = ErrorQueryParams( + tag = "ParsingError", + name = "IllegalArgumentException", + message = "bad input", + file = "MainActivity", + line = 100 + ) + ) + + } +}