From 27b6f8ec926d9855d3de0cc6acfdcef5c99a36c7 Mon Sep 17 00:00:00 2001 From: mudaafi Date: Thu, 22 Oct 2026 22:10:54 +0800 Subject: [PATCH 1/7] [CDX-709] Add Product Insights Agent tracking events Port the 9 PIA tracking events from the JS SDK so native Android apps can measure the Product Insights Agent. Each public method takes a single TrackingData request object with a builder, following SearchSubmitTrackingData. - section is sent as a query param on all 9 and defaults to defaultItemSection - analyticsTags are merged with defaultAnalyticsTags - items and followUpQuestions are capped at 100, the server limit - view timespans take epoch ms and are sent as ISO-8601 UTC date-times --- .../java/io/constructor/core/ConstructorIo.kt | 484 ++++++++++++++++++ .../java/io/constructor/data/DataManager.kt | 45 ++ ...InsightsAgentAnswerFeedbackTrackingData.kt | 85 +++ ...ductInsightsAgentAnswerViewTrackingData.kt | 111 ++++ .../ProductInsightsAgentFocusTrackingData.kt | 71 +++ ...oductInsightsAgentOutOfViewTrackingData.kt | 71 +++ ...tInsightsAgentQuestionClickTrackingData.kt | 83 +++ ...InsightsAgentQuestionSubmitTrackingData.kt | 83 +++ ...uctInsightsAgentResultClickTrackingData.kt | 109 ++++ .../ProductInsightsAgentViewTrackingData.kt | 78 +++ .../ProductInsightsAgentViewsTrackingData.kt | 84 +++ ...tInsightsAgentAnswerFeedbackRequestBody.kt | 28 + ...oductInsightsAgentAnswerViewRequestBody.kt | 33 ++ .../ProductInsightsAgentFeedbackLabel.kt | 16 + .../ProductInsightsAgentFocusRequestBody.kt | 26 + .../ProductInsightsAgentFollowUpQuestion.kt | 13 + ...roductInsightsAgentOutOfViewRequestBody.kt | 26 + .../tracking/ProductInsightsAgentQuestion.kt | 17 + ...ctInsightsAgentQuestionClickRequestBody.kt | 28 + ...tInsightsAgentQuestionSubmitRequestBody.kt | 28 + ...ductInsightsAgentResultClickRequestBody.kt | 32 ++ .../ProductInsightsAgentViewRequestBody.kt | 27 + .../ProductInsightsAgentViewTimespan.kt | 15 + .../ProductInsightsAgentViewTimespanBody.kt | 14 + .../ProductInsightsAgentViewsRequestBody.kt | 28 + .../io/constructor/data/remote/ApiPaths.kt | 9 + .../constructor/data/remote/ConstructorApi.kt | 36 ++ 27 files changed, 1680 insertions(+) create mode 100644 library/src/main/java/io/constructor/data/builder/ProductInsightsAgentAnswerFeedbackTrackingData.kt create mode 100644 library/src/main/java/io/constructor/data/builder/ProductInsightsAgentAnswerViewTrackingData.kt create mode 100644 library/src/main/java/io/constructor/data/builder/ProductInsightsAgentFocusTrackingData.kt create mode 100644 library/src/main/java/io/constructor/data/builder/ProductInsightsAgentOutOfViewTrackingData.kt create mode 100644 library/src/main/java/io/constructor/data/builder/ProductInsightsAgentQuestionClickTrackingData.kt create mode 100644 library/src/main/java/io/constructor/data/builder/ProductInsightsAgentQuestionSubmitTrackingData.kt create mode 100644 library/src/main/java/io/constructor/data/builder/ProductInsightsAgentResultClickTrackingData.kt create mode 100644 library/src/main/java/io/constructor/data/builder/ProductInsightsAgentViewTrackingData.kt create mode 100644 library/src/main/java/io/constructor/data/builder/ProductInsightsAgentViewsTrackingData.kt create mode 100644 library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentAnswerFeedbackRequestBody.kt create mode 100644 library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentAnswerViewRequestBody.kt create mode 100644 library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentFeedbackLabel.kt create mode 100644 library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentFocusRequestBody.kt create mode 100644 library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentFollowUpQuestion.kt create mode 100644 library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentOutOfViewRequestBody.kt create mode 100644 library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentQuestion.kt create mode 100644 library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentQuestionClickRequestBody.kt create mode 100644 library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentQuestionSubmitRequestBody.kt create mode 100644 library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentResultClickRequestBody.kt create mode 100644 library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentViewRequestBody.kt create mode 100644 library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentViewTimespan.kt create mode 100644 library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentViewTimespanBody.kt create mode 100644 library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentViewsRequestBody.kt diff --git a/library/src/main/java/io/constructor/core/ConstructorIo.kt b/library/src/main/java/io/constructor/core/ConstructorIo.kt index c161da67..9a86190c 100755 --- a/library/src/main/java/io/constructor/core/ConstructorIo.kt +++ b/library/src/main/java/io/constructor/core/ConstructorIo.kt @@ -26,6 +26,17 @@ import io.constructor.data.model.search.* import io.constructor.data.model.tracking.GenericResultClickRequestBody import io.constructor.data.model.tracking.ItemDetailLoadRequestBody import io.constructor.data.model.tracking.MediaImpressionRequestBody +import io.constructor.data.model.tracking.ProductInsightsAgentAnswerFeedbackRequestBody +import io.constructor.data.model.tracking.ProductInsightsAgentAnswerViewRequestBody +import io.constructor.data.model.tracking.ProductInsightsAgentFocusRequestBody +import io.constructor.data.model.tracking.ProductInsightsAgentFollowUpQuestion +import io.constructor.data.model.tracking.ProductInsightsAgentOutOfViewRequestBody +import io.constructor.data.model.tracking.ProductInsightsAgentQuestionClickRequestBody +import io.constructor.data.model.tracking.ProductInsightsAgentQuestionSubmitRequestBody +import io.constructor.data.model.tracking.ProductInsightsAgentResultClickRequestBody +import io.constructor.data.model.tracking.ProductInsightsAgentViewRequestBody +import io.constructor.data.model.tracking.ProductInsightsAgentViewTimespanBody +import io.constructor.data.model.tracking.ProductInsightsAgentViewsRequestBody import io.constructor.data.model.tracking.ResultsImpressionViewRequestBody import io.constructor.injection.component.AppComponent import io.constructor.injection.component.DaggerAppComponent @@ -40,6 +51,7 @@ import io.reactivex.exceptions.UndeliverableException import io.reactivex.plugins.RxJavaPlugins import io.reactivex.schedulers.Schedulers import java.io.IOException +import java.text.SimpleDateFormat import java.util.* typealias ConstructorError = ((Throwable) -> Unit)? @@ -360,6 +372,13 @@ object ConstructorIo { return analyticsTags } + // SimpleDateFormat isn't thread-safe and tracking runs on Schedulers.io(), so create one per call + private fun formatIsoTimestamp(epochMillis: Long): String { + val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US) + format.timeZone = TimeZone.getTimeZone("UTC") + return format.format(Date(epochMillis)) + } + /** * Returns a list of autocomplete suggestions. * @@ -2430,4 +2449,469 @@ object ConstructorIo { arrayOf(Constants.QueryConstants.SECTION to section) ) } + + /** + * Tracks Product Insights Agent focus events. + * + * Example: + * ``` + * val request = ProductInsightsAgentFocusTrackingData.build("1234567-AB") { + * itemName = "Waterproof Jacket" + * } + * ConstructorIo.trackProductInsightsAgentFocus(request) + * ``` + * @param request the Product Insights Agent focus request object holding all the tracking parameters. Any + * analytics tags set on it are merged with the default analytics tags set on + * [ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on + * key collision. + */ + fun trackProductInsightsAgentFocus(request: ProductInsightsAgentFocusTrackingData) { + val completable = trackProductInsightsAgentFocusInternal(request) + disposable.add(completable.subscribeOn(Schedulers.io()).subscribe({}, { + t -> e("Product Insights Agent Focus error: ${t.message}") + })) + } + + internal fun trackProductInsightsAgentFocusInternal(request: ProductInsightsAgentFocusTrackingData): Completable { + preferenceHelper.getSessionId(sessionIncrementHandler) + val section = request.sectionName ?: preferenceHelper.defaultItemSection + val requestBody = ProductInsightsAgentFocusRequestBody( + itemId = request.itemId, + itemName = request.itemName, + variationId = request.variationId, + threadId = request.threadId, + c = BuildConfig.CLIENT_VERSION, + i = preferenceHelper.id, + s = preferenceHelper.getSessionId(), + key = preferenceHelper.apiKey, + ui = configMemoryHolder.userId, + us = configMemoryHolder.segments, + analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags), + beacon = true, + section = section, + _dt = System.currentTimeMillis() + ) + + return dataManager.trackProductInsightsAgentFocus( + requestBody, + arrayOf(Constants.QueryConstants.SECTION to section) + ) + } + + /** + * Tracks Product Insights Agent out of view events. + * + * Example: + * ``` + * val request = ProductInsightsAgentOutOfViewTrackingData.build("1234567-AB") { + * itemName = "Waterproof Jacket" + * } + * ConstructorIo.trackProductInsightsAgentOutOfView(request) + * ``` + * @param request the Product Insights Agent out of view request object holding all the tracking parameters. Any + * analytics tags set on it are merged with the default analytics tags set on + * [ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on + * key collision. + */ + fun trackProductInsightsAgentOutOfView(request: ProductInsightsAgentOutOfViewTrackingData) { + val completable = trackProductInsightsAgentOutOfViewInternal(request) + disposable.add(completable.subscribeOn(Schedulers.io()).subscribe({}, { + t -> e("Product Insights Agent Out Of View error: ${t.message}") + })) + } + + internal fun trackProductInsightsAgentOutOfViewInternal(request: ProductInsightsAgentOutOfViewTrackingData): Completable { + preferenceHelper.getSessionId(sessionIncrementHandler) + val section = request.sectionName ?: preferenceHelper.defaultItemSection + val requestBody = ProductInsightsAgentOutOfViewRequestBody( + itemId = request.itemId, + itemName = request.itemName, + variationId = request.variationId, + threadId = request.threadId, + c = BuildConfig.CLIENT_VERSION, + i = preferenceHelper.id, + s = preferenceHelper.getSessionId(), + key = preferenceHelper.apiKey, + ui = configMemoryHolder.userId, + us = configMemoryHolder.segments, + analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags), + beacon = true, + section = section, + _dt = System.currentTimeMillis() + ) + + return dataManager.trackProductInsightsAgentOutOfView( + requestBody, + arrayOf(Constants.QueryConstants.SECTION to section) + ) + } + + /** + * Tracks Product Insights Agent view events. + * + * Example: + * ``` + * val request = ProductInsightsAgentViewTrackingData.build( + * "1234567-AB", + * listOf(ProductInsightsAgentQuestion("Is it waterproof?", "Durability")) + * ) { + * itemName = "Waterproof Jacket" + * } + * ConstructorIo.trackProductInsightsAgentView(request) + * ``` + * @param request the Product Insights Agent view request object holding all the tracking parameters. Any + * analytics tags set on it are merged with the default analytics tags set on + * [ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on + * key collision. + */ + fun trackProductInsightsAgentView(request: ProductInsightsAgentViewTrackingData) { + val completable = trackProductInsightsAgentViewInternal(request) + disposable.add(completable.subscribeOn(Schedulers.io()).subscribe({}, { + t -> e("Product Insights Agent View error: ${t.message}") + })) + } + + internal fun trackProductInsightsAgentViewInternal(request: ProductInsightsAgentViewTrackingData): Completable { + preferenceHelper.getSessionId(sessionIncrementHandler) + val section = request.sectionName ?: preferenceHelper.defaultItemSection + val requestBody = ProductInsightsAgentViewRequestBody( + itemId = request.itemId, + questions = request.questions, + itemName = request.itemName, + variationId = request.variationId, + threadId = request.threadId, + c = BuildConfig.CLIENT_VERSION, + i = preferenceHelper.id, + s = preferenceHelper.getSessionId(), + key = preferenceHelper.apiKey, + ui = configMemoryHolder.userId, + us = configMemoryHolder.segments, + analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags), + beacon = true, + section = section, + _dt = System.currentTimeMillis() + ) + + return dataManager.trackProductInsightsAgentView( + requestBody, + arrayOf(Constants.QueryConstants.SECTION to section) + ) + } + + /** + * Tracks Product Insights Agent views events. + * + * Example: + * ``` + * val request = ProductInsightsAgentViewsTrackingData.build( + * "1234567-AB", + * listOf(ProductInsightsAgentQuestion("Is it waterproof?")), + * listOf(ProductInsightsAgentViewTimespan(viewStartedAtMillis, System.currentTimeMillis())) + * ) { + * itemName = "Waterproof Jacket" + * } + * ConstructorIo.trackProductInsightsAgentViews(request) + * ``` + * @param request the Product Insights Agent views request object holding all the tracking parameters. Any + * analytics tags set on it are merged with the default analytics tags set on + * [ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on + * key collision. + */ + fun trackProductInsightsAgentViews(request: ProductInsightsAgentViewsTrackingData) { + val completable = trackProductInsightsAgentViewsInternal(request) + disposable.add(completable.subscribeOn(Schedulers.io()).subscribe({}, { + t -> e("Product Insights Agent Views error: ${t.message}") + })) + } + + internal fun trackProductInsightsAgentViewsInternal(request: ProductInsightsAgentViewsTrackingData): Completable { + preferenceHelper.getSessionId(sessionIncrementHandler) + val section = request.sectionName ?: preferenceHelper.defaultItemSection + val requestBody = ProductInsightsAgentViewsRequestBody( + itemId = request.itemId, + questions = request.questions, + viewTimespans = request.viewTimespans.map { + ProductInsightsAgentViewTimespanBody(formatIsoTimestamp(it.start), formatIsoTimestamp(it.end)) + }, + itemName = request.itemName, + variationId = request.variationId, + threadId = request.threadId, + c = BuildConfig.CLIENT_VERSION, + i = preferenceHelper.id, + s = preferenceHelper.getSessionId(), + key = preferenceHelper.apiKey, + ui = configMemoryHolder.userId, + us = configMemoryHolder.segments, + analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags), + beacon = true, + section = section, + _dt = System.currentTimeMillis() + ) + + return dataManager.trackProductInsightsAgentViews( + requestBody, + arrayOf(Constants.QueryConstants.SECTION to section) + ) + } + + /** + * Tracks Product Insights Agent question click events. + * + * Example: + * ``` + * val request = ProductInsightsAgentQuestionClickTrackingData.build("1234567-AB", "Is it waterproof?") { + * questionTopic = "Durability" + * } + * ConstructorIo.trackProductInsightsAgentQuestionClick(request) + * ``` + * @param request the Product Insights Agent question click request object holding all the tracking parameters. Any + * analytics tags set on it are merged with the default analytics tags set on + * [ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on + * key collision. + */ + fun trackProductInsightsAgentQuestionClick(request: ProductInsightsAgentQuestionClickTrackingData) { + val completable = trackProductInsightsAgentQuestionClickInternal(request) + disposable.add(completable.subscribeOn(Schedulers.io()).subscribe({}, { + t -> e("Product Insights Agent Question Click error: ${t.message}") + })) + } + + internal fun trackProductInsightsAgentQuestionClickInternal(request: ProductInsightsAgentQuestionClickTrackingData): Completable { + preferenceHelper.getSessionId(sessionIncrementHandler) + val section = request.sectionName ?: preferenceHelper.defaultItemSection + val requestBody = ProductInsightsAgentQuestionClickRequestBody( + itemId = request.itemId, + question = request.question, + questionTopic = request.questionTopic, + itemName = request.itemName, + variationId = request.variationId, + threadId = request.threadId, + c = BuildConfig.CLIENT_VERSION, + i = preferenceHelper.id, + s = preferenceHelper.getSessionId(), + key = preferenceHelper.apiKey, + ui = configMemoryHolder.userId, + us = configMemoryHolder.segments, + analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags), + beacon = true, + section = section, + _dt = System.currentTimeMillis() + ) + + return dataManager.trackProductInsightsAgentQuestionClick( + requestBody, + arrayOf(Constants.QueryConstants.SECTION to section) + ) + } + + /** + * Tracks Product Insights Agent question submit events. + * + * Example: + * ``` + * val request = ProductInsightsAgentQuestionSubmitTrackingData.build("1234567-AB", "Is it waterproof?") { + * threadId = "thread-123" + * } + * ConstructorIo.trackProductInsightsAgentQuestionSubmit(request) + * ``` + * @param request the Product Insights Agent question submit request object holding all the tracking parameters. Any + * analytics tags set on it are merged with the default analytics tags set on + * [ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on + * key collision. + */ + fun trackProductInsightsAgentQuestionSubmit(request: ProductInsightsAgentQuestionSubmitTrackingData) { + val completable = trackProductInsightsAgentQuestionSubmitInternal(request) + disposable.add(completable.subscribeOn(Schedulers.io()).subscribe({}, { + t -> e("Product Insights Agent Question Submit error: ${t.message}") + })) + } + + internal fun trackProductInsightsAgentQuestionSubmitInternal(request: ProductInsightsAgentQuestionSubmitTrackingData): Completable { + preferenceHelper.getSessionId(sessionIncrementHandler) + val section = request.sectionName ?: preferenceHelper.defaultItemSection + val requestBody = ProductInsightsAgentQuestionSubmitRequestBody( + itemId = request.itemId, + question = request.question, + questionTopic = request.questionTopic, + itemName = request.itemName, + variationId = request.variationId, + threadId = request.threadId, + c = BuildConfig.CLIENT_VERSION, + i = preferenceHelper.id, + s = preferenceHelper.getSessionId(), + key = preferenceHelper.apiKey, + ui = configMemoryHolder.userId, + us = configMemoryHolder.segments, + analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags), + beacon = true, + section = section, + _dt = System.currentTimeMillis() + ) + + return dataManager.trackProductInsightsAgentQuestionSubmit( + requestBody, + arrayOf(Constants.QueryConstants.SECTION to section) + ) + } + + /** + * Tracks Product Insights Agent answer feedback events. + * + * Example: + * ``` + * val request = ProductInsightsAgentAnswerFeedbackTrackingData.build("1234567-AB", ProductInsightsAgentFeedbackLabel.THUMBS_UP) { + * qnaResultId = "qna-result-123" + * } + * ConstructorIo.trackProductInsightsAgentAnswerFeedback(request) + * ``` + * @param request the Product Insights Agent answer feedback request object holding all the tracking parameters. Any + * analytics tags set on it are merged with the default analytics tags set on + * [ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on + * key collision. + */ + fun trackProductInsightsAgentAnswerFeedback(request: ProductInsightsAgentAnswerFeedbackTrackingData) { + val completable = trackProductInsightsAgentAnswerFeedbackInternal(request) + disposable.add(completable.subscribeOn(Schedulers.io()).subscribe({}, { + t -> e("Product Insights Agent Answer Feedback error: ${t.message}") + })) + } + + internal fun trackProductInsightsAgentAnswerFeedbackInternal(request: ProductInsightsAgentAnswerFeedbackTrackingData): Completable { + preferenceHelper.getSessionId(sessionIncrementHandler) + val section = request.sectionName ?: preferenceHelper.defaultItemSection + val requestBody = ProductInsightsAgentAnswerFeedbackRequestBody( + itemId = request.itemId, + feedbackLabel = request.feedbackLabel, + qnaResultId = request.qnaResultId, + itemName = request.itemName, + variationId = request.variationId, + threadId = request.threadId, + c = BuildConfig.CLIENT_VERSION, + i = preferenceHelper.id, + s = preferenceHelper.getSessionId(), + key = preferenceHelper.apiKey, + ui = configMemoryHolder.userId, + us = configMemoryHolder.segments, + analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags), + beacon = true, + section = section, + _dt = System.currentTimeMillis() + ) + + return dataManager.trackProductInsightsAgentAnswerFeedback( + requestBody, + arrayOf(Constants.QueryConstants.SECTION to section) + ) + } + + /** + * Tracks Product Insights Agent result click events. + * + * Example: + * ``` + * val request = ProductInsightsAgentResultClickTrackingData.build("rec-123", "Is it waterproof?", "1234567-AB") { + * position = 0 + * qnaResultId = "qna-result-123" + * } + * ConstructorIo.trackProductInsightsAgentResultClick(request) + * ``` + * @param request the Product Insights Agent result click request object holding all the tracking parameters. Any + * analytics tags set on it are merged with the default analytics tags set on + * [ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on + * key collision. + */ + fun trackProductInsightsAgentResultClick(request: ProductInsightsAgentResultClickTrackingData) { + val completable = trackProductInsightsAgentResultClickInternal(request) + disposable.add(completable.subscribeOn(Schedulers.io()).subscribe({}, { + t -> e("Product Insights Agent Result Click error: ${t.message}") + })) + } + + internal fun trackProductInsightsAgentResultClickInternal(request: ProductInsightsAgentResultClickTrackingData): Completable { + preferenceHelper.getSessionId(sessionIncrementHandler) + val section = request.sectionName ?: preferenceHelper.defaultItemSection + val requestBody = ProductInsightsAgentResultClickRequestBody( + itemId = request.itemId, + question = request.question, + seedItemId = request.seedItemId, + position = request.position, + seedItemName = request.seedItemName, + seedVariationId = request.seedVariationId, + qnaResultId = request.qnaResultId, + itemName = request.itemName, + variationId = request.variationId, + threadId = request.threadId, + c = BuildConfig.CLIENT_VERSION, + i = preferenceHelper.id, + s = preferenceHelper.getSessionId(), + key = preferenceHelper.apiKey, + ui = configMemoryHolder.userId, + us = configMemoryHolder.segments, + analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags), + beacon = true, + section = section, + _dt = System.currentTimeMillis() + ) + + return dataManager.trackProductInsightsAgentResultClick( + requestBody, + arrayOf(Constants.QueryConstants.SECTION to section) + ) + } + + /** + * Tracks Product Insights Agent answer view events. + * + * Example: + * ``` + * val request = ProductInsightsAgentAnswerViewTrackingData.build("1234567-AB", "Is it waterproof?", "Yes, it is fully waterproof.") { + * qnaResultId = "qna-result-123" + * items = listOf(TrackingItem("rec-123", null, null, null)) + * followUpQuestions = listOf("Is it breathable?") + * } + * ConstructorIo.trackProductInsightsAgentAnswerView(request) + * ``` + * @param request the Product Insights Agent answer view request object holding all the tracking parameters. Any + * analytics tags set on it are merged with the default analytics tags set on + * [ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on + * key collision. + */ + fun trackProductInsightsAgentAnswerView(request: ProductInsightsAgentAnswerViewTrackingData) { + val completable = trackProductInsightsAgentAnswerViewInternal(request) + disposable.add(completable.subscribeOn(Schedulers.io()).subscribe({}, { + t -> e("Product Insights Agent Answer View error: ${t.message}") + })) + } + + internal fun trackProductInsightsAgentAnswerViewInternal(request: ProductInsightsAgentAnswerViewTrackingData): Completable { + preferenceHelper.getSessionId(sessionIncrementHandler) + val section = request.sectionName ?: preferenceHelper.defaultItemSection + val requestBody = ProductInsightsAgentAnswerViewRequestBody( + itemId = request.itemId, + question = request.question, + answerText = request.answerText, + questionTopic = request.questionTopic, + qnaResultId = request.qnaResultId, + items = request.items?.take(100), + followUpQuestions = request.followUpQuestions?.take(100)?.map { ProductInsightsAgentFollowUpQuestion(it) }, + itemName = request.itemName, + variationId = request.variationId, + threadId = request.threadId, + c = BuildConfig.CLIENT_VERSION, + i = preferenceHelper.id, + s = preferenceHelper.getSessionId(), + key = preferenceHelper.apiKey, + ui = configMemoryHolder.userId, + us = configMemoryHolder.segments, + analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags), + beacon = true, + section = section, + _dt = System.currentTimeMillis() + ) + + return dataManager.trackProductInsightsAgentAnswerView( + requestBody, + arrayOf(Constants.QueryConstants.SECTION to section) + ) + } } diff --git a/library/src/main/java/io/constructor/data/DataManager.kt b/library/src/main/java/io/constructor/data/DataManager.kt index 07f54dbe..5bf6dddb 100755 --- a/library/src/main/java/io/constructor/data/DataManager.kt +++ b/library/src/main/java/io/constructor/data/DataManager.kt @@ -14,6 +14,15 @@ import io.constructor.data.model.recommendations.RecommendationsResponse import io.constructor.data.model.search.* import io.constructor.data.model.tracking.GenericResultClickRequestBody import io.constructor.data.model.tracking.MediaImpressionRequestBody +import io.constructor.data.model.tracking.ProductInsightsAgentAnswerFeedbackRequestBody +import io.constructor.data.model.tracking.ProductInsightsAgentAnswerViewRequestBody +import io.constructor.data.model.tracking.ProductInsightsAgentFocusRequestBody +import io.constructor.data.model.tracking.ProductInsightsAgentOutOfViewRequestBody +import io.constructor.data.model.tracking.ProductInsightsAgentQuestionClickRequestBody +import io.constructor.data.model.tracking.ProductInsightsAgentQuestionSubmitRequestBody +import io.constructor.data.model.tracking.ProductInsightsAgentResultClickRequestBody +import io.constructor.data.model.tracking.ProductInsightsAgentViewRequestBody +import io.constructor.data.model.tracking.ProductInsightsAgentViewsRequestBody import io.constructor.data.model.tracking.ResultsImpressionViewRequestBody import io.constructor.data.remote.ApiPaths import io.constructor.data.remote.ConstructorApi @@ -394,4 +403,40 @@ constructor(private val constructorApi: ConstructorApi, @ConstructorSdk private var url = "${preferencesHelper.scheme}://${preferencesHelper.quizzesServiceUrl}/${ApiPaths.URL_QUIZ_RESULTS.format(quizId)}${getAdditionalParamsQueryString(encodedParams)}" return constructorApi.getQuizResultsCRT(url) } + + fun trackProductInsightsAgentFocus(body: ProductInsightsAgentFocusRequestBody, params: Array> = arrayOf()): Completable { + return constructorApi.trackProductInsightsAgentFocus(body, params.toMap()) + } + + fun trackProductInsightsAgentOutOfView(body: ProductInsightsAgentOutOfViewRequestBody, params: Array> = arrayOf()): Completable { + return constructorApi.trackProductInsightsAgentOutOfView(body, params.toMap()) + } + + fun trackProductInsightsAgentView(body: ProductInsightsAgentViewRequestBody, params: Array> = arrayOf()): Completable { + return constructorApi.trackProductInsightsAgentView(body, params.toMap()) + } + + fun trackProductInsightsAgentViews(body: ProductInsightsAgentViewsRequestBody, params: Array> = arrayOf()): Completable { + return constructorApi.trackProductInsightsAgentViews(body, params.toMap()) + } + + fun trackProductInsightsAgentQuestionClick(body: ProductInsightsAgentQuestionClickRequestBody, params: Array> = arrayOf()): Completable { + return constructorApi.trackProductInsightsAgentQuestionClick(body, params.toMap()) + } + + fun trackProductInsightsAgentQuestionSubmit(body: ProductInsightsAgentQuestionSubmitRequestBody, params: Array> = arrayOf()): Completable { + return constructorApi.trackProductInsightsAgentQuestionSubmit(body, params.toMap()) + } + + fun trackProductInsightsAgentAnswerFeedback(body: ProductInsightsAgentAnswerFeedbackRequestBody, params: Array> = arrayOf()): Completable { + return constructorApi.trackProductInsightsAgentAnswerFeedback(body, params.toMap()) + } + + fun trackProductInsightsAgentResultClick(body: ProductInsightsAgentResultClickRequestBody, params: Array> = arrayOf()): Completable { + return constructorApi.trackProductInsightsAgentResultClick(body, params.toMap()) + } + + fun trackProductInsightsAgentAnswerView(body: ProductInsightsAgentAnswerViewRequestBody, params: Array> = arrayOf()): Completable { + return constructorApi.trackProductInsightsAgentAnswerView(body, params.toMap()) + } } diff --git a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentAnswerFeedbackTrackingData.kt b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentAnswerFeedbackTrackingData.kt new file mode 100644 index 00000000..3ec9fb2a --- /dev/null +++ b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentAnswerFeedbackTrackingData.kt @@ -0,0 +1,85 @@ +package io.constructor.data.builder + +import io.constructor.data.model.tracking.ProductInsightsAgentFeedbackLabel + +/** + * Create a Product Insights Agent answer feedback tracking request object utilizing a builder. Sent when the user gives feedback on an answer. + * + * @property itemId the ID of the item (product) the Product Insights Agent is displayed for, i.e. "1234567-AB" + * @property feedbackLabel the feedback the user gave on the answer + * @property qnaResultId the result ID of the answer, as returned by the Product Insights Agent + * @property itemName the name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" + * @property variationId the variation ID of the item (product) the Product Insights Agent is displayed for + * @property threadId the ID of the Product Insights Agent conversation thread + * @property sectionName the index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] + * @property analyticsTags additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision + */ +class ProductInsightsAgentAnswerFeedbackTrackingData( + val itemId: String, + val feedbackLabel: ProductInsightsAgentFeedbackLabel, + val qnaResultId: String? = null, + val itemName: String? = null, + val variationId: String? = null, + val threadId: String? = null, + val sectionName: String? = null, + val analyticsTags: Map? = null, +) { + private constructor(builder: Builder) : this( + builder.itemId, + builder.feedbackLabel, + builder.qnaResultId, + builder.itemName, + builder.variationId, + builder.threadId, + builder.sectionName, + builder.analyticsTags, + ) + + companion object { + /** + * Builds a [ProductInsightsAgentAnswerFeedbackTrackingData] from the required parameters, applying [block] to set optional parameters + */ + inline fun build( + itemId: String, + feedbackLabel: ProductInsightsAgentFeedbackLabel, + block: Builder.() -> Unit = {} + ) = Builder(itemId, feedbackLabel).apply(block).build() + } + + /** + * Builder for [ProductInsightsAgentAnswerFeedbackTrackingData] + */ + class Builder( + val itemId: String, + val feedbackLabel: ProductInsightsAgentFeedbackLabel + ) { + /** The result ID of the answer, as returned by the Product Insights Agent */ + var qnaResultId: String? = null + /** The name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" */ + var itemName: String? = null + /** The variation ID of the item (product) the Product Insights Agent is displayed for */ + var variationId: String? = null + /** The ID of the Product Insights Agent conversation thread */ + var threadId: String? = null + /** The index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] */ + var sectionName: String? = null + /** Additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ + var analyticsTags: Map? = null + + /** Sets [qnaResultId] */ + fun setQnaResultId(qnaResultId: String): Builder = apply { this.qnaResultId = qnaResultId } + /** Sets [itemName] */ + fun setItemName(itemName: String): Builder = apply { this.itemName = itemName } + /** Sets [variationId] */ + fun setVariationId(variationId: String): Builder = apply { this.variationId = variationId } + /** Sets [threadId] */ + fun setThreadId(threadId: String): Builder = apply { this.threadId = threadId } + /** Sets [sectionName] */ + fun setSectionName(sectionName: String): Builder = apply { this.sectionName = sectionName } + /** Sets [analyticsTags] */ + fun setAnalyticsTags(analyticsTags: Map): Builder = apply { this.analyticsTags = analyticsTags } + + /** Builds the [ProductInsightsAgentAnswerFeedbackTrackingData] */ + fun build(): ProductInsightsAgentAnswerFeedbackTrackingData = ProductInsightsAgentAnswerFeedbackTrackingData(this) + } +} diff --git a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentAnswerViewTrackingData.kt b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentAnswerViewTrackingData.kt new file mode 100644 index 00000000..6e3f1123 --- /dev/null +++ b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentAnswerViewTrackingData.kt @@ -0,0 +1,111 @@ +package io.constructor.data.builder + +import io.constructor.data.model.common.TrackingItem + +/** + * Create a Product Insights Agent answer view tracking request object utilizing a builder. Sent when an answer is shown to the user. + * + * @property itemId the ID of the item (product) the Product Insights Agent is displayed for, i.e. "1234567-AB" + * @property question the question text, i.e. "Is it waterproof?" + * @property answerText the answer text shown to the user. May be empty + * @property questionTopic the topic the question belongs to, i.e. "Durability" + * @property qnaResultId the result ID of the answer, as returned by the Product Insights Agent + * @property items the items (products) shown in the answer. Only the first 100 are sent + * @property followUpQuestions the follow-up questions shown with the answer. Only the first 100 are sent + * @property itemName the name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" + * @property variationId the variation ID of the item (product) the Product Insights Agent is displayed for + * @property threadId the ID of the Product Insights Agent conversation thread + * @property sectionName the index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] + * @property analyticsTags additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision + */ +class ProductInsightsAgentAnswerViewTrackingData( + val itemId: String, + val question: String, + val answerText: String, + val questionTopic: String? = null, + val qnaResultId: String? = null, + val items: List? = null, + val followUpQuestions: List? = null, + val itemName: String? = null, + val variationId: String? = null, + val threadId: String? = null, + val sectionName: String? = null, + val analyticsTags: Map? = null, +) { + private constructor(builder: Builder) : this( + builder.itemId, + builder.question, + builder.answerText, + builder.questionTopic, + builder.qnaResultId, + builder.items, + builder.followUpQuestions, + builder.itemName, + builder.variationId, + builder.threadId, + builder.sectionName, + builder.analyticsTags, + ) + + companion object { + /** + * Builds a [ProductInsightsAgentAnswerViewTrackingData] from the required parameters, applying [block] to set optional parameters + */ + inline fun build( + itemId: String, + question: String, + answerText: String, + block: Builder.() -> Unit = {} + ) = Builder(itemId, question, answerText).apply(block).build() + } + + /** + * Builder for [ProductInsightsAgentAnswerViewTrackingData] + */ + class Builder( + val itemId: String, + val question: String, + val answerText: String + ) { + /** The topic the question belongs to, i.e. "Durability" */ + var questionTopic: String? = null + /** The result ID of the answer, as returned by the Product Insights Agent */ + var qnaResultId: String? = null + /** The items (products) shown in the answer. Only the first 100 are sent */ + var items: List? = null + /** The follow-up questions shown with the answer. Only the first 100 are sent */ + var followUpQuestions: List? = null + /** The name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" */ + var itemName: String? = null + /** The variation ID of the item (product) the Product Insights Agent is displayed for */ + var variationId: String? = null + /** The ID of the Product Insights Agent conversation thread */ + var threadId: String? = null + /** The index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] */ + var sectionName: String? = null + /** Additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ + var analyticsTags: Map? = null + + /** Sets [questionTopic] */ + fun setQuestionTopic(questionTopic: String): Builder = apply { this.questionTopic = questionTopic } + /** Sets [qnaResultId] */ + fun setQnaResultId(qnaResultId: String): Builder = apply { this.qnaResultId = qnaResultId } + /** Sets [items] */ + fun setItems(items: List): Builder = apply { this.items = items } + /** Sets [followUpQuestions] */ + fun setFollowUpQuestions(followUpQuestions: List): Builder = apply { this.followUpQuestions = followUpQuestions } + /** Sets [itemName] */ + fun setItemName(itemName: String): Builder = apply { this.itemName = itemName } + /** Sets [variationId] */ + fun setVariationId(variationId: String): Builder = apply { this.variationId = variationId } + /** Sets [threadId] */ + fun setThreadId(threadId: String): Builder = apply { this.threadId = threadId } + /** Sets [sectionName] */ + fun setSectionName(sectionName: String): Builder = apply { this.sectionName = sectionName } + /** Sets [analyticsTags] */ + fun setAnalyticsTags(analyticsTags: Map): Builder = apply { this.analyticsTags = analyticsTags } + + /** Builds the [ProductInsightsAgentAnswerViewTrackingData] */ + fun build(): ProductInsightsAgentAnswerViewTrackingData = ProductInsightsAgentAnswerViewTrackingData(this) + } +} diff --git a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentFocusTrackingData.kt b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentFocusTrackingData.kt new file mode 100644 index 00000000..0d9a894b --- /dev/null +++ b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentFocusTrackingData.kt @@ -0,0 +1,71 @@ +package io.constructor.data.builder + +/** + * Create a Product Insights Agent focus tracking request object utilizing a builder. Sent when the user focuses on (i.e. taps into) the Product Insights Agent. + * + * @property itemId the ID of the item (product) the Product Insights Agent is displayed for, i.e. "1234567-AB" + * @property itemName the name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" + * @property variationId the variation ID of the item (product) the Product Insights Agent is displayed for + * @property threadId the ID of the Product Insights Agent conversation thread + * @property sectionName the index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] + * @property analyticsTags additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision + */ +class ProductInsightsAgentFocusTrackingData( + val itemId: String, + val itemName: String? = null, + val variationId: String? = null, + val threadId: String? = null, + val sectionName: String? = null, + val analyticsTags: Map? = null, +) { + private constructor(builder: Builder) : this( + builder.itemId, + builder.itemName, + builder.variationId, + builder.threadId, + builder.sectionName, + builder.analyticsTags, + ) + + companion object { + /** + * Builds a [ProductInsightsAgentFocusTrackingData] from the required parameters, applying [block] to set optional parameters + */ + inline fun build( + itemId: String, + block: Builder.() -> Unit = {} + ) = Builder(itemId).apply(block).build() + } + + /** + * Builder for [ProductInsightsAgentFocusTrackingData] + */ + class Builder( + val itemId: String + ) { + /** The name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" */ + var itemName: String? = null + /** The variation ID of the item (product) the Product Insights Agent is displayed for */ + var variationId: String? = null + /** The ID of the Product Insights Agent conversation thread */ + var threadId: String? = null + /** The index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] */ + var sectionName: String? = null + /** Additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ + var analyticsTags: Map? = null + + /** Sets [itemName] */ + fun setItemName(itemName: String): Builder = apply { this.itemName = itemName } + /** Sets [variationId] */ + fun setVariationId(variationId: String): Builder = apply { this.variationId = variationId } + /** Sets [threadId] */ + fun setThreadId(threadId: String): Builder = apply { this.threadId = threadId } + /** Sets [sectionName] */ + fun setSectionName(sectionName: String): Builder = apply { this.sectionName = sectionName } + /** Sets [analyticsTags] */ + fun setAnalyticsTags(analyticsTags: Map): Builder = apply { this.analyticsTags = analyticsTags } + + /** Builds the [ProductInsightsAgentFocusTrackingData] */ + fun build(): ProductInsightsAgentFocusTrackingData = ProductInsightsAgentFocusTrackingData(this) + } +} diff --git a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentOutOfViewTrackingData.kt b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentOutOfViewTrackingData.kt new file mode 100644 index 00000000..ff706944 --- /dev/null +++ b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentOutOfViewTrackingData.kt @@ -0,0 +1,71 @@ +package io.constructor.data.builder + +/** + * Create a Product Insights Agent out of view tracking request object utilizing a builder. Sent when the Product Insights Agent scrolls out of view. + * + * @property itemId the ID of the item (product) the Product Insights Agent is displayed for, i.e. "1234567-AB" + * @property itemName the name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" + * @property variationId the variation ID of the item (product) the Product Insights Agent is displayed for + * @property threadId the ID of the Product Insights Agent conversation thread + * @property sectionName the index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] + * @property analyticsTags additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision + */ +class ProductInsightsAgentOutOfViewTrackingData( + val itemId: String, + val itemName: String? = null, + val variationId: String? = null, + val threadId: String? = null, + val sectionName: String? = null, + val analyticsTags: Map? = null, +) { + private constructor(builder: Builder) : this( + builder.itemId, + builder.itemName, + builder.variationId, + builder.threadId, + builder.sectionName, + builder.analyticsTags, + ) + + companion object { + /** + * Builds a [ProductInsightsAgentOutOfViewTrackingData] from the required parameters, applying [block] to set optional parameters + */ + inline fun build( + itemId: String, + block: Builder.() -> Unit = {} + ) = Builder(itemId).apply(block).build() + } + + /** + * Builder for [ProductInsightsAgentOutOfViewTrackingData] + */ + class Builder( + val itemId: String + ) { + /** The name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" */ + var itemName: String? = null + /** The variation ID of the item (product) the Product Insights Agent is displayed for */ + var variationId: String? = null + /** The ID of the Product Insights Agent conversation thread */ + var threadId: String? = null + /** The index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] */ + var sectionName: String? = null + /** Additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ + var analyticsTags: Map? = null + + /** Sets [itemName] */ + fun setItemName(itemName: String): Builder = apply { this.itemName = itemName } + /** Sets [variationId] */ + fun setVariationId(variationId: String): Builder = apply { this.variationId = variationId } + /** Sets [threadId] */ + fun setThreadId(threadId: String): Builder = apply { this.threadId = threadId } + /** Sets [sectionName] */ + fun setSectionName(sectionName: String): Builder = apply { this.sectionName = sectionName } + /** Sets [analyticsTags] */ + fun setAnalyticsTags(analyticsTags: Map): Builder = apply { this.analyticsTags = analyticsTags } + + /** Builds the [ProductInsightsAgentOutOfViewTrackingData] */ + fun build(): ProductInsightsAgentOutOfViewTrackingData = ProductInsightsAgentOutOfViewTrackingData(this) + } +} diff --git a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentQuestionClickTrackingData.kt b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentQuestionClickTrackingData.kt new file mode 100644 index 00000000..7d0f70cf --- /dev/null +++ b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentQuestionClickTrackingData.kt @@ -0,0 +1,83 @@ +package io.constructor.data.builder + +/** + * Create a Product Insights Agent question click tracking request object utilizing a builder. Sent when the user clicks a suggested question. + * + * @property itemId the ID of the item (product) the Product Insights Agent is displayed for, i.e. "1234567-AB" + * @property question the question text, i.e. "Is it waterproof?" + * @property questionTopic the topic the question belongs to, i.e. "Durability" + * @property itemName the name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" + * @property variationId the variation ID of the item (product) the Product Insights Agent is displayed for + * @property threadId the ID of the Product Insights Agent conversation thread + * @property sectionName the index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] + * @property analyticsTags additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision + */ +class ProductInsightsAgentQuestionClickTrackingData( + val itemId: String, + val question: String, + val questionTopic: String? = null, + val itemName: String? = null, + val variationId: String? = null, + val threadId: String? = null, + val sectionName: String? = null, + val analyticsTags: Map? = null, +) { + private constructor(builder: Builder) : this( + builder.itemId, + builder.question, + builder.questionTopic, + builder.itemName, + builder.variationId, + builder.threadId, + builder.sectionName, + builder.analyticsTags, + ) + + companion object { + /** + * Builds a [ProductInsightsAgentQuestionClickTrackingData] from the required parameters, applying [block] to set optional parameters + */ + inline fun build( + itemId: String, + question: String, + block: Builder.() -> Unit = {} + ) = Builder(itemId, question).apply(block).build() + } + + /** + * Builder for [ProductInsightsAgentQuestionClickTrackingData] + */ + class Builder( + val itemId: String, + val question: String + ) { + /** The topic the question belongs to, i.e. "Durability" */ + var questionTopic: String? = null + /** The name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" */ + var itemName: String? = null + /** The variation ID of the item (product) the Product Insights Agent is displayed for */ + var variationId: String? = null + /** The ID of the Product Insights Agent conversation thread */ + var threadId: String? = null + /** The index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] */ + var sectionName: String? = null + /** Additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ + var analyticsTags: Map? = null + + /** Sets [questionTopic] */ + fun setQuestionTopic(questionTopic: String): Builder = apply { this.questionTopic = questionTopic } + /** Sets [itemName] */ + fun setItemName(itemName: String): Builder = apply { this.itemName = itemName } + /** Sets [variationId] */ + fun setVariationId(variationId: String): Builder = apply { this.variationId = variationId } + /** Sets [threadId] */ + fun setThreadId(threadId: String): Builder = apply { this.threadId = threadId } + /** Sets [sectionName] */ + fun setSectionName(sectionName: String): Builder = apply { this.sectionName = sectionName } + /** Sets [analyticsTags] */ + fun setAnalyticsTags(analyticsTags: Map): Builder = apply { this.analyticsTags = analyticsTags } + + /** Builds the [ProductInsightsAgentQuestionClickTrackingData] */ + fun build(): ProductInsightsAgentQuestionClickTrackingData = ProductInsightsAgentQuestionClickTrackingData(this) + } +} diff --git a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentQuestionSubmitTrackingData.kt b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentQuestionSubmitTrackingData.kt new file mode 100644 index 00000000..8a0711e9 --- /dev/null +++ b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentQuestionSubmitTrackingData.kt @@ -0,0 +1,83 @@ +package io.constructor.data.builder + +/** + * Create a Product Insights Agent question submit tracking request object utilizing a builder. Sent when the user submits a question. + * + * @property itemId the ID of the item (product) the Product Insights Agent is displayed for, i.e. "1234567-AB" + * @property question the question text, i.e. "Is it waterproof?" + * @property questionTopic the topic the question belongs to, i.e. "Durability" + * @property itemName the name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" + * @property variationId the variation ID of the item (product) the Product Insights Agent is displayed for + * @property threadId the ID of the Product Insights Agent conversation thread + * @property sectionName the index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] + * @property analyticsTags additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision + */ +class ProductInsightsAgentQuestionSubmitTrackingData( + val itemId: String, + val question: String, + val questionTopic: String? = null, + val itemName: String? = null, + val variationId: String? = null, + val threadId: String? = null, + val sectionName: String? = null, + val analyticsTags: Map? = null, +) { + private constructor(builder: Builder) : this( + builder.itemId, + builder.question, + builder.questionTopic, + builder.itemName, + builder.variationId, + builder.threadId, + builder.sectionName, + builder.analyticsTags, + ) + + companion object { + /** + * Builds a [ProductInsightsAgentQuestionSubmitTrackingData] from the required parameters, applying [block] to set optional parameters + */ + inline fun build( + itemId: String, + question: String, + block: Builder.() -> Unit = {} + ) = Builder(itemId, question).apply(block).build() + } + + /** + * Builder for [ProductInsightsAgentQuestionSubmitTrackingData] + */ + class Builder( + val itemId: String, + val question: String + ) { + /** The topic the question belongs to, i.e. "Durability" */ + var questionTopic: String? = null + /** The name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" */ + var itemName: String? = null + /** The variation ID of the item (product) the Product Insights Agent is displayed for */ + var variationId: String? = null + /** The ID of the Product Insights Agent conversation thread */ + var threadId: String? = null + /** The index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] */ + var sectionName: String? = null + /** Additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ + var analyticsTags: Map? = null + + /** Sets [questionTopic] */ + fun setQuestionTopic(questionTopic: String): Builder = apply { this.questionTopic = questionTopic } + /** Sets [itemName] */ + fun setItemName(itemName: String): Builder = apply { this.itemName = itemName } + /** Sets [variationId] */ + fun setVariationId(variationId: String): Builder = apply { this.variationId = variationId } + /** Sets [threadId] */ + fun setThreadId(threadId: String): Builder = apply { this.threadId = threadId } + /** Sets [sectionName] */ + fun setSectionName(sectionName: String): Builder = apply { this.sectionName = sectionName } + /** Sets [analyticsTags] */ + fun setAnalyticsTags(analyticsTags: Map): Builder = apply { this.analyticsTags = analyticsTags } + + /** Builds the [ProductInsightsAgentQuestionSubmitTrackingData] */ + fun build(): ProductInsightsAgentQuestionSubmitTrackingData = ProductInsightsAgentQuestionSubmitTrackingData(this) + } +} diff --git a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentResultClickTrackingData.kt b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentResultClickTrackingData.kt new file mode 100644 index 00000000..6e4b3bd8 --- /dev/null +++ b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentResultClickTrackingData.kt @@ -0,0 +1,109 @@ +package io.constructor.data.builder + +/** + * Create a Product Insights Agent result click tracking request object utilizing a builder. Sent when the user clicks an item (product) recommended in an answer. + * + * @property itemId the ID of the clicked item (product) recommended in the answer, i.e. "1234567-AB" + * @property question the question text, i.e. "Is it waterproof?" + * @property seedItemId the ID of the item (product) the Product Insights Agent is displayed for, i.e. the product detail page item + * @property position the zero-based position of the clicked item within the answer + * @property seedItemName the name of the item (product) the Product Insights Agent is displayed for + * @property seedVariationId the variation ID of the item (product) the Product Insights Agent is displayed for + * @property qnaResultId the result ID of the answer, as returned by the Product Insights Agent + * @property itemName the name of the clicked item (product) recommended in the answer + * @property variationId the variation ID of the clicked item (product) recommended in the answer + * @property threadId the ID of the Product Insights Agent conversation thread + * @property sectionName the index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] + * @property analyticsTags additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision + */ +class ProductInsightsAgentResultClickTrackingData( + val itemId: String, + val question: String, + val seedItemId: String, + val position: Int? = null, + val seedItemName: String? = null, + val seedVariationId: String? = null, + val qnaResultId: String? = null, + val itemName: String? = null, + val variationId: String? = null, + val threadId: String? = null, + val sectionName: String? = null, + val analyticsTags: Map? = null, +) { + private constructor(builder: Builder) : this( + builder.itemId, + builder.question, + builder.seedItemId, + builder.position, + builder.seedItemName, + builder.seedVariationId, + builder.qnaResultId, + builder.itemName, + builder.variationId, + builder.threadId, + builder.sectionName, + builder.analyticsTags, + ) + + companion object { + /** + * Builds a [ProductInsightsAgentResultClickTrackingData] from the required parameters, applying [block] to set optional parameters + */ + inline fun build( + itemId: String, + question: String, + seedItemId: String, + block: Builder.() -> Unit = {} + ) = Builder(itemId, question, seedItemId).apply(block).build() + } + + /** + * Builder for [ProductInsightsAgentResultClickTrackingData] + */ + class Builder( + val itemId: String, + val question: String, + val seedItemId: String + ) { + /** The zero-based position of the clicked item within the answer */ + var position: Int? = null + /** The name of the item (product) the Product Insights Agent is displayed for */ + var seedItemName: String? = null + /** The variation ID of the item (product) the Product Insights Agent is displayed for */ + var seedVariationId: String? = null + /** The result ID of the answer, as returned by the Product Insights Agent */ + var qnaResultId: String? = null + /** The name of the clicked item (product) recommended in the answer */ + var itemName: String? = null + /** The variation ID of the clicked item (product) recommended in the answer */ + var variationId: String? = null + /** The ID of the Product Insights Agent conversation thread */ + var threadId: String? = null + /** The index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] */ + var sectionName: String? = null + /** Additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ + var analyticsTags: Map? = null + + /** Sets [position] */ + fun setPosition(position: Int): Builder = apply { this.position = position } + /** Sets [seedItemName] */ + fun setSeedItemName(seedItemName: String): Builder = apply { this.seedItemName = seedItemName } + /** Sets [seedVariationId] */ + fun setSeedVariationId(seedVariationId: String): Builder = apply { this.seedVariationId = seedVariationId } + /** Sets [qnaResultId] */ + fun setQnaResultId(qnaResultId: String): Builder = apply { this.qnaResultId = qnaResultId } + /** Sets [itemName] */ + fun setItemName(itemName: String): Builder = apply { this.itemName = itemName } + /** Sets [variationId] */ + fun setVariationId(variationId: String): Builder = apply { this.variationId = variationId } + /** Sets [threadId] */ + fun setThreadId(threadId: String): Builder = apply { this.threadId = threadId } + /** Sets [sectionName] */ + fun setSectionName(sectionName: String): Builder = apply { this.sectionName = sectionName } + /** Sets [analyticsTags] */ + fun setAnalyticsTags(analyticsTags: Map): Builder = apply { this.analyticsTags = analyticsTags } + + /** Builds the [ProductInsightsAgentResultClickTrackingData] */ + fun build(): ProductInsightsAgentResultClickTrackingData = ProductInsightsAgentResultClickTrackingData(this) + } +} diff --git a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentViewTrackingData.kt b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentViewTrackingData.kt new file mode 100644 index 00000000..e989542f --- /dev/null +++ b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentViewTrackingData.kt @@ -0,0 +1,78 @@ +package io.constructor.data.builder + +import io.constructor.data.model.tracking.ProductInsightsAgentQuestion + +/** + * Create a Product Insights Agent view tracking request object utilizing a builder. Sent when the Product Insights Agent comes into view. + * + * @property itemId the ID of the item (product) the Product Insights Agent is displayed for, i.e. "1234567-AB" + * @property questions the questions shown to the user + * @property itemName the name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" + * @property variationId the variation ID of the item (product) the Product Insights Agent is displayed for + * @property threadId the ID of the Product Insights Agent conversation thread + * @property sectionName the index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] + * @property analyticsTags additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision + */ +class ProductInsightsAgentViewTrackingData( + val itemId: String, + val questions: List, + val itemName: String? = null, + val variationId: String? = null, + val threadId: String? = null, + val sectionName: String? = null, + val analyticsTags: Map? = null, +) { + private constructor(builder: Builder) : this( + builder.itemId, + builder.questions, + builder.itemName, + builder.variationId, + builder.threadId, + builder.sectionName, + builder.analyticsTags, + ) + + companion object { + /** + * Builds a [ProductInsightsAgentViewTrackingData] from the required parameters, applying [block] to set optional parameters + */ + inline fun build( + itemId: String, + questions: List, + block: Builder.() -> Unit = {} + ) = Builder(itemId, questions).apply(block).build() + } + + /** + * Builder for [ProductInsightsAgentViewTrackingData] + */ + class Builder( + val itemId: String, + val questions: List + ) { + /** The name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" */ + var itemName: String? = null + /** The variation ID of the item (product) the Product Insights Agent is displayed for */ + var variationId: String? = null + /** The ID of the Product Insights Agent conversation thread */ + var threadId: String? = null + /** The index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] */ + var sectionName: String? = null + /** Additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ + var analyticsTags: Map? = null + + /** Sets [itemName] */ + fun setItemName(itemName: String): Builder = apply { this.itemName = itemName } + /** Sets [variationId] */ + fun setVariationId(variationId: String): Builder = apply { this.variationId = variationId } + /** Sets [threadId] */ + fun setThreadId(threadId: String): Builder = apply { this.threadId = threadId } + /** Sets [sectionName] */ + fun setSectionName(sectionName: String): Builder = apply { this.sectionName = sectionName } + /** Sets [analyticsTags] */ + fun setAnalyticsTags(analyticsTags: Map): Builder = apply { this.analyticsTags = analyticsTags } + + /** Builds the [ProductInsightsAgentViewTrackingData] */ + fun build(): ProductInsightsAgentViewTrackingData = ProductInsightsAgentViewTrackingData(this) + } +} diff --git a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentViewsTrackingData.kt b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentViewsTrackingData.kt new file mode 100644 index 00000000..5093c215 --- /dev/null +++ b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentViewsTrackingData.kt @@ -0,0 +1,84 @@ +package io.constructor.data.builder + +import io.constructor.data.model.tracking.ProductInsightsAgentQuestion +import io.constructor.data.model.tracking.ProductInsightsAgentViewTimespan + +/** + * Create a Product Insights Agent views tracking request object utilizing a builder. Sent when the Product Insights Agent has been viewed, reporting each span of time it was in view. + * + * @property itemId the ID of the item (product) the Product Insights Agent is displayed for, i.e. "1234567-AB" + * @property questions the questions shown to the user + * @property viewTimespans the spans of time during which the Product Insights Agent was in view + * @property itemName the name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" + * @property variationId the variation ID of the item (product) the Product Insights Agent is displayed for + * @property threadId the ID of the Product Insights Agent conversation thread + * @property sectionName the index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] + * @property analyticsTags additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision + */ +class ProductInsightsAgentViewsTrackingData( + val itemId: String, + val questions: List, + val viewTimespans: List, + val itemName: String? = null, + val variationId: String? = null, + val threadId: String? = null, + val sectionName: String? = null, + val analyticsTags: Map? = null, +) { + private constructor(builder: Builder) : this( + builder.itemId, + builder.questions, + builder.viewTimespans, + builder.itemName, + builder.variationId, + builder.threadId, + builder.sectionName, + builder.analyticsTags, + ) + + companion object { + /** + * Builds a [ProductInsightsAgentViewsTrackingData] from the required parameters, applying [block] to set optional parameters + */ + inline fun build( + itemId: String, + questions: List, + viewTimespans: List, + block: Builder.() -> Unit = {} + ) = Builder(itemId, questions, viewTimespans).apply(block).build() + } + + /** + * Builder for [ProductInsightsAgentViewsTrackingData] + */ + class Builder( + val itemId: String, + val questions: List, + val viewTimespans: List + ) { + /** The name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" */ + var itemName: String? = null + /** The variation ID of the item (product) the Product Insights Agent is displayed for */ + var variationId: String? = null + /** The ID of the Product Insights Agent conversation thread */ + var threadId: String? = null + /** The index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] */ + var sectionName: String? = null + /** Additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ + var analyticsTags: Map? = null + + /** Sets [itemName] */ + fun setItemName(itemName: String): Builder = apply { this.itemName = itemName } + /** Sets [variationId] */ + fun setVariationId(variationId: String): Builder = apply { this.variationId = variationId } + /** Sets [threadId] */ + fun setThreadId(threadId: String): Builder = apply { this.threadId = threadId } + /** Sets [sectionName] */ + fun setSectionName(sectionName: String): Builder = apply { this.sectionName = sectionName } + /** Sets [analyticsTags] */ + fun setAnalyticsTags(analyticsTags: Map): Builder = apply { this.analyticsTags = analyticsTags } + + /** Builds the [ProductInsightsAgentViewsTrackingData] */ + fun build(): ProductInsightsAgentViewsTrackingData = ProductInsightsAgentViewsTrackingData(this) + } +} diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentAnswerFeedbackRequestBody.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentAnswerFeedbackRequestBody.kt new file mode 100644 index 00000000..3158ba5a --- /dev/null +++ b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentAnswerFeedbackRequestBody.kt @@ -0,0 +1,28 @@ +package io.constructor.data.model.tracking + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import java.io.Serializable + +/** + * @suppress + */ +@JsonClass(generateAdapter = true) +data class ProductInsightsAgentAnswerFeedbackRequestBody( + @Json(name = "item_id") val itemId: String, + @Json(name = "feedback_label") val feedbackLabel: ProductInsightsAgentFeedbackLabel, + @Json(name = "qna_result_id") val qnaResultId: String?, + @Json(name = "item_name") val itemName: String?, + @Json(name = "variation_id") val variationId: String?, + @Json(name = "thread_id") val threadId: String?, + @Json(name = "c") val c: String, + @Json(name = "i") val i: String, + @Json(name = "s") val s: Int, + @Json(name = "key") val key: String, + @Json(name = "ui") val ui: String?, + @Json(name = "us") val us: List, + @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? +) : Serializable diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentAnswerViewRequestBody.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentAnswerViewRequestBody.kt new file mode 100644 index 00000000..a9926bf8 --- /dev/null +++ b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentAnswerViewRequestBody.kt @@ -0,0 +1,33 @@ +package io.constructor.data.model.tracking + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.constructor.data.model.common.TrackingItem +import java.io.Serializable + +/** + * @suppress + */ +@JsonClass(generateAdapter = true) +data class ProductInsightsAgentAnswerViewRequestBody( + @Json(name = "item_id") val itemId: String, + @Json(name = "question") val question: String, + @Json(name = "answer_text") val answerText: String, + @Json(name = "question_topic") val questionTopic: String?, + @Json(name = "qna_result_id") val qnaResultId: String?, + @Json(name = "items") val items: List?, + @Json(name = "follow_up_questions") val followUpQuestions: List?, + @Json(name = "item_name") val itemName: String?, + @Json(name = "variation_id") val variationId: String?, + @Json(name = "thread_id") val threadId: String?, + @Json(name = "c") val c: String, + @Json(name = "i") val i: String, + @Json(name = "s") val s: Int, + @Json(name = "key") val key: String, + @Json(name = "ui") val ui: String?, + @Json(name = "us") val us: List, + @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? +) : Serializable diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentFeedbackLabel.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentFeedbackLabel.kt new file mode 100644 index 00000000..0a1ab5fe --- /dev/null +++ b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentFeedbackLabel.kt @@ -0,0 +1,16 @@ +package io.constructor.data.model.tracking + +import com.squareup.moshi.Json + +/** + * The feedback a user gave on a Product Insights Agent answer, used in Product Insights Agent tracking events + */ +enum class ProductInsightsAgentFeedbackLabel { + /** The user found the answer helpful */ + @Json(name = "thumbs_up") + THUMBS_UP, + + /** The user did not find the answer helpful */ + @Json(name = "thumbs_down") + THUMBS_DOWN, +} diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentFocusRequestBody.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentFocusRequestBody.kt new file mode 100644 index 00000000..46f461da --- /dev/null +++ b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentFocusRequestBody.kt @@ -0,0 +1,26 @@ +package io.constructor.data.model.tracking + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import java.io.Serializable + +/** + * @suppress + */ +@JsonClass(generateAdapter = true) +data class ProductInsightsAgentFocusRequestBody( + @Json(name = "item_id") val itemId: String, + @Json(name = "item_name") val itemName: String?, + @Json(name = "variation_id") val variationId: String?, + @Json(name = "thread_id") val threadId: String?, + @Json(name = "c") val c: String, + @Json(name = "i") val i: String, + @Json(name = "s") val s: Int, + @Json(name = "key") val key: String, + @Json(name = "ui") val ui: String?, + @Json(name = "us") val us: List, + @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? +) : Serializable diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentFollowUpQuestion.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentFollowUpQuestion.kt new file mode 100644 index 00000000..e28d3981 --- /dev/null +++ b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentFollowUpQuestion.kt @@ -0,0 +1,13 @@ +package io.constructor.data.model.tracking + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import java.io.Serializable + +/** + * @suppress + */ +@JsonClass(generateAdapter = true) +data class ProductInsightsAgentFollowUpQuestion( + @Json(name = "value") val value: String, +) : Serializable diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentOutOfViewRequestBody.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentOutOfViewRequestBody.kt new file mode 100644 index 00000000..6b490c1f --- /dev/null +++ b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentOutOfViewRequestBody.kt @@ -0,0 +1,26 @@ +package io.constructor.data.model.tracking + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import java.io.Serializable + +/** + * @suppress + */ +@JsonClass(generateAdapter = true) +data class ProductInsightsAgentOutOfViewRequestBody( + @Json(name = "item_id") val itemId: String, + @Json(name = "item_name") val itemName: String?, + @Json(name = "variation_id") val variationId: String?, + @Json(name = "thread_id") val threadId: String?, + @Json(name = "c") val c: String, + @Json(name = "i") val i: String, + @Json(name = "s") val s: Int, + @Json(name = "key") val key: String, + @Json(name = "ui") val ui: String?, + @Json(name = "us") val us: List, + @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? +) : Serializable diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentQuestion.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentQuestion.kt new file mode 100644 index 00000000..1789b190 --- /dev/null +++ b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentQuestion.kt @@ -0,0 +1,17 @@ +package io.constructor.data.model.tracking + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import java.io.Serializable + +/** + * Models a question shown by the Product Insights Agent, used in Product Insights Agent tracking events + * + * @property question the question text, i.e. "Is it waterproof?" + * @property questionTopic the topic the question belongs to, i.e. "Durability" + */ +@JsonClass(generateAdapter = true) +data class ProductInsightsAgentQuestion( + @Json(name = "question") val question: String, + @Json(name = "question_topic") val questionTopic: String? = null, +) : Serializable diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentQuestionClickRequestBody.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentQuestionClickRequestBody.kt new file mode 100644 index 00000000..ca08d13b --- /dev/null +++ b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentQuestionClickRequestBody.kt @@ -0,0 +1,28 @@ +package io.constructor.data.model.tracking + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import java.io.Serializable + +/** + * @suppress + */ +@JsonClass(generateAdapter = true) +data class ProductInsightsAgentQuestionClickRequestBody( + @Json(name = "item_id") val itemId: String, + @Json(name = "question") val question: String, + @Json(name = "question_topic") val questionTopic: String?, + @Json(name = "item_name") val itemName: String?, + @Json(name = "variation_id") val variationId: String?, + @Json(name = "thread_id") val threadId: String?, + @Json(name = "c") val c: String, + @Json(name = "i") val i: String, + @Json(name = "s") val s: Int, + @Json(name = "key") val key: String, + @Json(name = "ui") val ui: String?, + @Json(name = "us") val us: List, + @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? +) : Serializable diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentQuestionSubmitRequestBody.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentQuestionSubmitRequestBody.kt new file mode 100644 index 00000000..a1403711 --- /dev/null +++ b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentQuestionSubmitRequestBody.kt @@ -0,0 +1,28 @@ +package io.constructor.data.model.tracking + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import java.io.Serializable + +/** + * @suppress + */ +@JsonClass(generateAdapter = true) +data class ProductInsightsAgentQuestionSubmitRequestBody( + @Json(name = "item_id") val itemId: String, + @Json(name = "question") val question: String, + @Json(name = "question_topic") val questionTopic: String?, + @Json(name = "item_name") val itemName: String?, + @Json(name = "variation_id") val variationId: String?, + @Json(name = "thread_id") val threadId: String?, + @Json(name = "c") val c: String, + @Json(name = "i") val i: String, + @Json(name = "s") val s: Int, + @Json(name = "key") val key: String, + @Json(name = "ui") val ui: String?, + @Json(name = "us") val us: List, + @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? +) : Serializable diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentResultClickRequestBody.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentResultClickRequestBody.kt new file mode 100644 index 00000000..d82fe4ed --- /dev/null +++ b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentResultClickRequestBody.kt @@ -0,0 +1,32 @@ +package io.constructor.data.model.tracking + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import java.io.Serializable + +/** + * @suppress + */ +@JsonClass(generateAdapter = true) +data class ProductInsightsAgentResultClickRequestBody( + @Json(name = "item_id") val itemId: String, + @Json(name = "question") val question: String, + @Json(name = "seed_item_id") val seedItemId: String, + @Json(name = "position") val position: Int?, + @Json(name = "seed_item_name") val seedItemName: String?, + @Json(name = "seed_variation_id") val seedVariationId: String?, + @Json(name = "qna_result_id") val qnaResultId: String?, + @Json(name = "item_name") val itemName: String?, + @Json(name = "variation_id") val variationId: String?, + @Json(name = "thread_id") val threadId: String?, + @Json(name = "c") val c: String, + @Json(name = "i") val i: String, + @Json(name = "s") val s: Int, + @Json(name = "key") val key: String, + @Json(name = "ui") val ui: String?, + @Json(name = "us") val us: List, + @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? +) : Serializable diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentViewRequestBody.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentViewRequestBody.kt new file mode 100644 index 00000000..152bcee0 --- /dev/null +++ b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentViewRequestBody.kt @@ -0,0 +1,27 @@ +package io.constructor.data.model.tracking + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import java.io.Serializable + +/** + * @suppress + */ +@JsonClass(generateAdapter = true) +data class ProductInsightsAgentViewRequestBody( + @Json(name = "item_id") val itemId: String, + @Json(name = "questions") val questions: List, + @Json(name = "item_name") val itemName: String?, + @Json(name = "variation_id") val variationId: String?, + @Json(name = "thread_id") val threadId: String?, + @Json(name = "c") val c: String, + @Json(name = "i") val i: String, + @Json(name = "s") val s: Int, + @Json(name = "key") val key: String, + @Json(name = "ui") val ui: String?, + @Json(name = "us") val us: List, + @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? +) : Serializable diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentViewTimespan.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentViewTimespan.kt new file mode 100644 index 00000000..a4afce68 --- /dev/null +++ b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentViewTimespan.kt @@ -0,0 +1,15 @@ +package io.constructor.data.model.tracking + +import java.io.Serializable + +/** + * Models a span of time during which the Product Insights Agent was in view, used in Product Insights Agent tracking events. + * Sent as ISO-8601 UTC timestamps, i.e. "2026-10-22T02:40:05.123Z" + * + * @property start the time the agent came into view, in epoch milliseconds, i.e. `System.currentTimeMillis()` + * @property end the time the agent went out of view, in epoch milliseconds, i.e. `System.currentTimeMillis()` + */ +data class ProductInsightsAgentViewTimespan( + val start: Long, + val end: Long, +) : Serializable diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentViewTimespanBody.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentViewTimespanBody.kt new file mode 100644 index 00000000..7d43ebc7 --- /dev/null +++ b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentViewTimespanBody.kt @@ -0,0 +1,14 @@ +package io.constructor.data.model.tracking + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import java.io.Serializable + +/** + * @suppress + */ +@JsonClass(generateAdapter = true) +data class ProductInsightsAgentViewTimespanBody( + @Json(name = "start") val start: String, + @Json(name = "end") val end: String, +) : Serializable diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentViewsRequestBody.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentViewsRequestBody.kt new file mode 100644 index 00000000..082eb6bb --- /dev/null +++ b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentViewsRequestBody.kt @@ -0,0 +1,28 @@ +package io.constructor.data.model.tracking + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import java.io.Serializable + +/** + * @suppress + */ +@JsonClass(generateAdapter = true) +data class ProductInsightsAgentViewsRequestBody( + @Json(name = "item_id") val itemId: String, + @Json(name = "questions") val questions: List, + @Json(name = "view_timespans") val viewTimespans: List, + @Json(name = "item_name") val itemName: String?, + @Json(name = "variation_id") val variationId: String?, + @Json(name = "thread_id") val threadId: String?, + @Json(name = "c") val c: String, + @Json(name = "i") val i: String, + @Json(name = "s") val s: Int, + @Json(name = "key") val key: String, + @Json(name = "ui") val ui: String?, + @Json(name = "us") val us: List, + @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? +) : Serializable diff --git a/library/src/main/java/io/constructor/data/remote/ApiPaths.kt b/library/src/main/java/io/constructor/data/remote/ApiPaths.kt index 572d145c..5e9fcf41 100755 --- a/library/src/main/java/io/constructor/data/remote/ApiPaths.kt +++ b/library/src/main/java/io/constructor/data/remote/ApiPaths.kt @@ -38,4 +38,13 @@ object ApiPaths { const val URL_MEDIA_IMPRESSION_VIEW_EVENT = "v2/ad_behavioral_action/display_ad_view" const val URL_MEDIA_IMPRESSION_CLICK_EVENT = "v2/ad_behavioral_action/display_ad_click" const val URL_RESULTS_IMPRESSION_VIEW_EVENT = "v2/behavioral_action/impression_view" + const val URL_PRODUCT_INSIGHTS_AGENT_FOCUS_EVENT = "v2/behavioral_action/product_insights_agent_focus" + const val URL_PRODUCT_INSIGHTS_AGENT_OUT_OF_VIEW_EVENT = "v2/behavioral_action/product_insights_agent_out_of_view" + const val URL_PRODUCT_INSIGHTS_AGENT_VIEW_EVENT = "v2/behavioral_action/product_insights_agent_view" + const val URL_PRODUCT_INSIGHTS_AGENT_VIEWS_EVENT = "v2/behavioral_action/product_insights_agent_views" + const val URL_PRODUCT_INSIGHTS_AGENT_QUESTION_CLICK_EVENT = "v2/behavioral_action/product_insights_agent_question_click" + const val URL_PRODUCT_INSIGHTS_AGENT_QUESTION_SUBMIT_EVENT = "v2/behavioral_action/product_insights_agent_question_submit" + const val URL_PRODUCT_INSIGHTS_AGENT_ANSWER_FEEDBACK_EVENT = "v2/behavioral_action/product_insights_agent_answer_feedback" + const val URL_PRODUCT_INSIGHTS_AGENT_RESULT_CLICK_EVENT = "v2/behavioral_action/product_insights_agent_result_click" + const val URL_PRODUCT_INSIGHTS_AGENT_ANSWER_VIEW_EVENT = "v2/behavioral_action/product_insights_agent_answer_view" } diff --git a/library/src/main/java/io/constructor/data/remote/ConstructorApi.kt b/library/src/main/java/io/constructor/data/remote/ConstructorApi.kt index 86873ca8..4a118d23 100755 --- a/library/src/main/java/io/constructor/data/remote/ConstructorApi.kt +++ b/library/src/main/java/io/constructor/data/remote/ConstructorApi.kt @@ -5,6 +5,15 @@ import io.constructor.data.model.browse.* import io.constructor.data.model.tracking.GenericResultClickRequestBody import io.constructor.data.model.tracking.ItemDetailLoadRequestBody import io.constructor.data.model.tracking.MediaImpressionRequestBody +import io.constructor.data.model.tracking.ProductInsightsAgentAnswerFeedbackRequestBody +import io.constructor.data.model.tracking.ProductInsightsAgentAnswerViewRequestBody +import io.constructor.data.model.tracking.ProductInsightsAgentFocusRequestBody +import io.constructor.data.model.tracking.ProductInsightsAgentOutOfViewRequestBody +import io.constructor.data.model.tracking.ProductInsightsAgentQuestionClickRequestBody +import io.constructor.data.model.tracking.ProductInsightsAgentQuestionSubmitRequestBody +import io.constructor.data.model.tracking.ProductInsightsAgentResultClickRequestBody +import io.constructor.data.model.tracking.ProductInsightsAgentViewRequestBody +import io.constructor.data.model.tracking.ProductInsightsAgentViewsRequestBody import io.constructor.data.model.tracking.ResultsImpressionViewRequestBody import io.constructor.data.model.conversion.ConversionRequestBody import io.constructor.data.model.purchase.PurchaseRequestBody @@ -159,4 +168,31 @@ interface ConstructorApi { @GET suspend fun getQuizResultsCRT(@Url quizUrl: String): QuizResultsResponse + + @POST(ApiPaths.URL_PRODUCT_INSIGHTS_AGENT_FOCUS_EVENT) + fun trackProductInsightsAgentFocus(@Body body: ProductInsightsAgentFocusRequestBody, @QueryMap params: Map): Completable + + @POST(ApiPaths.URL_PRODUCT_INSIGHTS_AGENT_OUT_OF_VIEW_EVENT) + fun trackProductInsightsAgentOutOfView(@Body body: ProductInsightsAgentOutOfViewRequestBody, @QueryMap params: Map): Completable + + @POST(ApiPaths.URL_PRODUCT_INSIGHTS_AGENT_VIEW_EVENT) + fun trackProductInsightsAgentView(@Body body: ProductInsightsAgentViewRequestBody, @QueryMap params: Map): Completable + + @POST(ApiPaths.URL_PRODUCT_INSIGHTS_AGENT_VIEWS_EVENT) + fun trackProductInsightsAgentViews(@Body body: ProductInsightsAgentViewsRequestBody, @QueryMap params: Map): Completable + + @POST(ApiPaths.URL_PRODUCT_INSIGHTS_AGENT_QUESTION_CLICK_EVENT) + fun trackProductInsightsAgentQuestionClick(@Body body: ProductInsightsAgentQuestionClickRequestBody, @QueryMap params: Map): Completable + + @POST(ApiPaths.URL_PRODUCT_INSIGHTS_AGENT_QUESTION_SUBMIT_EVENT) + fun trackProductInsightsAgentQuestionSubmit(@Body body: ProductInsightsAgentQuestionSubmitRequestBody, @QueryMap params: Map): Completable + + @POST(ApiPaths.URL_PRODUCT_INSIGHTS_AGENT_ANSWER_FEEDBACK_EVENT) + fun trackProductInsightsAgentAnswerFeedback(@Body body: ProductInsightsAgentAnswerFeedbackRequestBody, @QueryMap params: Map): Completable + + @POST(ApiPaths.URL_PRODUCT_INSIGHTS_AGENT_RESULT_CLICK_EVENT) + fun trackProductInsightsAgentResultClick(@Body body: ProductInsightsAgentResultClickRequestBody, @QueryMap params: Map): Completable + + @POST(ApiPaths.URL_PRODUCT_INSIGHTS_AGENT_ANSWER_VIEW_EVENT) + fun trackProductInsightsAgentAnswerView(@Body body: ProductInsightsAgentAnswerViewRequestBody, @QueryMap params: Map): Completable } From 80da8d5590757d0538312fb19774d059e62cacc9 Mon Sep 17 00:00:00 2001 From: mudaafi Date: Thu, 22 Oct 2026 22:10:54 +0800 Subject: [PATCH 2/7] [CDX-709] Add tests for Product Insights Agent tracking events The endpoints validate bodies strictly and the public methods are fire-and-forget, so a malformed body would fail as a silent 400. - MockWebServer tests check field presence and omission per event, plus edge cases: position 0, empty answerText, the 100 caps, UTC timespans - Builder tests cover each TrackingData class - One live test per event confirms the server returns 204 --- .../core/ConstructorIoIntegrationTest.kt | 148 +++ ...uctorIoProductInsightsAgentTrackingTest.kt | 839 ++++++++++++++++++ ...ghtsAgentAnswerFeedbackTrackingDataTest.kt | 92 ++ ...InsightsAgentAnswerViewTrackingDataTest.kt | 128 +++ ...oductInsightsAgentFocusTrackingDataTest.kt | 77 ++ ...tInsightsAgentOutOfViewTrackingDataTest.kt | 77 ++ ...ightsAgentQuestionClickTrackingDataTest.kt | 91 ++ ...ghtsAgentQuestionSubmitTrackingDataTest.kt | 91 ++ ...nsightsAgentResultClickTrackingDataTest.kt | 129 +++ ...roductInsightsAgentViewTrackingDataTest.kt | 84 ++ ...oductInsightsAgentViewsTrackingDataTest.kt | 91 ++ 11 files changed, 1847 insertions(+) create mode 100644 library/src/test/java/io/constructor/core/ConstructorIoProductInsightsAgentTrackingTest.kt create mode 100644 library/src/test/java/io/constructor/data/builder/ProductInsightsAgentAnswerFeedbackTrackingDataTest.kt create mode 100644 library/src/test/java/io/constructor/data/builder/ProductInsightsAgentAnswerViewTrackingDataTest.kt create mode 100644 library/src/test/java/io/constructor/data/builder/ProductInsightsAgentFocusTrackingDataTest.kt create mode 100644 library/src/test/java/io/constructor/data/builder/ProductInsightsAgentOutOfViewTrackingDataTest.kt create mode 100644 library/src/test/java/io/constructor/data/builder/ProductInsightsAgentQuestionClickTrackingDataTest.kt create mode 100644 library/src/test/java/io/constructor/data/builder/ProductInsightsAgentQuestionSubmitTrackingDataTest.kt create mode 100644 library/src/test/java/io/constructor/data/builder/ProductInsightsAgentResultClickTrackingDataTest.kt create mode 100644 library/src/test/java/io/constructor/data/builder/ProductInsightsAgentViewTrackingDataTest.kt create mode 100644 library/src/test/java/io/constructor/data/builder/ProductInsightsAgentViewsTrackingDataTest.kt diff --git a/library/src/test/java/io/constructor/core/ConstructorIoIntegrationTest.kt b/library/src/test/java/io/constructor/core/ConstructorIoIntegrationTest.kt index 8e864163..92ed2fd8 100644 --- a/library/src/test/java/io/constructor/core/ConstructorIoIntegrationTest.kt +++ b/library/src/test/java/io/constructor/core/ConstructorIoIntegrationTest.kt @@ -6,6 +6,9 @@ import io.constructor.data.local.PreferencesHelper import io.constructor.data.memory.ConfigMemoryHolder import io.constructor.data.model.common.VariationsMap import io.constructor.data.model.common.TrackingItem +import io.constructor.data.model.tracking.ProductInsightsAgentFeedbackLabel +import io.constructor.data.model.tracking.ProductInsightsAgentQuestion +import io.constructor.data.model.tracking.ProductInsightsAgentViewTimespan import io.constructor.data.model.purchase.PurchaseItem import io.constructor.test.createTestDataManager import io.constructor.util.RxSchedulersOverrideRule @@ -1867,4 +1870,149 @@ class ConstructorIoIntegrationTest { Thread.sleep(timeBetweenTests) } + + @Test + fun trackProductInsightsAgentFocusAgainstRealResponse() { + val request = ProductInsightsAgentFocusTrackingData.build("10001") { + itemName = "item1" + variationId = "20001" + threadId = "thread-cdx-709" + analyticsTags = mapOf("test" to "test1") + } + val observer = constructorIo.trackProductInsightsAgentFocusInternal(request).test() + observer.assertComplete() + observer.assertNoErrors() + Thread.sleep(timeBetweenTests) + } + + @Test + fun trackProductInsightsAgentOutOfViewAgainstRealResponse() { + val request = ProductInsightsAgentOutOfViewTrackingData.build("10001") { + itemName = "item1" + variationId = "20001" + threadId = "thread-cdx-709" + analyticsTags = mapOf("test" to "test1") + } + val observer = constructorIo.trackProductInsightsAgentOutOfViewInternal(request).test() + observer.assertComplete() + observer.assertNoErrors() + Thread.sleep(timeBetweenTests) + } + + @Test + fun trackProductInsightsAgentViewAgainstRealResponse() { + val request = ProductInsightsAgentViewTrackingData.build( + "10001", + listOf(ProductInsightsAgentQuestion("Is it waterproof?", "Durability"), ProductInsightsAgentQuestion("Is it machine washable?")) + ) { + itemName = "item1" + variationId = "20001" + threadId = "thread-cdx-709" + analyticsTags = mapOf("test" to "test1") + } + val observer = constructorIo.trackProductInsightsAgentViewInternal(request).test() + observer.assertComplete() + observer.assertNoErrors() + Thread.sleep(timeBetweenTests) + } + + @Test + fun trackProductInsightsAgentViewsAgainstRealResponse() { + val now = System.currentTimeMillis() + val request = ProductInsightsAgentViewsTrackingData.build( + "10001", + listOf(ProductInsightsAgentQuestion("Is it waterproof?", "Durability")), + listOf(ProductInsightsAgentViewTimespan(now - 10000, now - 5000), ProductInsightsAgentViewTimespan(now - 2000, now)) + ) { + itemName = "item1" + variationId = "20001" + threadId = "thread-cdx-709" + analyticsTags = mapOf("test" to "test1") + } + val observer = constructorIo.trackProductInsightsAgentViewsInternal(request).test() + observer.assertComplete() + observer.assertNoErrors() + Thread.sleep(timeBetweenTests) + } + + @Test + fun trackProductInsightsAgentQuestionClickAgainstRealResponse() { + val request = ProductInsightsAgentQuestionClickTrackingData.build("10001", "Is it waterproof?") { + questionTopic = "Durability" + itemName = "item1" + variationId = "20001" + threadId = "thread-cdx-709" + analyticsTags = mapOf("test" to "test1") + } + val observer = constructorIo.trackProductInsightsAgentQuestionClickInternal(request).test() + observer.assertComplete() + observer.assertNoErrors() + Thread.sleep(timeBetweenTests) + } + + @Test + fun trackProductInsightsAgentQuestionSubmitAgainstRealResponse() { + val request = ProductInsightsAgentQuestionSubmitTrackingData.build("10001", "Is it waterproof?") { + questionTopic = "Durability" + itemName = "item1" + variationId = "20001" + threadId = "thread-cdx-709" + analyticsTags = mapOf("test" to "test1") + } + val observer = constructorIo.trackProductInsightsAgentQuestionSubmitInternal(request).test() + observer.assertComplete() + observer.assertNoErrors() + Thread.sleep(timeBetweenTests) + } + + @Test + fun trackProductInsightsAgentAnswerFeedbackAgainstRealResponse() { + val request = ProductInsightsAgentAnswerFeedbackTrackingData.build("10001", ProductInsightsAgentFeedbackLabel.THUMBS_UP) { + qnaResultId = "qna-cdx-709" + itemName = "item1" + variationId = "20001" + threadId = "thread-cdx-709" + analyticsTags = mapOf("test" to "test1") + } + val observer = constructorIo.trackProductInsightsAgentAnswerFeedbackInternal(request).test() + observer.assertComplete() + observer.assertNoErrors() + Thread.sleep(timeBetweenTests) + } + + @Test + fun trackProductInsightsAgentResultClickAgainstRealResponse() { + val request = ProductInsightsAgentResultClickTrackingData.build("10002", "Is it waterproof?", "10001") { + position = 0 + seedItemName = "item1" + seedVariationId = "20001" + qnaResultId = "qna-cdx-709" + itemName = "item2" + variationId = "20002" + threadId = "thread-cdx-709" + analyticsTags = mapOf("test" to "test1") + } + val observer = constructorIo.trackProductInsightsAgentResultClickInternal(request).test() + observer.assertComplete() + observer.assertNoErrors() + Thread.sleep(timeBetweenTests) + } + + @Test + fun trackProductInsightsAgentAnswerViewAgainstRealResponse() { + val request = ProductInsightsAgentAnswerViewTrackingData.build("10001", "Is it waterproof?", "") { + questionTopic = "Durability" + qnaResultId = "qna-cdx-709" + items = listOf(TrackingItem("10002", "20002", testCampaignId, testCampaignOwner), TrackingItem("10003", null, null, null)) + followUpQuestions = listOf("Is it breathable?") + itemName = "item1" + variationId = "20001" + threadId = "thread-cdx-709" + analyticsTags = mapOf("test" to "test1") + } + val observer = constructorIo.trackProductInsightsAgentAnswerViewInternal(request).test() + observer.assertComplete() + observer.assertNoErrors() + Thread.sleep(timeBetweenTests) + } } diff --git a/library/src/test/java/io/constructor/core/ConstructorIoProductInsightsAgentTrackingTest.kt b/library/src/test/java/io/constructor/core/ConstructorIoProductInsightsAgentTrackingTest.kt new file mode 100644 index 00000000..91f2f434 --- /dev/null +++ b/library/src/test/java/io/constructor/core/ConstructorIoProductInsightsAgentTrackingTest.kt @@ -0,0 +1,839 @@ +package io.constructor.core + +import android.content.Context +import io.constructor.data.builder.* +import io.constructor.data.local.PreferencesHelper +import io.constructor.data.memory.ConfigMemoryHolder +import io.constructor.data.model.common.TrackingItem +import io.constructor.data.model.tracking.ProductInsightsAgentFeedbackLabel +import io.constructor.data.model.tracking.ProductInsightsAgentQuestion +import io.constructor.data.model.tracking.ProductInsightsAgentViewTimespan +import io.constructor.test.createTestDataManager +import io.constructor.util.RxSchedulersOverrideRule +import io.mockk.every +import io.mockk.mockk +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import okhttp3.mockwebserver.RecordedRequest +import org.json.JSONObject +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import java.net.SocketTimeoutException +import java.util.concurrent.TimeUnit +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class ConstructorIoProductInsightsAgentTrackingTest { + + @Rule + @JvmField + val overrideSchedulersRule = RxSchedulersOverrideRule() + + private lateinit var mockServer: MockWebServer + private var constructorIo = ConstructorIo + private val ctx = mockk() + private val preferencesHelper = mockk() + private val configMemoryHolder = mockk() + + private val tailFields = listOf("item_name", "variation_id", "thread_id") + + @Before + fun setup() { + mockServer = MockWebServer() + mockServer.start() + + every { ctx.applicationContext } returns ctx + + every { preferencesHelper.apiKey } returns "copper-key" + every { preferencesHelper.id } returns "wacko-the-guid" + every { preferencesHelper.serviceUrl } returns mockServer.hostName + every { preferencesHelper.port } returns mockServer.port + every { preferencesHelper.scheme } returns "http" + every { preferencesHelper.defaultItemSection } returns "Products" + every { preferencesHelper.getSessionId(any(), any()) } returns 67 + every { preferencesHelper.getSessionId() } returns 67 + + every { configMemoryHolder.autocompleteResultCount } returns null + every { configMemoryHolder.defaultAnalyticsTags } returns mapOf("appVersion" to "123", "appPlatform" to "Android") + every { configMemoryHolder.userId } returns "player-three" + every { configMemoryHolder.testCellParams } returns emptyList() + every { configMemoryHolder.segments } returns emptyList() + + val config = ConstructorIoConfig("dummyKey") + val dataManager = createTestDataManager(preferencesHelper, configMemoryHolder) + + constructorIo.testInit(ctx, config, dataManager, preferencesHelper, configMemoryHolder) + } + + @After + fun tearDown() { + mockServer.shutdown() + } + + // region Focus + + @Test + fun trackProductInsightsAgentFocus() { + mockServer.enqueue(MockResponse().setResponseCode(204)) + val request = ProductInsightsAgentFocusTrackingData.build("item-1") { + itemName = "Waterproof Jacket" + variationId = "var-1" + threadId = "thread-1" + } + val observer = ConstructorIo.trackProductInsightsAgentFocusInternal(request).test() + observer.assertComplete() + val recordedRequest = awaitRequest() + assertEquals("POST", recordedRequest.method) + assertPathAndSection(recordedRequest, "focus", "Products") + + val body = getJsonBody(recordedRequest) + assertEquals("item-1", body.getString("item_id")) + assertEquals("Waterproof Jacket", body.getString("item_name")) + assertEquals("var-1", body.getString("variation_id")) + assertEquals("thread-1", body.getString("thread_id")) + assertWrapperFields(body, "Products") + } + + @Test + fun trackProductInsightsAgentFocusWithRequiredFieldsOnly() { + mockServer.enqueue(MockResponse().setResponseCode(204)) + val request = ProductInsightsAgentFocusTrackingData.build("item-1") + val observer = ConstructorIo.trackProductInsightsAgentFocusInternal(request).test() + observer.assertComplete() + val body = getJsonBody(awaitRequest()) + assertEquals("item-1", body.getString("item_id")) + assertAbsent(body, tailFields) + } + + @Test + fun trackProductInsightsAgentFocusWithSectionAndAnalyticsTags() { + mockServer.enqueue(MockResponse().setResponseCode(204)) + val request = ProductInsightsAgentFocusTrackingData.build("item-1") { + sectionName = "Search Suggestions" + analyticsTags = mapOf("test" to "test1", "appVersion" to "150") + } + val observer = ConstructorIo.trackProductInsightsAgentFocusInternal(request).test() + observer.assertComplete() + val recordedRequest = awaitRequest() + assertPathAndSection(recordedRequest, "focus", "Search Suggestions") + val body = getJsonBody(recordedRequest) + assertEquals("Search Suggestions", body.getString("section")) + assertMergedAnalyticsTags(body) + } + + @Test + fun trackProductInsightsAgentFocusUsingPublicMethod() { + mockServer.enqueue(MockResponse().setResponseCode(204)) + ConstructorIo.trackProductInsightsAgentFocus(ProductInsightsAgentFocusTrackingData.build("item-1")) + val recordedRequest = awaitRequest() + assertPathAndSection(recordedRequest, "focus", "Products") + assertEquals("item-1", getJsonBody(recordedRequest).getString("item_id")) + } + + @Test + fun trackProductInsightsAgentFocus500() { + mockServer.enqueue(MockResponse().setResponseCode(500).setBody("Internal server error")) + val observer = ConstructorIo.trackProductInsightsAgentFocusInternal(ProductInsightsAgentFocusTrackingData.build("item-1")).test() + observer.assertError { true } + assertPathAndSection(awaitRequest(), "focus", "Products") + } + + @Test + fun trackProductInsightsAgentFocusTimeout() { + mockServer.enqueue(MockResponse().setResponseCode(500).setBody("Internal server error").setBodyDelay(5, TimeUnit.SECONDS)) + val observer = ConstructorIo.trackProductInsightsAgentFocusInternal(ProductInsightsAgentFocusTrackingData.build("item-1")).test() + observer.awaitDone(6, TimeUnit.SECONDS) + observer.assertError(SocketTimeoutException::class.java) + assertPathAndSection(awaitRequest(), "focus", "Products") + } + + // endregion + + // region OutOfView + + @Test + fun trackProductInsightsAgentOutOfView() { + mockServer.enqueue(MockResponse().setResponseCode(204)) + val request = ProductInsightsAgentOutOfViewTrackingData.build("item-1") { + itemName = "Waterproof Jacket" + variationId = "var-1" + threadId = "thread-1" + } + val observer = ConstructorIo.trackProductInsightsAgentOutOfViewInternal(request).test() + observer.assertComplete() + val recordedRequest = awaitRequest() + assertEquals("POST", recordedRequest.method) + assertPathAndSection(recordedRequest, "out_of_view", "Products") + + val body = getJsonBody(recordedRequest) + assertEquals("item-1", body.getString("item_id")) + assertEquals("Waterproof Jacket", body.getString("item_name")) + assertEquals("var-1", body.getString("variation_id")) + assertEquals("thread-1", body.getString("thread_id")) + assertWrapperFields(body, "Products") + } + + @Test + fun trackProductInsightsAgentOutOfViewWithRequiredFieldsOnly() { + mockServer.enqueue(MockResponse().setResponseCode(204)) + val observer = ConstructorIo.trackProductInsightsAgentOutOfViewInternal(ProductInsightsAgentOutOfViewTrackingData.build("item-1")).test() + observer.assertComplete() + val body = getJsonBody(awaitRequest()) + assertEquals("item-1", body.getString("item_id")) + assertAbsent(body, tailFields) + } + + @Test + fun trackProductInsightsAgentOutOfViewWithSectionAndAnalyticsTags() { + mockServer.enqueue(MockResponse().setResponseCode(204)) + val request = ProductInsightsAgentOutOfViewTrackingData.build("item-1") { + sectionName = "Search Suggestions" + analyticsTags = mapOf("test" to "test1", "appVersion" to "150") + } + val observer = ConstructorIo.trackProductInsightsAgentOutOfViewInternal(request).test() + observer.assertComplete() + val recordedRequest = awaitRequest() + assertPathAndSection(recordedRequest, "out_of_view", "Search Suggestions") + assertMergedAnalyticsTags(getJsonBody(recordedRequest)) + } + + @Test + fun trackProductInsightsAgentOutOfView500() { + mockServer.enqueue(MockResponse().setResponseCode(500).setBody("Internal server error")) + val observer = ConstructorIo.trackProductInsightsAgentOutOfViewInternal(ProductInsightsAgentOutOfViewTrackingData.build("item-1")).test() + observer.assertError { true } + assertPathAndSection(awaitRequest(), "out_of_view", "Products") + } + + // endregion + + // region View + + @Test + fun trackProductInsightsAgentView() { + mockServer.enqueue(MockResponse().setResponseCode(204)) + val questions = listOf( + ProductInsightsAgentQuestion("Is it waterproof?", "Durability"), + ProductInsightsAgentQuestion("Is it machine washable?"), + ) + val request = ProductInsightsAgentViewTrackingData.build("item-1", questions) { + itemName = "Waterproof Jacket" + variationId = "var-1" + threadId = "thread-1" + } + val observer = ConstructorIo.trackProductInsightsAgentViewInternal(request).test() + observer.assertComplete() + val recordedRequest = awaitRequest() + assertEquals("POST", recordedRequest.method) + assertPathAndSection(recordedRequest, "view", "Products") + + val body = getJsonBody(recordedRequest) + assertEquals("item-1", body.getString("item_id")) + assertEquals("Waterproof Jacket", body.getString("item_name")) + assertEquals("var-1", body.getString("variation_id")) + assertEquals("thread-1", body.getString("thread_id")) + val sentQuestions = body.getJSONArray("questions") + assertEquals(2, sentQuestions.length()) + assertEquals("Is it waterproof?", sentQuestions.getJSONObject(0).getString("question")) + assertEquals("Durability", sentQuestions.getJSONObject(0).getString("question_topic")) + assertEquals("Is it machine washable?", sentQuestions.getJSONObject(1).getString("question")) + assertFalse(sentQuestions.getJSONObject(1).has("question_topic")) + assertWrapperFields(body, "Products") + } + + @Test + fun trackProductInsightsAgentViewWithRequiredFieldsOnly() { + mockServer.enqueue(MockResponse().setResponseCode(204)) + val request = ProductInsightsAgentViewTrackingData.build("item-1", listOf(ProductInsightsAgentQuestion("Is it waterproof?"))) + val observer = ConstructorIo.trackProductInsightsAgentViewInternal(request).test() + observer.assertComplete() + val body = getJsonBody(awaitRequest()) + assertEquals("item-1", body.getString("item_id")) + assertEquals(1, body.getJSONArray("questions").length()) + assertAbsent(body, tailFields) + } + + @Test + fun trackProductInsightsAgentViewWithSectionAndAnalyticsTags() { + mockServer.enqueue(MockResponse().setResponseCode(204)) + val request = ProductInsightsAgentViewTrackingData.build("item-1", listOf(ProductInsightsAgentQuestion("Is it waterproof?"))) { + sectionName = "Search Suggestions" + analyticsTags = mapOf("test" to "test1", "appVersion" to "150") + } + val observer = ConstructorIo.trackProductInsightsAgentViewInternal(request).test() + observer.assertComplete() + val recordedRequest = awaitRequest() + assertPathAndSection(recordedRequest, "view", "Search Suggestions") + assertMergedAnalyticsTags(getJsonBody(recordedRequest)) + } + + @Test + fun trackProductInsightsAgentView500() { + mockServer.enqueue(MockResponse().setResponseCode(500).setBody("Internal server error")) + val request = ProductInsightsAgentViewTrackingData.build("item-1", listOf(ProductInsightsAgentQuestion("Is it waterproof?"))) + val observer = ConstructorIo.trackProductInsightsAgentViewInternal(request).test() + observer.assertError { true } + assertPathAndSection(awaitRequest(), "view", "Products") + } + + // endregion + + // region Views + + @Test + fun trackProductInsightsAgentViews() { + mockServer.enqueue(MockResponse().setResponseCode(204)) + val request = ProductInsightsAgentViewsTrackingData.build( + "item-1", + listOf(ProductInsightsAgentQuestion("Is it waterproof?", "Durability")), + listOf( + ProductInsightsAgentViewTimespan(1792550405123, 1792550410456), + ProductInsightsAgentViewTimespan(1792550420000, 1792550425000), + ) + ) { + itemName = "Waterproof Jacket" + variationId = "var-1" + threadId = "thread-1" + } + val observer = ConstructorIo.trackProductInsightsAgentViewsInternal(request).test() + observer.assertComplete() + val recordedRequest = awaitRequest() + assertEquals("POST", recordedRequest.method) + assertPathAndSection(recordedRequest, "views", "Products") + + val body = getJsonBody(recordedRequest) + assertEquals("item-1", body.getString("item_id")) + assertEquals("Waterproof Jacket", body.getString("item_name")) + assertEquals("var-1", body.getString("variation_id")) + assertEquals("thread-1", body.getString("thread_id")) + val sentQuestions = body.getJSONArray("questions") + assertEquals("Is it waterproof?", sentQuestions.getJSONObject(0).getString("question")) + assertEquals("Durability", sentQuestions.getJSONObject(0).getString("question_topic")) + // Timespans are given as epoch milliseconds and sent as ISO-8601 UTC timestamps + val timespans = body.getJSONArray("view_timespans") + assertEquals(2, timespans.length()) + assertEquals("2026-10-21T02:40:05.123Z", timespans.getJSONObject(0).getString("start")) + assertEquals("2026-10-21T02:40:10.456Z", timespans.getJSONObject(0).getString("end")) + assertEquals("2026-10-21T02:40:20.000Z", timespans.getJSONObject(1).getString("start")) + assertEquals("2026-10-21T02:40:25.000Z", timespans.getJSONObject(1).getString("end")) + assertWrapperFields(body, "Products") + } + + @Test + fun trackProductInsightsAgentViewsWithRequiredFieldsOnly() { + mockServer.enqueue(MockResponse().setResponseCode(204)) + val request = ProductInsightsAgentViewsTrackingData.build( + "item-1", + listOf(ProductInsightsAgentQuestion("Is it waterproof?")), + listOf(ProductInsightsAgentViewTimespan(1792550405123, 1792550410456)) + ) + val observer = ConstructorIo.trackProductInsightsAgentViewsInternal(request).test() + observer.assertComplete() + val body = getJsonBody(awaitRequest()) + assertEquals("item-1", body.getString("item_id")) + assertEquals(1, body.getJSONArray("view_timespans").length()) + assertAbsent(body, tailFields) + } + + @Test + fun trackProductInsightsAgentViewsSendsUtcTimespansRegardlessOfDefaultTimeZone() { + mockServer.enqueue(MockResponse().setResponseCode(204)) + val defaultTimeZone = java.util.TimeZone.getDefault() + try { + java.util.TimeZone.setDefault(java.util.TimeZone.getTimeZone("America/Santiago")) + val request = ProductInsightsAgentViewsTrackingData.build( + "item-1", + listOf(ProductInsightsAgentQuestion("Is it waterproof?")), + listOf(ProductInsightsAgentViewTimespan(1792550405123, 1792550410456)) + ) + val observer = ConstructorIo.trackProductInsightsAgentViewsInternal(request).test() + observer.assertComplete() + val timespan = getJsonBody(awaitRequest()).getJSONArray("view_timespans").getJSONObject(0) + assertEquals("2026-10-21T02:40:05.123Z", timespan.getString("start")) + assertEquals("2026-10-21T02:40:10.456Z", timespan.getString("end")) + } finally { + java.util.TimeZone.setDefault(defaultTimeZone) + } + } + + @Test + fun trackProductInsightsAgentViewsWithSectionAndAnalyticsTags() { + mockServer.enqueue(MockResponse().setResponseCode(204)) + val request = ProductInsightsAgentViewsTrackingData.build( + "item-1", + listOf(ProductInsightsAgentQuestion("Is it waterproof?")), + listOf(ProductInsightsAgentViewTimespan(1792550405123, 1792550410456)) + ) { + sectionName = "Search Suggestions" + analyticsTags = mapOf("test" to "test1", "appVersion" to "150") + } + val observer = ConstructorIo.trackProductInsightsAgentViewsInternal(request).test() + observer.assertComplete() + val recordedRequest = awaitRequest() + assertPathAndSection(recordedRequest, "views", "Search Suggestions") + assertMergedAnalyticsTags(getJsonBody(recordedRequest)) + } + + @Test + fun trackProductInsightsAgentViews500() { + mockServer.enqueue(MockResponse().setResponseCode(500).setBody("Internal server error")) + val request = ProductInsightsAgentViewsTrackingData.build( + "item-1", + listOf(ProductInsightsAgentQuestion("Is it waterproof?")), + listOf(ProductInsightsAgentViewTimespan(1792550405123, 1792550410456)) + ) + val observer = ConstructorIo.trackProductInsightsAgentViewsInternal(request).test() + observer.assertError { true } + assertPathAndSection(awaitRequest(), "views", "Products") + } + + // endregion + + // region QuestionClick + + @Test + fun trackProductInsightsAgentQuestionClick() { + mockServer.enqueue(MockResponse().setResponseCode(204)) + val request = ProductInsightsAgentQuestionClickTrackingData.build("item-1", "Is it waterproof?") { + questionTopic = "Durability" + itemName = "Waterproof Jacket" + variationId = "var-1" + threadId = "thread-1" + } + val observer = ConstructorIo.trackProductInsightsAgentQuestionClickInternal(request).test() + observer.assertComplete() + val recordedRequest = awaitRequest() + assertEquals("POST", recordedRequest.method) + assertPathAndSection(recordedRequest, "question_click", "Products") + + val body = getJsonBody(recordedRequest) + assertEquals("item-1", body.getString("item_id")) + assertEquals("Is it waterproof?", body.getString("question")) + assertEquals("Durability", body.getString("question_topic")) + assertEquals("Waterproof Jacket", body.getString("item_name")) + assertEquals("var-1", body.getString("variation_id")) + assertEquals("thread-1", body.getString("thread_id")) + assertWrapperFields(body, "Products") + } + + @Test + fun trackProductInsightsAgentQuestionClickWithRequiredFieldsOnly() { + mockServer.enqueue(MockResponse().setResponseCode(204)) + val request = ProductInsightsAgentQuestionClickTrackingData.build("item-1", "Is it waterproof?") + val observer = ConstructorIo.trackProductInsightsAgentQuestionClickInternal(request).test() + observer.assertComplete() + val body = getJsonBody(awaitRequest()) + assertEquals("item-1", body.getString("item_id")) + assertEquals("Is it waterproof?", body.getString("question")) + assertAbsent(body, listOf("question_topic") + tailFields) + } + + @Test + fun trackProductInsightsAgentQuestionClickWithSectionAndAnalyticsTags() { + mockServer.enqueue(MockResponse().setResponseCode(204)) + val request = ProductInsightsAgentQuestionClickTrackingData.build("item-1", "Is it waterproof?") { + sectionName = "Search Suggestions" + analyticsTags = mapOf("test" to "test1", "appVersion" to "150") + } + val observer = ConstructorIo.trackProductInsightsAgentQuestionClickInternal(request).test() + observer.assertComplete() + val recordedRequest = awaitRequest() + assertPathAndSection(recordedRequest, "question_click", "Search Suggestions") + assertMergedAnalyticsTags(getJsonBody(recordedRequest)) + } + + @Test + fun trackProductInsightsAgentQuestionClick500() { + mockServer.enqueue(MockResponse().setResponseCode(500).setBody("Internal server error")) + val request = ProductInsightsAgentQuestionClickTrackingData.build("item-1", "Is it waterproof?") + val observer = ConstructorIo.trackProductInsightsAgentQuestionClickInternal(request).test() + observer.assertError { true } + assertPathAndSection(awaitRequest(), "question_click", "Products") + } + + // endregion + + // region QuestionSubmit + + @Test + fun trackProductInsightsAgentQuestionSubmit() { + mockServer.enqueue(MockResponse().setResponseCode(204)) + val request = ProductInsightsAgentQuestionSubmitTrackingData.build("item-1", "Is it waterproof?") { + questionTopic = "Durability" + itemName = "Waterproof Jacket" + variationId = "var-1" + threadId = "thread-1" + } + val observer = ConstructorIo.trackProductInsightsAgentQuestionSubmitInternal(request).test() + observer.assertComplete() + val recordedRequest = awaitRequest() + assertEquals("POST", recordedRequest.method) + assertPathAndSection(recordedRequest, "question_submit", "Products") + + val body = getJsonBody(recordedRequest) + assertEquals("item-1", body.getString("item_id")) + assertEquals("Is it waterproof?", body.getString("question")) + assertEquals("Durability", body.getString("question_topic")) + assertEquals("Waterproof Jacket", body.getString("item_name")) + assertEquals("var-1", body.getString("variation_id")) + assertEquals("thread-1", body.getString("thread_id")) + assertWrapperFields(body, "Products") + } + + @Test + fun trackProductInsightsAgentQuestionSubmitWithRequiredFieldsOnly() { + mockServer.enqueue(MockResponse().setResponseCode(204)) + val request = ProductInsightsAgentQuestionSubmitTrackingData.build("item-1", "Is it waterproof?") + val observer = ConstructorIo.trackProductInsightsAgentQuestionSubmitInternal(request).test() + observer.assertComplete() + val body = getJsonBody(awaitRequest()) + assertEquals("item-1", body.getString("item_id")) + assertEquals("Is it waterproof?", body.getString("question")) + assertAbsent(body, listOf("question_topic") + tailFields) + } + + @Test + fun trackProductInsightsAgentQuestionSubmitWithSectionAndAnalyticsTags() { + mockServer.enqueue(MockResponse().setResponseCode(204)) + val request = ProductInsightsAgentQuestionSubmitTrackingData.build("item-1", "Is it waterproof?") { + sectionName = "Search Suggestions" + analyticsTags = mapOf("test" to "test1", "appVersion" to "150") + } + val observer = ConstructorIo.trackProductInsightsAgentQuestionSubmitInternal(request).test() + observer.assertComplete() + val recordedRequest = awaitRequest() + assertPathAndSection(recordedRequest, "question_submit", "Search Suggestions") + assertMergedAnalyticsTags(getJsonBody(recordedRequest)) + } + + @Test + fun trackProductInsightsAgentQuestionSubmit500() { + mockServer.enqueue(MockResponse().setResponseCode(500).setBody("Internal server error")) + val request = ProductInsightsAgentQuestionSubmitTrackingData.build("item-1", "Is it waterproof?") + val observer = ConstructorIo.trackProductInsightsAgentQuestionSubmitInternal(request).test() + observer.assertError { true } + assertPathAndSection(awaitRequest(), "question_submit", "Products") + } + + // endregion + + // region AnswerFeedback + + @Test + fun trackProductInsightsAgentAnswerFeedback() { + mockServer.enqueue(MockResponse().setResponseCode(204)) + val request = ProductInsightsAgentAnswerFeedbackTrackingData.build("item-1", ProductInsightsAgentFeedbackLabel.THUMBS_UP) { + qnaResultId = "qna-1" + itemName = "Waterproof Jacket" + variationId = "var-1" + threadId = "thread-1" + } + val observer = ConstructorIo.trackProductInsightsAgentAnswerFeedbackInternal(request).test() + observer.assertComplete() + val recordedRequest = awaitRequest() + assertEquals("POST", recordedRequest.method) + assertPathAndSection(recordedRequest, "answer_feedback", "Products") + + val body = getJsonBody(recordedRequest) + assertEquals("item-1", body.getString("item_id")) + assertEquals("thumbs_up", body.getString("feedback_label")) + assertEquals("qna-1", body.getString("qna_result_id")) + assertEquals("Waterproof Jacket", body.getString("item_name")) + assertEquals("var-1", body.getString("variation_id")) + assertEquals("thread-1", body.getString("thread_id")) + assertWrapperFields(body, "Products") + } + + @Test + fun trackProductInsightsAgentAnswerFeedbackThumbsDown() { + mockServer.enqueue(MockResponse().setResponseCode(204)) + val request = ProductInsightsAgentAnswerFeedbackTrackingData.build("item-1", ProductInsightsAgentFeedbackLabel.THUMBS_DOWN) + val observer = ConstructorIo.trackProductInsightsAgentAnswerFeedbackInternal(request).test() + observer.assertComplete() + assertEquals("thumbs_down", getJsonBody(awaitRequest()).getString("feedback_label")) + } + + @Test + fun trackProductInsightsAgentAnswerFeedbackWithRequiredFieldsOnly() { + mockServer.enqueue(MockResponse().setResponseCode(204)) + val request = ProductInsightsAgentAnswerFeedbackTrackingData.build("item-1", ProductInsightsAgentFeedbackLabel.THUMBS_UP) + val observer = ConstructorIo.trackProductInsightsAgentAnswerFeedbackInternal(request).test() + observer.assertComplete() + val body = getJsonBody(awaitRequest()) + assertEquals("item-1", body.getString("item_id")) + assertEquals("thumbs_up", body.getString("feedback_label")) + assertAbsent(body, listOf("qna_result_id") + tailFields) + } + + @Test + fun trackProductInsightsAgentAnswerFeedbackWithSectionAndAnalyticsTags() { + mockServer.enqueue(MockResponse().setResponseCode(204)) + val request = ProductInsightsAgentAnswerFeedbackTrackingData.build("item-1", ProductInsightsAgentFeedbackLabel.THUMBS_UP) { + sectionName = "Search Suggestions" + analyticsTags = mapOf("test" to "test1", "appVersion" to "150") + } + val observer = ConstructorIo.trackProductInsightsAgentAnswerFeedbackInternal(request).test() + observer.assertComplete() + val recordedRequest = awaitRequest() + assertPathAndSection(recordedRequest, "answer_feedback", "Search Suggestions") + assertMergedAnalyticsTags(getJsonBody(recordedRequest)) + } + + @Test + fun trackProductInsightsAgentAnswerFeedback500() { + mockServer.enqueue(MockResponse().setResponseCode(500).setBody("Internal server error")) + val request = ProductInsightsAgentAnswerFeedbackTrackingData.build("item-1", ProductInsightsAgentFeedbackLabel.THUMBS_UP) + val observer = ConstructorIo.trackProductInsightsAgentAnswerFeedbackInternal(request).test() + observer.assertError { true } + assertPathAndSection(awaitRequest(), "answer_feedback", "Products") + } + + // endregion + + // region ResultClick + + @Test + fun trackProductInsightsAgentResultClick() { + mockServer.enqueue(MockResponse().setResponseCode(204)) + val request = ProductInsightsAgentResultClickTrackingData.build("rec-1", "Is it waterproof?", "item-1") { + position = 2 + seedItemName = "Waterproof Jacket" + seedVariationId = "var-1" + qnaResultId = "qna-1" + itemName = "Rain Boots" + variationId = "rec-var-1" + threadId = "thread-1" + } + val observer = ConstructorIo.trackProductInsightsAgentResultClickInternal(request).test() + observer.assertComplete() + val recordedRequest = awaitRequest() + assertEquals("POST", recordedRequest.method) + assertPathAndSection(recordedRequest, "result_click", "Products") + + val body = getJsonBody(recordedRequest) + assertEquals("rec-1", body.getString("item_id")) + assertEquals("Is it waterproof?", body.getString("question")) + assertEquals("item-1", body.getString("seed_item_id")) + assertEquals(2, body.getInt("position")) + assertEquals("Waterproof Jacket", body.getString("seed_item_name")) + assertEquals("var-1", body.getString("seed_variation_id")) + assertEquals("qna-1", body.getString("qna_result_id")) + assertEquals("Rain Boots", body.getString("item_name")) + assertEquals("rec-var-1", body.getString("variation_id")) + assertEquals("thread-1", body.getString("thread_id")) + assertWrapperFields(body, "Products") + } + + @Test + fun trackProductInsightsAgentResultClickWithPositionZero() { + mockServer.enqueue(MockResponse().setResponseCode(204)) + val request = ProductInsightsAgentResultClickTrackingData.build("rec-1", "Is it waterproof?", "item-1") { + position = 0 + } + val observer = ConstructorIo.trackProductInsightsAgentResultClickInternal(request).test() + observer.assertComplete() + val body = getJsonBody(awaitRequest()) + assertTrue(body.has("position")) + assertEquals(0, body.getInt("position")) + } + + @Test + fun trackProductInsightsAgentResultClickWithRequiredFieldsOnly() { + mockServer.enqueue(MockResponse().setResponseCode(204)) + val request = ProductInsightsAgentResultClickTrackingData.build("rec-1", "Is it waterproof?", "item-1") + val observer = ConstructorIo.trackProductInsightsAgentResultClickInternal(request).test() + observer.assertComplete() + val body = getJsonBody(awaitRequest()) + assertEquals("rec-1", body.getString("item_id")) + assertEquals("Is it waterproof?", body.getString("question")) + assertEquals("item-1", body.getString("seed_item_id")) + assertAbsent(body, listOf("position", "seed_item_name", "seed_variation_id", "qna_result_id") + tailFields) + } + + @Test + fun trackProductInsightsAgentResultClickWithSectionAndAnalyticsTags() { + mockServer.enqueue(MockResponse().setResponseCode(204)) + val request = ProductInsightsAgentResultClickTrackingData.build("rec-1", "Is it waterproof?", "item-1") { + sectionName = "Search Suggestions" + analyticsTags = mapOf("test" to "test1", "appVersion" to "150") + } + val observer = ConstructorIo.trackProductInsightsAgentResultClickInternal(request).test() + observer.assertComplete() + val recordedRequest = awaitRequest() + assertPathAndSection(recordedRequest, "result_click", "Search Suggestions") + assertMergedAnalyticsTags(getJsonBody(recordedRequest)) + } + + @Test + fun trackProductInsightsAgentResultClick500() { + mockServer.enqueue(MockResponse().setResponseCode(500).setBody("Internal server error")) + val request = ProductInsightsAgentResultClickTrackingData.build("rec-1", "Is it waterproof?", "item-1") + val observer = ConstructorIo.trackProductInsightsAgentResultClickInternal(request).test() + observer.assertError { true } + assertPathAndSection(awaitRequest(), "result_click", "Products") + } + + // endregion + + // region AnswerView + + @Test + fun trackProductInsightsAgentAnswerView() { + mockServer.enqueue(MockResponse().setResponseCode(204)) + val request = ProductInsightsAgentAnswerViewTrackingData.build("item-1", "Is it waterproof?", "Yes, it is fully waterproof.") { + questionTopic = "Durability" + qnaResultId = "qna-1" + items = listOf( + TrackingItem("rec-1", "rec-var-1", "camp-1", "owner-a"), + TrackingItem("rec-2", null, null, null), + ) + followUpQuestions = listOf("Is it breathable?", "Does it come in blue?") + itemName = "Waterproof Jacket" + variationId = "var-1" + threadId = "thread-1" + } + val observer = ConstructorIo.trackProductInsightsAgentAnswerViewInternal(request).test() + observer.assertComplete() + val recordedRequest = awaitRequest() + assertEquals("POST", recordedRequest.method) + assertPathAndSection(recordedRequest, "answer_view", "Products") + + val body = getJsonBody(recordedRequest) + assertEquals("item-1", body.getString("item_id")) + assertEquals("Is it waterproof?", body.getString("question")) + assertEquals("Yes, it is fully waterproof.", body.getString("answer_text")) + assertEquals("Durability", body.getString("question_topic")) + assertEquals("qna-1", body.getString("qna_result_id")) + assertEquals("Waterproof Jacket", body.getString("item_name")) + assertEquals("var-1", body.getString("variation_id")) + assertEquals("thread-1", body.getString("thread_id")) + + val items = body.getJSONArray("items") + assertEquals(2, items.length()) + val firstItem = items.getJSONObject(0) + assertEquals("rec-1", firstItem.getString("item_id")) + assertEquals("rec-var-1", firstItem.getString("variation_id")) + assertEquals("camp-1", firstItem.getString("sl_campaign_id")) + assertEquals("owner-a", firstItem.getString("sl_campaign_owner")) + val secondItem = items.getJSONObject(1) + assertEquals("rec-2", secondItem.getString("item_id")) + assertEquals(setOf("item_id"), secondItem.keys().asSequence().toSet()) + + val followUpQuestions = body.getJSONArray("follow_up_questions") + assertEquals(2, followUpQuestions.length()) + assertEquals("Is it breathable?", followUpQuestions.getJSONObject(0).getString("value")) + assertEquals("Does it come in blue?", followUpQuestions.getJSONObject(1).getString("value")) + assertWrapperFields(body, "Products") + } + + @Test + fun trackProductInsightsAgentAnswerViewWithEmptyAnswerText() { + mockServer.enqueue(MockResponse().setResponseCode(204)) + val request = ProductInsightsAgentAnswerViewTrackingData.build("item-1", "Is it waterproof?", "") + val observer = ConstructorIo.trackProductInsightsAgentAnswerViewInternal(request).test() + observer.assertComplete() + val body = getJsonBody(awaitRequest()) + assertTrue(body.has("answer_text")) + assertEquals("", body.getString("answer_text")) + } + + @Test + fun trackProductInsightsAgentAnswerViewCapsListsAt100() { + mockServer.enqueue(MockResponse().setResponseCode(204)) + val request = ProductInsightsAgentAnswerViewTrackingData.build("item-1", "Is it waterproof?", "Yes.") { + items = (1..150).map { TrackingItem("rec-$it", null, null, null) } + followUpQuestions = (1..150).map { "Question $it?" } + } + val observer = ConstructorIo.trackProductInsightsAgentAnswerViewInternal(request).test() + observer.assertComplete() + val body = getJsonBody(awaitRequest()) + val items = body.getJSONArray("items") + assertEquals(100, items.length()) + assertEquals("rec-1", items.getJSONObject(0).getString("item_id")) + assertEquals("rec-100", items.getJSONObject(99).getString("item_id")) + val followUpQuestions = body.getJSONArray("follow_up_questions") + assertEquals(100, followUpQuestions.length()) + assertEquals("Question 1?", followUpQuestions.getJSONObject(0).getString("value")) + assertEquals("Question 100?", followUpQuestions.getJSONObject(99).getString("value")) + } + + @Test + fun trackProductInsightsAgentAnswerViewWithRequiredFieldsOnly() { + mockServer.enqueue(MockResponse().setResponseCode(204)) + val request = ProductInsightsAgentAnswerViewTrackingData.build("item-1", "Is it waterproof?", "Yes.") + val observer = ConstructorIo.trackProductInsightsAgentAnswerViewInternal(request).test() + observer.assertComplete() + val body = getJsonBody(awaitRequest()) + assertEquals("item-1", body.getString("item_id")) + assertEquals("Is it waterproof?", body.getString("question")) + assertEquals("Yes.", body.getString("answer_text")) + assertAbsent(body, listOf("question_topic", "qna_result_id", "items", "follow_up_questions") + tailFields) + } + + @Test + fun trackProductInsightsAgentAnswerViewWithSectionAndAnalyticsTags() { + mockServer.enqueue(MockResponse().setResponseCode(204)) + val request = ProductInsightsAgentAnswerViewTrackingData.build("item-1", "Is it waterproof?", "Yes.") { + sectionName = "Search Suggestions" + analyticsTags = mapOf("test" to "test1", "appVersion" to "150") + } + val observer = ConstructorIo.trackProductInsightsAgentAnswerViewInternal(request).test() + observer.assertComplete() + val recordedRequest = awaitRequest() + assertPathAndSection(recordedRequest, "answer_view", "Search Suggestions") + assertMergedAnalyticsTags(getJsonBody(recordedRequest)) + } + + @Test + fun trackProductInsightsAgentAnswerView500() { + mockServer.enqueue(MockResponse().setResponseCode(500).setBody("Internal server error")) + val request = ProductInsightsAgentAnswerViewTrackingData.build("item-1", "Is it waterproof?", "Yes.") + val observer = ConstructorIo.trackProductInsightsAgentAnswerViewInternal(request).test() + observer.assertError { true } + assertPathAndSection(awaitRequest(), "answer_view", "Products") + } + + // endregion + + private fun awaitRequest(): RecordedRequest { + return mockServer.takeRequest(1, TimeUnit.SECONDS) + ?: throw AssertionError("Expected request but timed out") + } + + private fun getJsonBody(request: RecordedRequest): JSONObject = JSONObject(request.body.readUtf8()) + + private fun assertPathAndSection(request: RecordedRequest, suffix: String, section: String) { + val url = request.requestUrl!! + assertEquals("/v2/behavioral_action/product_insights_agent_$suffix", url.encodedPath) + assertEquals(section, url.queryParameter("section")) + } + + private fun assertWrapperFields(body: JSONObject, section: String) { + assertEquals("wacko-the-guid", body.getString("i")) + assertEquals(67, body.getInt("s")) + assertEquals("copper-key", body.getString("key")) + assertEquals("player-three", body.getString("ui")) + assertEquals(true, body.getBoolean("beacon")) + assertEquals(section, body.getString("section")) + assertTrue(body.getString("c").startsWith("cioand-")) + assertTrue(body.getLong("_dt") > 0) + val analyticsTags = body.getJSONObject("analytics_tags") + assertEquals("123", analyticsTags.getString("appVersion")) + assertEquals("Android", analyticsTags.getString("appPlatform")) + } + + private fun assertMergedAnalyticsTags(body: JSONObject) { + val analyticsTags = body.getJSONObject("analytics_tags") + assertEquals(3, analyticsTags.length()) + assertEquals("150", analyticsTags.getString("appVersion")) + assertEquals("Android", analyticsTags.getString("appPlatform")) + assertEquals("test1", analyticsTags.getString("test")) + } + + private fun assertAbsent(body: JSONObject, keys: List) { + keys.forEach { assertFalse(body.has(it), "Expected \"$it\" to be absent from the request body") } + } +} diff --git a/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentAnswerFeedbackTrackingDataTest.kt b/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentAnswerFeedbackTrackingDataTest.kt new file mode 100644 index 00000000..036dd5c6 --- /dev/null +++ b/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentAnswerFeedbackTrackingDataTest.kt @@ -0,0 +1,92 @@ +package io.constructor.data.builder + +import io.constructor.data.model.tracking.ProductInsightsAgentFeedbackLabel +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class ProductInsightsAgentAnswerFeedbackTrackingDataTest { + private val itemId = "item-1" + private val feedbackLabel = ProductInsightsAgentFeedbackLabel.THUMBS_UP + private val qnaResultId = "qna-1" + private val itemName = "Waterproof Jacket" + private val variationId = "var-1" + private val threadId = "thread-1" + private val sectionName = "Search Suggestions" + private val analyticsTags = mapOf("test" to "test1") + + @Test + fun answerFeedbackTrackingDataWithRequiredParams() { + val request = ProductInsightsAgentAnswerFeedbackTrackingData.build(itemId, feedbackLabel) + assertEquals(itemId, request.itemId) + assertEquals(feedbackLabel, request.feedbackLabel) + assertNull(request.qnaResultId) + assertNull(request.itemName) + assertNull(request.variationId) + assertNull(request.threadId) + assertNull(request.sectionName) + assertNull(request.analyticsTags) + } + + @Test + fun answerFeedbackTrackingDataUsingDsl() { + val request = ProductInsightsAgentAnswerFeedbackTrackingData.build(itemId, feedbackLabel) { + this.qnaResultId = this@ProductInsightsAgentAnswerFeedbackTrackingDataTest.qnaResultId + this.itemName = this@ProductInsightsAgentAnswerFeedbackTrackingDataTest.itemName + this.variationId = this@ProductInsightsAgentAnswerFeedbackTrackingDataTest.variationId + this.threadId = this@ProductInsightsAgentAnswerFeedbackTrackingDataTest.threadId + this.sectionName = this@ProductInsightsAgentAnswerFeedbackTrackingDataTest.sectionName + this.analyticsTags = this@ProductInsightsAgentAnswerFeedbackTrackingDataTest.analyticsTags + } + assertEquals(itemId, request.itemId) + assertEquals(feedbackLabel, request.feedbackLabel) + assertEquals(qnaResultId, request.qnaResultId) + assertEquals(itemName, request.itemName) + assertEquals(variationId, request.variationId) + assertEquals(threadId, request.threadId) + assertEquals(sectionName, request.sectionName) + assertEquals(analyticsTags, request.analyticsTags) + } + + @Test + fun answerFeedbackTrackingDataUsingBuilder() { + val request = ProductInsightsAgentAnswerFeedbackTrackingData.Builder(itemId, feedbackLabel) + .setQnaResultId(qnaResultId) + .setItemName(itemName) + .setVariationId(variationId) + .setThreadId(threadId) + .setSectionName(sectionName) + .setAnalyticsTags(analyticsTags) + .build() + assertEquals(itemId, request.itemId) + assertEquals(feedbackLabel, request.feedbackLabel) + assertEquals(qnaResultId, request.qnaResultId) + assertEquals(itemName, request.itemName) + assertEquals(variationId, request.variationId) + assertEquals(threadId, request.threadId) + assertEquals(sectionName, request.sectionName) + assertEquals(analyticsTags, request.analyticsTags) + } + + @Test + fun answerFeedbackTrackingDataUsingConstructor() { + val request = ProductInsightsAgentAnswerFeedbackTrackingData( + itemId = itemId, + feedbackLabel = feedbackLabel, + qnaResultId = qnaResultId, + itemName = itemName, + variationId = variationId, + threadId = threadId, + sectionName = sectionName, + analyticsTags = analyticsTags, + ) + assertEquals(itemId, request.itemId) + assertEquals(feedbackLabel, request.feedbackLabel) + assertEquals(qnaResultId, request.qnaResultId) + assertEquals(itemName, request.itemName) + assertEquals(variationId, request.variationId) + assertEquals(threadId, request.threadId) + assertEquals(sectionName, request.sectionName) + assertEquals(analyticsTags, request.analyticsTags) + } +} diff --git a/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentAnswerViewTrackingDataTest.kt b/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentAnswerViewTrackingDataTest.kt new file mode 100644 index 00000000..1bfa412d --- /dev/null +++ b/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentAnswerViewTrackingDataTest.kt @@ -0,0 +1,128 @@ +package io.constructor.data.builder + +import io.constructor.data.model.common.TrackingItem +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class ProductInsightsAgentAnswerViewTrackingDataTest { + private val itemId = "item-1" + private val question = "Is it waterproof?" + private val answerText = "Yes, it is fully waterproof." + private val questionTopic = "Durability" + private val qnaResultId = "qna-1" + private val items = listOf(TrackingItem("rec-1", null, null, null)) + private val followUpQuestions = listOf("Is it breathable?") + private val itemName = "Waterproof Jacket" + private val variationId = "var-1" + private val threadId = "thread-1" + private val sectionName = "Search Suggestions" + private val analyticsTags = mapOf("test" to "test1") + + @Test + fun answerViewTrackingDataWithRequiredParams() { + val request = ProductInsightsAgentAnswerViewTrackingData.build(itemId, question, answerText) + assertEquals(itemId, request.itemId) + assertEquals(question, request.question) + assertEquals(answerText, request.answerText) + assertNull(request.questionTopic) + assertNull(request.qnaResultId) + assertNull(request.items) + assertNull(request.followUpQuestions) + assertNull(request.itemName) + assertNull(request.variationId) + assertNull(request.threadId) + assertNull(request.sectionName) + assertNull(request.analyticsTags) + } + + @Test + fun answerViewTrackingDataUsingDsl() { + val request = ProductInsightsAgentAnswerViewTrackingData.build(itemId, question, answerText) { + this.questionTopic = this@ProductInsightsAgentAnswerViewTrackingDataTest.questionTopic + this.qnaResultId = this@ProductInsightsAgentAnswerViewTrackingDataTest.qnaResultId + this.items = this@ProductInsightsAgentAnswerViewTrackingDataTest.items + this.followUpQuestions = this@ProductInsightsAgentAnswerViewTrackingDataTest.followUpQuestions + this.itemName = this@ProductInsightsAgentAnswerViewTrackingDataTest.itemName + this.variationId = this@ProductInsightsAgentAnswerViewTrackingDataTest.variationId + this.threadId = this@ProductInsightsAgentAnswerViewTrackingDataTest.threadId + this.sectionName = this@ProductInsightsAgentAnswerViewTrackingDataTest.sectionName + this.analyticsTags = this@ProductInsightsAgentAnswerViewTrackingDataTest.analyticsTags + } + assertEquals(itemId, request.itemId) + assertEquals(question, request.question) + assertEquals(answerText, request.answerText) + assertEquals(questionTopic, request.questionTopic) + assertEquals(qnaResultId, request.qnaResultId) + assertEquals(items, request.items) + assertEquals(followUpQuestions, request.followUpQuestions) + assertEquals(itemName, request.itemName) + assertEquals(variationId, request.variationId) + assertEquals(threadId, request.threadId) + assertEquals(sectionName, request.sectionName) + assertEquals(analyticsTags, request.analyticsTags) + } + + @Test + fun answerViewTrackingDataUsingBuilder() { + val request = ProductInsightsAgentAnswerViewTrackingData.Builder(itemId, question, answerText) + .setQuestionTopic(questionTopic) + .setQnaResultId(qnaResultId) + .setItems(items) + .setFollowUpQuestions(followUpQuestions) + .setItemName(itemName) + .setVariationId(variationId) + .setThreadId(threadId) + .setSectionName(sectionName) + .setAnalyticsTags(analyticsTags) + .build() + assertEquals(itemId, request.itemId) + assertEquals(question, request.question) + assertEquals(answerText, request.answerText) + assertEquals(questionTopic, request.questionTopic) + assertEquals(qnaResultId, request.qnaResultId) + assertEquals(items, request.items) + assertEquals(followUpQuestions, request.followUpQuestions) + assertEquals(itemName, request.itemName) + assertEquals(variationId, request.variationId) + assertEquals(threadId, request.threadId) + assertEquals(sectionName, request.sectionName) + assertEquals(analyticsTags, request.analyticsTags) + } + + @Test + fun answerViewTrackingDataUsingConstructor() { + val request = ProductInsightsAgentAnswerViewTrackingData( + itemId = itemId, + question = question, + answerText = answerText, + questionTopic = questionTopic, + qnaResultId = qnaResultId, + items = items, + followUpQuestions = followUpQuestions, + itemName = itemName, + variationId = variationId, + threadId = threadId, + sectionName = sectionName, + analyticsTags = analyticsTags, + ) + assertEquals(itemId, request.itemId) + assertEquals(question, request.question) + assertEquals(answerText, request.answerText) + assertEquals(questionTopic, request.questionTopic) + assertEquals(qnaResultId, request.qnaResultId) + assertEquals(items, request.items) + assertEquals(followUpQuestions, request.followUpQuestions) + assertEquals(itemName, request.itemName) + assertEquals(variationId, request.variationId) + assertEquals(threadId, request.threadId) + assertEquals(sectionName, request.sectionName) + assertEquals(analyticsTags, request.analyticsTags) + } + + @Test + fun answerViewTrackingDataKeepsEmptyAnswerText() { + val request = ProductInsightsAgentAnswerViewTrackingData.build(itemId, question, "") + assertEquals("", request.answerText) + } +} diff --git a/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentFocusTrackingDataTest.kt b/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentFocusTrackingDataTest.kt new file mode 100644 index 00000000..61a780a5 --- /dev/null +++ b/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentFocusTrackingDataTest.kt @@ -0,0 +1,77 @@ +package io.constructor.data.builder + +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class ProductInsightsAgentFocusTrackingDataTest { + private val itemId = "item-1" + private val itemName = "Waterproof Jacket" + private val variationId = "var-1" + private val threadId = "thread-1" + private val sectionName = "Search Suggestions" + private val analyticsTags = mapOf("test" to "test1") + + @Test + fun focusTrackingDataWithRequiredParams() { + val request = ProductInsightsAgentFocusTrackingData.build(itemId) + assertEquals(itemId, request.itemId) + assertNull(request.itemName) + assertNull(request.variationId) + assertNull(request.threadId) + assertNull(request.sectionName) + assertNull(request.analyticsTags) + } + + @Test + fun focusTrackingDataUsingDsl() { + val request = ProductInsightsAgentFocusTrackingData.build(itemId) { + this.itemName = this@ProductInsightsAgentFocusTrackingDataTest.itemName + this.variationId = this@ProductInsightsAgentFocusTrackingDataTest.variationId + this.threadId = this@ProductInsightsAgentFocusTrackingDataTest.threadId + this.sectionName = this@ProductInsightsAgentFocusTrackingDataTest.sectionName + this.analyticsTags = this@ProductInsightsAgentFocusTrackingDataTest.analyticsTags + } + assertEquals(itemId, request.itemId) + assertEquals(itemName, request.itemName) + assertEquals(variationId, request.variationId) + assertEquals(threadId, request.threadId) + assertEquals(sectionName, request.sectionName) + assertEquals(analyticsTags, request.analyticsTags) + } + + @Test + fun focusTrackingDataUsingBuilder() { + val request = ProductInsightsAgentFocusTrackingData.Builder(itemId) + .setItemName(itemName) + .setVariationId(variationId) + .setThreadId(threadId) + .setSectionName(sectionName) + .setAnalyticsTags(analyticsTags) + .build() + assertEquals(itemId, request.itemId) + assertEquals(itemName, request.itemName) + assertEquals(variationId, request.variationId) + assertEquals(threadId, request.threadId) + assertEquals(sectionName, request.sectionName) + assertEquals(analyticsTags, request.analyticsTags) + } + + @Test + fun focusTrackingDataUsingConstructor() { + val request = ProductInsightsAgentFocusTrackingData( + itemId = itemId, + itemName = itemName, + variationId = variationId, + threadId = threadId, + sectionName = sectionName, + analyticsTags = analyticsTags, + ) + assertEquals(itemId, request.itemId) + assertEquals(itemName, request.itemName) + assertEquals(variationId, request.variationId) + assertEquals(threadId, request.threadId) + assertEquals(sectionName, request.sectionName) + assertEquals(analyticsTags, request.analyticsTags) + } +} diff --git a/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentOutOfViewTrackingDataTest.kt b/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentOutOfViewTrackingDataTest.kt new file mode 100644 index 00000000..610b4699 --- /dev/null +++ b/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentOutOfViewTrackingDataTest.kt @@ -0,0 +1,77 @@ +package io.constructor.data.builder + +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class ProductInsightsAgentOutOfViewTrackingDataTest { + private val itemId = "item-1" + private val itemName = "Waterproof Jacket" + private val variationId = "var-1" + private val threadId = "thread-1" + private val sectionName = "Search Suggestions" + private val analyticsTags = mapOf("test" to "test1") + + @Test + fun outOfViewTrackingDataWithRequiredParams() { + val request = ProductInsightsAgentOutOfViewTrackingData.build(itemId) + assertEquals(itemId, request.itemId) + assertNull(request.itemName) + assertNull(request.variationId) + assertNull(request.threadId) + assertNull(request.sectionName) + assertNull(request.analyticsTags) + } + + @Test + fun outOfViewTrackingDataUsingDsl() { + val request = ProductInsightsAgentOutOfViewTrackingData.build(itemId) { + this.itemName = this@ProductInsightsAgentOutOfViewTrackingDataTest.itemName + this.variationId = this@ProductInsightsAgentOutOfViewTrackingDataTest.variationId + this.threadId = this@ProductInsightsAgentOutOfViewTrackingDataTest.threadId + this.sectionName = this@ProductInsightsAgentOutOfViewTrackingDataTest.sectionName + this.analyticsTags = this@ProductInsightsAgentOutOfViewTrackingDataTest.analyticsTags + } + assertEquals(itemId, request.itemId) + assertEquals(itemName, request.itemName) + assertEquals(variationId, request.variationId) + assertEquals(threadId, request.threadId) + assertEquals(sectionName, request.sectionName) + assertEquals(analyticsTags, request.analyticsTags) + } + + @Test + fun outOfViewTrackingDataUsingBuilder() { + val request = ProductInsightsAgentOutOfViewTrackingData.Builder(itemId) + .setItemName(itemName) + .setVariationId(variationId) + .setThreadId(threadId) + .setSectionName(sectionName) + .setAnalyticsTags(analyticsTags) + .build() + assertEquals(itemId, request.itemId) + assertEquals(itemName, request.itemName) + assertEquals(variationId, request.variationId) + assertEquals(threadId, request.threadId) + assertEquals(sectionName, request.sectionName) + assertEquals(analyticsTags, request.analyticsTags) + } + + @Test + fun outOfViewTrackingDataUsingConstructor() { + val request = ProductInsightsAgentOutOfViewTrackingData( + itemId = itemId, + itemName = itemName, + variationId = variationId, + threadId = threadId, + sectionName = sectionName, + analyticsTags = analyticsTags, + ) + assertEquals(itemId, request.itemId) + assertEquals(itemName, request.itemName) + assertEquals(variationId, request.variationId) + assertEquals(threadId, request.threadId) + assertEquals(sectionName, request.sectionName) + assertEquals(analyticsTags, request.analyticsTags) + } +} diff --git a/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentQuestionClickTrackingDataTest.kt b/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentQuestionClickTrackingDataTest.kt new file mode 100644 index 00000000..4abc492e --- /dev/null +++ b/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentQuestionClickTrackingDataTest.kt @@ -0,0 +1,91 @@ +package io.constructor.data.builder + +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class ProductInsightsAgentQuestionClickTrackingDataTest { + private val itemId = "item-1" + private val question = "Is it waterproof?" + private val questionTopic = "Durability" + private val itemName = "Waterproof Jacket" + private val variationId = "var-1" + private val threadId = "thread-1" + private val sectionName = "Search Suggestions" + private val analyticsTags = mapOf("test" to "test1") + + @Test + fun questionClickTrackingDataWithRequiredParams() { + val request = ProductInsightsAgentQuestionClickTrackingData.build(itemId, question) + assertEquals(itemId, request.itemId) + assertEquals(question, request.question) + assertNull(request.questionTopic) + assertNull(request.itemName) + assertNull(request.variationId) + assertNull(request.threadId) + assertNull(request.sectionName) + assertNull(request.analyticsTags) + } + + @Test + fun questionClickTrackingDataUsingDsl() { + val request = ProductInsightsAgentQuestionClickTrackingData.build(itemId, question) { + this.questionTopic = this@ProductInsightsAgentQuestionClickTrackingDataTest.questionTopic + this.itemName = this@ProductInsightsAgentQuestionClickTrackingDataTest.itemName + this.variationId = this@ProductInsightsAgentQuestionClickTrackingDataTest.variationId + this.threadId = this@ProductInsightsAgentQuestionClickTrackingDataTest.threadId + this.sectionName = this@ProductInsightsAgentQuestionClickTrackingDataTest.sectionName + this.analyticsTags = this@ProductInsightsAgentQuestionClickTrackingDataTest.analyticsTags + } + assertEquals(itemId, request.itemId) + assertEquals(question, request.question) + assertEquals(questionTopic, request.questionTopic) + assertEquals(itemName, request.itemName) + assertEquals(variationId, request.variationId) + assertEquals(threadId, request.threadId) + assertEquals(sectionName, request.sectionName) + assertEquals(analyticsTags, request.analyticsTags) + } + + @Test + fun questionClickTrackingDataUsingBuilder() { + val request = ProductInsightsAgentQuestionClickTrackingData.Builder(itemId, question) + .setQuestionTopic(questionTopic) + .setItemName(itemName) + .setVariationId(variationId) + .setThreadId(threadId) + .setSectionName(sectionName) + .setAnalyticsTags(analyticsTags) + .build() + assertEquals(itemId, request.itemId) + assertEquals(question, request.question) + assertEquals(questionTopic, request.questionTopic) + assertEquals(itemName, request.itemName) + assertEquals(variationId, request.variationId) + assertEquals(threadId, request.threadId) + assertEquals(sectionName, request.sectionName) + assertEquals(analyticsTags, request.analyticsTags) + } + + @Test + fun questionClickTrackingDataUsingConstructor() { + val request = ProductInsightsAgentQuestionClickTrackingData( + itemId = itemId, + question = question, + questionTopic = questionTopic, + itemName = itemName, + variationId = variationId, + threadId = threadId, + sectionName = sectionName, + analyticsTags = analyticsTags, + ) + assertEquals(itemId, request.itemId) + assertEquals(question, request.question) + assertEquals(questionTopic, request.questionTopic) + assertEquals(itemName, request.itemName) + assertEquals(variationId, request.variationId) + assertEquals(threadId, request.threadId) + assertEquals(sectionName, request.sectionName) + assertEquals(analyticsTags, request.analyticsTags) + } +} diff --git a/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentQuestionSubmitTrackingDataTest.kt b/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentQuestionSubmitTrackingDataTest.kt new file mode 100644 index 00000000..398c4fc2 --- /dev/null +++ b/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentQuestionSubmitTrackingDataTest.kt @@ -0,0 +1,91 @@ +package io.constructor.data.builder + +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class ProductInsightsAgentQuestionSubmitTrackingDataTest { + private val itemId = "item-1" + private val question = "Is it waterproof?" + private val questionTopic = "Durability" + private val itemName = "Waterproof Jacket" + private val variationId = "var-1" + private val threadId = "thread-1" + private val sectionName = "Search Suggestions" + private val analyticsTags = mapOf("test" to "test1") + + @Test + fun questionSubmitTrackingDataWithRequiredParams() { + val request = ProductInsightsAgentQuestionSubmitTrackingData.build(itemId, question) + assertEquals(itemId, request.itemId) + assertEquals(question, request.question) + assertNull(request.questionTopic) + assertNull(request.itemName) + assertNull(request.variationId) + assertNull(request.threadId) + assertNull(request.sectionName) + assertNull(request.analyticsTags) + } + + @Test + fun questionSubmitTrackingDataUsingDsl() { + val request = ProductInsightsAgentQuestionSubmitTrackingData.build(itemId, question) { + this.questionTopic = this@ProductInsightsAgentQuestionSubmitTrackingDataTest.questionTopic + this.itemName = this@ProductInsightsAgentQuestionSubmitTrackingDataTest.itemName + this.variationId = this@ProductInsightsAgentQuestionSubmitTrackingDataTest.variationId + this.threadId = this@ProductInsightsAgentQuestionSubmitTrackingDataTest.threadId + this.sectionName = this@ProductInsightsAgentQuestionSubmitTrackingDataTest.sectionName + this.analyticsTags = this@ProductInsightsAgentQuestionSubmitTrackingDataTest.analyticsTags + } + assertEquals(itemId, request.itemId) + assertEquals(question, request.question) + assertEquals(questionTopic, request.questionTopic) + assertEquals(itemName, request.itemName) + assertEquals(variationId, request.variationId) + assertEquals(threadId, request.threadId) + assertEquals(sectionName, request.sectionName) + assertEquals(analyticsTags, request.analyticsTags) + } + + @Test + fun questionSubmitTrackingDataUsingBuilder() { + val request = ProductInsightsAgentQuestionSubmitTrackingData.Builder(itemId, question) + .setQuestionTopic(questionTopic) + .setItemName(itemName) + .setVariationId(variationId) + .setThreadId(threadId) + .setSectionName(sectionName) + .setAnalyticsTags(analyticsTags) + .build() + assertEquals(itemId, request.itemId) + assertEquals(question, request.question) + assertEquals(questionTopic, request.questionTopic) + assertEquals(itemName, request.itemName) + assertEquals(variationId, request.variationId) + assertEquals(threadId, request.threadId) + assertEquals(sectionName, request.sectionName) + assertEquals(analyticsTags, request.analyticsTags) + } + + @Test + fun questionSubmitTrackingDataUsingConstructor() { + val request = ProductInsightsAgentQuestionSubmitTrackingData( + itemId = itemId, + question = question, + questionTopic = questionTopic, + itemName = itemName, + variationId = variationId, + threadId = threadId, + sectionName = sectionName, + analyticsTags = analyticsTags, + ) + assertEquals(itemId, request.itemId) + assertEquals(question, request.question) + assertEquals(questionTopic, request.questionTopic) + assertEquals(itemName, request.itemName) + assertEquals(variationId, request.variationId) + assertEquals(threadId, request.threadId) + assertEquals(sectionName, request.sectionName) + assertEquals(analyticsTags, request.analyticsTags) + } +} diff --git a/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentResultClickTrackingDataTest.kt b/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentResultClickTrackingDataTest.kt new file mode 100644 index 00000000..4f9f4434 --- /dev/null +++ b/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentResultClickTrackingDataTest.kt @@ -0,0 +1,129 @@ +package io.constructor.data.builder + +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class ProductInsightsAgentResultClickTrackingDataTest { + private val itemId = "item-1" + private val question = "Is it waterproof?" + private val seedItemId = "seed-1" + private val position = 0 + private val seedItemName = "Seed Jacket" + private val seedVariationId = "seed-var-1" + private val qnaResultId = "qna-1" + private val itemName = "Waterproof Jacket" + private val variationId = "var-1" + private val threadId = "thread-1" + private val sectionName = "Search Suggestions" + private val analyticsTags = mapOf("test" to "test1") + + @Test + fun resultClickTrackingDataWithRequiredParams() { + val request = ProductInsightsAgentResultClickTrackingData.build(itemId, question, seedItemId) + assertEquals(itemId, request.itemId) + assertEquals(question, request.question) + assertEquals(seedItemId, request.seedItemId) + assertNull(request.position) + assertNull(request.seedItemName) + assertNull(request.seedVariationId) + assertNull(request.qnaResultId) + assertNull(request.itemName) + assertNull(request.variationId) + assertNull(request.threadId) + assertNull(request.sectionName) + assertNull(request.analyticsTags) + } + + @Test + fun resultClickTrackingDataUsingDsl() { + val request = ProductInsightsAgentResultClickTrackingData.build(itemId, question, seedItemId) { + this.position = this@ProductInsightsAgentResultClickTrackingDataTest.position + this.seedItemName = this@ProductInsightsAgentResultClickTrackingDataTest.seedItemName + this.seedVariationId = this@ProductInsightsAgentResultClickTrackingDataTest.seedVariationId + this.qnaResultId = this@ProductInsightsAgentResultClickTrackingDataTest.qnaResultId + this.itemName = this@ProductInsightsAgentResultClickTrackingDataTest.itemName + this.variationId = this@ProductInsightsAgentResultClickTrackingDataTest.variationId + this.threadId = this@ProductInsightsAgentResultClickTrackingDataTest.threadId + this.sectionName = this@ProductInsightsAgentResultClickTrackingDataTest.sectionName + this.analyticsTags = this@ProductInsightsAgentResultClickTrackingDataTest.analyticsTags + } + assertEquals(itemId, request.itemId) + assertEquals(question, request.question) + assertEquals(seedItemId, request.seedItemId) + assertEquals(position, request.position) + assertEquals(seedItemName, request.seedItemName) + assertEquals(seedVariationId, request.seedVariationId) + assertEquals(qnaResultId, request.qnaResultId) + assertEquals(itemName, request.itemName) + assertEquals(variationId, request.variationId) + assertEquals(threadId, request.threadId) + assertEquals(sectionName, request.sectionName) + assertEquals(analyticsTags, request.analyticsTags) + } + + @Test + fun resultClickTrackingDataUsingBuilder() { + val request = ProductInsightsAgentResultClickTrackingData.Builder(itemId, question, seedItemId) + .setPosition(position) + .setSeedItemName(seedItemName) + .setSeedVariationId(seedVariationId) + .setQnaResultId(qnaResultId) + .setItemName(itemName) + .setVariationId(variationId) + .setThreadId(threadId) + .setSectionName(sectionName) + .setAnalyticsTags(analyticsTags) + .build() + assertEquals(itemId, request.itemId) + assertEquals(question, request.question) + assertEquals(seedItemId, request.seedItemId) + assertEquals(position, request.position) + assertEquals(seedItemName, request.seedItemName) + assertEquals(seedVariationId, request.seedVariationId) + assertEquals(qnaResultId, request.qnaResultId) + assertEquals(itemName, request.itemName) + assertEquals(variationId, request.variationId) + assertEquals(threadId, request.threadId) + assertEquals(sectionName, request.sectionName) + assertEquals(analyticsTags, request.analyticsTags) + } + + @Test + fun resultClickTrackingDataUsingConstructor() { + val request = ProductInsightsAgentResultClickTrackingData( + itemId = itemId, + question = question, + seedItemId = seedItemId, + position = position, + seedItemName = seedItemName, + seedVariationId = seedVariationId, + qnaResultId = qnaResultId, + itemName = itemName, + variationId = variationId, + threadId = threadId, + sectionName = sectionName, + analyticsTags = analyticsTags, + ) + assertEquals(itemId, request.itemId) + assertEquals(question, request.question) + assertEquals(seedItemId, request.seedItemId) + assertEquals(position, request.position) + assertEquals(seedItemName, request.seedItemName) + assertEquals(seedVariationId, request.seedVariationId) + assertEquals(qnaResultId, request.qnaResultId) + assertEquals(itemName, request.itemName) + assertEquals(variationId, request.variationId) + assertEquals(threadId, request.threadId) + assertEquals(sectionName, request.sectionName) + assertEquals(analyticsTags, request.analyticsTags) + } + + @Test + fun resultClickTrackingDataKeepsPositionZero() { + val request = ProductInsightsAgentResultClickTrackingData.build(itemId, question, seedItemId) { + position = 0 + } + assertEquals(0, request.position) + } +} diff --git a/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentViewTrackingDataTest.kt b/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentViewTrackingDataTest.kt new file mode 100644 index 00000000..e64a17d4 --- /dev/null +++ b/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentViewTrackingDataTest.kt @@ -0,0 +1,84 @@ +package io.constructor.data.builder + +import io.constructor.data.model.tracking.ProductInsightsAgentQuestion +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class ProductInsightsAgentViewTrackingDataTest { + private val itemId = "item-1" + private val questions = listOf(ProductInsightsAgentQuestion("Is it waterproof?", "Durability")) + private val itemName = "Waterproof Jacket" + private val variationId = "var-1" + private val threadId = "thread-1" + private val sectionName = "Search Suggestions" + private val analyticsTags = mapOf("test" to "test1") + + @Test + fun viewTrackingDataWithRequiredParams() { + val request = ProductInsightsAgentViewTrackingData.build(itemId, questions) + assertEquals(itemId, request.itemId) + assertEquals(questions, request.questions) + assertNull(request.itemName) + assertNull(request.variationId) + assertNull(request.threadId) + assertNull(request.sectionName) + assertNull(request.analyticsTags) + } + + @Test + fun viewTrackingDataUsingDsl() { + val request = ProductInsightsAgentViewTrackingData.build(itemId, questions) { + this.itemName = this@ProductInsightsAgentViewTrackingDataTest.itemName + this.variationId = this@ProductInsightsAgentViewTrackingDataTest.variationId + this.threadId = this@ProductInsightsAgentViewTrackingDataTest.threadId + this.sectionName = this@ProductInsightsAgentViewTrackingDataTest.sectionName + this.analyticsTags = this@ProductInsightsAgentViewTrackingDataTest.analyticsTags + } + assertEquals(itemId, request.itemId) + assertEquals(questions, request.questions) + assertEquals(itemName, request.itemName) + assertEquals(variationId, request.variationId) + assertEquals(threadId, request.threadId) + assertEquals(sectionName, request.sectionName) + assertEquals(analyticsTags, request.analyticsTags) + } + + @Test + fun viewTrackingDataUsingBuilder() { + val request = ProductInsightsAgentViewTrackingData.Builder(itemId, questions) + .setItemName(itemName) + .setVariationId(variationId) + .setThreadId(threadId) + .setSectionName(sectionName) + .setAnalyticsTags(analyticsTags) + .build() + assertEquals(itemId, request.itemId) + assertEquals(questions, request.questions) + assertEquals(itemName, request.itemName) + assertEquals(variationId, request.variationId) + assertEquals(threadId, request.threadId) + assertEquals(sectionName, request.sectionName) + assertEquals(analyticsTags, request.analyticsTags) + } + + @Test + fun viewTrackingDataUsingConstructor() { + val request = ProductInsightsAgentViewTrackingData( + itemId = itemId, + questions = questions, + itemName = itemName, + variationId = variationId, + threadId = threadId, + sectionName = sectionName, + analyticsTags = analyticsTags, + ) + assertEquals(itemId, request.itemId) + assertEquals(questions, request.questions) + assertEquals(itemName, request.itemName) + assertEquals(variationId, request.variationId) + assertEquals(threadId, request.threadId) + assertEquals(sectionName, request.sectionName) + assertEquals(analyticsTags, request.analyticsTags) + } +} diff --git a/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentViewsTrackingDataTest.kt b/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentViewsTrackingDataTest.kt new file mode 100644 index 00000000..b7efc459 --- /dev/null +++ b/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentViewsTrackingDataTest.kt @@ -0,0 +1,91 @@ +package io.constructor.data.builder + +import io.constructor.data.model.tracking.ProductInsightsAgentQuestion +import io.constructor.data.model.tracking.ProductInsightsAgentViewTimespan +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class ProductInsightsAgentViewsTrackingDataTest { + private val itemId = "item-1" + private val questions = listOf(ProductInsightsAgentQuestion("Is it waterproof?", "Durability")) + private val viewTimespans = listOf(ProductInsightsAgentViewTimespan(1792550405123, 1792550410456)) + private val itemName = "Waterproof Jacket" + private val variationId = "var-1" + private val threadId = "thread-1" + private val sectionName = "Search Suggestions" + private val analyticsTags = mapOf("test" to "test1") + + @Test + fun viewsTrackingDataWithRequiredParams() { + val request = ProductInsightsAgentViewsTrackingData.build(itemId, questions, viewTimespans) + assertEquals(itemId, request.itemId) + assertEquals(questions, request.questions) + assertEquals(viewTimespans, request.viewTimespans) + assertNull(request.itemName) + assertNull(request.variationId) + assertNull(request.threadId) + assertNull(request.sectionName) + assertNull(request.analyticsTags) + } + + @Test + fun viewsTrackingDataUsingDsl() { + val request = ProductInsightsAgentViewsTrackingData.build(itemId, questions, viewTimespans) { + this.itemName = this@ProductInsightsAgentViewsTrackingDataTest.itemName + this.variationId = this@ProductInsightsAgentViewsTrackingDataTest.variationId + this.threadId = this@ProductInsightsAgentViewsTrackingDataTest.threadId + this.sectionName = this@ProductInsightsAgentViewsTrackingDataTest.sectionName + this.analyticsTags = this@ProductInsightsAgentViewsTrackingDataTest.analyticsTags + } + assertEquals(itemId, request.itemId) + assertEquals(questions, request.questions) + assertEquals(viewTimespans, request.viewTimespans) + assertEquals(itemName, request.itemName) + assertEquals(variationId, request.variationId) + assertEquals(threadId, request.threadId) + assertEquals(sectionName, request.sectionName) + assertEquals(analyticsTags, request.analyticsTags) + } + + @Test + fun viewsTrackingDataUsingBuilder() { + val request = ProductInsightsAgentViewsTrackingData.Builder(itemId, questions, viewTimespans) + .setItemName(itemName) + .setVariationId(variationId) + .setThreadId(threadId) + .setSectionName(sectionName) + .setAnalyticsTags(analyticsTags) + .build() + assertEquals(itemId, request.itemId) + assertEquals(questions, request.questions) + assertEquals(viewTimespans, request.viewTimespans) + assertEquals(itemName, request.itemName) + assertEquals(variationId, request.variationId) + assertEquals(threadId, request.threadId) + assertEquals(sectionName, request.sectionName) + assertEquals(analyticsTags, request.analyticsTags) + } + + @Test + fun viewsTrackingDataUsingConstructor() { + val request = ProductInsightsAgentViewsTrackingData( + itemId = itemId, + questions = questions, + viewTimespans = viewTimespans, + itemName = itemName, + variationId = variationId, + threadId = threadId, + sectionName = sectionName, + analyticsTags = analyticsTags, + ) + assertEquals(itemId, request.itemId) + assertEquals(questions, request.questions) + assertEquals(viewTimespans, request.viewTimespans) + assertEquals(itemName, request.itemName) + assertEquals(variationId, request.variationId) + assertEquals(threadId, request.threadId) + assertEquals(sectionName, request.sectionName) + assertEquals(analyticsTags, request.analyticsTags) + } +} From 81d51461cacc99252d7e5f741948b3ec79829a1a Mon Sep 17 00:00:00 2001 From: mudaafi Date: Thu, 22 Oct 2026 22:10:54 +0800 Subject: [PATCH 3/7] [CDX-709] Document Product Insights Agent tracking events Add a Product Insights Agent Events subsection to README section 12 with a build { } example for each of the 9 events. --- README.md | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/README.md b/README.md index b248383f..0641f945 100755 --- a/README.md +++ b/README.md @@ -514,6 +514,7 @@ Three types of these events exist: 4. **Recommendation Events** measure user interaction with recommendation results 5. **Quiz Events** measure user interaction with quiz results 6. **Conversion Events** measure user events like `add to cart` or `purchase` +7. **Product Insights Agent Events** measure user interaction with the Product Insights Agent ### Autocomplete Events @@ -679,3 +680,79 @@ ConstructorIo.trackResultsImpressionView( searchTerm = "sneakers" ) ``` + +### Product Insights Agent Events + +```kotlin +// Track when the user focuses on the Product Insights Agent (itemId) +ConstructorIo.trackProductInsightsAgentFocus( + ProductInsightsAgentFocusTrackingData.build("1234567-AB") { + itemName = "Waterproof Jacket" + } +) + +// Track when the Product Insights Agent scrolls out of view (itemId) +ConstructorIo.trackProductInsightsAgentOutOfView( + ProductInsightsAgentOutOfViewTrackingData.build("1234567-AB") +) + +// Track when the Product Insights Agent comes into view (itemId, questions) +ConstructorIo.trackProductInsightsAgentView( + ProductInsightsAgentViewTrackingData.build( + "1234567-AB", + listOf(ProductInsightsAgentQuestion("Is it waterproof?", "Durability")) + ) +) + +// Track the spans of time the Product Insights Agent was in view (itemId, questions, viewTimespans) +// Timespan start and end are epoch milliseconds, i.e. System.currentTimeMillis(), and are sent as ISO-8601 UTC timestamps +ConstructorIo.trackProductInsightsAgentViews( + ProductInsightsAgentViewsTrackingData.build( + "1234567-AB", + listOf(ProductInsightsAgentQuestion("Is it waterproof?")), + listOf(ProductInsightsAgentViewTimespan(viewStartedAtMillis, System.currentTimeMillis())) + ) +) + +// Track when the user clicks a suggested question (itemId, question) +ConstructorIo.trackProductInsightsAgentQuestionClick( + ProductInsightsAgentQuestionClickTrackingData.build("1234567-AB", "Is it waterproof?") { + questionTopic = "Durability" + } +) + +// Track when the user submits a question (itemId, question) +ConstructorIo.trackProductInsightsAgentQuestionSubmit( + ProductInsightsAgentQuestionSubmitTrackingData.build("1234567-AB", "Is it waterproof?") { + threadId = "thread-123" + } +) + +// Track when an answer is shown to the user (itemId, question, answerText) +// Only the first 100 items and follow-up questions are sent +ConstructorIo.trackProductInsightsAgentAnswerView( + ProductInsightsAgentAnswerViewTrackingData.build("1234567-AB", "Is it waterproof?", "Yes, it is fully waterproof.") { + qnaResultId = "qna-result-123" + items = listOf(TrackingItem("7654321-BA", null, null, null)) + followUpQuestions = listOf("Is it breathable?") + } +) + +// Track when the user gives feedback on an answer (itemId, feedbackLabel) +ConstructorIo.trackProductInsightsAgentAnswerFeedback( + ProductInsightsAgentAnswerFeedbackTrackingData.build("1234567-AB", ProductInsightsAgentFeedbackLabel.THUMBS_UP) { + qnaResultId = "qna-result-123" + } +) + +// Track when the user clicks an item recommended in an answer (itemId of the clicked item, question, seedItemId of the item the agent is displayed for) +ConstructorIo.trackProductInsightsAgentResultClick( + ProductInsightsAgentResultClickTrackingData.build("7654321-BA", "Is it waterproof?", "1234567-AB") { + position = 0 + qnaResultId = "qna-result-123" + } +) + +// All Product Insights Agent requests also accept sectionName, threadId, itemName, variationId and analyticsTags +// Request level analytics tags are merged with the default analytics tags passed on initialization +``` From 43aac5d22fe3f0b23f2dd5da94b4bd4b13748ba6 Mon Sep 17 00:00:00 2001 From: mudaafi Date: Mon, 26 Oct 2026 21:13:50 +0800 Subject: [PATCH 4/7] [CDX-709] Extract shared Product Insights Agent base models Address review feedback on the PIA tracking events. - Drop c/i/s/key/ui/us/_dt/beacon/section from the request bodies. RequestInterceptor already adds the client params to the query, and section is sent as a query param, which is where the spec expects them. - Add ProductInsightsAgentRequestBody holding item_id, item_name, variation_id, thread_id and analytics_tags for the 9 bodies. - Add ProductInsightsAgentTrackingData with a self-typed Builder for the shared itemId/itemName/variationId/threadId/sectionName/analyticsTags, so chained setters keep the concrete builder type. - Assert the client params on the query and their absence from the body. --- .../java/io/constructor/core/ConstructorIo.kt | 99 ++----------------- ...InsightsAgentAnswerFeedbackTrackingData.kt | 36 ++----- ...ductInsightsAgentAnswerViewTrackingData.kt | 36 ++----- .../ProductInsightsAgentFocusTrackingData.kt | 38 ++----- ...oductInsightsAgentOutOfViewTrackingData.kt | 38 ++----- ...tInsightsAgentQuestionClickTrackingData.kt | 36 ++----- ...InsightsAgentQuestionSubmitTrackingData.kt | 36 ++----- ...uctInsightsAgentResultClickTrackingData.kt | 36 ++----- .../ProductInsightsAgentTrackingData.kt | 52 ++++++++++ .../ProductInsightsAgentViewTrackingData.kt | 38 ++----- .../ProductInsightsAgentViewsTrackingData.kt | 38 ++----- ...tInsightsAgentAnswerFeedbackRequestBody.kt | 28 ++---- ...oductInsightsAgentAnswerViewRequestBody.kt | 36 +++---- .../ProductInsightsAgentFocusRequestBody.kt | 25 ++--- ...roductInsightsAgentOutOfViewRequestBody.kt | 25 ++--- ...ctInsightsAgentQuestionClickRequestBody.kt | 28 ++---- ...tInsightsAgentQuestionSubmitRequestBody.kt | 28 ++---- .../ProductInsightsAgentRequestBody.kt | 23 +++++ ...ductInsightsAgentResultClickRequestBody.kt | 36 +++---- .../ProductInsightsAgentViewRequestBody.kt | 26 ++--- .../ProductInsightsAgentViewsRequestBody.kt | 28 ++---- ...uctorIoProductInsightsAgentTrackingTest.kt | 39 ++++---- ...nsightsAgentResultClickTrackingDataTest.kt | 15 +++ 23 files changed, 275 insertions(+), 545 deletions(-) create mode 100644 library/src/main/java/io/constructor/data/builder/ProductInsightsAgentTrackingData.kt create mode 100644 library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentRequestBody.kt diff --git a/library/src/main/java/io/constructor/core/ConstructorIo.kt b/library/src/main/java/io/constructor/core/ConstructorIo.kt index 9a86190c..9cc8d412 100755 --- a/library/src/main/java/io/constructor/core/ConstructorIo.kt +++ b/library/src/main/java/io/constructor/core/ConstructorIo.kt @@ -2480,16 +2480,7 @@ object ConstructorIo { itemName = request.itemName, variationId = request.variationId, threadId = request.threadId, - c = BuildConfig.CLIENT_VERSION, - i = preferenceHelper.id, - s = preferenceHelper.getSessionId(), - key = preferenceHelper.apiKey, - ui = configMemoryHolder.userId, - us = configMemoryHolder.segments, - analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags), - beacon = true, - section = section, - _dt = System.currentTimeMillis() + analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags) ) return dataManager.trackProductInsightsAgentFocus( @@ -2528,16 +2519,7 @@ object ConstructorIo { itemName = request.itemName, variationId = request.variationId, threadId = request.threadId, - c = BuildConfig.CLIENT_VERSION, - i = preferenceHelper.id, - s = preferenceHelper.getSessionId(), - key = preferenceHelper.apiKey, - ui = configMemoryHolder.userId, - us = configMemoryHolder.segments, - analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags), - beacon = true, - section = section, - _dt = System.currentTimeMillis() + analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags) ) return dataManager.trackProductInsightsAgentOutOfView( @@ -2580,16 +2562,7 @@ object ConstructorIo { itemName = request.itemName, variationId = request.variationId, threadId = request.threadId, - c = BuildConfig.CLIENT_VERSION, - i = preferenceHelper.id, - s = preferenceHelper.getSessionId(), - key = preferenceHelper.apiKey, - ui = configMemoryHolder.userId, - us = configMemoryHolder.segments, - analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags), - beacon = true, - section = section, - _dt = System.currentTimeMillis() + analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags) ) return dataManager.trackProductInsightsAgentView( @@ -2636,16 +2609,7 @@ object ConstructorIo { itemName = request.itemName, variationId = request.variationId, threadId = request.threadId, - c = BuildConfig.CLIENT_VERSION, - i = preferenceHelper.id, - s = preferenceHelper.getSessionId(), - key = preferenceHelper.apiKey, - ui = configMemoryHolder.userId, - us = configMemoryHolder.segments, - analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags), - beacon = true, - section = section, - _dt = System.currentTimeMillis() + analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags) ) return dataManager.trackProductInsightsAgentViews( @@ -2686,16 +2650,7 @@ object ConstructorIo { itemName = request.itemName, variationId = request.variationId, threadId = request.threadId, - c = BuildConfig.CLIENT_VERSION, - i = preferenceHelper.id, - s = preferenceHelper.getSessionId(), - key = preferenceHelper.apiKey, - ui = configMemoryHolder.userId, - us = configMemoryHolder.segments, - analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags), - beacon = true, - section = section, - _dt = System.currentTimeMillis() + analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags) ) return dataManager.trackProductInsightsAgentQuestionClick( @@ -2736,16 +2691,7 @@ object ConstructorIo { itemName = request.itemName, variationId = request.variationId, threadId = request.threadId, - c = BuildConfig.CLIENT_VERSION, - i = preferenceHelper.id, - s = preferenceHelper.getSessionId(), - key = preferenceHelper.apiKey, - ui = configMemoryHolder.userId, - us = configMemoryHolder.segments, - analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags), - beacon = true, - section = section, - _dt = System.currentTimeMillis() + analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags) ) return dataManager.trackProductInsightsAgentQuestionSubmit( @@ -2786,16 +2732,7 @@ object ConstructorIo { itemName = request.itemName, variationId = request.variationId, threadId = request.threadId, - c = BuildConfig.CLIENT_VERSION, - i = preferenceHelper.id, - s = preferenceHelper.getSessionId(), - key = preferenceHelper.apiKey, - ui = configMemoryHolder.userId, - us = configMemoryHolder.segments, - analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags), - beacon = true, - section = section, - _dt = System.currentTimeMillis() + analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags) ) return dataManager.trackProductInsightsAgentAnswerFeedback( @@ -2841,16 +2778,7 @@ object ConstructorIo { itemName = request.itemName, variationId = request.variationId, threadId = request.threadId, - c = BuildConfig.CLIENT_VERSION, - i = preferenceHelper.id, - s = preferenceHelper.getSessionId(), - key = preferenceHelper.apiKey, - ui = configMemoryHolder.userId, - us = configMemoryHolder.segments, - analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags), - beacon = true, - section = section, - _dt = System.currentTimeMillis() + analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags) ) return dataManager.trackProductInsightsAgentResultClick( @@ -2897,16 +2825,7 @@ object ConstructorIo { itemName = request.itemName, variationId = request.variationId, threadId = request.threadId, - c = BuildConfig.CLIENT_VERSION, - i = preferenceHelper.id, - s = preferenceHelper.getSessionId(), - key = preferenceHelper.apiKey, - ui = configMemoryHolder.userId, - us = configMemoryHolder.segments, - analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags), - beacon = true, - section = section, - _dt = System.currentTimeMillis() + analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags) ) return dataManager.trackProductInsightsAgentAnswerView( diff --git a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentAnswerFeedbackTrackingData.kt b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentAnswerFeedbackTrackingData.kt index 3ec9fb2a..be46ec5b 100644 --- a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentAnswerFeedbackTrackingData.kt +++ b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentAnswerFeedbackTrackingData.kt @@ -15,15 +15,15 @@ import io.constructor.data.model.tracking.ProductInsightsAgentFeedbackLabel * @property analyticsTags additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ class ProductInsightsAgentAnswerFeedbackTrackingData( - val itemId: String, + itemId: String, val feedbackLabel: ProductInsightsAgentFeedbackLabel, val qnaResultId: String? = null, - val itemName: String? = null, - val variationId: String? = null, - val threadId: String? = null, - val sectionName: String? = null, - val analyticsTags: Map? = null, -) { + itemName: String? = null, + variationId: String? = null, + threadId: String? = null, + sectionName: String? = null, + analyticsTags: Map? = null, +) : ProductInsightsAgentTrackingData(itemId, itemName, variationId, threadId, sectionName, analyticsTags) { private constructor(builder: Builder) : this( builder.itemId, builder.feedbackLabel, @@ -52,32 +52,12 @@ class ProductInsightsAgentAnswerFeedbackTrackingData( class Builder( val itemId: String, val feedbackLabel: ProductInsightsAgentFeedbackLabel - ) { + ) : ProductInsightsAgentTrackingData.Builder() { /** The result ID of the answer, as returned by the Product Insights Agent */ var qnaResultId: String? = null - /** The name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" */ - var itemName: String? = null - /** The variation ID of the item (product) the Product Insights Agent is displayed for */ - var variationId: String? = null - /** The ID of the Product Insights Agent conversation thread */ - var threadId: String? = null - /** The index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] */ - var sectionName: String? = null - /** Additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ - var analyticsTags: Map? = null /** Sets [qnaResultId] */ fun setQnaResultId(qnaResultId: String): Builder = apply { this.qnaResultId = qnaResultId } - /** Sets [itemName] */ - fun setItemName(itemName: String): Builder = apply { this.itemName = itemName } - /** Sets [variationId] */ - fun setVariationId(variationId: String): Builder = apply { this.variationId = variationId } - /** Sets [threadId] */ - fun setThreadId(threadId: String): Builder = apply { this.threadId = threadId } - /** Sets [sectionName] */ - fun setSectionName(sectionName: String): Builder = apply { this.sectionName = sectionName } - /** Sets [analyticsTags] */ - fun setAnalyticsTags(analyticsTags: Map): Builder = apply { this.analyticsTags = analyticsTags } /** Builds the [ProductInsightsAgentAnswerFeedbackTrackingData] */ fun build(): ProductInsightsAgentAnswerFeedbackTrackingData = ProductInsightsAgentAnswerFeedbackTrackingData(this) diff --git a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentAnswerViewTrackingData.kt b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentAnswerViewTrackingData.kt index 6e3f1123..07c8e954 100644 --- a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentAnswerViewTrackingData.kt +++ b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentAnswerViewTrackingData.kt @@ -19,19 +19,19 @@ import io.constructor.data.model.common.TrackingItem * @property analyticsTags additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ class ProductInsightsAgentAnswerViewTrackingData( - val itemId: String, + itemId: String, val question: String, val answerText: String, val questionTopic: String? = null, val qnaResultId: String? = null, val items: List? = null, val followUpQuestions: List? = null, - val itemName: String? = null, - val variationId: String? = null, - val threadId: String? = null, - val sectionName: String? = null, - val analyticsTags: Map? = null, -) { + itemName: String? = null, + variationId: String? = null, + threadId: String? = null, + sectionName: String? = null, + analyticsTags: Map? = null, +) : ProductInsightsAgentTrackingData(itemId, itemName, variationId, threadId, sectionName, analyticsTags) { private constructor(builder: Builder) : this( builder.itemId, builder.question, @@ -66,7 +66,7 @@ class ProductInsightsAgentAnswerViewTrackingData( val itemId: String, val question: String, val answerText: String - ) { + ) : ProductInsightsAgentTrackingData.Builder() { /** The topic the question belongs to, i.e. "Durability" */ var questionTopic: String? = null /** The result ID of the answer, as returned by the Product Insights Agent */ @@ -75,16 +75,6 @@ class ProductInsightsAgentAnswerViewTrackingData( var items: List? = null /** The follow-up questions shown with the answer. Only the first 100 are sent */ var followUpQuestions: List? = null - /** The name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" */ - var itemName: String? = null - /** The variation ID of the item (product) the Product Insights Agent is displayed for */ - var variationId: String? = null - /** The ID of the Product Insights Agent conversation thread */ - var threadId: String? = null - /** The index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] */ - var sectionName: String? = null - /** Additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ - var analyticsTags: Map? = null /** Sets [questionTopic] */ fun setQuestionTopic(questionTopic: String): Builder = apply { this.questionTopic = questionTopic } @@ -94,16 +84,6 @@ class ProductInsightsAgentAnswerViewTrackingData( fun setItems(items: List): Builder = apply { this.items = items } /** Sets [followUpQuestions] */ fun setFollowUpQuestions(followUpQuestions: List): Builder = apply { this.followUpQuestions = followUpQuestions } - /** Sets [itemName] */ - fun setItemName(itemName: String): Builder = apply { this.itemName = itemName } - /** Sets [variationId] */ - fun setVariationId(variationId: String): Builder = apply { this.variationId = variationId } - /** Sets [threadId] */ - fun setThreadId(threadId: String): Builder = apply { this.threadId = threadId } - /** Sets [sectionName] */ - fun setSectionName(sectionName: String): Builder = apply { this.sectionName = sectionName } - /** Sets [analyticsTags] */ - fun setAnalyticsTags(analyticsTags: Map): Builder = apply { this.analyticsTags = analyticsTags } /** Builds the [ProductInsightsAgentAnswerViewTrackingData] */ fun build(): ProductInsightsAgentAnswerViewTrackingData = ProductInsightsAgentAnswerViewTrackingData(this) diff --git a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentFocusTrackingData.kt b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentFocusTrackingData.kt index 0d9a894b..6bbc3a26 100644 --- a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentFocusTrackingData.kt +++ b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentFocusTrackingData.kt @@ -11,13 +11,13 @@ package io.constructor.data.builder * @property analyticsTags additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ class ProductInsightsAgentFocusTrackingData( - val itemId: String, - val itemName: String? = null, - val variationId: String? = null, - val threadId: String? = null, - val sectionName: String? = null, - val analyticsTags: Map? = null, -) { + itemId: String, + itemName: String? = null, + variationId: String? = null, + threadId: String? = null, + sectionName: String? = null, + analyticsTags: Map? = null, +) : ProductInsightsAgentTrackingData(itemId, itemName, variationId, threadId, sectionName, analyticsTags) { private constructor(builder: Builder) : this( builder.itemId, builder.itemName, @@ -42,29 +42,7 @@ class ProductInsightsAgentFocusTrackingData( */ class Builder( val itemId: String - ) { - /** The name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" */ - var itemName: String? = null - /** The variation ID of the item (product) the Product Insights Agent is displayed for */ - var variationId: String? = null - /** The ID of the Product Insights Agent conversation thread */ - var threadId: String? = null - /** The index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] */ - var sectionName: String? = null - /** Additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ - var analyticsTags: Map? = null - - /** Sets [itemName] */ - fun setItemName(itemName: String): Builder = apply { this.itemName = itemName } - /** Sets [variationId] */ - fun setVariationId(variationId: String): Builder = apply { this.variationId = variationId } - /** Sets [threadId] */ - fun setThreadId(threadId: String): Builder = apply { this.threadId = threadId } - /** Sets [sectionName] */ - fun setSectionName(sectionName: String): Builder = apply { this.sectionName = sectionName } - /** Sets [analyticsTags] */ - fun setAnalyticsTags(analyticsTags: Map): Builder = apply { this.analyticsTags = analyticsTags } - + ) : ProductInsightsAgentTrackingData.Builder() { /** Builds the [ProductInsightsAgentFocusTrackingData] */ fun build(): ProductInsightsAgentFocusTrackingData = ProductInsightsAgentFocusTrackingData(this) } diff --git a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentOutOfViewTrackingData.kt b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentOutOfViewTrackingData.kt index ff706944..bcf27320 100644 --- a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentOutOfViewTrackingData.kt +++ b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentOutOfViewTrackingData.kt @@ -11,13 +11,13 @@ package io.constructor.data.builder * @property analyticsTags additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ class ProductInsightsAgentOutOfViewTrackingData( - val itemId: String, - val itemName: String? = null, - val variationId: String? = null, - val threadId: String? = null, - val sectionName: String? = null, - val analyticsTags: Map? = null, -) { + itemId: String, + itemName: String? = null, + variationId: String? = null, + threadId: String? = null, + sectionName: String? = null, + analyticsTags: Map? = null, +) : ProductInsightsAgentTrackingData(itemId, itemName, variationId, threadId, sectionName, analyticsTags) { private constructor(builder: Builder) : this( builder.itemId, builder.itemName, @@ -42,29 +42,7 @@ class ProductInsightsAgentOutOfViewTrackingData( */ class Builder( val itemId: String - ) { - /** The name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" */ - var itemName: String? = null - /** The variation ID of the item (product) the Product Insights Agent is displayed for */ - var variationId: String? = null - /** The ID of the Product Insights Agent conversation thread */ - var threadId: String? = null - /** The index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] */ - var sectionName: String? = null - /** Additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ - var analyticsTags: Map? = null - - /** Sets [itemName] */ - fun setItemName(itemName: String): Builder = apply { this.itemName = itemName } - /** Sets [variationId] */ - fun setVariationId(variationId: String): Builder = apply { this.variationId = variationId } - /** Sets [threadId] */ - fun setThreadId(threadId: String): Builder = apply { this.threadId = threadId } - /** Sets [sectionName] */ - fun setSectionName(sectionName: String): Builder = apply { this.sectionName = sectionName } - /** Sets [analyticsTags] */ - fun setAnalyticsTags(analyticsTags: Map): Builder = apply { this.analyticsTags = analyticsTags } - + ) : ProductInsightsAgentTrackingData.Builder() { /** Builds the [ProductInsightsAgentOutOfViewTrackingData] */ fun build(): ProductInsightsAgentOutOfViewTrackingData = ProductInsightsAgentOutOfViewTrackingData(this) } diff --git a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentQuestionClickTrackingData.kt b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentQuestionClickTrackingData.kt index 7d0f70cf..8d61a470 100644 --- a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentQuestionClickTrackingData.kt +++ b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentQuestionClickTrackingData.kt @@ -13,15 +13,15 @@ package io.constructor.data.builder * @property analyticsTags additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ class ProductInsightsAgentQuestionClickTrackingData( - val itemId: String, + itemId: String, val question: String, val questionTopic: String? = null, - val itemName: String? = null, - val variationId: String? = null, - val threadId: String? = null, - val sectionName: String? = null, - val analyticsTags: Map? = null, -) { + itemName: String? = null, + variationId: String? = null, + threadId: String? = null, + sectionName: String? = null, + analyticsTags: Map? = null, +) : ProductInsightsAgentTrackingData(itemId, itemName, variationId, threadId, sectionName, analyticsTags) { private constructor(builder: Builder) : this( builder.itemId, builder.question, @@ -50,32 +50,12 @@ class ProductInsightsAgentQuestionClickTrackingData( class Builder( val itemId: String, val question: String - ) { + ) : ProductInsightsAgentTrackingData.Builder() { /** The topic the question belongs to, i.e. "Durability" */ var questionTopic: String? = null - /** The name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" */ - var itemName: String? = null - /** The variation ID of the item (product) the Product Insights Agent is displayed for */ - var variationId: String? = null - /** The ID of the Product Insights Agent conversation thread */ - var threadId: String? = null - /** The index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] */ - var sectionName: String? = null - /** Additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ - var analyticsTags: Map? = null /** Sets [questionTopic] */ fun setQuestionTopic(questionTopic: String): Builder = apply { this.questionTopic = questionTopic } - /** Sets [itemName] */ - fun setItemName(itemName: String): Builder = apply { this.itemName = itemName } - /** Sets [variationId] */ - fun setVariationId(variationId: String): Builder = apply { this.variationId = variationId } - /** Sets [threadId] */ - fun setThreadId(threadId: String): Builder = apply { this.threadId = threadId } - /** Sets [sectionName] */ - fun setSectionName(sectionName: String): Builder = apply { this.sectionName = sectionName } - /** Sets [analyticsTags] */ - fun setAnalyticsTags(analyticsTags: Map): Builder = apply { this.analyticsTags = analyticsTags } /** Builds the [ProductInsightsAgentQuestionClickTrackingData] */ fun build(): ProductInsightsAgentQuestionClickTrackingData = ProductInsightsAgentQuestionClickTrackingData(this) diff --git a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentQuestionSubmitTrackingData.kt b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentQuestionSubmitTrackingData.kt index 8a0711e9..e85af6d1 100644 --- a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentQuestionSubmitTrackingData.kt +++ b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentQuestionSubmitTrackingData.kt @@ -13,15 +13,15 @@ package io.constructor.data.builder * @property analyticsTags additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ class ProductInsightsAgentQuestionSubmitTrackingData( - val itemId: String, + itemId: String, val question: String, val questionTopic: String? = null, - val itemName: String? = null, - val variationId: String? = null, - val threadId: String? = null, - val sectionName: String? = null, - val analyticsTags: Map? = null, -) { + itemName: String? = null, + variationId: String? = null, + threadId: String? = null, + sectionName: String? = null, + analyticsTags: Map? = null, +) : ProductInsightsAgentTrackingData(itemId, itemName, variationId, threadId, sectionName, analyticsTags) { private constructor(builder: Builder) : this( builder.itemId, builder.question, @@ -50,32 +50,12 @@ class ProductInsightsAgentQuestionSubmitTrackingData( class Builder( val itemId: String, val question: String - ) { + ) : ProductInsightsAgentTrackingData.Builder() { /** The topic the question belongs to, i.e. "Durability" */ var questionTopic: String? = null - /** The name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" */ - var itemName: String? = null - /** The variation ID of the item (product) the Product Insights Agent is displayed for */ - var variationId: String? = null - /** The ID of the Product Insights Agent conversation thread */ - var threadId: String? = null - /** The index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] */ - var sectionName: String? = null - /** Additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ - var analyticsTags: Map? = null /** Sets [questionTopic] */ fun setQuestionTopic(questionTopic: String): Builder = apply { this.questionTopic = questionTopic } - /** Sets [itemName] */ - fun setItemName(itemName: String): Builder = apply { this.itemName = itemName } - /** Sets [variationId] */ - fun setVariationId(variationId: String): Builder = apply { this.variationId = variationId } - /** Sets [threadId] */ - fun setThreadId(threadId: String): Builder = apply { this.threadId = threadId } - /** Sets [sectionName] */ - fun setSectionName(sectionName: String): Builder = apply { this.sectionName = sectionName } - /** Sets [analyticsTags] */ - fun setAnalyticsTags(analyticsTags: Map): Builder = apply { this.analyticsTags = analyticsTags } /** Builds the [ProductInsightsAgentQuestionSubmitTrackingData] */ fun build(): ProductInsightsAgentQuestionSubmitTrackingData = ProductInsightsAgentQuestionSubmitTrackingData(this) diff --git a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentResultClickTrackingData.kt b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentResultClickTrackingData.kt index 6e4b3bd8..11561899 100644 --- a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentResultClickTrackingData.kt +++ b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentResultClickTrackingData.kt @@ -17,19 +17,19 @@ package io.constructor.data.builder * @property analyticsTags additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ class ProductInsightsAgentResultClickTrackingData( - val itemId: String, + itemId: String, val question: String, val seedItemId: String, val position: Int? = null, val seedItemName: String? = null, val seedVariationId: String? = null, val qnaResultId: String? = null, - val itemName: String? = null, - val variationId: String? = null, - val threadId: String? = null, - val sectionName: String? = null, - val analyticsTags: Map? = null, -) { + itemName: String? = null, + variationId: String? = null, + threadId: String? = null, + sectionName: String? = null, + analyticsTags: Map? = null, +) : ProductInsightsAgentTrackingData(itemId, itemName, variationId, threadId, sectionName, analyticsTags) { private constructor(builder: Builder) : this( builder.itemId, builder.question, @@ -64,7 +64,7 @@ class ProductInsightsAgentResultClickTrackingData( val itemId: String, val question: String, val seedItemId: String - ) { + ) : ProductInsightsAgentTrackingData.Builder() { /** The zero-based position of the clicked item within the answer */ var position: Int? = null /** The name of the item (product) the Product Insights Agent is displayed for */ @@ -73,16 +73,6 @@ class ProductInsightsAgentResultClickTrackingData( var seedVariationId: String? = null /** The result ID of the answer, as returned by the Product Insights Agent */ var qnaResultId: String? = null - /** The name of the clicked item (product) recommended in the answer */ - var itemName: String? = null - /** The variation ID of the clicked item (product) recommended in the answer */ - var variationId: String? = null - /** The ID of the Product Insights Agent conversation thread */ - var threadId: String? = null - /** The index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] */ - var sectionName: String? = null - /** Additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ - var analyticsTags: Map? = null /** Sets [position] */ fun setPosition(position: Int): Builder = apply { this.position = position } @@ -92,16 +82,6 @@ class ProductInsightsAgentResultClickTrackingData( fun setSeedVariationId(seedVariationId: String): Builder = apply { this.seedVariationId = seedVariationId } /** Sets [qnaResultId] */ fun setQnaResultId(qnaResultId: String): Builder = apply { this.qnaResultId = qnaResultId } - /** Sets [itemName] */ - fun setItemName(itemName: String): Builder = apply { this.itemName = itemName } - /** Sets [variationId] */ - fun setVariationId(variationId: String): Builder = apply { this.variationId = variationId } - /** Sets [threadId] */ - fun setThreadId(threadId: String): Builder = apply { this.threadId = threadId } - /** Sets [sectionName] */ - fun setSectionName(sectionName: String): Builder = apply { this.sectionName = sectionName } - /** Sets [analyticsTags] */ - fun setAnalyticsTags(analyticsTags: Map): Builder = apply { this.analyticsTags = analyticsTags } /** Builds the [ProductInsightsAgentResultClickTrackingData] */ fun build(): ProductInsightsAgentResultClickTrackingData = ProductInsightsAgentResultClickTrackingData(this) diff --git a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentTrackingData.kt b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentTrackingData.kt new file mode 100644 index 00000000..bc8067ca --- /dev/null +++ b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentTrackingData.kt @@ -0,0 +1,52 @@ +package io.constructor.data.builder + +/** + * Base class for the Product Insights Agent tracking request objects, holding the parameters shared by every event + * + * @property itemId the ID of the item (product) the event is for, i.e. "1234567-AB" + * @property itemName the name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" + * @property variationId the variation ID of the item (product) the Product Insights Agent is displayed for + * @property threadId the ID of the Product Insights Agent conversation thread + * @property sectionName the index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] + * @property analyticsTags additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision + */ +abstract class ProductInsightsAgentTrackingData( + val itemId: String, + val itemName: String?, + val variationId: String?, + val threadId: String?, + val sectionName: String?, + val analyticsTags: Map?, +) { + /** + * Base builder holding the optional parameters shared by every Product Insights Agent event + */ + abstract class Builder> { + /** The name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" */ + var itemName: String? = null + /** The variation ID of the item (product) the Product Insights Agent is displayed for */ + var variationId: String? = null + /** The ID of the Product Insights Agent conversation thread */ + var threadId: String? = null + /** The index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] */ + var sectionName: String? = null + /** Additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ + var analyticsTags: Map? = null + + /** Sets [itemName] */ + @Suppress("UNCHECKED_CAST") + fun setItemName(itemName: String): B = apply { this.itemName = itemName } as B + /** Sets [variationId] */ + @Suppress("UNCHECKED_CAST") + fun setVariationId(variationId: String): B = apply { this.variationId = variationId } as B + /** Sets [threadId] */ + @Suppress("UNCHECKED_CAST") + fun setThreadId(threadId: String): B = apply { this.threadId = threadId } as B + /** Sets [sectionName] */ + @Suppress("UNCHECKED_CAST") + fun setSectionName(sectionName: String): B = apply { this.sectionName = sectionName } as B + /** Sets [analyticsTags] */ + @Suppress("UNCHECKED_CAST") + fun setAnalyticsTags(analyticsTags: Map): B = apply { this.analyticsTags = analyticsTags } as B + } +} diff --git a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentViewTrackingData.kt b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentViewTrackingData.kt index e989542f..8f30f79f 100644 --- a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentViewTrackingData.kt +++ b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentViewTrackingData.kt @@ -14,14 +14,14 @@ import io.constructor.data.model.tracking.ProductInsightsAgentQuestion * @property analyticsTags additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ class ProductInsightsAgentViewTrackingData( - val itemId: String, + itemId: String, val questions: List, - val itemName: String? = null, - val variationId: String? = null, - val threadId: String? = null, - val sectionName: String? = null, - val analyticsTags: Map? = null, -) { + itemName: String? = null, + variationId: String? = null, + threadId: String? = null, + sectionName: String? = null, + analyticsTags: Map? = null, +) : ProductInsightsAgentTrackingData(itemId, itemName, variationId, threadId, sectionName, analyticsTags) { private constructor(builder: Builder) : this( builder.itemId, builder.questions, @@ -49,29 +49,7 @@ class ProductInsightsAgentViewTrackingData( class Builder( val itemId: String, val questions: List - ) { - /** The name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" */ - var itemName: String? = null - /** The variation ID of the item (product) the Product Insights Agent is displayed for */ - var variationId: String? = null - /** The ID of the Product Insights Agent conversation thread */ - var threadId: String? = null - /** The index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] */ - var sectionName: String? = null - /** Additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ - var analyticsTags: Map? = null - - /** Sets [itemName] */ - fun setItemName(itemName: String): Builder = apply { this.itemName = itemName } - /** Sets [variationId] */ - fun setVariationId(variationId: String): Builder = apply { this.variationId = variationId } - /** Sets [threadId] */ - fun setThreadId(threadId: String): Builder = apply { this.threadId = threadId } - /** Sets [sectionName] */ - fun setSectionName(sectionName: String): Builder = apply { this.sectionName = sectionName } - /** Sets [analyticsTags] */ - fun setAnalyticsTags(analyticsTags: Map): Builder = apply { this.analyticsTags = analyticsTags } - + ) : ProductInsightsAgentTrackingData.Builder() { /** Builds the [ProductInsightsAgentViewTrackingData] */ fun build(): ProductInsightsAgentViewTrackingData = ProductInsightsAgentViewTrackingData(this) } diff --git a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentViewsTrackingData.kt b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentViewsTrackingData.kt index 5093c215..8e6017d4 100644 --- a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentViewsTrackingData.kt +++ b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentViewsTrackingData.kt @@ -16,15 +16,15 @@ import io.constructor.data.model.tracking.ProductInsightsAgentViewTimespan * @property analyticsTags additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ class ProductInsightsAgentViewsTrackingData( - val itemId: String, + itemId: String, val questions: List, val viewTimespans: List, - val itemName: String? = null, - val variationId: String? = null, - val threadId: String? = null, - val sectionName: String? = null, - val analyticsTags: Map? = null, -) { + itemName: String? = null, + variationId: String? = null, + threadId: String? = null, + sectionName: String? = null, + analyticsTags: Map? = null, +) : ProductInsightsAgentTrackingData(itemId, itemName, variationId, threadId, sectionName, analyticsTags) { private constructor(builder: Builder) : this( builder.itemId, builder.questions, @@ -55,29 +55,7 @@ class ProductInsightsAgentViewsTrackingData( val itemId: String, val questions: List, val viewTimespans: List - ) { - /** The name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" */ - var itemName: String? = null - /** The variation ID of the item (product) the Product Insights Agent is displayed for */ - var variationId: String? = null - /** The ID of the Product Insights Agent conversation thread */ - var threadId: String? = null - /** The index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] */ - var sectionName: String? = null - /** Additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ - var analyticsTags: Map? = null - - /** Sets [itemName] */ - fun setItemName(itemName: String): Builder = apply { this.itemName = itemName } - /** Sets [variationId] */ - fun setVariationId(variationId: String): Builder = apply { this.variationId = variationId } - /** Sets [threadId] */ - fun setThreadId(threadId: String): Builder = apply { this.threadId = threadId } - /** Sets [sectionName] */ - fun setSectionName(sectionName: String): Builder = apply { this.sectionName = sectionName } - /** Sets [analyticsTags] */ - fun setAnalyticsTags(analyticsTags: Map): Builder = apply { this.analyticsTags = analyticsTags } - + ) : ProductInsightsAgentTrackingData.Builder() { /** Builds the [ProductInsightsAgentViewsTrackingData] */ fun build(): ProductInsightsAgentViewsTrackingData = ProductInsightsAgentViewsTrackingData(this) } diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentAnswerFeedbackRequestBody.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentAnswerFeedbackRequestBody.kt index 3158ba5a..c41f9a39 100644 --- a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentAnswerFeedbackRequestBody.kt +++ b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentAnswerFeedbackRequestBody.kt @@ -2,27 +2,17 @@ package io.constructor.data.model.tracking import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import java.io.Serializable /** * @suppress */ @JsonClass(generateAdapter = true) -data class ProductInsightsAgentAnswerFeedbackRequestBody( - @Json(name = "item_id") val itemId: String, - @Json(name = "feedback_label") val feedbackLabel: ProductInsightsAgentFeedbackLabel, - @Json(name = "qna_result_id") val qnaResultId: String?, - @Json(name = "item_name") val itemName: String?, - @Json(name = "variation_id") val variationId: String?, - @Json(name = "thread_id") val threadId: String?, - @Json(name = "c") val c: String, - @Json(name = "i") val i: String, - @Json(name = "s") val s: Int, - @Json(name = "key") val key: String, - @Json(name = "ui") val ui: String?, - @Json(name = "us") val us: List, - @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? -) : Serializable +class ProductInsightsAgentAnswerFeedbackRequestBody( + itemId: String, + @Json(name = "feedback_label") val feedbackLabel: ProductInsightsAgentFeedbackLabel, + @Json(name = "qna_result_id") val qnaResultId: String?, + itemName: String?, + variationId: String?, + threadId: String?, + analyticsTags: Map?, +) : ProductInsightsAgentRequestBody(itemId, itemName, variationId, threadId, analyticsTags) diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentAnswerViewRequestBody.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentAnswerViewRequestBody.kt index a9926bf8..480d6aca 100644 --- a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentAnswerViewRequestBody.kt +++ b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentAnswerViewRequestBody.kt @@ -3,31 +3,21 @@ package io.constructor.data.model.tracking import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import io.constructor.data.model.common.TrackingItem -import java.io.Serializable /** * @suppress */ @JsonClass(generateAdapter = true) -data class ProductInsightsAgentAnswerViewRequestBody( - @Json(name = "item_id") val itemId: String, - @Json(name = "question") val question: String, - @Json(name = "answer_text") val answerText: String, - @Json(name = "question_topic") val questionTopic: String?, - @Json(name = "qna_result_id") val qnaResultId: String?, - @Json(name = "items") val items: List?, - @Json(name = "follow_up_questions") val followUpQuestions: List?, - @Json(name = "item_name") val itemName: String?, - @Json(name = "variation_id") val variationId: String?, - @Json(name = "thread_id") val threadId: String?, - @Json(name = "c") val c: String, - @Json(name = "i") val i: String, - @Json(name = "s") val s: Int, - @Json(name = "key") val key: String, - @Json(name = "ui") val ui: String?, - @Json(name = "us") val us: List, - @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? -) : Serializable +class ProductInsightsAgentAnswerViewRequestBody( + itemId: String, + @Json(name = "question") val question: String, + @Json(name = "answer_text") val answerText: String, + @Json(name = "question_topic") val questionTopic: String?, + @Json(name = "qna_result_id") val qnaResultId: String?, + @Json(name = "items") val items: List?, + @Json(name = "follow_up_questions") val followUpQuestions: List?, + itemName: String?, + variationId: String?, + threadId: String?, + analyticsTags: Map?, +) : ProductInsightsAgentRequestBody(itemId, itemName, variationId, threadId, analyticsTags) diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentFocusRequestBody.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentFocusRequestBody.kt index 46f461da..1003c2c2 100644 --- a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentFocusRequestBody.kt +++ b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentFocusRequestBody.kt @@ -1,26 +1,15 @@ package io.constructor.data.model.tracking -import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import java.io.Serializable /** * @suppress */ @JsonClass(generateAdapter = true) -data class ProductInsightsAgentFocusRequestBody( - @Json(name = "item_id") val itemId: String, - @Json(name = "item_name") val itemName: String?, - @Json(name = "variation_id") val variationId: String?, - @Json(name = "thread_id") val threadId: String?, - @Json(name = "c") val c: String, - @Json(name = "i") val i: String, - @Json(name = "s") val s: Int, - @Json(name = "key") val key: String, - @Json(name = "ui") val ui: String?, - @Json(name = "us") val us: List, - @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? -) : Serializable +class ProductInsightsAgentFocusRequestBody( + itemId: String, + itemName: String?, + variationId: String?, + threadId: String?, + analyticsTags: Map?, +) : ProductInsightsAgentRequestBody(itemId, itemName, variationId, threadId, analyticsTags) diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentOutOfViewRequestBody.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentOutOfViewRequestBody.kt index 6b490c1f..d42216ad 100644 --- a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentOutOfViewRequestBody.kt +++ b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentOutOfViewRequestBody.kt @@ -1,26 +1,15 @@ package io.constructor.data.model.tracking -import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import java.io.Serializable /** * @suppress */ @JsonClass(generateAdapter = true) -data class ProductInsightsAgentOutOfViewRequestBody( - @Json(name = "item_id") val itemId: String, - @Json(name = "item_name") val itemName: String?, - @Json(name = "variation_id") val variationId: String?, - @Json(name = "thread_id") val threadId: String?, - @Json(name = "c") val c: String, - @Json(name = "i") val i: String, - @Json(name = "s") val s: Int, - @Json(name = "key") val key: String, - @Json(name = "ui") val ui: String?, - @Json(name = "us") val us: List, - @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? -) : Serializable +class ProductInsightsAgentOutOfViewRequestBody( + itemId: String, + itemName: String?, + variationId: String?, + threadId: String?, + analyticsTags: Map?, +) : ProductInsightsAgentRequestBody(itemId, itemName, variationId, threadId, analyticsTags) diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentQuestionClickRequestBody.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentQuestionClickRequestBody.kt index ca08d13b..e80da168 100644 --- a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentQuestionClickRequestBody.kt +++ b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentQuestionClickRequestBody.kt @@ -2,27 +2,17 @@ package io.constructor.data.model.tracking import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import java.io.Serializable /** * @suppress */ @JsonClass(generateAdapter = true) -data class ProductInsightsAgentQuestionClickRequestBody( - @Json(name = "item_id") val itemId: String, - @Json(name = "question") val question: String, - @Json(name = "question_topic") val questionTopic: String?, - @Json(name = "item_name") val itemName: String?, - @Json(name = "variation_id") val variationId: String?, - @Json(name = "thread_id") val threadId: String?, - @Json(name = "c") val c: String, - @Json(name = "i") val i: String, - @Json(name = "s") val s: Int, - @Json(name = "key") val key: String, - @Json(name = "ui") val ui: String?, - @Json(name = "us") val us: List, - @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? -) : Serializable +class ProductInsightsAgentQuestionClickRequestBody( + itemId: String, + @Json(name = "question") val question: String, + @Json(name = "question_topic") val questionTopic: String?, + itemName: String?, + variationId: String?, + threadId: String?, + analyticsTags: Map?, +) : ProductInsightsAgentRequestBody(itemId, itemName, variationId, threadId, analyticsTags) diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentQuestionSubmitRequestBody.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentQuestionSubmitRequestBody.kt index a1403711..d94028fb 100644 --- a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentQuestionSubmitRequestBody.kt +++ b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentQuestionSubmitRequestBody.kt @@ -2,27 +2,17 @@ package io.constructor.data.model.tracking import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import java.io.Serializable /** * @suppress */ @JsonClass(generateAdapter = true) -data class ProductInsightsAgentQuestionSubmitRequestBody( - @Json(name = "item_id") val itemId: String, - @Json(name = "question") val question: String, - @Json(name = "question_topic") val questionTopic: String?, - @Json(name = "item_name") val itemName: String?, - @Json(name = "variation_id") val variationId: String?, - @Json(name = "thread_id") val threadId: String?, - @Json(name = "c") val c: String, - @Json(name = "i") val i: String, - @Json(name = "s") val s: Int, - @Json(name = "key") val key: String, - @Json(name = "ui") val ui: String?, - @Json(name = "us") val us: List, - @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? -) : Serializable +class ProductInsightsAgentQuestionSubmitRequestBody( + itemId: String, + @Json(name = "question") val question: String, + @Json(name = "question_topic") val questionTopic: String?, + itemName: String?, + variationId: String?, + threadId: String?, + analyticsTags: Map?, +) : ProductInsightsAgentRequestBody(itemId, itemName, variationId, threadId, analyticsTags) diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentRequestBody.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentRequestBody.kt new file mode 100644 index 00000000..93f6155b --- /dev/null +++ b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentRequestBody.kt @@ -0,0 +1,23 @@ +package io.constructor.data.model.tracking + +import com.squareup.moshi.Json +import java.io.Serializable + +/** + * @suppress + * Base request body holding the fields shared by every Product Insights Agent event. The common + * client parameters (c, i, s, key, ui, us, _dt) are added as query parameters by RequestInterceptor. + */ +abstract class ProductInsightsAgentRequestBody( + itemId: String, + itemName: String?, + variationId: String?, + threadId: String?, + analyticsTags: Map?, +) : Serializable { + @Json(name = "item_id") val itemId: String = itemId + @Json(name = "item_name") val itemName: String? = itemName + @Json(name = "variation_id") val variationId: String? = variationId + @Json(name = "thread_id") val threadId: String? = threadId + @Json(name = "analytics_tags") val analyticsTags: Map? = analyticsTags +} diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentResultClickRequestBody.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentResultClickRequestBody.kt index d82fe4ed..1711c745 100644 --- a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentResultClickRequestBody.kt +++ b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentResultClickRequestBody.kt @@ -2,31 +2,21 @@ package io.constructor.data.model.tracking import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import java.io.Serializable /** * @suppress */ @JsonClass(generateAdapter = true) -data class ProductInsightsAgentResultClickRequestBody( - @Json(name = "item_id") val itemId: String, - @Json(name = "question") val question: String, - @Json(name = "seed_item_id") val seedItemId: String, - @Json(name = "position") val position: Int?, - @Json(name = "seed_item_name") val seedItemName: String?, - @Json(name = "seed_variation_id") val seedVariationId: String?, - @Json(name = "qna_result_id") val qnaResultId: String?, - @Json(name = "item_name") val itemName: String?, - @Json(name = "variation_id") val variationId: String?, - @Json(name = "thread_id") val threadId: String?, - @Json(name = "c") val c: String, - @Json(name = "i") val i: String, - @Json(name = "s") val s: Int, - @Json(name = "key") val key: String, - @Json(name = "ui") val ui: String?, - @Json(name = "us") val us: List, - @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? -) : Serializable +class ProductInsightsAgentResultClickRequestBody( + itemId: String, + @Json(name = "question") val question: String, + @Json(name = "seed_item_id") val seedItemId: String, + @Json(name = "position") val position: Int?, + @Json(name = "seed_item_name") val seedItemName: String?, + @Json(name = "seed_variation_id") val seedVariationId: String?, + @Json(name = "qna_result_id") val qnaResultId: String?, + itemName: String?, + variationId: String?, + threadId: String?, + analyticsTags: Map?, +) : ProductInsightsAgentRequestBody(itemId, itemName, variationId, threadId, analyticsTags) diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentViewRequestBody.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentViewRequestBody.kt index 152bcee0..63bff392 100644 --- a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentViewRequestBody.kt +++ b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentViewRequestBody.kt @@ -2,26 +2,16 @@ package io.constructor.data.model.tracking import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import java.io.Serializable /** * @suppress */ @JsonClass(generateAdapter = true) -data class ProductInsightsAgentViewRequestBody( - @Json(name = "item_id") val itemId: String, - @Json(name = "questions") val questions: List, - @Json(name = "item_name") val itemName: String?, - @Json(name = "variation_id") val variationId: String?, - @Json(name = "thread_id") val threadId: String?, - @Json(name = "c") val c: String, - @Json(name = "i") val i: String, - @Json(name = "s") val s: Int, - @Json(name = "key") val key: String, - @Json(name = "ui") val ui: String?, - @Json(name = "us") val us: List, - @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? -) : Serializable +class ProductInsightsAgentViewRequestBody( + itemId: String, + @Json(name = "questions") val questions: List, + itemName: String?, + variationId: String?, + threadId: String?, + analyticsTags: Map?, +) : ProductInsightsAgentRequestBody(itemId, itemName, variationId, threadId, analyticsTags) diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentViewsRequestBody.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentViewsRequestBody.kt index 082eb6bb..6587dd46 100644 --- a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentViewsRequestBody.kt +++ b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentViewsRequestBody.kt @@ -2,27 +2,17 @@ package io.constructor.data.model.tracking import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import java.io.Serializable /** * @suppress */ @JsonClass(generateAdapter = true) -data class ProductInsightsAgentViewsRequestBody( - @Json(name = "item_id") val itemId: String, - @Json(name = "questions") val questions: List, - @Json(name = "view_timespans") val viewTimespans: List, - @Json(name = "item_name") val itemName: String?, - @Json(name = "variation_id") val variationId: String?, - @Json(name = "thread_id") val threadId: String?, - @Json(name = "c") val c: String, - @Json(name = "i") val i: String, - @Json(name = "s") val s: Int, - @Json(name = "key") val key: String, - @Json(name = "ui") val ui: String?, - @Json(name = "us") val us: List, - @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? -) : Serializable +class ProductInsightsAgentViewsRequestBody( + itemId: String, + @Json(name = "questions") val questions: List, + @Json(name = "view_timespans") val viewTimespans: List, + itemName: String?, + variationId: String?, + threadId: String?, + analyticsTags: Map?, +) : ProductInsightsAgentRequestBody(itemId, itemName, variationId, threadId, analyticsTags) diff --git a/library/src/test/java/io/constructor/core/ConstructorIoProductInsightsAgentTrackingTest.kt b/library/src/test/java/io/constructor/core/ConstructorIoProductInsightsAgentTrackingTest.kt index 91f2f434..1ab29ed0 100644 --- a/library/src/test/java/io/constructor/core/ConstructorIoProductInsightsAgentTrackingTest.kt +++ b/library/src/test/java/io/constructor/core/ConstructorIoProductInsightsAgentTrackingTest.kt @@ -94,7 +94,7 @@ class ConstructorIoProductInsightsAgentTrackingTest { assertEquals("Waterproof Jacket", body.getString("item_name")) assertEquals("var-1", body.getString("variation_id")) assertEquals("thread-1", body.getString("thread_id")) - assertWrapperFields(body, "Products") + assertClientParams(recordedRequest, body) } @Test @@ -120,7 +120,7 @@ class ConstructorIoProductInsightsAgentTrackingTest { val recordedRequest = awaitRequest() assertPathAndSection(recordedRequest, "focus", "Search Suggestions") val body = getJsonBody(recordedRequest) - assertEquals("Search Suggestions", body.getString("section")) + assertAbsent(body, listOf("section")) assertMergedAnalyticsTags(body) } @@ -173,7 +173,7 @@ class ConstructorIoProductInsightsAgentTrackingTest { assertEquals("Waterproof Jacket", body.getString("item_name")) assertEquals("var-1", body.getString("variation_id")) assertEquals("thread-1", body.getString("thread_id")) - assertWrapperFields(body, "Products") + assertClientParams(recordedRequest, body) } @Test @@ -241,7 +241,7 @@ class ConstructorIoProductInsightsAgentTrackingTest { assertEquals("Durability", sentQuestions.getJSONObject(0).getString("question_topic")) assertEquals("Is it machine washable?", sentQuestions.getJSONObject(1).getString("question")) assertFalse(sentQuestions.getJSONObject(1).has("question_topic")) - assertWrapperFields(body, "Products") + assertClientParams(recordedRequest, body) } @Test @@ -319,7 +319,7 @@ class ConstructorIoProductInsightsAgentTrackingTest { assertEquals("2026-10-21T02:40:10.456Z", timespans.getJSONObject(0).getString("end")) assertEquals("2026-10-21T02:40:20.000Z", timespans.getJSONObject(1).getString("start")) assertEquals("2026-10-21T02:40:25.000Z", timespans.getJSONObject(1).getString("end")) - assertWrapperFields(body, "Products") + assertClientParams(recordedRequest, body) } @Test @@ -416,7 +416,7 @@ class ConstructorIoProductInsightsAgentTrackingTest { assertEquals("Waterproof Jacket", body.getString("item_name")) assertEquals("var-1", body.getString("variation_id")) assertEquals("thread-1", body.getString("thread_id")) - assertWrapperFields(body, "Products") + assertClientParams(recordedRequest, body) } @Test @@ -480,7 +480,7 @@ class ConstructorIoProductInsightsAgentTrackingTest { assertEquals("Waterproof Jacket", body.getString("item_name")) assertEquals("var-1", body.getString("variation_id")) assertEquals("thread-1", body.getString("thread_id")) - assertWrapperFields(body, "Products") + assertClientParams(recordedRequest, body) } @Test @@ -544,7 +544,7 @@ class ConstructorIoProductInsightsAgentTrackingTest { assertEquals("Waterproof Jacket", body.getString("item_name")) assertEquals("var-1", body.getString("variation_id")) assertEquals("thread-1", body.getString("thread_id")) - assertWrapperFields(body, "Products") + assertClientParams(recordedRequest, body) } @Test @@ -624,7 +624,7 @@ class ConstructorIoProductInsightsAgentTrackingTest { assertEquals("Rain Boots", body.getString("item_name")) assertEquals("rec-var-1", body.getString("variation_id")) assertEquals("thread-1", body.getString("thread_id")) - assertWrapperFields(body, "Products") + assertClientParams(recordedRequest, body) } @Test @@ -726,7 +726,7 @@ class ConstructorIoProductInsightsAgentTrackingTest { assertEquals(2, followUpQuestions.length()) assertEquals("Is it breathable?", followUpQuestions.getJSONObject(0).getString("value")) assertEquals("Does it come in blue?", followUpQuestions.getJSONObject(1).getString("value")) - assertWrapperFields(body, "Products") + assertClientParams(recordedRequest, body) } @Test @@ -811,15 +811,16 @@ class ConstructorIoProductInsightsAgentTrackingTest { assertEquals(section, url.queryParameter("section")) } - private fun assertWrapperFields(body: JSONObject, section: String) { - assertEquals("wacko-the-guid", body.getString("i")) - assertEquals(67, body.getInt("s")) - assertEquals("copper-key", body.getString("key")) - assertEquals("player-three", body.getString("ui")) - assertEquals(true, body.getBoolean("beacon")) - assertEquals(section, body.getString("section")) - assertTrue(body.getString("c").startsWith("cioand-")) - assertTrue(body.getLong("_dt") > 0) + // The client parameters are added to the query by RequestInterceptor, not duplicated in the body + private fun assertClientParams(request: RecordedRequest, body: JSONObject) { + val url = request.requestUrl!! + assertEquals("wacko-the-guid", url.queryParameter("i")) + assertEquals("67", url.queryParameter("s")) + assertEquals("copper-key", url.queryParameter("key")) + assertEquals("player-three", url.queryParameter("ui")) + assertTrue(url.queryParameter("c")!!.startsWith("cioand-")) + assertTrue(url.queryParameter("_dt")!!.toLong() > 0) + assertAbsent(body, listOf("c", "i", "s", "key", "ui", "us", "_dt", "beacon", "section")) val analyticsTags = body.getJSONObject("analytics_tags") assertEquals("123", analyticsTags.getString("appVersion")) assertEquals("Android", analyticsTags.getString("appPlatform")) diff --git a/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentResultClickTrackingDataTest.kt b/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentResultClickTrackingDataTest.kt index 4f9f4434..11b2f276 100644 --- a/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentResultClickTrackingDataTest.kt +++ b/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentResultClickTrackingDataTest.kt @@ -62,6 +62,21 @@ class ProductInsightsAgentResultClickTrackingDataTest { assertEquals(analyticsTags, request.analyticsTags) } + @Test + fun resultClickTrackingDataUsingBuilderWithSharedSettersFirst() { + // Shared setters come from the base builder and must return this builder so event setters can follow + val request = ProductInsightsAgentResultClickTrackingData.Builder(itemId, question, seedItemId) + .setItemName(itemName) + .setSectionName(sectionName) + .setPosition(position) + .setQnaResultId(qnaResultId) + .build() + assertEquals(itemName, request.itemName) + assertEquals(sectionName, request.sectionName) + assertEquals(position, request.position) + assertEquals(qnaResultId, request.qnaResultId) + } + @Test fun resultClickTrackingDataUsingBuilder() { val request = ProductInsightsAgentResultClickTrackingData.Builder(itemId, question, seedItemId) From b42efab896805503d77f7b301fb9f1d6be4d12d9 Mon Sep 17 00:00:00 2001 From: mudaafi Date: Mon, 26 Oct 2026 21:52:22 +0800 Subject: [PATCH 5/7] [CDX-709] Replace PIA base classes with a shared interface The abstract base classes added a generic public builder and unchecked casts while saving little, so go back to flat classes. - Add a ProductInsightsAgentTrackingData interface for the fields every PIA event shares, implemented by the 9 TrackingData classes. Section and analytics tag resolution in ConstructorIo now goes through it. - Make the request bodies flat data classes again, still without the client params that RequestInterceptor adds to the query. - Share one body between focus and out_of_view, and one between question_click and question_submit, since each pair sends the same fields. --- .../java/io/constructor/core/ConstructorIo.kt | 57 ++++++++++--------- .../java/io/constructor/data/DataManager.kt | 18 +++--- ...InsightsAgentAnswerFeedbackTrackingData.kt | 36 +++++++++--- ...ductInsightsAgentAnswerViewTrackingData.kt | 36 +++++++++--- .../ProductInsightsAgentFocusTrackingData.kt | 38 ++++++++++--- ...oductInsightsAgentOutOfViewTrackingData.kt | 38 ++++++++++--- ...tInsightsAgentQuestionClickTrackingData.kt | 36 +++++++++--- ...InsightsAgentQuestionSubmitTrackingData.kt | 36 +++++++++--- ...uctInsightsAgentResultClickTrackingData.kt | 36 +++++++++--- .../ProductInsightsAgentTrackingData.kt | 52 ++++------------- .../ProductInsightsAgentViewTrackingData.kt | 38 ++++++++++--- .../ProductInsightsAgentViewsTrackingData.kt | 38 ++++++++++--- ...tInsightsAgentAnswerFeedbackRequestBody.kt | 19 ++++--- ...oductInsightsAgentAnswerViewRequestBody.kt | 27 ++++----- .../ProductInsightsAgentFocusRequestBody.kt | 15 ----- .../ProductInsightsAgentItemRequestBody.kt | 18 ++++++ ...roductInsightsAgentOutOfViewRequestBody.kt | 15 ----- ...ctInsightsAgentQuestionClickRequestBody.kt | 18 ------ ...ProductInsightsAgentQuestionRequestBody.kt | 20 +++++++ ...tInsightsAgentQuestionSubmitRequestBody.kt | 18 ------ .../ProductInsightsAgentRequestBody.kt | 23 -------- ...ductInsightsAgentResultClickRequestBody.kt | 27 ++++----- .../ProductInsightsAgentViewRequestBody.kt | 17 +++--- .../ProductInsightsAgentViewsRequestBody.kt | 19 ++++--- .../constructor/data/remote/ConstructorApi.kt | 14 ++--- ...nsightsAgentResultClickTrackingDataTest.kt | 15 ----- .../ProductInsightsAgentTrackingDataTest.kt | 36 ++++++++++++ 27 files changed, 446 insertions(+), 314 deletions(-) delete mode 100644 library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentFocusRequestBody.kt create mode 100644 library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentItemRequestBody.kt delete mode 100644 library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentOutOfViewRequestBody.kt delete mode 100644 library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentQuestionClickRequestBody.kt create mode 100644 library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentQuestionRequestBody.kt delete mode 100644 library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentQuestionSubmitRequestBody.kt delete mode 100644 library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentRequestBody.kt create mode 100644 library/src/test/java/io/constructor/data/builder/ProductInsightsAgentTrackingDataTest.kt diff --git a/library/src/main/java/io/constructor/core/ConstructorIo.kt b/library/src/main/java/io/constructor/core/ConstructorIo.kt index 9cc8d412..9a4505fe 100755 --- a/library/src/main/java/io/constructor/core/ConstructorIo.kt +++ b/library/src/main/java/io/constructor/core/ConstructorIo.kt @@ -28,11 +28,9 @@ import io.constructor.data.model.tracking.ItemDetailLoadRequestBody import io.constructor.data.model.tracking.MediaImpressionRequestBody import io.constructor.data.model.tracking.ProductInsightsAgentAnswerFeedbackRequestBody import io.constructor.data.model.tracking.ProductInsightsAgentAnswerViewRequestBody -import io.constructor.data.model.tracking.ProductInsightsAgentFocusRequestBody import io.constructor.data.model.tracking.ProductInsightsAgentFollowUpQuestion -import io.constructor.data.model.tracking.ProductInsightsAgentOutOfViewRequestBody -import io.constructor.data.model.tracking.ProductInsightsAgentQuestionClickRequestBody -import io.constructor.data.model.tracking.ProductInsightsAgentQuestionSubmitRequestBody +import io.constructor.data.model.tracking.ProductInsightsAgentItemRequestBody +import io.constructor.data.model.tracking.ProductInsightsAgentQuestionRequestBody import io.constructor.data.model.tracking.ProductInsightsAgentResultClickRequestBody import io.constructor.data.model.tracking.ProductInsightsAgentViewRequestBody import io.constructor.data.model.tracking.ProductInsightsAgentViewTimespanBody @@ -373,6 +371,13 @@ object ConstructorIo { } // SimpleDateFormat isn't thread-safe and tracking runs on Schedulers.io(), so create one per call + // Section and analytics tags are resolved the same way for every Product Insights Agent event + private fun ProductInsightsAgentTrackingData.resolvedSection(): String = + sectionName ?: preferenceHelper.defaultItemSection + + private fun ProductInsightsAgentTrackingData.mergedAnalyticsTags(): Map? = + mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, analyticsTags) + private fun formatIsoTimestamp(epochMillis: Long): String { val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US) format.timeZone = TimeZone.getTimeZone("UTC") @@ -2474,13 +2479,13 @@ object ConstructorIo { internal fun trackProductInsightsAgentFocusInternal(request: ProductInsightsAgentFocusTrackingData): Completable { preferenceHelper.getSessionId(sessionIncrementHandler) - val section = request.sectionName ?: preferenceHelper.defaultItemSection - val requestBody = ProductInsightsAgentFocusRequestBody( + val section = request.resolvedSection() + val requestBody = ProductInsightsAgentItemRequestBody( itemId = request.itemId, itemName = request.itemName, variationId = request.variationId, threadId = request.threadId, - analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags) + analyticsTags = request.mergedAnalyticsTags() ) return dataManager.trackProductInsightsAgentFocus( @@ -2513,13 +2518,13 @@ object ConstructorIo { internal fun trackProductInsightsAgentOutOfViewInternal(request: ProductInsightsAgentOutOfViewTrackingData): Completable { preferenceHelper.getSessionId(sessionIncrementHandler) - val section = request.sectionName ?: preferenceHelper.defaultItemSection - val requestBody = ProductInsightsAgentOutOfViewRequestBody( + val section = request.resolvedSection() + val requestBody = ProductInsightsAgentItemRequestBody( itemId = request.itemId, itemName = request.itemName, variationId = request.variationId, threadId = request.threadId, - analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags) + analyticsTags = request.mergedAnalyticsTags() ) return dataManager.trackProductInsightsAgentOutOfView( @@ -2555,14 +2560,14 @@ object ConstructorIo { internal fun trackProductInsightsAgentViewInternal(request: ProductInsightsAgentViewTrackingData): Completable { preferenceHelper.getSessionId(sessionIncrementHandler) - val section = request.sectionName ?: preferenceHelper.defaultItemSection + val section = request.resolvedSection() val requestBody = ProductInsightsAgentViewRequestBody( itemId = request.itemId, questions = request.questions, itemName = request.itemName, variationId = request.variationId, threadId = request.threadId, - analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags) + analyticsTags = request.mergedAnalyticsTags() ) return dataManager.trackProductInsightsAgentView( @@ -2599,7 +2604,7 @@ object ConstructorIo { internal fun trackProductInsightsAgentViewsInternal(request: ProductInsightsAgentViewsTrackingData): Completable { preferenceHelper.getSessionId(sessionIncrementHandler) - val section = request.sectionName ?: preferenceHelper.defaultItemSection + val section = request.resolvedSection() val requestBody = ProductInsightsAgentViewsRequestBody( itemId = request.itemId, questions = request.questions, @@ -2609,7 +2614,7 @@ object ConstructorIo { itemName = request.itemName, variationId = request.variationId, threadId = request.threadId, - analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags) + analyticsTags = request.mergedAnalyticsTags() ) return dataManager.trackProductInsightsAgentViews( @@ -2642,15 +2647,15 @@ object ConstructorIo { internal fun trackProductInsightsAgentQuestionClickInternal(request: ProductInsightsAgentQuestionClickTrackingData): Completable { preferenceHelper.getSessionId(sessionIncrementHandler) - val section = request.sectionName ?: preferenceHelper.defaultItemSection - val requestBody = ProductInsightsAgentQuestionClickRequestBody( + val section = request.resolvedSection() + val requestBody = ProductInsightsAgentQuestionRequestBody( itemId = request.itemId, question = request.question, questionTopic = request.questionTopic, itemName = request.itemName, variationId = request.variationId, threadId = request.threadId, - analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags) + analyticsTags = request.mergedAnalyticsTags() ) return dataManager.trackProductInsightsAgentQuestionClick( @@ -2683,15 +2688,15 @@ object ConstructorIo { internal fun trackProductInsightsAgentQuestionSubmitInternal(request: ProductInsightsAgentQuestionSubmitTrackingData): Completable { preferenceHelper.getSessionId(sessionIncrementHandler) - val section = request.sectionName ?: preferenceHelper.defaultItemSection - val requestBody = ProductInsightsAgentQuestionSubmitRequestBody( + val section = request.resolvedSection() + val requestBody = ProductInsightsAgentQuestionRequestBody( itemId = request.itemId, question = request.question, questionTopic = request.questionTopic, itemName = request.itemName, variationId = request.variationId, threadId = request.threadId, - analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags) + analyticsTags = request.mergedAnalyticsTags() ) return dataManager.trackProductInsightsAgentQuestionSubmit( @@ -2724,7 +2729,7 @@ object ConstructorIo { internal fun trackProductInsightsAgentAnswerFeedbackInternal(request: ProductInsightsAgentAnswerFeedbackTrackingData): Completable { preferenceHelper.getSessionId(sessionIncrementHandler) - val section = request.sectionName ?: preferenceHelper.defaultItemSection + val section = request.resolvedSection() val requestBody = ProductInsightsAgentAnswerFeedbackRequestBody( itemId = request.itemId, feedbackLabel = request.feedbackLabel, @@ -2732,7 +2737,7 @@ object ConstructorIo { itemName = request.itemName, variationId = request.variationId, threadId = request.threadId, - analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags) + analyticsTags = request.mergedAnalyticsTags() ) return dataManager.trackProductInsightsAgentAnswerFeedback( @@ -2766,7 +2771,7 @@ object ConstructorIo { internal fun trackProductInsightsAgentResultClickInternal(request: ProductInsightsAgentResultClickTrackingData): Completable { preferenceHelper.getSessionId(sessionIncrementHandler) - val section = request.sectionName ?: preferenceHelper.defaultItemSection + val section = request.resolvedSection() val requestBody = ProductInsightsAgentResultClickRequestBody( itemId = request.itemId, question = request.question, @@ -2778,7 +2783,7 @@ object ConstructorIo { itemName = request.itemName, variationId = request.variationId, threadId = request.threadId, - analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags) + analyticsTags = request.mergedAnalyticsTags() ) return dataManager.trackProductInsightsAgentResultClick( @@ -2813,7 +2818,7 @@ object ConstructorIo { internal fun trackProductInsightsAgentAnswerViewInternal(request: ProductInsightsAgentAnswerViewTrackingData): Completable { preferenceHelper.getSessionId(sessionIncrementHandler) - val section = request.sectionName ?: preferenceHelper.defaultItemSection + val section = request.resolvedSection() val requestBody = ProductInsightsAgentAnswerViewRequestBody( itemId = request.itemId, question = request.question, @@ -2825,7 +2830,7 @@ object ConstructorIo { itemName = request.itemName, variationId = request.variationId, threadId = request.threadId, - analyticsTags = mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, request.analyticsTags) + analyticsTags = request.mergedAnalyticsTags() ) return dataManager.trackProductInsightsAgentAnswerView( diff --git a/library/src/main/java/io/constructor/data/DataManager.kt b/library/src/main/java/io/constructor/data/DataManager.kt index 5bf6dddb..ea158c95 100755 --- a/library/src/main/java/io/constructor/data/DataManager.kt +++ b/library/src/main/java/io/constructor/data/DataManager.kt @@ -4,7 +4,7 @@ import com.squareup.moshi.Moshi import io.constructor.data.local.PreferencesHelper import io.constructor.data.model.autocomplete.AutocompleteResponse import io.constructor.data.model.browse.* -import io.constructor.data.model.tracking.ItemDetailLoadRequestBody +import io.constructor.data.model.tracking.GenericResultClickRequestBody import io.constructor.data.model.conversion.ConversionRequestBody import io.constructor.data.model.purchase.PurchaseRequestBody import io.constructor.data.model.quiz.* @@ -12,14 +12,12 @@ import io.constructor.data.model.recommendations.RecommendationResultClickReques import io.constructor.data.model.recommendations.RecommendationResultViewRequestBody import io.constructor.data.model.recommendations.RecommendationsResponse import io.constructor.data.model.search.* -import io.constructor.data.model.tracking.GenericResultClickRequestBody +import io.constructor.data.model.tracking.ItemDetailLoadRequestBody import io.constructor.data.model.tracking.MediaImpressionRequestBody import io.constructor.data.model.tracking.ProductInsightsAgentAnswerFeedbackRequestBody import io.constructor.data.model.tracking.ProductInsightsAgentAnswerViewRequestBody -import io.constructor.data.model.tracking.ProductInsightsAgentFocusRequestBody -import io.constructor.data.model.tracking.ProductInsightsAgentOutOfViewRequestBody -import io.constructor.data.model.tracking.ProductInsightsAgentQuestionClickRequestBody -import io.constructor.data.model.tracking.ProductInsightsAgentQuestionSubmitRequestBody +import io.constructor.data.model.tracking.ProductInsightsAgentItemRequestBody +import io.constructor.data.model.tracking.ProductInsightsAgentQuestionRequestBody import io.constructor.data.model.tracking.ProductInsightsAgentResultClickRequestBody import io.constructor.data.model.tracking.ProductInsightsAgentViewRequestBody import io.constructor.data.model.tracking.ProductInsightsAgentViewsRequestBody @@ -404,11 +402,11 @@ constructor(private val constructorApi: ConstructorApi, @ConstructorSdk private return constructorApi.getQuizResultsCRT(url) } - fun trackProductInsightsAgentFocus(body: ProductInsightsAgentFocusRequestBody, params: Array> = arrayOf()): Completable { + fun trackProductInsightsAgentFocus(body: ProductInsightsAgentItemRequestBody, params: Array> = arrayOf()): Completable { return constructorApi.trackProductInsightsAgentFocus(body, params.toMap()) } - fun trackProductInsightsAgentOutOfView(body: ProductInsightsAgentOutOfViewRequestBody, params: Array> = arrayOf()): Completable { + fun trackProductInsightsAgentOutOfView(body: ProductInsightsAgentItemRequestBody, params: Array> = arrayOf()): Completable { return constructorApi.trackProductInsightsAgentOutOfView(body, params.toMap()) } @@ -420,11 +418,11 @@ constructor(private val constructorApi: ConstructorApi, @ConstructorSdk private return constructorApi.trackProductInsightsAgentViews(body, params.toMap()) } - fun trackProductInsightsAgentQuestionClick(body: ProductInsightsAgentQuestionClickRequestBody, params: Array> = arrayOf()): Completable { + fun trackProductInsightsAgentQuestionClick(body: ProductInsightsAgentQuestionRequestBody, params: Array> = arrayOf()): Completable { return constructorApi.trackProductInsightsAgentQuestionClick(body, params.toMap()) } - fun trackProductInsightsAgentQuestionSubmit(body: ProductInsightsAgentQuestionSubmitRequestBody, params: Array> = arrayOf()): Completable { + fun trackProductInsightsAgentQuestionSubmit(body: ProductInsightsAgentQuestionRequestBody, params: Array> = arrayOf()): Completable { return constructorApi.trackProductInsightsAgentQuestionSubmit(body, params.toMap()) } diff --git a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentAnswerFeedbackTrackingData.kt b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentAnswerFeedbackTrackingData.kt index be46ec5b..07a921e1 100644 --- a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentAnswerFeedbackTrackingData.kt +++ b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentAnswerFeedbackTrackingData.kt @@ -15,15 +15,15 @@ import io.constructor.data.model.tracking.ProductInsightsAgentFeedbackLabel * @property analyticsTags additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ class ProductInsightsAgentAnswerFeedbackTrackingData( - itemId: String, + override val itemId: String, val feedbackLabel: ProductInsightsAgentFeedbackLabel, val qnaResultId: String? = null, - itemName: String? = null, - variationId: String? = null, - threadId: String? = null, - sectionName: String? = null, - analyticsTags: Map? = null, -) : ProductInsightsAgentTrackingData(itemId, itemName, variationId, threadId, sectionName, analyticsTags) { + override val itemName: String? = null, + override val variationId: String? = null, + override val threadId: String? = null, + override val sectionName: String? = null, + override val analyticsTags: Map? = null, +) : ProductInsightsAgentTrackingData { private constructor(builder: Builder) : this( builder.itemId, builder.feedbackLabel, @@ -52,12 +52,32 @@ class ProductInsightsAgentAnswerFeedbackTrackingData( class Builder( val itemId: String, val feedbackLabel: ProductInsightsAgentFeedbackLabel - ) : ProductInsightsAgentTrackingData.Builder() { + ) { /** The result ID of the answer, as returned by the Product Insights Agent */ var qnaResultId: String? = null + /** The name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" */ + var itemName: String? = null + /** The variation ID of the item (product) the Product Insights Agent is displayed for */ + var variationId: String? = null + /** The ID of the Product Insights Agent conversation thread */ + var threadId: String? = null + /** The index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] */ + var sectionName: String? = null + /** Additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ + var analyticsTags: Map? = null /** Sets [qnaResultId] */ fun setQnaResultId(qnaResultId: String): Builder = apply { this.qnaResultId = qnaResultId } + /** Sets [itemName] */ + fun setItemName(itemName: String): Builder = apply { this.itemName = itemName } + /** Sets [variationId] */ + fun setVariationId(variationId: String): Builder = apply { this.variationId = variationId } + /** Sets [threadId] */ + fun setThreadId(threadId: String): Builder = apply { this.threadId = threadId } + /** Sets [sectionName] */ + fun setSectionName(sectionName: String): Builder = apply { this.sectionName = sectionName } + /** Sets [analyticsTags] */ + fun setAnalyticsTags(analyticsTags: Map): Builder = apply { this.analyticsTags = analyticsTags } /** Builds the [ProductInsightsAgentAnswerFeedbackTrackingData] */ fun build(): ProductInsightsAgentAnswerFeedbackTrackingData = ProductInsightsAgentAnswerFeedbackTrackingData(this) diff --git a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentAnswerViewTrackingData.kt b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentAnswerViewTrackingData.kt index 07c8e954..66cd3c64 100644 --- a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentAnswerViewTrackingData.kt +++ b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentAnswerViewTrackingData.kt @@ -19,19 +19,19 @@ import io.constructor.data.model.common.TrackingItem * @property analyticsTags additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ class ProductInsightsAgentAnswerViewTrackingData( - itemId: String, + override val itemId: String, val question: String, val answerText: String, val questionTopic: String? = null, val qnaResultId: String? = null, val items: List? = null, val followUpQuestions: List? = null, - itemName: String? = null, - variationId: String? = null, - threadId: String? = null, - sectionName: String? = null, - analyticsTags: Map? = null, -) : ProductInsightsAgentTrackingData(itemId, itemName, variationId, threadId, sectionName, analyticsTags) { + override val itemName: String? = null, + override val variationId: String? = null, + override val threadId: String? = null, + override val sectionName: String? = null, + override val analyticsTags: Map? = null, +) : ProductInsightsAgentTrackingData { private constructor(builder: Builder) : this( builder.itemId, builder.question, @@ -66,7 +66,7 @@ class ProductInsightsAgentAnswerViewTrackingData( val itemId: String, val question: String, val answerText: String - ) : ProductInsightsAgentTrackingData.Builder() { + ) { /** The topic the question belongs to, i.e. "Durability" */ var questionTopic: String? = null /** The result ID of the answer, as returned by the Product Insights Agent */ @@ -75,6 +75,16 @@ class ProductInsightsAgentAnswerViewTrackingData( var items: List? = null /** The follow-up questions shown with the answer. Only the first 100 are sent */ var followUpQuestions: List? = null + /** The name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" */ + var itemName: String? = null + /** The variation ID of the item (product) the Product Insights Agent is displayed for */ + var variationId: String? = null + /** The ID of the Product Insights Agent conversation thread */ + var threadId: String? = null + /** The index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] */ + var sectionName: String? = null + /** Additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ + var analyticsTags: Map? = null /** Sets [questionTopic] */ fun setQuestionTopic(questionTopic: String): Builder = apply { this.questionTopic = questionTopic } @@ -84,6 +94,16 @@ class ProductInsightsAgentAnswerViewTrackingData( fun setItems(items: List): Builder = apply { this.items = items } /** Sets [followUpQuestions] */ fun setFollowUpQuestions(followUpQuestions: List): Builder = apply { this.followUpQuestions = followUpQuestions } + /** Sets [itemName] */ + fun setItemName(itemName: String): Builder = apply { this.itemName = itemName } + /** Sets [variationId] */ + fun setVariationId(variationId: String): Builder = apply { this.variationId = variationId } + /** Sets [threadId] */ + fun setThreadId(threadId: String): Builder = apply { this.threadId = threadId } + /** Sets [sectionName] */ + fun setSectionName(sectionName: String): Builder = apply { this.sectionName = sectionName } + /** Sets [analyticsTags] */ + fun setAnalyticsTags(analyticsTags: Map): Builder = apply { this.analyticsTags = analyticsTags } /** Builds the [ProductInsightsAgentAnswerViewTrackingData] */ fun build(): ProductInsightsAgentAnswerViewTrackingData = ProductInsightsAgentAnswerViewTrackingData(this) diff --git a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentFocusTrackingData.kt b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentFocusTrackingData.kt index 6bbc3a26..0ae6a70c 100644 --- a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentFocusTrackingData.kt +++ b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentFocusTrackingData.kt @@ -11,13 +11,13 @@ package io.constructor.data.builder * @property analyticsTags additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ class ProductInsightsAgentFocusTrackingData( - itemId: String, - itemName: String? = null, - variationId: String? = null, - threadId: String? = null, - sectionName: String? = null, - analyticsTags: Map? = null, -) : ProductInsightsAgentTrackingData(itemId, itemName, variationId, threadId, sectionName, analyticsTags) { + override val itemId: String, + override val itemName: String? = null, + override val variationId: String? = null, + override val threadId: String? = null, + override val sectionName: String? = null, + override val analyticsTags: Map? = null, +) : ProductInsightsAgentTrackingData { private constructor(builder: Builder) : this( builder.itemId, builder.itemName, @@ -42,7 +42,29 @@ class ProductInsightsAgentFocusTrackingData( */ class Builder( val itemId: String - ) : ProductInsightsAgentTrackingData.Builder() { + ) { + /** The name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" */ + var itemName: String? = null + /** The variation ID of the item (product) the Product Insights Agent is displayed for */ + var variationId: String? = null + /** The ID of the Product Insights Agent conversation thread */ + var threadId: String? = null + /** The index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] */ + var sectionName: String? = null + /** Additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ + var analyticsTags: Map? = null + + /** Sets [itemName] */ + fun setItemName(itemName: String): Builder = apply { this.itemName = itemName } + /** Sets [variationId] */ + fun setVariationId(variationId: String): Builder = apply { this.variationId = variationId } + /** Sets [threadId] */ + fun setThreadId(threadId: String): Builder = apply { this.threadId = threadId } + /** Sets [sectionName] */ + fun setSectionName(sectionName: String): Builder = apply { this.sectionName = sectionName } + /** Sets [analyticsTags] */ + fun setAnalyticsTags(analyticsTags: Map): Builder = apply { this.analyticsTags = analyticsTags } + /** Builds the [ProductInsightsAgentFocusTrackingData] */ fun build(): ProductInsightsAgentFocusTrackingData = ProductInsightsAgentFocusTrackingData(this) } diff --git a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentOutOfViewTrackingData.kt b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentOutOfViewTrackingData.kt index bcf27320..6380c386 100644 --- a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentOutOfViewTrackingData.kt +++ b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentOutOfViewTrackingData.kt @@ -11,13 +11,13 @@ package io.constructor.data.builder * @property analyticsTags additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ class ProductInsightsAgentOutOfViewTrackingData( - itemId: String, - itemName: String? = null, - variationId: String? = null, - threadId: String? = null, - sectionName: String? = null, - analyticsTags: Map? = null, -) : ProductInsightsAgentTrackingData(itemId, itemName, variationId, threadId, sectionName, analyticsTags) { + override val itemId: String, + override val itemName: String? = null, + override val variationId: String? = null, + override val threadId: String? = null, + override val sectionName: String? = null, + override val analyticsTags: Map? = null, +) : ProductInsightsAgentTrackingData { private constructor(builder: Builder) : this( builder.itemId, builder.itemName, @@ -42,7 +42,29 @@ class ProductInsightsAgentOutOfViewTrackingData( */ class Builder( val itemId: String - ) : ProductInsightsAgentTrackingData.Builder() { + ) { + /** The name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" */ + var itemName: String? = null + /** The variation ID of the item (product) the Product Insights Agent is displayed for */ + var variationId: String? = null + /** The ID of the Product Insights Agent conversation thread */ + var threadId: String? = null + /** The index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] */ + var sectionName: String? = null + /** Additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ + var analyticsTags: Map? = null + + /** Sets [itemName] */ + fun setItemName(itemName: String): Builder = apply { this.itemName = itemName } + /** Sets [variationId] */ + fun setVariationId(variationId: String): Builder = apply { this.variationId = variationId } + /** Sets [threadId] */ + fun setThreadId(threadId: String): Builder = apply { this.threadId = threadId } + /** Sets [sectionName] */ + fun setSectionName(sectionName: String): Builder = apply { this.sectionName = sectionName } + /** Sets [analyticsTags] */ + fun setAnalyticsTags(analyticsTags: Map): Builder = apply { this.analyticsTags = analyticsTags } + /** Builds the [ProductInsightsAgentOutOfViewTrackingData] */ fun build(): ProductInsightsAgentOutOfViewTrackingData = ProductInsightsAgentOutOfViewTrackingData(this) } diff --git a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentQuestionClickTrackingData.kt b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentQuestionClickTrackingData.kt index 8d61a470..f875508b 100644 --- a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentQuestionClickTrackingData.kt +++ b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentQuestionClickTrackingData.kt @@ -13,15 +13,15 @@ package io.constructor.data.builder * @property analyticsTags additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ class ProductInsightsAgentQuestionClickTrackingData( - itemId: String, + override val itemId: String, val question: String, val questionTopic: String? = null, - itemName: String? = null, - variationId: String? = null, - threadId: String? = null, - sectionName: String? = null, - analyticsTags: Map? = null, -) : ProductInsightsAgentTrackingData(itemId, itemName, variationId, threadId, sectionName, analyticsTags) { + override val itemName: String? = null, + override val variationId: String? = null, + override val threadId: String? = null, + override val sectionName: String? = null, + override val analyticsTags: Map? = null, +) : ProductInsightsAgentTrackingData { private constructor(builder: Builder) : this( builder.itemId, builder.question, @@ -50,12 +50,32 @@ class ProductInsightsAgentQuestionClickTrackingData( class Builder( val itemId: String, val question: String - ) : ProductInsightsAgentTrackingData.Builder() { + ) { /** The topic the question belongs to, i.e. "Durability" */ var questionTopic: String? = null + /** The name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" */ + var itemName: String? = null + /** The variation ID of the item (product) the Product Insights Agent is displayed for */ + var variationId: String? = null + /** The ID of the Product Insights Agent conversation thread */ + var threadId: String? = null + /** The index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] */ + var sectionName: String? = null + /** Additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ + var analyticsTags: Map? = null /** Sets [questionTopic] */ fun setQuestionTopic(questionTopic: String): Builder = apply { this.questionTopic = questionTopic } + /** Sets [itemName] */ + fun setItemName(itemName: String): Builder = apply { this.itemName = itemName } + /** Sets [variationId] */ + fun setVariationId(variationId: String): Builder = apply { this.variationId = variationId } + /** Sets [threadId] */ + fun setThreadId(threadId: String): Builder = apply { this.threadId = threadId } + /** Sets [sectionName] */ + fun setSectionName(sectionName: String): Builder = apply { this.sectionName = sectionName } + /** Sets [analyticsTags] */ + fun setAnalyticsTags(analyticsTags: Map): Builder = apply { this.analyticsTags = analyticsTags } /** Builds the [ProductInsightsAgentQuestionClickTrackingData] */ fun build(): ProductInsightsAgentQuestionClickTrackingData = ProductInsightsAgentQuestionClickTrackingData(this) diff --git a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentQuestionSubmitTrackingData.kt b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentQuestionSubmitTrackingData.kt index e85af6d1..e3eb4c7f 100644 --- a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentQuestionSubmitTrackingData.kt +++ b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentQuestionSubmitTrackingData.kt @@ -13,15 +13,15 @@ package io.constructor.data.builder * @property analyticsTags additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ class ProductInsightsAgentQuestionSubmitTrackingData( - itemId: String, + override val itemId: String, val question: String, val questionTopic: String? = null, - itemName: String? = null, - variationId: String? = null, - threadId: String? = null, - sectionName: String? = null, - analyticsTags: Map? = null, -) : ProductInsightsAgentTrackingData(itemId, itemName, variationId, threadId, sectionName, analyticsTags) { + override val itemName: String? = null, + override val variationId: String? = null, + override val threadId: String? = null, + override val sectionName: String? = null, + override val analyticsTags: Map? = null, +) : ProductInsightsAgentTrackingData { private constructor(builder: Builder) : this( builder.itemId, builder.question, @@ -50,12 +50,32 @@ class ProductInsightsAgentQuestionSubmitTrackingData( class Builder( val itemId: String, val question: String - ) : ProductInsightsAgentTrackingData.Builder() { + ) { /** The topic the question belongs to, i.e. "Durability" */ var questionTopic: String? = null + /** The name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" */ + var itemName: String? = null + /** The variation ID of the item (product) the Product Insights Agent is displayed for */ + var variationId: String? = null + /** The ID of the Product Insights Agent conversation thread */ + var threadId: String? = null + /** The index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] */ + var sectionName: String? = null + /** Additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ + var analyticsTags: Map? = null /** Sets [questionTopic] */ fun setQuestionTopic(questionTopic: String): Builder = apply { this.questionTopic = questionTopic } + /** Sets [itemName] */ + fun setItemName(itemName: String): Builder = apply { this.itemName = itemName } + /** Sets [variationId] */ + fun setVariationId(variationId: String): Builder = apply { this.variationId = variationId } + /** Sets [threadId] */ + fun setThreadId(threadId: String): Builder = apply { this.threadId = threadId } + /** Sets [sectionName] */ + fun setSectionName(sectionName: String): Builder = apply { this.sectionName = sectionName } + /** Sets [analyticsTags] */ + fun setAnalyticsTags(analyticsTags: Map): Builder = apply { this.analyticsTags = analyticsTags } /** Builds the [ProductInsightsAgentQuestionSubmitTrackingData] */ fun build(): ProductInsightsAgentQuestionSubmitTrackingData = ProductInsightsAgentQuestionSubmitTrackingData(this) diff --git a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentResultClickTrackingData.kt b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentResultClickTrackingData.kt index 11561899..e38efde7 100644 --- a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentResultClickTrackingData.kt +++ b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentResultClickTrackingData.kt @@ -17,19 +17,19 @@ package io.constructor.data.builder * @property analyticsTags additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ class ProductInsightsAgentResultClickTrackingData( - itemId: String, + override val itemId: String, val question: String, val seedItemId: String, val position: Int? = null, val seedItemName: String? = null, val seedVariationId: String? = null, val qnaResultId: String? = null, - itemName: String? = null, - variationId: String? = null, - threadId: String? = null, - sectionName: String? = null, - analyticsTags: Map? = null, -) : ProductInsightsAgentTrackingData(itemId, itemName, variationId, threadId, sectionName, analyticsTags) { + override val itemName: String? = null, + override val variationId: String? = null, + override val threadId: String? = null, + override val sectionName: String? = null, + override val analyticsTags: Map? = null, +) : ProductInsightsAgentTrackingData { private constructor(builder: Builder) : this( builder.itemId, builder.question, @@ -64,7 +64,7 @@ class ProductInsightsAgentResultClickTrackingData( val itemId: String, val question: String, val seedItemId: String - ) : ProductInsightsAgentTrackingData.Builder() { + ) { /** The zero-based position of the clicked item within the answer */ var position: Int? = null /** The name of the item (product) the Product Insights Agent is displayed for */ @@ -73,6 +73,16 @@ class ProductInsightsAgentResultClickTrackingData( var seedVariationId: String? = null /** The result ID of the answer, as returned by the Product Insights Agent */ var qnaResultId: String? = null + /** The name of the clicked item (product) recommended in the answer */ + var itemName: String? = null + /** The variation ID of the clicked item (product) recommended in the answer */ + var variationId: String? = null + /** The ID of the Product Insights Agent conversation thread */ + var threadId: String? = null + /** The index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] */ + var sectionName: String? = null + /** Additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ + var analyticsTags: Map? = null /** Sets [position] */ fun setPosition(position: Int): Builder = apply { this.position = position } @@ -82,6 +92,16 @@ class ProductInsightsAgentResultClickTrackingData( fun setSeedVariationId(seedVariationId: String): Builder = apply { this.seedVariationId = seedVariationId } /** Sets [qnaResultId] */ fun setQnaResultId(qnaResultId: String): Builder = apply { this.qnaResultId = qnaResultId } + /** Sets [itemName] */ + fun setItemName(itemName: String): Builder = apply { this.itemName = itemName } + /** Sets [variationId] */ + fun setVariationId(variationId: String): Builder = apply { this.variationId = variationId } + /** Sets [threadId] */ + fun setThreadId(threadId: String): Builder = apply { this.threadId = threadId } + /** Sets [sectionName] */ + fun setSectionName(sectionName: String): Builder = apply { this.sectionName = sectionName } + /** Sets [analyticsTags] */ + fun setAnalyticsTags(analyticsTags: Map): Builder = apply { this.analyticsTags = analyticsTags } /** Builds the [ProductInsightsAgentResultClickTrackingData] */ fun build(): ProductInsightsAgentResultClickTrackingData = ProductInsightsAgentResultClickTrackingData(this) diff --git a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentTrackingData.kt b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentTrackingData.kt index bc8067ca..d4293ed7 100644 --- a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentTrackingData.kt +++ b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentTrackingData.kt @@ -1,52 +1,20 @@ package io.constructor.data.builder /** - * Base class for the Product Insights Agent tracking request objects, holding the parameters shared by every event + * The parameters shared by every Product Insights Agent tracking request object * * @property itemId the ID of the item (product) the event is for, i.e. "1234567-AB" - * @property itemName the name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" - * @property variationId the variation ID of the item (product) the Product Insights Agent is displayed for + * @property itemName the name of the item (product) the event is for, i.e. "Waterproof Jacket" + * @property variationId the variation ID of the item (product) the event is for * @property threadId the ID of the Product Insights Agent conversation thread * @property sectionName the index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] * @property analyticsTags additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ -abstract class ProductInsightsAgentTrackingData( - val itemId: String, - val itemName: String?, - val variationId: String?, - val threadId: String?, - val sectionName: String?, - val analyticsTags: Map?, -) { - /** - * Base builder holding the optional parameters shared by every Product Insights Agent event - */ - abstract class Builder> { - /** The name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" */ - var itemName: String? = null - /** The variation ID of the item (product) the Product Insights Agent is displayed for */ - var variationId: String? = null - /** The ID of the Product Insights Agent conversation thread */ - var threadId: String? = null - /** The index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] */ - var sectionName: String? = null - /** Additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ - var analyticsTags: Map? = null - - /** Sets [itemName] */ - @Suppress("UNCHECKED_CAST") - fun setItemName(itemName: String): B = apply { this.itemName = itemName } as B - /** Sets [variationId] */ - @Suppress("UNCHECKED_CAST") - fun setVariationId(variationId: String): B = apply { this.variationId = variationId } as B - /** Sets [threadId] */ - @Suppress("UNCHECKED_CAST") - fun setThreadId(threadId: String): B = apply { this.threadId = threadId } as B - /** Sets [sectionName] */ - @Suppress("UNCHECKED_CAST") - fun setSectionName(sectionName: String): B = apply { this.sectionName = sectionName } as B - /** Sets [analyticsTags] */ - @Suppress("UNCHECKED_CAST") - fun setAnalyticsTags(analyticsTags: Map): B = apply { this.analyticsTags = analyticsTags } as B - } +interface ProductInsightsAgentTrackingData { + val itemId: String + val itemName: String? + val variationId: String? + val threadId: String? + val sectionName: String? + val analyticsTags: Map? } diff --git a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentViewTrackingData.kt b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentViewTrackingData.kt index 8f30f79f..5ac65918 100644 --- a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentViewTrackingData.kt +++ b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentViewTrackingData.kt @@ -14,14 +14,14 @@ import io.constructor.data.model.tracking.ProductInsightsAgentQuestion * @property analyticsTags additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ class ProductInsightsAgentViewTrackingData( - itemId: String, + override val itemId: String, val questions: List, - itemName: String? = null, - variationId: String? = null, - threadId: String? = null, - sectionName: String? = null, - analyticsTags: Map? = null, -) : ProductInsightsAgentTrackingData(itemId, itemName, variationId, threadId, sectionName, analyticsTags) { + override val itemName: String? = null, + override val variationId: String? = null, + override val threadId: String? = null, + override val sectionName: String? = null, + override val analyticsTags: Map? = null, +) : ProductInsightsAgentTrackingData { private constructor(builder: Builder) : this( builder.itemId, builder.questions, @@ -49,7 +49,29 @@ class ProductInsightsAgentViewTrackingData( class Builder( val itemId: String, val questions: List - ) : ProductInsightsAgentTrackingData.Builder() { + ) { + /** The name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" */ + var itemName: String? = null + /** The variation ID of the item (product) the Product Insights Agent is displayed for */ + var variationId: String? = null + /** The ID of the Product Insights Agent conversation thread */ + var threadId: String? = null + /** The index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] */ + var sectionName: String? = null + /** Additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ + var analyticsTags: Map? = null + + /** Sets [itemName] */ + fun setItemName(itemName: String): Builder = apply { this.itemName = itemName } + /** Sets [variationId] */ + fun setVariationId(variationId: String): Builder = apply { this.variationId = variationId } + /** Sets [threadId] */ + fun setThreadId(threadId: String): Builder = apply { this.threadId = threadId } + /** Sets [sectionName] */ + fun setSectionName(sectionName: String): Builder = apply { this.sectionName = sectionName } + /** Sets [analyticsTags] */ + fun setAnalyticsTags(analyticsTags: Map): Builder = apply { this.analyticsTags = analyticsTags } + /** Builds the [ProductInsightsAgentViewTrackingData] */ fun build(): ProductInsightsAgentViewTrackingData = ProductInsightsAgentViewTrackingData(this) } diff --git a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentViewsTrackingData.kt b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentViewsTrackingData.kt index 8e6017d4..a9d37dc9 100644 --- a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentViewsTrackingData.kt +++ b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentViewsTrackingData.kt @@ -16,15 +16,15 @@ import io.constructor.data.model.tracking.ProductInsightsAgentViewTimespan * @property analyticsTags additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ class ProductInsightsAgentViewsTrackingData( - itemId: String, + override val itemId: String, val questions: List, val viewTimespans: List, - itemName: String? = null, - variationId: String? = null, - threadId: String? = null, - sectionName: String? = null, - analyticsTags: Map? = null, -) : ProductInsightsAgentTrackingData(itemId, itemName, variationId, threadId, sectionName, analyticsTags) { + override val itemName: String? = null, + override val variationId: String? = null, + override val threadId: String? = null, + override val sectionName: String? = null, + override val analyticsTags: Map? = null, +) : ProductInsightsAgentTrackingData { private constructor(builder: Builder) : this( builder.itemId, builder.questions, @@ -55,7 +55,29 @@ class ProductInsightsAgentViewsTrackingData( val itemId: String, val questions: List, val viewTimespans: List - ) : ProductInsightsAgentTrackingData.Builder() { + ) { + /** The name of the item (product) the Product Insights Agent is displayed for, i.e. "Waterproof Jacket" */ + var itemName: String? = null + /** The variation ID of the item (product) the Product Insights Agent is displayed for */ + var variationId: String? = null + /** The ID of the Product Insights Agent conversation thread */ + var threadId: String? = null + /** The index section, i.e. "Products". Defaults to [io.constructor.core.ConstructorIoConfig.defaultItemSection] */ + var sectionName: String? = null + /** Additional analytics tags to pass. Merged with [io.constructor.core.ConstructorIoConfig.defaultAnalyticsTags], with request-level values overriding defaults on key collision */ + var analyticsTags: Map? = null + + /** Sets [itemName] */ + fun setItemName(itemName: String): Builder = apply { this.itemName = itemName } + /** Sets [variationId] */ + fun setVariationId(variationId: String): Builder = apply { this.variationId = variationId } + /** Sets [threadId] */ + fun setThreadId(threadId: String): Builder = apply { this.threadId = threadId } + /** Sets [sectionName] */ + fun setSectionName(sectionName: String): Builder = apply { this.sectionName = sectionName } + /** Sets [analyticsTags] */ + fun setAnalyticsTags(analyticsTags: Map): Builder = apply { this.analyticsTags = analyticsTags } + /** Builds the [ProductInsightsAgentViewsTrackingData] */ fun build(): ProductInsightsAgentViewsTrackingData = ProductInsightsAgentViewsTrackingData(this) } diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentAnswerFeedbackRequestBody.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentAnswerFeedbackRequestBody.kt index c41f9a39..7305a4d1 100644 --- a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentAnswerFeedbackRequestBody.kt +++ b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentAnswerFeedbackRequestBody.kt @@ -2,17 +2,18 @@ package io.constructor.data.model.tracking import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import java.io.Serializable /** * @suppress */ @JsonClass(generateAdapter = true) -class ProductInsightsAgentAnswerFeedbackRequestBody( - itemId: String, - @Json(name = "feedback_label") val feedbackLabel: ProductInsightsAgentFeedbackLabel, - @Json(name = "qna_result_id") val qnaResultId: String?, - itemName: String?, - variationId: String?, - threadId: String?, - analyticsTags: Map?, -) : ProductInsightsAgentRequestBody(itemId, itemName, variationId, threadId, analyticsTags) +data class ProductInsightsAgentAnswerFeedbackRequestBody( + @Json(name = "item_id") val itemId: String, + @Json(name = "feedback_label") val feedbackLabel: ProductInsightsAgentFeedbackLabel, + @Json(name = "qna_result_id") val qnaResultId: String?, + @Json(name = "item_name") val itemName: String?, + @Json(name = "variation_id") val variationId: String?, + @Json(name = "thread_id") val threadId: String?, + @Json(name = "analytics_tags") val analyticsTags: Map? +) : Serializable diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentAnswerViewRequestBody.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentAnswerViewRequestBody.kt index 480d6aca..a2d58898 100644 --- a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentAnswerViewRequestBody.kt +++ b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentAnswerViewRequestBody.kt @@ -3,21 +3,22 @@ package io.constructor.data.model.tracking import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import io.constructor.data.model.common.TrackingItem +import java.io.Serializable /** * @suppress */ @JsonClass(generateAdapter = true) -class ProductInsightsAgentAnswerViewRequestBody( - itemId: String, - @Json(name = "question") val question: String, - @Json(name = "answer_text") val answerText: String, - @Json(name = "question_topic") val questionTopic: String?, - @Json(name = "qna_result_id") val qnaResultId: String?, - @Json(name = "items") val items: List?, - @Json(name = "follow_up_questions") val followUpQuestions: List?, - itemName: String?, - variationId: String?, - threadId: String?, - analyticsTags: Map?, -) : ProductInsightsAgentRequestBody(itemId, itemName, variationId, threadId, analyticsTags) +data class ProductInsightsAgentAnswerViewRequestBody( + @Json(name = "item_id") val itemId: String, + @Json(name = "question") val question: String, + @Json(name = "answer_text") val answerText: String, + @Json(name = "question_topic") val questionTopic: String?, + @Json(name = "qna_result_id") val qnaResultId: String?, + @Json(name = "items") val items: List?, + @Json(name = "follow_up_questions") val followUpQuestions: List?, + @Json(name = "item_name") val itemName: String?, + @Json(name = "variation_id") val variationId: String?, + @Json(name = "thread_id") val threadId: String?, + @Json(name = "analytics_tags") val analyticsTags: Map? +) : Serializable diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentFocusRequestBody.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentFocusRequestBody.kt deleted file mode 100644 index 1003c2c2..00000000 --- a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentFocusRequestBody.kt +++ /dev/null @@ -1,15 +0,0 @@ -package io.constructor.data.model.tracking - -import com.squareup.moshi.JsonClass - -/** - * @suppress - */ -@JsonClass(generateAdapter = true) -class ProductInsightsAgentFocusRequestBody( - itemId: String, - itemName: String?, - variationId: String?, - threadId: String?, - analyticsTags: Map?, -) : ProductInsightsAgentRequestBody(itemId, itemName, variationId, threadId, analyticsTags) diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentItemRequestBody.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentItemRequestBody.kt new file mode 100644 index 00000000..9f398d03 --- /dev/null +++ b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentItemRequestBody.kt @@ -0,0 +1,18 @@ +package io.constructor.data.model.tracking + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import java.io.Serializable + +/** + * @suppress + * Shared by the Product Insights Agent focus and out of view events, which send the same fields + */ +@JsonClass(generateAdapter = true) +data class ProductInsightsAgentItemRequestBody( + @Json(name = "item_id") val itemId: String, + @Json(name = "item_name") val itemName: String?, + @Json(name = "variation_id") val variationId: String?, + @Json(name = "thread_id") val threadId: String?, + @Json(name = "analytics_tags") val analyticsTags: Map? +) : Serializable diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentOutOfViewRequestBody.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentOutOfViewRequestBody.kt deleted file mode 100644 index d42216ad..00000000 --- a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentOutOfViewRequestBody.kt +++ /dev/null @@ -1,15 +0,0 @@ -package io.constructor.data.model.tracking - -import com.squareup.moshi.JsonClass - -/** - * @suppress - */ -@JsonClass(generateAdapter = true) -class ProductInsightsAgentOutOfViewRequestBody( - itemId: String, - itemName: String?, - variationId: String?, - threadId: String?, - analyticsTags: Map?, -) : ProductInsightsAgentRequestBody(itemId, itemName, variationId, threadId, analyticsTags) diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentQuestionClickRequestBody.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentQuestionClickRequestBody.kt deleted file mode 100644 index e80da168..00000000 --- a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentQuestionClickRequestBody.kt +++ /dev/null @@ -1,18 +0,0 @@ -package io.constructor.data.model.tracking - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass - -/** - * @suppress - */ -@JsonClass(generateAdapter = true) -class ProductInsightsAgentQuestionClickRequestBody( - itemId: String, - @Json(name = "question") val question: String, - @Json(name = "question_topic") val questionTopic: String?, - itemName: String?, - variationId: String?, - threadId: String?, - analyticsTags: Map?, -) : ProductInsightsAgentRequestBody(itemId, itemName, variationId, threadId, analyticsTags) diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentQuestionRequestBody.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentQuestionRequestBody.kt new file mode 100644 index 00000000..66091f58 --- /dev/null +++ b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentQuestionRequestBody.kt @@ -0,0 +1,20 @@ +package io.constructor.data.model.tracking + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import java.io.Serializable + +/** + * @suppress + * Shared by the Product Insights Agent question click and question submit events, which send the same fields + */ +@JsonClass(generateAdapter = true) +data class ProductInsightsAgentQuestionRequestBody( + @Json(name = "item_id") val itemId: String, + @Json(name = "question") val question: String, + @Json(name = "question_topic") val questionTopic: String?, + @Json(name = "item_name") val itemName: String?, + @Json(name = "variation_id") val variationId: String?, + @Json(name = "thread_id") val threadId: String?, + @Json(name = "analytics_tags") val analyticsTags: Map? +) : Serializable diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentQuestionSubmitRequestBody.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentQuestionSubmitRequestBody.kt deleted file mode 100644 index d94028fb..00000000 --- a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentQuestionSubmitRequestBody.kt +++ /dev/null @@ -1,18 +0,0 @@ -package io.constructor.data.model.tracking - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass - -/** - * @suppress - */ -@JsonClass(generateAdapter = true) -class ProductInsightsAgentQuestionSubmitRequestBody( - itemId: String, - @Json(name = "question") val question: String, - @Json(name = "question_topic") val questionTopic: String?, - itemName: String?, - variationId: String?, - threadId: String?, - analyticsTags: Map?, -) : ProductInsightsAgentRequestBody(itemId, itemName, variationId, threadId, analyticsTags) diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentRequestBody.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentRequestBody.kt deleted file mode 100644 index 93f6155b..00000000 --- a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentRequestBody.kt +++ /dev/null @@ -1,23 +0,0 @@ -package io.constructor.data.model.tracking - -import com.squareup.moshi.Json -import java.io.Serializable - -/** - * @suppress - * Base request body holding the fields shared by every Product Insights Agent event. The common - * client parameters (c, i, s, key, ui, us, _dt) are added as query parameters by RequestInterceptor. - */ -abstract class ProductInsightsAgentRequestBody( - itemId: String, - itemName: String?, - variationId: String?, - threadId: String?, - analyticsTags: Map?, -) : Serializable { - @Json(name = "item_id") val itemId: String = itemId - @Json(name = "item_name") val itemName: String? = itemName - @Json(name = "variation_id") val variationId: String? = variationId - @Json(name = "thread_id") val threadId: String? = threadId - @Json(name = "analytics_tags") val analyticsTags: Map? = analyticsTags -} diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentResultClickRequestBody.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentResultClickRequestBody.kt index 1711c745..8002e285 100644 --- a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentResultClickRequestBody.kt +++ b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentResultClickRequestBody.kt @@ -2,21 +2,22 @@ package io.constructor.data.model.tracking import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import java.io.Serializable /** * @suppress */ @JsonClass(generateAdapter = true) -class ProductInsightsAgentResultClickRequestBody( - itemId: String, - @Json(name = "question") val question: String, - @Json(name = "seed_item_id") val seedItemId: String, - @Json(name = "position") val position: Int?, - @Json(name = "seed_item_name") val seedItemName: String?, - @Json(name = "seed_variation_id") val seedVariationId: String?, - @Json(name = "qna_result_id") val qnaResultId: String?, - itemName: String?, - variationId: String?, - threadId: String?, - analyticsTags: Map?, -) : ProductInsightsAgentRequestBody(itemId, itemName, variationId, threadId, analyticsTags) +data class ProductInsightsAgentResultClickRequestBody( + @Json(name = "item_id") val itemId: String, + @Json(name = "question") val question: String, + @Json(name = "seed_item_id") val seedItemId: String, + @Json(name = "position") val position: Int?, + @Json(name = "seed_item_name") val seedItemName: String?, + @Json(name = "seed_variation_id") val seedVariationId: String?, + @Json(name = "qna_result_id") val qnaResultId: String?, + @Json(name = "item_name") val itemName: String?, + @Json(name = "variation_id") val variationId: String?, + @Json(name = "thread_id") val threadId: String?, + @Json(name = "analytics_tags") val analyticsTags: Map? +) : Serializable diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentViewRequestBody.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentViewRequestBody.kt index 63bff392..f98888f8 100644 --- a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentViewRequestBody.kt +++ b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentViewRequestBody.kt @@ -2,16 +2,17 @@ package io.constructor.data.model.tracking import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import java.io.Serializable /** * @suppress */ @JsonClass(generateAdapter = true) -class ProductInsightsAgentViewRequestBody( - itemId: String, - @Json(name = "questions") val questions: List, - itemName: String?, - variationId: String?, - threadId: String?, - analyticsTags: Map?, -) : ProductInsightsAgentRequestBody(itemId, itemName, variationId, threadId, analyticsTags) +data class ProductInsightsAgentViewRequestBody( + @Json(name = "item_id") val itemId: String, + @Json(name = "questions") val questions: List, + @Json(name = "item_name") val itemName: String?, + @Json(name = "variation_id") val variationId: String?, + @Json(name = "thread_id") val threadId: String?, + @Json(name = "analytics_tags") val analyticsTags: Map? +) : Serializable diff --git a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentViewsRequestBody.kt b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentViewsRequestBody.kt index 6587dd46..f1c7f37f 100644 --- a/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentViewsRequestBody.kt +++ b/library/src/main/java/io/constructor/data/model/tracking/ProductInsightsAgentViewsRequestBody.kt @@ -2,17 +2,18 @@ package io.constructor.data.model.tracking import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import java.io.Serializable /** * @suppress */ @JsonClass(generateAdapter = true) -class ProductInsightsAgentViewsRequestBody( - itemId: String, - @Json(name = "questions") val questions: List, - @Json(name = "view_timespans") val viewTimespans: List, - itemName: String?, - variationId: String?, - threadId: String?, - analyticsTags: Map?, -) : ProductInsightsAgentRequestBody(itemId, itemName, variationId, threadId, analyticsTags) +data class ProductInsightsAgentViewsRequestBody( + @Json(name = "item_id") val itemId: String, + @Json(name = "questions") val questions: List, + @Json(name = "view_timespans") val viewTimespans: List, + @Json(name = "item_name") val itemName: String?, + @Json(name = "variation_id") val variationId: String?, + @Json(name = "thread_id") val threadId: String?, + @Json(name = "analytics_tags") val analyticsTags: Map? +) : Serializable diff --git a/library/src/main/java/io/constructor/data/remote/ConstructorApi.kt b/library/src/main/java/io/constructor/data/remote/ConstructorApi.kt index 4a118d23..aa9ff818 100755 --- a/library/src/main/java/io/constructor/data/remote/ConstructorApi.kt +++ b/library/src/main/java/io/constructor/data/remote/ConstructorApi.kt @@ -7,10 +7,8 @@ import io.constructor.data.model.tracking.ItemDetailLoadRequestBody import io.constructor.data.model.tracking.MediaImpressionRequestBody import io.constructor.data.model.tracking.ProductInsightsAgentAnswerFeedbackRequestBody import io.constructor.data.model.tracking.ProductInsightsAgentAnswerViewRequestBody -import io.constructor.data.model.tracking.ProductInsightsAgentFocusRequestBody -import io.constructor.data.model.tracking.ProductInsightsAgentOutOfViewRequestBody -import io.constructor.data.model.tracking.ProductInsightsAgentQuestionClickRequestBody -import io.constructor.data.model.tracking.ProductInsightsAgentQuestionSubmitRequestBody +import io.constructor.data.model.tracking.ProductInsightsAgentItemRequestBody +import io.constructor.data.model.tracking.ProductInsightsAgentQuestionRequestBody import io.constructor.data.model.tracking.ProductInsightsAgentResultClickRequestBody import io.constructor.data.model.tracking.ProductInsightsAgentViewRequestBody import io.constructor.data.model.tracking.ProductInsightsAgentViewsRequestBody @@ -170,10 +168,10 @@ interface ConstructorApi { suspend fun getQuizResultsCRT(@Url quizUrl: String): QuizResultsResponse @POST(ApiPaths.URL_PRODUCT_INSIGHTS_AGENT_FOCUS_EVENT) - fun trackProductInsightsAgentFocus(@Body body: ProductInsightsAgentFocusRequestBody, @QueryMap params: Map): Completable + fun trackProductInsightsAgentFocus(@Body body: ProductInsightsAgentItemRequestBody, @QueryMap params: Map): Completable @POST(ApiPaths.URL_PRODUCT_INSIGHTS_AGENT_OUT_OF_VIEW_EVENT) - fun trackProductInsightsAgentOutOfView(@Body body: ProductInsightsAgentOutOfViewRequestBody, @QueryMap params: Map): Completable + fun trackProductInsightsAgentOutOfView(@Body body: ProductInsightsAgentItemRequestBody, @QueryMap params: Map): Completable @POST(ApiPaths.URL_PRODUCT_INSIGHTS_AGENT_VIEW_EVENT) fun trackProductInsightsAgentView(@Body body: ProductInsightsAgentViewRequestBody, @QueryMap params: Map): Completable @@ -182,10 +180,10 @@ interface ConstructorApi { fun trackProductInsightsAgentViews(@Body body: ProductInsightsAgentViewsRequestBody, @QueryMap params: Map): Completable @POST(ApiPaths.URL_PRODUCT_INSIGHTS_AGENT_QUESTION_CLICK_EVENT) - fun trackProductInsightsAgentQuestionClick(@Body body: ProductInsightsAgentQuestionClickRequestBody, @QueryMap params: Map): Completable + fun trackProductInsightsAgentQuestionClick(@Body body: ProductInsightsAgentQuestionRequestBody, @QueryMap params: Map): Completable @POST(ApiPaths.URL_PRODUCT_INSIGHTS_AGENT_QUESTION_SUBMIT_EVENT) - fun trackProductInsightsAgentQuestionSubmit(@Body body: ProductInsightsAgentQuestionSubmitRequestBody, @QueryMap params: Map): Completable + fun trackProductInsightsAgentQuestionSubmit(@Body body: ProductInsightsAgentQuestionRequestBody, @QueryMap params: Map): Completable @POST(ApiPaths.URL_PRODUCT_INSIGHTS_AGENT_ANSWER_FEEDBACK_EVENT) fun trackProductInsightsAgentAnswerFeedback(@Body body: ProductInsightsAgentAnswerFeedbackRequestBody, @QueryMap params: Map): Completable diff --git a/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentResultClickTrackingDataTest.kt b/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentResultClickTrackingDataTest.kt index 11b2f276..4f9f4434 100644 --- a/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentResultClickTrackingDataTest.kt +++ b/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentResultClickTrackingDataTest.kt @@ -62,21 +62,6 @@ class ProductInsightsAgentResultClickTrackingDataTest { assertEquals(analyticsTags, request.analyticsTags) } - @Test - fun resultClickTrackingDataUsingBuilderWithSharedSettersFirst() { - // Shared setters come from the base builder and must return this builder so event setters can follow - val request = ProductInsightsAgentResultClickTrackingData.Builder(itemId, question, seedItemId) - .setItemName(itemName) - .setSectionName(sectionName) - .setPosition(position) - .setQnaResultId(qnaResultId) - .build() - assertEquals(itemName, request.itemName) - assertEquals(sectionName, request.sectionName) - assertEquals(position, request.position) - assertEquals(qnaResultId, request.qnaResultId) - } - @Test fun resultClickTrackingDataUsingBuilder() { val request = ProductInsightsAgentResultClickTrackingData.Builder(itemId, question, seedItemId) diff --git a/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentTrackingDataTest.kt b/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentTrackingDataTest.kt new file mode 100644 index 00000000..2cb4d8cd --- /dev/null +++ b/library/src/test/java/io/constructor/data/builder/ProductInsightsAgentTrackingDataTest.kt @@ -0,0 +1,36 @@ +package io.constructor.data.builder + +import io.constructor.data.model.tracking.ProductInsightsAgentFeedbackLabel +import io.constructor.data.model.tracking.ProductInsightsAgentQuestion +import io.constructor.data.model.tracking.ProductInsightsAgentViewTimespan +import org.junit.Test +import kotlin.test.assertEquals + +class ProductInsightsAgentTrackingDataTest { + private val itemId = "item-1" + private val questions = listOf(ProductInsightsAgentQuestion("Is it waterproof?")) + + @Test + fun everyEventExposesTheSharedFieldsThroughTheInterface() { + val requests: List = listOf( + ProductInsightsAgentFocusTrackingData(itemId, "Waterproof Jacket", "var-1", "thread-1", "Search Suggestions", mapOf("test" to "test1")), + ProductInsightsAgentOutOfViewTrackingData(itemId, "Waterproof Jacket", "var-1", "thread-1", "Search Suggestions", mapOf("test" to "test1")), + ProductInsightsAgentViewTrackingData(itemId, questions, "Waterproof Jacket", "var-1", "thread-1", "Search Suggestions", mapOf("test" to "test1")), + ProductInsightsAgentViewsTrackingData(itemId, questions, listOf(ProductInsightsAgentViewTimespan(1, 2)), "Waterproof Jacket", "var-1", "thread-1", "Search Suggestions", mapOf("test" to "test1")), + ProductInsightsAgentQuestionClickTrackingData(itemId, "Is it waterproof?", itemName = "Waterproof Jacket", variationId = "var-1", threadId = "thread-1", sectionName = "Search Suggestions", analyticsTags = mapOf("test" to "test1")), + ProductInsightsAgentQuestionSubmitTrackingData(itemId, "Is it waterproof?", itemName = "Waterproof Jacket", variationId = "var-1", threadId = "thread-1", sectionName = "Search Suggestions", analyticsTags = mapOf("test" to "test1")), + ProductInsightsAgentAnswerFeedbackTrackingData(itemId, ProductInsightsAgentFeedbackLabel.THUMBS_UP, itemName = "Waterproof Jacket", variationId = "var-1", threadId = "thread-1", sectionName = "Search Suggestions", analyticsTags = mapOf("test" to "test1")), + ProductInsightsAgentResultClickTrackingData(itemId, "Is it waterproof?", "seed-1", itemName = "Waterproof Jacket", variationId = "var-1", threadId = "thread-1", sectionName = "Search Suggestions", analyticsTags = mapOf("test" to "test1")), + ProductInsightsAgentAnswerViewTrackingData(itemId, "Is it waterproof?", "Yes", itemName = "Waterproof Jacket", variationId = "var-1", threadId = "thread-1", sectionName = "Search Suggestions", analyticsTags = mapOf("test" to "test1")), + ) + requests.forEach { + val name = it::class.simpleName + assertEquals(itemId, it.itemId, name) + assertEquals("Waterproof Jacket", it.itemName, name) + assertEquals("var-1", it.variationId, name) + assertEquals("thread-1", it.threadId, name) + assertEquals("Search Suggestions", it.sectionName, name) + assertEquals(mapOf("test" to "test1"), it.analyticsTags, name) + } + } +} From c2257f3ccd0510add90db0a3af4a507a17fc9195 Mon Sep 17 00:00:00 2001 From: mudaafi Date: Tue, 27 Oct 2026 01:14:50 +0800 Subject: [PATCH 6/7] add item_name test + TrackingItem --- .../io/constructor/data/model/common/TrackingItem.kt | 1 + .../constructor/core/ConstructorIoIntegrationTest.kt | 12 +++++++++++- .../ConstructorIoProductInsightsAgentTrackingTest.kt | 3 ++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/library/src/main/java/io/constructor/data/model/common/TrackingItem.kt b/library/src/main/java/io/constructor/data/model/common/TrackingItem.kt index 23d14b1d..bc6f56c1 100644 --- a/library/src/main/java/io/constructor/data/model/common/TrackingItem.kt +++ b/library/src/main/java/io/constructor/data/model/common/TrackingItem.kt @@ -13,4 +13,5 @@ data class TrackingItem( @Json(name = "variation_id") val variationId: String?, @Json(name = "sl_campaign_id") val slCampaignId: String?, @Json(name = "sl_campaign_owner") val slCampaignOwner: String?, + @Json(name = "item_name") val itemName: String? = null, ) : Serializable diff --git a/library/src/test/java/io/constructor/core/ConstructorIoIntegrationTest.kt b/library/src/test/java/io/constructor/core/ConstructorIoIntegrationTest.kt index 92ed2fd8..1553b18d 100644 --- a/library/src/test/java/io/constructor/core/ConstructorIoIntegrationTest.kt +++ b/library/src/test/java/io/constructor/core/ConstructorIoIntegrationTest.kt @@ -1885,6 +1885,16 @@ class ConstructorIoIntegrationTest { Thread.sleep(timeBetweenTests) } + // itemName is optional: the API only needs one of item_id or item_name, and itemId is always required + @Test + fun trackProductInsightsAgentFocusWithoutItemNameAgainstRealResponse() { + val request = ProductInsightsAgentFocusTrackingData.build("10001") + val observer = constructorIo.trackProductInsightsAgentFocusInternal(request).test() + observer.assertComplete() + observer.assertNoErrors() + Thread.sleep(timeBetweenTests) + } + @Test fun trackProductInsightsAgentOutOfViewAgainstRealResponse() { val request = ProductInsightsAgentOutOfViewTrackingData.build("10001") { @@ -2003,7 +2013,7 @@ class ConstructorIoIntegrationTest { val request = ProductInsightsAgentAnswerViewTrackingData.build("10001", "Is it waterproof?", "") { questionTopic = "Durability" qnaResultId = "qna-cdx-709" - items = listOf(TrackingItem("10002", "20002", testCampaignId, testCampaignOwner), TrackingItem("10003", null, null, null)) + items = listOf(TrackingItem("10002", "20002", testCampaignId, testCampaignOwner, "item2"), TrackingItem("10003", null, null, null)) followUpQuestions = listOf("Is it breathable?") itemName = "item1" variationId = "20001" diff --git a/library/src/test/java/io/constructor/core/ConstructorIoProductInsightsAgentTrackingTest.kt b/library/src/test/java/io/constructor/core/ConstructorIoProductInsightsAgentTrackingTest.kt index 1ab29ed0..9ffeb774 100644 --- a/library/src/test/java/io/constructor/core/ConstructorIoProductInsightsAgentTrackingTest.kt +++ b/library/src/test/java/io/constructor/core/ConstructorIoProductInsightsAgentTrackingTest.kt @@ -687,7 +687,7 @@ class ConstructorIoProductInsightsAgentTrackingTest { questionTopic = "Durability" qnaResultId = "qna-1" items = listOf( - TrackingItem("rec-1", "rec-var-1", "camp-1", "owner-a"), + TrackingItem("rec-1", "rec-var-1", "camp-1", "owner-a", "Rain Jacket"), TrackingItem("rec-2", null, null, null), ) followUpQuestions = listOf("Is it breathable?", "Does it come in blue?") @@ -718,6 +718,7 @@ class ConstructorIoProductInsightsAgentTrackingTest { assertEquals("rec-var-1", firstItem.getString("variation_id")) assertEquals("camp-1", firstItem.getString("sl_campaign_id")) assertEquals("owner-a", firstItem.getString("sl_campaign_owner")) + assertEquals("Rain Jacket", firstItem.getString("item_name")) val secondItem = items.getJSONObject(1) assertEquals("rec-2", secondItem.getString("item_id")) assertEquals(setOf("item_id"), secondItem.keys().asSequence().toSet()) From 27f1c719379c6db2afb30e21b726e12eb8ed586c Mon Sep 17 00:00:00 2001 From: mudaafi Date: Tue, 27 Oct 2026 02:33:47 +0800 Subject: [PATCH 7/7] [CDX-709] Truncate PIA answer view answerText to 2000 code points The API rejects answer_text over 2000 Unicode code points with a 400, which drops the whole event silently. Truncate client-side, as items and followUpQuestions are already capped, cutting on a code point boundary so a surrogate pair is never split. --- README.md | 2 +- .../java/io/constructor/core/ConstructorIo.kt | 3 +- ...ductInsightsAgentAnswerViewTrackingData.kt | 2 +- .../java/io/constructor/util/Extensions.kt | 8 +++++ .../core/ConstructorIoIntegrationTest.kt | 10 ++++++ ...uctorIoProductInsightsAgentTrackingTest.kt | 32 +++++++++++++++++++ 6 files changed, 54 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0641f945..121e27bd 100755 --- a/README.md +++ b/README.md @@ -729,7 +729,7 @@ ConstructorIo.trackProductInsightsAgentQuestionSubmit( ) // Track when an answer is shown to the user (itemId, question, answerText) -// Only the first 100 items and follow-up questions are sent +// Only the first 100 items and follow-up questions are sent, and answerText over 2000 Unicode code points is truncated to fit ConstructorIo.trackProductInsightsAgentAnswerView( ProductInsightsAgentAnswerViewTrackingData.build("1234567-AB", "Is it waterproof?", "Yes, it is fully waterproof.") { qnaResultId = "qna-result-123" diff --git a/library/src/main/java/io/constructor/core/ConstructorIo.kt b/library/src/main/java/io/constructor/core/ConstructorIo.kt index 9a4505fe..dce477c3 100755 --- a/library/src/main/java/io/constructor/core/ConstructorIo.kt +++ b/library/src/main/java/io/constructor/core/ConstructorIo.kt @@ -41,6 +41,7 @@ import io.constructor.injection.component.DaggerAppComponent import io.constructor.injection.module.AppModule import io.constructor.util.broadcastIntent import io.constructor.util.e +import io.constructor.util.truncateToCodePoints import io.constructor.util.urlEncode import io.reactivex.Completable import io.reactivex.Observable @@ -2822,7 +2823,7 @@ object ConstructorIo { val requestBody = ProductInsightsAgentAnswerViewRequestBody( itemId = request.itemId, question = request.question, - answerText = request.answerText, + answerText = request.answerText.truncateToCodePoints(2000), questionTopic = request.questionTopic, qnaResultId = request.qnaResultId, items = request.items?.take(100), diff --git a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentAnswerViewTrackingData.kt b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentAnswerViewTrackingData.kt index 66cd3c64..6eaedccc 100644 --- a/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentAnswerViewTrackingData.kt +++ b/library/src/main/java/io/constructor/data/builder/ProductInsightsAgentAnswerViewTrackingData.kt @@ -7,7 +7,7 @@ import io.constructor.data.model.common.TrackingItem * * @property itemId the ID of the item (product) the Product Insights Agent is displayed for, i.e. "1234567-AB" * @property question the question text, i.e. "Is it waterproof?" - * @property answerText the answer text shown to the user. May be empty + * @property answerText the answer text shown to the user. May be empty. Text over 2000 Unicode code points is truncated to fit * @property questionTopic the topic the question belongs to, i.e. "Durability" * @property qnaResultId the result ID of the answer, as returned by the Product Insights Agent * @property items the items (products) shown in the answer. Only the first 100 are sent diff --git a/library/src/main/java/io/constructor/util/Extensions.kt b/library/src/main/java/io/constructor/util/Extensions.kt index 66e4f868..40243fb6 100755 --- a/library/src/main/java/io/constructor/util/Extensions.kt +++ b/library/src/main/java/io/constructor/util/Extensions.kt @@ -44,6 +44,14 @@ fun Intent.setExtra(key: String, value: Any) { */ fun String.urlEncode() = URLEncoder.encode(this, "UTF-8").replace("+", "%20") +/** + * Truncates to at most [maxCodePoints] Unicode code points, never splitting a surrogate pair + * + * @suppress + */ +fun String.truncateToCodePoints(maxCodePoints: Int): String = + if (codePointCount(0, length) <= maxCodePoints) this else substring(0, offsetByCodePoints(0, maxCodePoints)) + /** * @suppress */ diff --git a/library/src/test/java/io/constructor/core/ConstructorIoIntegrationTest.kt b/library/src/test/java/io/constructor/core/ConstructorIoIntegrationTest.kt index 1553b18d..ca6ed3b9 100644 --- a/library/src/test/java/io/constructor/core/ConstructorIoIntegrationTest.kt +++ b/library/src/test/java/io/constructor/core/ConstructorIoIntegrationTest.kt @@ -2025,4 +2025,14 @@ class ConstructorIoIntegrationTest { observer.assertNoErrors() Thread.sleep(timeBetweenTests) } + + // Required fields only, with answerText over the API's 2000 code point limit so the truncated text is sent + @Test + fun trackProductInsightsAgentAnswerViewWithRequiredFieldsOnlyAgainstRealResponse() { + val request = ProductInsightsAgentAnswerViewTrackingData.build("10001", "Is it waterproof?", "a".repeat(2001)) + val observer = constructorIo.trackProductInsightsAgentAnswerViewInternal(request).test() + observer.assertComplete() + observer.assertNoErrors() + Thread.sleep(timeBetweenTests) + } } diff --git a/library/src/test/java/io/constructor/core/ConstructorIoProductInsightsAgentTrackingTest.kt b/library/src/test/java/io/constructor/core/ConstructorIoProductInsightsAgentTrackingTest.kt index 9ffeb774..1da2d773 100644 --- a/library/src/test/java/io/constructor/core/ConstructorIoProductInsightsAgentTrackingTest.kt +++ b/library/src/test/java/io/constructor/core/ConstructorIoProductInsightsAgentTrackingTest.kt @@ -761,6 +761,38 @@ class ConstructorIoProductInsightsAgentTrackingTest { assertEquals("Question 100?", followUpQuestions.getJSONObject(99).getString("value")) } + private fun trackAnswerViewAndGetAnswerText(answerText: String): String { + mockServer.enqueue(MockResponse().setResponseCode(204)) + val request = ProductInsightsAgentAnswerViewTrackingData.build("item-1", "Is it waterproof?", answerText) + val observer = ConstructorIo.trackProductInsightsAgentAnswerViewInternal(request).test() + observer.assertComplete() + return getJsonBody(awaitRequest()).getString("answer_text") + } + + @Test + fun trackProductInsightsAgentAnswerViewKeepsAnswerTextAt2000CodePoints() { + val answerText = "a".repeat(2000) + assertEquals(answerText, trackAnswerViewAndGetAnswerText(answerText)) + } + + @Test + fun trackProductInsightsAgentAnswerViewTruncatesAnswerTextTo2000CodePoints() { + assertEquals("a".repeat(2000), trackAnswerViewAndGetAnswerText("a".repeat(2500))) + } + + @Test + fun trackProductInsightsAgentAnswerViewCountsAnswerTextInCodePointsNotUtf16Units() { + // U+1F600 is 2 UTF-16 units but 1 code point, so 2000 of them are under the limit + val answerText = "😀".repeat(2000) + assertEquals(answerText, trackAnswerViewAndGetAnswerText(answerText)) + } + + @Test + fun trackProductInsightsAgentAnswerViewTruncatesAnswerTextWithoutSplittingSurrogatePairs() { + // 2001 code points: the cut falls between emoji, so no half surrogate pair is sent + assertEquals("a" + "\uD83D\uDE00".repeat(1999), trackAnswerViewAndGetAnswerText("a" + "\uD83D\uDE00".repeat(2000))) + } + @Test fun trackProductInsightsAgentAnswerViewWithRequiredFieldsOnly() { mockServer.enqueue(MockResponse().setResponseCode(204))