From 0b34a857255d3f86ec4be28e5cf9887b23a40d51 Mon Sep 17 00:00:00 2001 From: Hossam Hindawy Date: Fri, 20 Mar 2026 03:15:48 +0200 Subject: [PATCH 1/7] [CDX-387] Support passing seed item ids to recommendations events --- .../java/io/constructor/core/ConstructorIo.kt | 55 +++++++++++++--- .../RecommendationResultClickRequestBody.kt | 3 +- .../RecommendationResultViewRequestBody.kt | 3 +- .../core/ConstructorIoTrackingTest.kt | 66 +++++++++++++++++++ 4 files changed, 115 insertions(+), 12 deletions(-) diff --git a/library/src/main/java/io/constructor/core/ConstructorIo.kt b/library/src/main/java/io/constructor/core/ConstructorIo.kt index 365223cd..3b3a75e6 100755 --- a/library/src/main/java/io/constructor/core/ConstructorIo.kt +++ b/library/src/main/java/io/constructor/core/ConstructorIo.kt @@ -2131,14 +2131,26 @@ 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 { + /** + * Tracks recommendation result click events with a single seed item ID. + * + * @see trackRecommendationResultClick + */ + 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, seedItemId: String) { + trackRecommendationResultClick(podId, strategyId, customerId, variationId, sectionName, resultId, numResultsPerPage, resultPage, resultCount, resultPositionOnPage, analyticsTags, listOf(seedItemId)) + } + 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, seedItemId: String): Completable { + return trackRecommendationResultClickInternal(podId, strategyId, customerId, variationId, sectionName, resultId, numResultsPerPage, resultPage, resultCount, resultPositionOnPage, analyticsTags, listOf(seedItemId)) + } + 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( @@ -2160,7 +2172,8 @@ object ConstructorIo { mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, analyticsTags), true, section, - System.currentTimeMillis() + System.currentTimeMillis(), + seedItemIds ) return dataManager.trackRecommendationResultClick( @@ -2184,13 +2197,22 @@ 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}") })) } + /** + * Tracks recommendation result view events with a single seed item ID. + * + * @see trackRecommendationResultsView + */ + 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, seedItemId: String) { + trackRecommendationResultsView(podId, itemIds, numResultsViewed, resultPage, resultCount, resultId, sectionName, url, analyticsTags, listOf(seedItemId)) + } /** * Tracks recommendation result view events. @@ -2206,14 +2228,26 @@ 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 { + /** + * Tracks recommendation result view events with a single seed item ID. + * + * @see trackRecommendationResultsView + */ + 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, seedItemId: String) { + trackRecommendationResultsView(podId, numResultsViewed, resultPage, resultCount, resultId, sectionName, url, analyticsTags, listOf(seedItemId)) + } + 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, seedItemId: String): Completable { + return trackRecommendationResultsViewInternal(podId, itemIds, numResultsViewed, resultPage, resultCount, resultId, sectionName, url, analyticsTags, listOf(seedItemId)) + } + 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)} @@ -2234,7 +2268,8 @@ object ConstructorIo { mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, analyticsTags), true, section, - System.currentTimeMillis() + System.currentTimeMillis(), + seedItemIds ) 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 abfd9cf9..4efba24c 100755 --- a/library/src/test/java/io/constructor/core/ConstructorIoTrackingTest.kt +++ b/library/src/test/java/io/constructor/core/ConstructorIoTrackingTest.kt @@ -1170,6 +1170,40 @@ 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", seedItemId = "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.38.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.38.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 trackRecommendationResultClick500() { val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") @@ -1264,6 +1298,38 @@ 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, seedItemId = "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.38.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.38.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 trackRecommendationResultsView500() { val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") From 5d1d573bf7084ae0a980b11cb623fd30d4755216 Mon Sep 17 00:00:00 2001 From: Hossam Hindawy Date: Thu, 26 Mar 2026 06:44:16 +0200 Subject: [PATCH 2/7] add seedItemId param documentation --- .../src/main/java/io/constructor/core/ConstructorIo.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/library/src/main/java/io/constructor/core/ConstructorIo.kt b/library/src/main/java/io/constructor/core/ConstructorIo.kt index 3b3a75e6..3a509926 100755 --- a/library/src/main/java/io/constructor/core/ConstructorIo.kt +++ b/library/src/main/java/io/constructor/core/ConstructorIo.kt @@ -2139,17 +2139,21 @@ object ConstructorIo { t -> e("Recommendation Result Click error: ${t.message}") })) } + /** * Tracks recommendation result click events with a single seed item ID. * + * @param seedItemId The seed item ID used to generate the recommendation results * @see trackRecommendationResultClick */ 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, seedItemId: String) { trackRecommendationResultClick(podId, strategyId, customerId, variationId, sectionName, resultId, numResultsPerPage, resultPage, resultCount, resultPositionOnPage, analyticsTags, listOf(seedItemId)) } + 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, seedItemId: String): Completable { return trackRecommendationResultClickInternal(podId, strategyId, customerId, variationId, sectionName, resultId, numResultsPerPage, resultPage, resultCount, resultPositionOnPage, analyticsTags, listOf(seedItemId)) } + 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 @@ -2205,9 +2209,11 @@ object ConstructorIo { t -> e("Recommendation Results View error: ${t.message}") })) } + /** * Tracks recommendation result view events with a single seed item ID. * + * @param seedItemId The seed item ID used to generate the recommendation results * @see trackRecommendationResultsView */ 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, seedItemId: String) { @@ -2236,6 +2242,7 @@ object ConstructorIo { t -> e("Recommendation Results View error: ${t.message}") })) } + /** * Tracks recommendation result view events with a single seed item ID. * @@ -2244,9 +2251,11 @@ object ConstructorIo { 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, seedItemId: String) { trackRecommendationResultsView(podId, numResultsViewed, resultPage, resultCount, resultId, sectionName, url, analyticsTags, listOf(seedItemId)) } + 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, seedItemId: String): Completable { return trackRecommendationResultsViewInternal(podId, itemIds, numResultsViewed, resultPage, resultCount, resultId, sectionName, url, analyticsTags, listOf(seedItemId)) } + 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 From 6492d260b388152f6b88328990a306ba0434c5d4 Mon Sep 17 00:00:00 2001 From: Hossam Hindawy Date: Thu, 26 Mar 2026 07:05:32 +0200 Subject: [PATCH 3/7] Remove unnecessary internal overload --- .../java/io/constructor/core/ConstructorIo.kt | 20 ++++++------------- .../core/ConstructorIoTrackingTest.kt | 4 ++-- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/library/src/main/java/io/constructor/core/ConstructorIo.kt b/library/src/main/java/io/constructor/core/ConstructorIo.kt index 3a509926..fd0d5bf0 100755 --- a/library/src/main/java/io/constructor/core/ConstructorIo.kt +++ b/library/src/main/java/io/constructor/core/ConstructorIo.kt @@ -2146,12 +2146,8 @@ object ConstructorIo { * @param seedItemId The seed item ID used to generate the recommendation results * @see trackRecommendationResultClick */ - 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, seedItemId: String) { - trackRecommendationResultClick(podId, strategyId, customerId, variationId, sectionName, resultId, numResultsPerPage, resultPage, resultCount, resultPositionOnPage, analyticsTags, listOf(seedItemId)) - } - - 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, seedItemId: String): Completable { - return trackRecommendationResultClickInternal(podId, strategyId, customerId, variationId, sectionName, resultId, numResultsPerPage, resultPage, resultCount, resultPositionOnPage, analyticsTags, listOf(seedItemId)) + 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, seedItemId: String? = null) { + trackRecommendationResultClick(podId, strategyId, customerId, variationId, sectionName, resultId, numResultsPerPage, resultPage, resultCount, resultPositionOnPage, analyticsTags, seedItemId?.let { listOf(it) }) } 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 { @@ -2216,8 +2212,8 @@ object ConstructorIo { * @param seedItemId The seed item ID used to generate the recommendation results * @see trackRecommendationResultsView */ - 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, seedItemId: String) { - trackRecommendationResultsView(podId, itemIds, numResultsViewed, resultPage, resultCount, resultId, sectionName, url, analyticsTags, listOf(seedItemId)) + 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, seedItemId: String? = null) { + trackRecommendationResultsView(podId, itemIds, numResultsViewed, resultPage, resultCount, resultId, sectionName, url, analyticsTags, seedItemId?.let { listOf(it) }) } /** @@ -2248,12 +2244,8 @@ object ConstructorIo { * * @see trackRecommendationResultsView */ - 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, seedItemId: String) { - trackRecommendationResultsView(podId, numResultsViewed, resultPage, resultCount, resultId, sectionName, url, analyticsTags, listOf(seedItemId)) - } - - 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, seedItemId: String): Completable { - return trackRecommendationResultsViewInternal(podId, itemIds, numResultsViewed, resultPage, resultCount, resultId, sectionName, url, analyticsTags, listOf(seedItemId)) + 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, seedItemId: String? = null) { + trackRecommendationResultsView(podId, numResultsViewed, resultPage, resultCount, resultId, sectionName, url, analyticsTags, seedItemId?.let { listOf(it) }) } 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 { diff --git a/library/src/test/java/io/constructor/core/ConstructorIoTrackingTest.kt b/library/src/test/java/io/constructor/core/ConstructorIoTrackingTest.kt index 4efba24c..817a4a44 100755 --- a/library/src/test/java/io/constructor/core/ConstructorIoTrackingTest.kt +++ b/library/src/test/java/io/constructor/core/ConstructorIoTrackingTest.kt @@ -1174,7 +1174,7 @@ class ConstructorIoTrackingTest { fun trackRecommendationResultClickWithSingleSeedItemId() { val mockResponse = MockResponse().setResponseCode(204) mockServer.enqueue(mockResponse) - val observer = ConstructorIo.trackRecommendationResultClickInternal("pdp5", "User Featured","TIT-REP-1997", seedItemId = "seed-item-123").test() + 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) @@ -1302,7 +1302,7 @@ class ConstructorIoTrackingTest { fun trackRecommendationResultsViewWithSingleSeedItemId() { val mockResponse = MockResponse().setResponseCode(204) mockServer.enqueue(mockResponse) - val observer = ConstructorIo.trackRecommendationResultsViewInternal("pdp5", null, 4, seedItemId = "seed-item-123").test() + val observer = ConstructorIo.trackRecommendationResultsViewInternal("pdp5", null, 4, seedItemIds = listOf("seed-item-123")).test() observer.assertComplete() val request = mockServer.takeRequest() val requestBody = getRequestBody(request) From d621becda49002d6244d2f766be372f5936b4695 Mon Sep 17 00:00:00 2001 From: Hossam Hindawy Date: Thu, 26 Mar 2026 14:22:59 +0200 Subject: [PATCH 4/7] Remove single seedItemId param --- .../java/io/constructor/core/ConstructorIo.kt | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/library/src/main/java/io/constructor/core/ConstructorIo.kt b/library/src/main/java/io/constructor/core/ConstructorIo.kt index fd0d5bf0..90707089 100755 --- a/library/src/main/java/io/constructor/core/ConstructorIo.kt +++ b/library/src/main/java/io/constructor/core/ConstructorIo.kt @@ -2140,16 +2140,6 @@ object ConstructorIo { })) } - /** - * Tracks recommendation result click events with a single seed item ID. - * - * @param seedItemId The seed item ID used to generate the recommendation results - * @see trackRecommendationResultClick - */ - 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, seedItemId: String? = null) { - trackRecommendationResultClick(podId, strategyId, customerId, variationId, sectionName, resultId, numResultsPerPage, resultPage, resultCount, resultPositionOnPage, analyticsTags, seedItemId?.let { listOf(it) }) - } - 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 @@ -2206,16 +2196,6 @@ object ConstructorIo { })) } - /** - * Tracks recommendation result view events with a single seed item ID. - * - * @param seedItemId The seed item ID used to generate the recommendation results - * @see trackRecommendationResultsView - */ - 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, seedItemId: String? = null) { - trackRecommendationResultsView(podId, itemIds, numResultsViewed, resultPage, resultCount, resultId, sectionName, url, analyticsTags, seedItemId?.let { listOf(it) }) - } - /** * Tracks recommendation result view events. * @@ -2239,15 +2219,6 @@ object ConstructorIo { })) } - /** - * Tracks recommendation result view events with a single seed item ID. - * - * @see trackRecommendationResultsView - */ - 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, seedItemId: String? = null) { - trackRecommendationResultsView(podId, numResultsViewed, resultPage, resultCount, resultId, sectionName, url, analyticsTags, seedItemId?.let { listOf(it) }) - } - 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 From 8e49a95a0184edd102d5de5711b3b46e3ea54815 Mon Sep 17 00:00:00 2001 From: Hossam Hindawy Date: Thu, 26 Mar 2026 17:00:39 +0200 Subject: [PATCH 5/7] Update tests --- .../test/java/io/constructor/core/ConstructorIoTrackingTest.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/src/test/java/io/constructor/core/ConstructorIoTrackingTest.kt b/library/src/test/java/io/constructor/core/ConstructorIoTrackingTest.kt index 817a4a44..068bb186 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)) } @@ -1242,6 +1244,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.38.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)) } From 1e1caab7f26ce977e75c35995db64dbba2fbd214 Mon Sep 17 00:00:00 2001 From: Hossam Hindawy Date: Thu, 26 Mar 2026 17:08:07 +0200 Subject: [PATCH 6/7] Update version --- .../java/io/constructor/core/ConstructorIoTrackingTest.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/src/test/java/io/constructor/core/ConstructorIoTrackingTest.kt b/library/src/test/java/io/constructor/core/ConstructorIoTrackingTest.kt index 73520177..b106b3ba 100755 --- a/library/src/test/java/io/constructor/core/ConstructorIoTrackingTest.kt +++ b/library/src/test/java/io/constructor/core/ConstructorIoTrackingTest.kt @@ -1180,7 +1180,7 @@ class ConstructorIoTrackingTest { 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.38.0&_dt=" + 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"]) @@ -1197,7 +1197,7 @@ class ConstructorIoTrackingTest { 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.38.0&_dt=" + 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"]) @@ -1309,7 +1309,7 @@ class ConstructorIoTrackingTest { 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.38.0&_dt=" + 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"]) @@ -1325,7 +1325,7 @@ class ConstructorIoTrackingTest { 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.38.0&_dt=" + 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"]) From 7d2fe8d40dfa645d735eda8dc61b88e8c7b7c05f Mon Sep 17 00:00:00 2001 From: Hossam Hindawy Date: Thu, 26 Mar 2026 17:20:52 +0200 Subject: [PATCH 7/7] Guard empty seed ids list --- .../java/io/constructor/core/ConstructorIo.kt | 4 +-- .../core/ConstructorIoTrackingTest.kt | 33 +++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/library/src/main/java/io/constructor/core/ConstructorIo.kt b/library/src/main/java/io/constructor/core/ConstructorIo.kt index 7e4eb1e2..29c56193 100755 --- a/library/src/main/java/io/constructor/core/ConstructorIo.kt +++ b/library/src/main/java/io/constructor/core/ConstructorIo.kt @@ -2223,7 +2223,7 @@ object ConstructorIo { true, section, System.currentTimeMillis(), - seedItemIds + seedItemIds?.takeIf { it.isNotEmpty() } ) return dataManager.trackRecommendationResultClick( @@ -2301,7 +2301,7 @@ object ConstructorIo { true, section, System.currentTimeMillis(), - seedItemIds + seedItemIds?.takeIf { it.isNotEmpty() } ) return dataManager.trackRecommendationResultsView( diff --git a/library/src/test/java/io/constructor/core/ConstructorIoTrackingTest.kt b/library/src/test/java/io/constructor/core/ConstructorIoTrackingTest.kt index b106b3ba..931e72a8 100755 --- a/library/src/test/java/io/constructor/core/ConstructorIoTrackingTest.kt +++ b/library/src/test/java/io/constructor/core/ConstructorIoTrackingTest.kt @@ -1206,6 +1206,23 @@ class ConstructorIoTrackingTest { 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") @@ -1333,6 +1350,22 @@ class ConstructorIoTrackingTest { 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")