diff --git a/app/build.gradle b/app/build.gradle index 27e5962..fda7e90 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -14,7 +14,7 @@ android { defaultConfig { applicationId "com.crobox.sdk.testapp" - minSdk 22 + minSdk 26 targetSdk 34 versionCode 1 versionName "1.0" 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 15ab752..9912bc2 100644 --- a/app/src/main/kotlin/com/crobox/sdk/testapp/MainActivity.kt +++ b/app/src/main/kotlin/com/crobox/sdk/testapp/MainActivity.kt @@ -115,9 +115,9 @@ class MainActivity : AppCompatActivity() { val TAG = "PromotionCallback" override fun onPromotions(response: PromotionsResponse?) { - val experiments: List? = - response?.context?.experiments?.map { experiment -> - "Experiment[Id: ${experiment.id}, Name: ${experiment.name}]" + val campaigns: List? = + response?.context?.campaigns?.map { campaign -> + "Campaign[Id: ${campaign.id}, Name: ${campaign.name}]" } Log.d( @@ -126,7 +126,7 @@ class MainActivity : AppCompatActivity() { Context [ VisitorId: ${response?.context?.visitorId} SessionId: ${response?.context?.sessionId} - Experiments: ${experiments?.joinToString()} + Campaigns: ${campaigns?.joinToString()} ] """.trimIndent() ) diff --git a/sdk/build.gradle b/sdk/build.gradle index c4b32f5..0033122 100644 --- a/sdk/build.gradle +++ b/sdk/build.gradle @@ -9,7 +9,7 @@ android { compileSdk 34 defaultConfig { - minSdk 22 + minSdk 26 targetSdk 34 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" diff --git a/sdk/src/main/kotlin/com/crobox/sdk/data/api/CroboxAPIClient.kt b/sdk/src/main/kotlin/com/crobox/sdk/data/api/CroboxAPIClient.kt index b6231ed..fb9145b 100644 --- a/sdk/src/main/kotlin/com/crobox/sdk/data/api/CroboxAPIClient.kt +++ b/sdk/src/main/kotlin/com/crobox/sdk/data/api/CroboxAPIClient.kt @@ -6,6 +6,7 @@ import com.google.gson.Strictness import okhttp3.OkHttpClient import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory +import java.util.UUID import java.util.concurrent.TimeUnit internal object CroboxAPIClient { @@ -13,8 +14,13 @@ internal object CroboxAPIClient { get() { val client: OkHttpClient = client() - val gson = GsonBuilder().setStrictness(Strictness.LENIENT).disableHtmlEscaping() - .enableComplexMapKeySerialization().create() + val gson = + GsonBuilder() + .setStrictness(Strictness.LENIENT) + .disableHtmlEscaping() + .enableComplexMapKeySerialization() + .registerTypeAdapter(UUID::class.java, CustomUUIDDeserializer()) + .create() return Retrofit.Builder().client(client).baseUrl(Constant.API_URL) .addConverterFactory(GsonConverterFactory.create(gson)).build() diff --git a/sdk/src/main/kotlin/com/crobox/sdk/data/api/CustomUUIDDeserializer.kt b/sdk/src/main/kotlin/com/crobox/sdk/data/api/CustomUUIDDeserializer.kt new file mode 100644 index 0000000..fb5db49 --- /dev/null +++ b/sdk/src/main/kotlin/com/crobox/sdk/data/api/CustomUUIDDeserializer.kt @@ -0,0 +1,28 @@ +package com.crobox.sdk.data.api + +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonDeserializer +import com.google.gson.JsonElement +import java.lang.reflect.Type +import java.nio.ByteBuffer +import java.util.Base64 +import java.util.UUID + +internal class CustomUUIDDeserializer : JsonDeserializer { + + override fun deserialize( + json: JsonElement?, + typeOfT: Type?, + context: JsonDeserializationContext? + ): UUID { + val uuidEncoded = json?.asString + return uuidEncoded?.let { + if (it.length == 36) { + UUID.fromString(uuidEncoded) + } else { + val bb = ByteBuffer.wrap(Base64.getUrlDecoder().decode(uuidEncoded)) + UUID(bb.getLong(), bb.getLong()) + } + } ?: UUID.randomUUID() + } +} \ No newline at end of file diff --git a/sdk/src/main/kotlin/com/crobox/sdk/domain/Experiment.kt b/sdk/src/main/kotlin/com/crobox/sdk/domain/Campaign.kt similarity index 92% rename from sdk/src/main/kotlin/com/crobox/sdk/domain/Experiment.kt rename to sdk/src/main/kotlin/com/crobox/sdk/domain/Campaign.kt index 33eeb9e..1fdeff2 100644 --- a/sdk/src/main/kotlin/com/crobox/sdk/domain/Experiment.kt +++ b/sdk/src/main/kotlin/com/crobox/sdk/domain/Campaign.kt @@ -3,7 +3,7 @@ package com.crobox.sdk.domain import com.google.gson.annotations.SerializedName /** - * Represents an ongoing campaign + * Represents an ongoing Campaign * * @property id Campaign ID * @property name Campaign Name @@ -11,7 +11,7 @@ import com.google.gson.annotations.SerializedName * @property variantName Name of the Campaign Variant * @property control Indicates if variant is allocated to the control group */ -class Experiment { +class Campaign { @SerializedName("id") val id: String? = null @SerializedName("name") val name: String? = null @SerializedName("variantId")val variantId: String? = null diff --git a/sdk/src/main/kotlin/com/crobox/sdk/domain/PromotionContext.kt b/sdk/src/main/kotlin/com/crobox/sdk/domain/PromotionContext.kt index cbf8917..6a2b15f 100644 --- a/sdk/src/main/kotlin/com/crobox/sdk/domain/PromotionContext.kt +++ b/sdk/src/main/kotlin/com/crobox/sdk/domain/PromotionContext.kt @@ -1,19 +1,27 @@ package com.crobox.sdk.domain import com.google.gson.annotations.SerializedName +import java.util.UUID /** * The context about campaigns * - * @property experiments The list of ongoing campaigns + * @property campaigns The list of ongoing campaigns * @property sessionId Session ID * @property visitorId Visitor ID * @property groupName List of campaign and variant names, combined * */ class PromotionContext { - @SerializedName("experiments") val experiments: List? = null - @SerializedName("sid") val sessionId: String? = null - @SerializedName("pid") val visitorId: String? = null - @SerializedName("groupName") val groupName: String? = null + @SerializedName("experiments") + val campaigns: List? = null + + @SerializedName("sid") + val sessionId: UUID? = null + + @SerializedName("pid") + val visitorId: UUID? = null + + @SerializedName("groupName") + val groupName: String? = null } \ No newline at end of file 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 7aa63f3..69d0ffd 100644 --- a/sdk/src/main/kotlin/com/crobox/sdk/presenter/CroboxAPIPresenter.kt +++ b/sdk/src/main/kotlin/com/crobox/sdk/presenter/CroboxAPIPresenter.kt @@ -14,6 +14,7 @@ import com.crobox.sdk.data.model.RequestQueryParams import com.crobox.sdk.domain.BaseResponse import com.crobox.sdk.domain.PromotionsResponse import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.RequestBody import okhttp3.RequestBody.Companion.toRequestBody import retrofit2.Call import retrofit2.Callback @@ -34,15 +35,12 @@ internal class CroboxAPIPresenter(private val config: CroboxConfig) { val parameters = promotionsQuery(placeholderId, queryParams) val stringParameters = parameters.mapValues { it.value.toString() } - val body = promotionRequestBody(impressions) - - val requestBody = body.toRequestBody("text/plain".toMediaTypeOrNull()) + val requestBody = promotionRequestBody(impressions) apiInterface.promotions(stringParameters, requestBody) ?.enqueue(object : Callback { override fun onResponse( - call: Call, - response: Response + call: Call, response: Response ) { try { if (response.isSuccessful) { @@ -65,19 +63,15 @@ internal class CroboxAPIPresenter(private val config: CroboxConfig) { } fun event( - eventType: EventType, - queryParams: RequestQueryParams, - additionalParams: Any? + eventType: EventType, queryParams: RequestQueryParams, additionalParams: Any? ) { 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 + call: Call, response: Response ) { try { if (!response.isSuccessful) { @@ -95,9 +89,7 @@ internal class CroboxAPIPresenter(private val config: CroboxConfig) { } private fun eventQuery( - queryParams: RequestQueryParams, - additionalParams: Any?, - eventType: EventType + queryParams: RequestQueryParams, additionalParams: Any?, eventType: EventType ): Map { // Mandatory parameters @@ -108,36 +100,31 @@ internal class CroboxAPIPresenter(private val config: CroboxConfig) { when (eventType) { EventType.Error -> (additionalParams as? ErrorQueryParams)?.let { errorEvent( - it, - parameters + it, parameters ) } EventType.Click -> (additionalParams as? ClickQueryParams)?.let { clickEvent( - it, - parameters + it, parameters ) } EventType.AddCart -> (additionalParams as? CartQueryParams)?.let { addToCartEvent( - it, - parameters + it, parameters ) } EventType.RemoveCart -> (additionalParams as? CartQueryParams)?.let { removeFromCartEvent( - it, - parameters + it, parameters ) } EventType.CustomEvent -> (additionalParams as? CustomQueryParams)?.let { customEvent( - it, - parameters + it, parameters ) } @@ -152,14 +139,15 @@ internal class CroboxAPIPresenter(private val config: CroboxConfig) { private fun promotionRequestBody( impressions: List - ): String { - return impressions.indices.zip(impressions) - .joinToString("&") { t -> "${t.first}=${t.second}" } + ): RequestBody { + val bodyStr = + impressions.indices.zip(impressions).joinToString("&") { t -> "${t.first}=${t.second}" } + + return bodyStr.toRequestBody("text/plain".toMediaTypeOrNull()) } private fun promotionsQuery( - placeholderId: Int, - queryParams: RequestQueryParams + placeholderId: Int, queryParams: RequestQueryParams ): Map { val parameters = commonQueryParams(queryParams) parameters["vpid"] = placeholderId.toString() @@ -199,8 +187,7 @@ internal class CroboxAPIPresenter(private val config: CroboxConfig) { The following arguments are applicable for error events( where t=error ). They are all optional. */ private fun errorEvent( - errorQueryParams: ErrorQueryParams, - parameters: MutableMap + errorQueryParams: ErrorQueryParams, parameters: MutableMap ) { errorQueryParams.tag?.let { parameters["tg"] = it } errorQueryParams.name?.let { parameters["nm"] = it } @@ -226,8 +213,7 @@ internal class CroboxAPIPresenter(private val config: CroboxConfig) { */ private fun addToCartEvent( - addCartQueryParams: CartQueryParams, - parameters: MutableMap + addCartQueryParams: CartQueryParams, parameters: MutableMap ) { addCartQueryParams.productId?.let { parameters["pi"] = it } addCartQueryParams.price?.let { parameters["price"] = it } @@ -240,8 +226,7 @@ internal class CroboxAPIPresenter(private val config: CroboxConfig) { */ private fun removeFromCartEvent( - removeFromCartQueryParams: CartQueryParams, - parameters: MutableMap + removeFromCartQueryParams: CartQueryParams, parameters: MutableMap ) { removeFromCartQueryParams.productId?.let { parameters["pi"] = it } removeFromCartQueryParams.price?.let { parameters["price"] = it } @@ -254,8 +239,7 @@ internal class CroboxAPIPresenter(private val config: CroboxConfig) { */ private fun customEvent( - customQueryParams: CustomQueryParams, - parameters: MutableMap + customQueryParams: CustomQueryParams, parameters: MutableMap ) { customQueryParams.name?.let { parameters["nm"] = it } customQueryParams.promotionId?.let { parameters["promoId"] = it } diff --git a/sdk/src/test/kotlin/com/crobox/sdk/PromotionsIT.kt b/sdk/src/test/kotlin/com/crobox/sdk/PromotionsIT.kt index 81b65e5..4215770 100644 --- a/sdk/src/test/kotlin/com/crobox/sdk/PromotionsIT.kt +++ b/sdk/src/test/kotlin/com/crobox/sdk/PromotionsIT.kt @@ -2,8 +2,8 @@ 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.PageType import com.crobox.sdk.data.model.RequestQueryParams import com.crobox.sdk.domain.PromotionsResponse @@ -14,19 +14,25 @@ import java.util.UUID class PromotionsIT { + private val vid = UUID.randomUUID() + private val pid = UUID.randomUUID() + + // FL TEST private val containerId = "xlrc9t" + private val placeholderId = 1 + private val placeholderId2 = 2 private val croboxInstance = Crobox.getInstance( CroboxConfig( containerId = containerId, - visitorId = UUID.randomUUID(), + visitorId = pid, currencyCode = CurrencyCode.USD, localeCode = LocaleCode.EN_US ) ) private val overviewPageParams = RequestQueryParams( - viewId = UUID.randomUUID(), + viewId = vid, pageType = PageType.PageOverview ) @@ -35,39 +41,37 @@ class PromotionsIT { pageType = PageType.PageDetail ) - private val impressions: List = listOf("product1", "product2", "product3", "product4", "product5") + private val impressions: List = + listOf("product1", "product2", "product3", "product4", "product5") private val stubPromotionCallback = object : PromotionCallback { override fun onPromotions(response: PromotionsResponse?) { - val experiments: List? = - response?.context?.experiments?.map { experiment -> - "Experiment[Id: ${experiment.id}, Name: ${experiment.name}]" - } - - println( + val contextStr = """ - Context [ - VisitorId: ${response?.context?.visitorId} - SessionId: ${response?.context?.sessionId} - Experiments: ${experiments?.joinToString()} - ] + Context.VisitorId: ${response?.context?.visitorId} + Context.SessionId: ${response?.context?.sessionId} + Context.Campaigns: ${ + response?.context?.campaigns?.map { campaign -> + "[Id: ${campaign.id}, Name: ${campaign.name}]" + }?.joinToString(",") + } """.trimIndent() - ) - response?.promotions?.forEach { promotion -> - println( - """ + val promotionsStr = response?.promotions?.map { promotion -> + """ Promotion[ Id:${promotion.id} Product:${promotion.productId} Campaign:${promotion.campaignId} Variant:${promotion.variantId} Msg Id:${promotion.content?.id} + Msg Component:${promotion.content?.component} Msg Config:${promotion.content?.config} ] """.trimIndent() - ) - } + }?.joinToString(",") + + println("$contextStr\n$promotionsStr") } override fun onError(msg: String?) { @@ -83,7 +87,7 @@ class PromotionsIT { @Test fun testMultipleProducts() { croboxInstance.promotions( - placeholderId = 1, + placeholderId = placeholderId, queryParams = overviewPageParams, impressions = impressions, promotionCallback = stubPromotionCallback @@ -93,7 +97,7 @@ class PromotionsIT { @Test fun testOneProduct() { croboxInstance.promotions( - placeholderId = 2, + placeholderId = placeholderId2, queryParams = detailPageParams, impressions = impressions.subList(0, 1), promotionCallback = stubPromotionCallback