Repository navigation
Conversation
Add 6 Agent Search Assistant tracking events: - trackAgentSubmit - trackAgentResultLoadStarted - trackAgentResultLoadFinished - trackAgentResultClick - trackAgentResultView - trackAgentSearchSubmit Also fix quiz integration tests to pass quiz_session_id with answers per updated API requirement.
There was a problem hiding this comment.
Pull request overview
Adds support for Agent Search Assistant (ASA) behavioral tracking in the Android SDK, wiring new ASA event APIs through request models → Retrofit endpoints → DataManager → public ConstructorIo fire-and-forget methods, and updates quiz integration tests/docs to match current API requirements.
Changes:
- Added 6 ASA tracking events (
trackAgentSubmit,trackAgentResultLoadStarted,trackAgentResultLoadFinished,trackAgentResultClick,trackAgentResultView,trackAgentSearchSubmit) including request-body models and Retrofit/DataManager plumbing. - Added a dedicated unit test suite (
ConstructorIoAgentTrackingTest) covering success/error/timeout, optional params, analytics tags merging, and 100-item cap behavior. - Updated quiz integration tests to include
quizVersionId/quizSessionIdin requests and updated README documentation to include ASA event examples.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | Documents new ASA tracking event APIs and adds “Agent Events” to the tracking taxonomy. |
| library/src/test/java/io/constructor/core/ConstructorIoIntegrationQuizTest.kt | Updates quiz integration tests to include quizVersionId/quizSessionId and adjust answer payloads. |
| library/src/test/java/io/constructor/core/ConstructorIoAgentTrackingTest.kt | Adds unit tests validating ASA tracking request shapes and error/timeout behavior. |
| library/src/main/java/io/constructor/data/remote/ConstructorApi.kt | Introduces Retrofit endpoints for the new ASA behavioral action routes. |
| library/src/main/java/io/constructor/data/remote/ApiPaths.kt | Adds API path constants for the 6 ASA tracking endpoints. |
| library/src/main/java/io/constructor/data/model/tracking/AgentSubmitRequestBody.kt | Adds request body model for ASA submit tracking. |
| library/src/main/java/io/constructor/data/model/tracking/AgentResultLoadStartedRequestBody.kt | Adds request body model for ASA “load started” tracking. |
| library/src/main/java/io/constructor/data/model/tracking/AgentResultLoadFinishedRequestBody.kt | Adds request body model for ASA “load finished” tracking. |
| library/src/main/java/io/constructor/data/model/tracking/AgentResultClickRequestBody.kt | Adds request body model for ASA result click tracking. |
| library/src/main/java/io/constructor/data/model/tracking/AgentResultViewRequestBody.kt | Adds request body model for ASA result view tracking (with items list). |
| library/src/main/java/io/constructor/data/model/tracking/AgentSearchSubmitRequestBody.kt | Adds request body model for ASA search submit tracking. |
| library/src/main/java/io/constructor/data/DataManager.kt | Adds pass-through methods to call the new Retrofit ASA endpoints. |
| library/src/main/java/io/constructor/core/ConstructorIo.kt | Adds public ASA tracking APIs plus internal implementations that build request bodies and dispatch to DataManager. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| return dataManager.trackAgentSubmit(requestBody) | ||
| } |
There was a problem hiding this comment.
@Mudaafi is there a reason to add section as a query param for trackAgentResultClick and trackAgentResultView only? 👀
There was a problem hiding this comment.
Not particularly. I didn't realize they would cater to different sections. I'll add it in since the API supports it.
|
|
||
| return dataManager.trackAgentResultLoadStarted(requestBody) | ||
| } |
|
|
||
| return dataManager.trackAgentResultLoadFinished(requestBody) | ||
| } |
|
|
||
| return dataManager.trackAgentSearchSubmit(requestBody) | ||
| } |
|
|
||
| return dataManager.trackAgentSubmit(requestBody) | ||
| } |
There was a problem hiding this comment.
@Mudaafi is there a reason to add section as a query param for trackAgentResultClick and trackAgentResultView only? 👀
Section is already included in the request body — no need to pass it as a query parameter. Aligns with Swift SDK behavior.
The API supports section as a query parameter on every agent behavioral endpoint, so pass it alongside the body field for consistency with the other sectioned tracking calls.
There was a problem hiding this comment.
Code Review
This PR implements 6 ASA (Agent Search Assistant) tracking events, following the established patterns in the codebase well overall — but there are a few issues worth addressing before merging.
Inline comments: 7 discussions added
Overall Assessment:
| */ | ||
| fun trackAgentSubmit(intent: String, sectionName: String? = null, analyticsTags: Map<String, String>? = null) { | ||
| val completable = trackAgentSubmitInternal(intent, sectionName, analyticsTags) | ||
| disposable.add(completable.subscribeOn(Schedulers.io()).subscribe({}, { |
There was a problem hiding this comment.
Suggestion: Inconsistent lambda style. The existing codebase uses two styles for the error lambda — either a single-line { t -> e("...") } or a multi-line block — but the new Agent methods split the parameter name t onto the next line after {, which isn't used elsewhere. Compare with the existing pattern at lines like 1253-1255:
disposable.add(completable.subscribeOn(Schedulers.io()).subscribe({}, {
t -> e("Quiz Result Click error: ${t.message}")
}))The new code writes:
disposable.add(completable.subscribeOn(Schedulers.io()).subscribe({}, {
t -> e("Agent Submit error: ${t.message}")
}))This is a minor inconsistency; prefer the single-line style used by the nearest neighbours (trackItemDetailLoaded, trackMediaImpressionView):
disposable.add(completable.subscribeOn(Schedulers.io()).subscribe({}, { t -> e("Agent Submit error: ${t.message}") }))| internal fun trackAgentResultViewInternal(intent: String, searchResultId: String, numResultsViewed: Int, items: List<ResultsImpressionItem>? = null, sectionName: String? = null, intentResultId: String? = null, analyticsTags: Map<String, String>? = null): Completable { | ||
| preferenceHelper.getSessionId(sessionIncrementHandler) | ||
| val section = sectionName ?: preferenceHelper.defaultItemSection | ||
| val cappedItems = items?.take(100) |
There was a problem hiding this comment.
Suggestion: Magic number 100 should be extracted into a named constant. The existing Constants.kt doesn't define any numeric limits, so this introduces the first magic number for a cap. Define it clearly, e.g.:
private const val MAX_AGENT_ITEMS = 100
// ...
val cappedItems = items?.take(MAX_AGENT_ITEMS)This matches the self-documenting style used throughout the rest of the codebase and makes the cap easy to change if the API spec evolves.
| * @param intentResultId the intent result identifier | ||
| * @param analyticsTags additional analytics tags to pass | ||
| */ | ||
| fun trackAgentResultView(intent: String, searchResultId: String, numResultsViewed: Int, items: List<ResultsImpressionItem>? = null, sectionName: String? = null, intentResultId: String? = null, analyticsTags: Map<String, String>? = null) { |
There was a problem hiding this comment.
Suggestion: Missing numResultsViewed validation. The analogous trackResultsImpressionView guards against an empty list (if (items.isEmpty()) return). Consider whether a numResultsViewed <= 0 should also be rejected or at least logged — particularly if the API treats it as invalid. Even a doc comment noting the expected range would be helpful for callers.
|
|
||
| @JsonClass(generateAdapter = true) | ||
| data class AgentSubmitRequestBody( | ||
| @Json(name = "intent") val intent: String, |
There was a problem hiding this comment.
Important Issue: AgentSubmitRequestBody is missing the intent_result_id field that every other Agent request body includes. Looking at the Swift mirror PR and the other five bodies, all of them carry intentResultId. If trackAgentSubmit genuinely doesn't support it at the API level that's fine, but it should be explicitly documented in the KDoc of trackAgentSubmit. If it does need it, add the field and propagate the parameter through ConstructorIo.trackAgentSubmit and trackAgentSubmitInternal.
| @Json(name = "ui") val ui: String?, | ||
| @Json(name = "us") val us: List<String?>, | ||
| @Json(name = "analytics_tags") val analyticsTags: Map<String, String>?, | ||
| @Json(name = "_dt") val _dt: Long? |
There was a problem hiding this comment.
Suggestion: The field name _dt on a Kotlin property starts with an underscore, which is a Kotlin naming convention violation (used to signal private backing properties). The same pattern exists in other existing request bodies in the codebase so this is pre-existing, but since all six new request bodies replicate it, it's worth flagging. Consider an alias:
@Json(name = "_dt") val dt: Long?This avoids suppressing potential lint warnings across six new files.
| val request = awaitRequest() | ||
|
|
||
| val requestBody = getRequestBody(request) | ||
| assert(requestBody["items"]!!.contains("item_id:item-100")) |
There was a problem hiding this comment.
Important Issue: The 100-item cap test (trackAgentResultViewCapsItemsAt100) uses assert(!requestBody["items"]!!.contains("item_id:item-101")). This assertion is fragile — item_id:item-10 is a substring of item_id:item-101, so the negative assertion could false-pass if the serialisation format changes slightly. Use a more robust check, e.g. verify the number of item_id: occurrences in the string:
val itemCount = requestBody["items"]!!.split("item_id:").size - 1
assertEquals(100, itemCount)Or at minimum assert !contains("item_id:item-101,") with a trailing delimiter.
| } | ||
|
|
||
| @Test | ||
| fun trackAgentSubmitTimeout() { |
There was a problem hiding this comment.
Suggestion: The timeout tests set setBodyDelay(5, TimeUnit.SECONDS) and then awaitDone(6, TimeUnit.SECONDS), but the default OkHttp read timeout configured for tests should already fire before 5 s. Double-check the test DataManager/OkHttpClient configuration to confirm the timeout is shorter than 5 s, otherwise these tests will be slow in CI. If the actual timeout is 1 s (common default), reduce the delay to e.g. 2 s and awaitDone to 3 s — consistent with patterns in ConstructorIoMediaTrackingTest.
Summary
This implementation mirrors the swift one: Constructor-io/constructorio-client-swift#278
trackAgentSubmit,trackAgentResultLoadStarted,trackAgentResultLoadFinished,trackAgentResultClick,trackAgentResultView,trackAgentSearchSubmitquiz_session_idwith answers per updated API requirementTest plan
ConstructorIoAgentTrackingTest(23 tests covering all 6 events)ConstructorIoIntegrationQuizTestto verify quiz test fix/v2/behavioral_action/endpointsNote: There are some request/response mismatches due to searchability issues. I've opened a thread to investigate it.