diff --git a/library/src/main/java/io/constructor/core/ConstructorIo.kt b/library/src/main/java/io/constructor/core/ConstructorIo.kt index c72217cc..29c56193 100755 --- a/library/src/main/java/io/constructor/core/ConstructorIo.kt +++ b/library/src/main/java/io/constructor/core/ConstructorIo.kt @@ -2191,14 +2191,16 @@ object ConstructorIo { * @param resultCount The total number of recommendation results * @param resultPositionOnPage The position of the recommendation result that was clicked on * @param analyticsTags Additional analytics tags to pass + * @param seedItemIds The seed item ID(s) used to generate the recommendation results */ - fun trackRecommendationResultClick(podId: String, strategyId: String, customerId: String, variationId: String? = null, sectionName: String? = null, resultId: String? = null, numResultsPerPage: Int? = null, resultPage: Int? = null, resultCount: Int? = null, resultPositionOnPage: Int? = null, analyticsTags: Map? = null) { - var completable = trackRecommendationResultClickInternal(podId, strategyId, customerId, variationId, sectionName, resultId, numResultsPerPage, resultPage, resultCount, resultPositionOnPage, analyticsTags) + fun trackRecommendationResultClick(podId: String, strategyId: String, customerId: String, variationId: String? = null, sectionName: String? = null, resultId: String? = null, numResultsPerPage: Int? = null, resultPage: Int? = null, resultCount: Int? = null, resultPositionOnPage: Int? = null, analyticsTags: Map? = null, seedItemIds: List? = null) { + var completable = trackRecommendationResultClickInternal(podId, strategyId, customerId, variationId, sectionName, resultId, numResultsPerPage, resultPage, resultCount, resultPositionOnPage, analyticsTags, seedItemIds) disposable.add(completable.subscribeOn(Schedulers.io()).subscribe({}, { t -> e("Recommendation Result Click error: ${t.message}") })) } - internal fun trackRecommendationResultClickInternal(podId: String, strategyId: String, customerId: String, variationId: String? = null, sectionName: String? = null, resultId: String? = null, numResultsPerPage: Int? = null, resultPage: Int? = null, resultCount: Int? = null, resultPositionOnPage: Int? = null, analyticsTags: Map? = null): Completable { + + internal fun trackRecommendationResultClickInternal(podId: String, strategyId: String, customerId: String, variationId: String? = null, sectionName: String? = null, resultId: String? = null, numResultsPerPage: Int? = null, resultPage: Int? = null, resultCount: Int? = null, resultPositionOnPage: Int? = null, analyticsTags: Map? = null, seedItemIds: List? = null): Completable { preferenceHelper.getSessionId(sessionIncrementHandler) val section = sectionName ?: preferenceHelper.defaultItemSection val recommendationsResultClickRequestBody = RecommendationResultClickRequestBody( @@ -2220,7 +2222,8 @@ object ConstructorIo { mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, analyticsTags), true, section, - System.currentTimeMillis() + System.currentTimeMillis(), + seedItemIds?.takeIf { it.isNotEmpty() } ) return dataManager.trackRecommendationResultClick( @@ -2244,9 +2247,10 @@ object ConstructorIo { * @param resultId The result ID of the recommendation response that the selection came from * @param sectionName The section that the results came from, i.e. "Products" * @param analyticsTags Additional analytics tags to pass + * @param seedItemIds The seed item ID(s) used to generate the recommendation results */ - fun trackRecommendationResultsView(podId: String, itemIds: Array, numResultsViewed: Int, resultPage: Int? = null, resultCount: Int? = null, resultId: String? = null, sectionName: String? = null, url: String = "Not Available", analyticsTags: Map? = null) { - var completable = trackRecommendationResultsViewInternal(podId, itemIds, numResultsViewed, resultPage, resultCount, resultId, sectionName, url, analyticsTags) + fun trackRecommendationResultsView(podId: String, itemIds: Array, numResultsViewed: Int, resultPage: Int? = null, resultCount: Int? = null, resultId: String? = null, sectionName: String? = null, url: String = "Not Available", analyticsTags: Map? = null, seedItemIds: List? = null) { + var completable = trackRecommendationResultsViewInternal(podId, itemIds, numResultsViewed, resultPage, resultCount, resultId, sectionName, url, analyticsTags, seedItemIds) disposable.add(completable.subscribeOn(Schedulers.io()).subscribe({}, { t -> e("Recommendation Results View error: ${t.message}") })) @@ -2266,14 +2270,16 @@ object ConstructorIo { * @param resultId The result ID of the recommendation response that the selection came from * @param sectionName The section that the results came from, i.e. "Products" * @param analyticsTags Additional analytics tags to pass + * @param seedItemIds The seed item ID(s) used to generate the recommendation results */ - fun trackRecommendationResultsView(podId: String, numResultsViewed: Int, resultPage: Int? = null, resultCount: Int? = null, resultId: String? = null, sectionName: String? = null, url: String = "Not Available", analyticsTags: Map? = null) { - var completable = trackRecommendationResultsViewInternal(podId, null, numResultsViewed, resultPage, resultCount, resultId, sectionName, url, analyticsTags) + fun trackRecommendationResultsView(podId: String, numResultsViewed: Int, resultPage: Int? = null, resultCount: Int? = null, resultId: String? = null, sectionName: String? = null, url: String = "Not Available", analyticsTags: Map? = null, seedItemIds: List? = null) { + var completable = trackRecommendationResultsViewInternal(podId, null, numResultsViewed, resultPage, resultCount, resultId, sectionName, url, analyticsTags, seedItemIds) disposable.add(completable.subscribeOn(Schedulers.io()).subscribe({}, { t -> e("Recommendation Results View error: ${t.message}") })) } - internal fun trackRecommendationResultsViewInternal(podId: String, itemIds: Array? = null, numResultsViewed: Int, resultPage: Int? = null, resultCount: Int? = null, resultId: String? = null, sectionName: String? = null, url: String = "Not Available", analyticsTags: Map? = null): Completable { + + internal fun trackRecommendationResultsViewInternal(podId: String, itemIds: Array? = null, numResultsViewed: Int, resultPage: Int? = null, resultCount: Int? = null, resultId: String? = null, sectionName: String? = null, url: String = "Not Available", analyticsTags: Map? = null, seedItemIds: List? = null): Completable { preferenceHelper.getSessionId(sessionIncrementHandler) val section = sectionName ?: preferenceHelper.defaultItemSection val items = itemIds?.map{ item -> TrackingItem(item, null, null, null)} @@ -2294,7 +2300,8 @@ object ConstructorIo { mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, analyticsTags), true, section, - System.currentTimeMillis() + System.currentTimeMillis(), + seedItemIds?.takeIf { it.isNotEmpty() } ) return dataManager.trackRecommendationResultsView( diff --git a/library/src/main/java/io/constructor/data/model/recommendations/RecommendationResultClickRequestBody.kt b/library/src/main/java/io/constructor/data/model/recommendations/RecommendationResultClickRequestBody.kt index f6881704..3f5a2fb7 100644 --- a/library/src/main/java/io/constructor/data/model/recommendations/RecommendationResultClickRequestBody.kt +++ b/library/src/main/java/io/constructor/data/model/recommendations/RecommendationResultClickRequestBody.kt @@ -25,5 +25,6 @@ data class RecommendationResultClickRequestBody( @Json(name = "analytics_tags") val analyticsTags: Map?, @Json(name= "beacon") val beacon: Boolean?, @Json(name= "section") val section: String?, - @Json(name= "_dt") val _dt: Long? + @Json(name= "_dt") val _dt: Long?, + @Json(name = "seed_item_ids") val seedItemIds: List? ) : Serializable diff --git a/library/src/main/java/io/constructor/data/model/recommendations/RecommendationResultViewRequestBody.kt b/library/src/main/java/io/constructor/data/model/recommendations/RecommendationResultViewRequestBody.kt index 4cb7ceea..6c790c29 100644 --- a/library/src/main/java/io/constructor/data/model/recommendations/RecommendationResultViewRequestBody.kt +++ b/library/src/main/java/io/constructor/data/model/recommendations/RecommendationResultViewRequestBody.kt @@ -23,5 +23,6 @@ data class RecommendationResultViewRequestBody( @Json(name = "analytics_tags") val analyticsTags: Map?, @Json(name= "beacon") val beacon: Boolean?, @Json(name= "section") val section: String?, - @Json(name= "_dt") val _dt: Long? + @Json(name= "_dt") val _dt: Long?, + @Json(name = "seed_item_ids") val seedItemIds: List? ) : Serializable diff --git a/library/src/test/java/io/constructor/core/ConstructorIoTrackingTest.kt b/library/src/test/java/io/constructor/core/ConstructorIoTrackingTest.kt index 09c4b4f9..931e72a8 100755 --- a/library/src/test/java/io/constructor/core/ConstructorIoTrackingTest.kt +++ b/library/src/test/java/io/constructor/core/ConstructorIoTrackingTest.kt @@ -22,6 +22,7 @@ import java.net.URLEncoder import java.util.* import java.util.concurrent.TimeUnit import kotlin.test.assertEquals +import kotlin.test.assertNull internal fun getRequestBody(request: RecordedRequest): Map { val requestBodyString = request.body.readUtf8().drop(1).dropLast(1).replace("\"", "") @@ -1133,6 +1134,7 @@ class ConstructorIoTrackingTest { assertEquals("pdp5", requestBody["pod_id"]) assertEquals("User Featured", requestBody["strategy_id"]) assertEquals("TIT-REP-1997", requestBody["item_id"]) + assertNull(requestBody["seed_item_ids"]) assertEquals("POST", request.method) assert(request.path!!.startsWith(path)) } @@ -1170,6 +1172,57 @@ class ConstructorIoTrackingTest { assert(request.path!!.startsWith(path)) } + @Test + fun trackRecommendationResultClickWithSingleSeedItemId() { + val mockResponse = MockResponse().setResponseCode(204) + mockServer.enqueue(mockResponse) + val observer = ConstructorIo.trackRecommendationResultClickInternal("pdp5", "User Featured","TIT-REP-1997", seedItemIds = listOf("seed-item-123")).test() + observer.assertComplete() + val request = mockServer.takeRequest() + val requestBody = getRequestBody(request) + val path = "/v2/behavioral_action/recommendation_result_click?section=Products&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-2.39.0&_dt=" + assertEquals("pdp5", requestBody["pod_id"]) + assertEquals("User Featured", requestBody["strategy_id"]) + assertEquals("TIT-REP-1997", requestBody["item_id"]) + assertEquals("[seed-item-123]", requestBody["seed_item_ids"]) + assertEquals("POST", request.method) + assert(request.path!!.startsWith(path)) + } + + @Test + fun trackRecommendationResultClickWithMultipleSeedItemIds() { + val mockResponse = MockResponse().setResponseCode(204) + mockServer.enqueue(mockResponse) + val observer = ConstructorIo.trackRecommendationResultClickInternal("pdp5", "User Featured","TIT-REP-1997", seedItemIds = listOf("seed-item-1", "seed-item-2", "seed-item-3")).test() + observer.assertComplete() + val request = mockServer.takeRequest() + val requestBody = getRequestBody(request) + val path = "/v2/behavioral_action/recommendation_result_click?section=Products&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-2.39.0&_dt=" + assertEquals("pdp5", requestBody["pod_id"]) + assertEquals("User Featured", requestBody["strategy_id"]) + assertEquals("TIT-REP-1997", requestBody["item_id"]) + assertEquals("[seed-item-1,seed-item-2,seed-item-3]", requestBody["seed_item_ids"]) + assertEquals("POST", request.method) + assert(request.path!!.startsWith(path)) + } + + @Test + fun trackRecommendationResultClickWithEmptySeedItemIds() { + val mockResponse = MockResponse().setResponseCode(204) + mockServer.enqueue(mockResponse) + val observer = ConstructorIo.trackRecommendationResultClickInternal("pdp5", "User Featured", "TIT-REP-1997", seedItemIds = listOf()).test() + observer.assertComplete() + val request = mockServer.takeRequest() + val requestBody = getRequestBody(request) + val path = "/v2/behavioral_action/recommendation_result_click?section=Products&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-2.39.0&_dt=" + assertEquals("pdp5", requestBody["pod_id"]) + assertEquals("User Featured", requestBody["strategy_id"]) + assertEquals("TIT-REP-1997", requestBody["item_id"]) + assertNull(requestBody["seed_item_ids"]) + assertEquals("POST", request.method) + assert(request.path!!.startsWith(path)) + } + @Test fun trackRecommendationResultClick500() { val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") @@ -1208,6 +1261,7 @@ class ConstructorIoTrackingTest { val path = "/v2/behavioral_action/recommendation_result_view?section=Products&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-2.39.0&_dt=" assertEquals("pdp5", requestBody["pod_id"]) assertEquals("4", requestBody["num_results_viewed"]) + assertNull(requestBody["seed_item_ids"]) assertEquals("POST", request.method) assert(request.path!!.startsWith(path)) } @@ -1264,6 +1318,54 @@ class ConstructorIoTrackingTest { assert(request.path!!.startsWith(path)) } + @Test + fun trackRecommendationResultsViewWithSingleSeedItemId() { + val mockResponse = MockResponse().setResponseCode(204) + mockServer.enqueue(mockResponse) + val observer = ConstructorIo.trackRecommendationResultsViewInternal("pdp5", null, 4, seedItemIds = listOf("seed-item-123")).test() + observer.assertComplete() + val request = mockServer.takeRequest() + val requestBody = getRequestBody(request) + val path = "/v2/behavioral_action/recommendation_result_view?section=Products&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-2.39.0&_dt=" + assertEquals("pdp5", requestBody["pod_id"]) + assertEquals("4", requestBody["num_results_viewed"]) + assertEquals("[seed-item-123]", requestBody["seed_item_ids"]) + assertEquals("POST", request.method) + assert(request.path!!.startsWith(path)) + } + + @Test + fun trackRecommendationResultsViewWithMultipleSeedItemIds() { + val mockResponse = MockResponse().setResponseCode(204) + mockServer.enqueue(mockResponse) + val observer = ConstructorIo.trackRecommendationResultsViewInternal("pdp5", null, 4, seedItemIds = listOf("seed-item-1", "seed-item-2", "seed-item-3")).test() + observer.assertComplete() + val request = mockServer.takeRequest() + val requestBody = getRequestBody(request) + val path = "/v2/behavioral_action/recommendation_result_view?section=Products&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-2.39.0&_dt=" + assertEquals("pdp5", requestBody["pod_id"]) + assertEquals("4", requestBody["num_results_viewed"]) + assertEquals("[seed-item-1,seed-item-2,seed-item-3]", requestBody["seed_item_ids"]) + assertEquals("POST", request.method) + assert(request.path!!.startsWith(path)) + } + + @Test + fun trackRecommendationResultsViewWithEmptySeedItemIds() { + val mockResponse = MockResponse().setResponseCode(204) + mockServer.enqueue(mockResponse) + val observer = ConstructorIo.trackRecommendationResultsViewInternal("pdp5", null, 4, seedItemIds = listOf()).test() + observer.assertComplete() + val request = mockServer.takeRequest() + val requestBody = getRequestBody(request) + val path = "/v2/behavioral_action/recommendation_result_view?section=Products&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-2.39.0&_dt=" + assertEquals("pdp5", requestBody["pod_id"]) + assertEquals("4", requestBody["num_results_viewed"]) + assertNull(requestBody["seed_item_ids"]) + assertEquals("POST", request.method) + assert(request.path!!.startsWith(path)) + } + @Test fun trackRecommendationResultsView500() { val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error")