diff --git a/README.md b/README.md index d51d8eb6..87c10ed5 100755 --- a/README.md +++ b/README.md @@ -529,6 +529,13 @@ ConstructorIo.trackAutocompleteSelect("Fashionable Toothpicks", "tooth", "Produc // Track when the user submits a search (searchTerm, originalQuery) ConstructorIo.trackSearchSubmit("toothpicks", "tooth") + +// Track when the user submits a search with additional parameters, i.e. analytics tags +// Request level analytics tags are merged with the default analytics tags passed on initialization +val request = SearchSubmitTrackingData.build("toothpicks", "tooth") { + setAnalyticsTags(mapOf("relatedSearchTerm" to "true")) +} +ConstructorIo.trackSearchSubmit(request) ``` ### Search Events diff --git a/library/src/main/java/io/constructor/core/Constants.kt b/library/src/main/java/io/constructor/core/Constants.kt index ae4473fb..209399b1 100755 --- a/library/src/main/java/io/constructor/core/Constants.kt +++ b/library/src/main/java/io/constructor/core/Constants.kt @@ -64,6 +64,7 @@ class Constants { const val GROUPS_MAX_DEPTH = "groups_max_depth" const val FILTER_GROUP_ID = "filters[group_id]" const val PRE_FILTER_EXPRESSION = "pre_filter_expression" + const val ANALYTICS_TAGS = "analytics_tags[%s]" } object QueryValues { diff --git a/library/src/main/java/io/constructor/core/ConstructorIo.kt b/library/src/main/java/io/constructor/core/ConstructorIo.kt index fd964082..bcd5a5c7 100755 --- a/library/src/main/java/io/constructor/core/ConstructorIo.kt +++ b/library/src/main/java/io/constructor/core/ConstructorIo.kt @@ -1490,9 +1490,38 @@ object ConstructorIo { t -> e("Search Submit error: ${t.message}") })) } - internal fun trackSearchSubmitInternal(searchTerm: String, originalQuery: String, resultGroup: ResultGroup?): Completable { + + /** + * Tracks search submit events. + * + * Example: + * ``` + * val request = SearchSubmitTrackingData.build("toothpicks", "tooth") { + * setResultGroup(ResultGroup("Canned Goods", "canned-goods")) + * setAnalyticsTags(mapOf("relatedSearchTerm" to "true")) + * } + * ConstructorIo.trackSearchSubmit(request) + * ``` + * @param request the search 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 trackSearchSubmit(request: SearchSubmitTrackingData) { + val completable = trackSearchSubmitInternal(request.searchTerm, request.originalQuery, request.resultGroup, request.analyticsTags) + disposable.add(completable.subscribeOn(Schedulers.io()).subscribe({ + context.broadcastIntent(Constants.EVENT_QUERY_SENT, Constants.EXTRA_TERM to request.searchTerm) + }, { + t -> e("Search Submit error: ${t.message}") + })) + } + + internal fun trackSearchSubmitInternal(searchTerm: String, originalQuery: String, resultGroup: ResultGroup?, analyticsTags: Map? = null): Completable { preferenceHelper.getSessionId(sessionIncrementHandler) val encodedParams: ArrayList> = getEncodedParams(groupId = resultGroup?.groupId, groupDisplayName = resultGroup?.displayName) + mergeAnalyticsTags(configMemoryHolder.defaultAnalyticsTags, analyticsTags)?.forEach { (key, value) -> + encodedParams.add(Constants.QueryConstants.ANALYTICS_TAGS.format(key).urlEncode() to value.urlEncode()) + } return dataManager.trackSearchSubmit(searchTerm, arrayOf( Constants.QueryConstants.ORIGINAL_QUERY to originalQuery, diff --git a/library/src/main/java/io/constructor/data/builder/SearchSubmitTrackingData.kt b/library/src/main/java/io/constructor/data/builder/SearchSubmitTrackingData.kt new file mode 100644 index 00000000..ab086396 --- /dev/null +++ b/library/src/main/java/io/constructor/data/builder/SearchSubmitTrackingData.kt @@ -0,0 +1,40 @@ +package io.constructor.data.builder + +import io.constructor.data.model.common.ResultGroup + +/** + * Create a Search Submit tracking request object utilizing a builder + */ +class SearchSubmitTrackingData( + val searchTerm: String, + val originalQuery: String, + val resultGroup: ResultGroup? = null, + val analyticsTags: Map? = null, +) { + private constructor(builder: Builder) : this( + builder.searchTerm, + builder.originalQuery, + builder.resultGroup, + builder.analyticsTags, + ) + + companion object { + inline fun build( + searchTerm: String, + originalQuery: String, + block: Builder.() -> Unit = {} + ) = Builder(searchTerm, originalQuery).apply(block).build() + } + + class Builder( + val searchTerm: String, + val originalQuery: String + ) { + var resultGroup: ResultGroup? = null + var analyticsTags: Map? = null + + fun setResultGroup(resultGroup: ResultGroup): Builder = apply { this.resultGroup = resultGroup } + fun setAnalyticsTags(analyticsTags: Map): Builder = apply { this.analyticsTags = analyticsTags } + fun build(): SearchSubmitTrackingData = SearchSubmitTrackingData(this) + } +} diff --git a/library/src/test/java/io/constructor/core/ConstructorIoTrackingTest.kt b/library/src/test/java/io/constructor/core/ConstructorIoTrackingTest.kt index 2bd1be12..ce72ce18 100755 --- a/library/src/test/java/io/constructor/core/ConstructorIoTrackingTest.kt +++ b/library/src/test/java/io/constructor/core/ConstructorIoTrackingTest.kt @@ -6,6 +6,7 @@ import io.constructor.data.memory.ConfigMemoryHolder import io.constructor.data.model.common.ResultGroup import io.constructor.data.model.purchase.PurchaseItem import io.constructor.data.model.common.TrackingItem +import io.constructor.data.builder.SearchSubmitTrackingData import io.constructor.test.createTestDataManager import io.constructor.util.RxSchedulersOverrideRule import io.mockk.every @@ -340,10 +341,35 @@ class ConstructorIoTrackingTest { val observer = ConstructorIo.trackSearchSubmitInternal("titanic", "tit", null).test() observer.assertComplete() val request = mockServer.takeRequest() - val path = "/autocomplete/titanic/search?original_query=tit&tr=search&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-2.44.0&_dt=" + val path = "/autocomplete/titanic/search?original_query=tit&tr=search&analytics_tags%5BappVersion%5D=123&analytics_tags%5BappPlatform%5D=Android&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-2.44.0&_dt=" assert(request.path!!.startsWith(path)) } + @Test + fun trackSearchSubmitWithAnalyticsTags() { + val mockResponse = MockResponse().setResponseCode(204) + mockServer.enqueue(mockResponse) + val observer = ConstructorIo.trackSearchSubmitInternal("titanic", "tit", null, mapOf("test" to "test1", "appVersion" to "150")).test() + observer.assertComplete() + val request = mockServer.takeRequest() + val path = "/autocomplete/titanic/search?original_query=tit&tr=search&analytics_tags%5BappVersion%5D=150&analytics_tags%5BappPlatform%5D=Android&analytics_tags%5Btest%5D=test1&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-2.44.0&_dt=" + assert(request.path!!.startsWith(path)) + } + + @Test + fun trackSearchSubmitWithRequestBuilder() { + val mockResponse = MockResponse().setResponseCode(204) + mockServer.enqueue(mockResponse) + val request = SearchSubmitTrackingData.build("titanic", "tit") { + setResultGroup(ResultGroup("Movies", "group_id")) + setAnalyticsTags(mapOf("relatedSearchTerm" to "true")) + } + ConstructorIo.trackSearchSubmit(request) + val recordedRequest = mockServer.takeRequest() + val path = "/autocomplete/titanic/search?original_query=tit&tr=search&group%5Bgroup_id%5D=group_id&group%5Bdisplay_name%5D=Movies&analytics_tags%5BappVersion%5D=123&analytics_tags%5BappPlatform%5D=Android&analytics_tags%5BrelatedSearchTerm%5D=true&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-2.44.0&_dt=" + assert(recordedRequest.path!!.startsWith(path)) + } + @Test fun trackSearchSubmit500() { val mockResponse = MockResponse().setResponseCode(500).setBody("Internal server error") @@ -351,7 +377,7 @@ class ConstructorIoTrackingTest { val observer = ConstructorIo.trackSearchSubmitInternal("titanic", "tit", null).test() observer.assertError { true } val request = mockServer.takeRequest() - val path = "/autocomplete/titanic/search?original_query=tit&tr=search&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-2.44.0&_dt=" + val path = "/autocomplete/titanic/search?original_query=tit&tr=search&analytics_tags%5BappVersion%5D=123&analytics_tags%5BappPlatform%5D=Android&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-2.44.0&_dt=" assert(request.path!!.startsWith(path)) } @@ -363,7 +389,7 @@ class ConstructorIoTrackingTest { val observer = ConstructorIo.trackSearchSubmitInternal("titanic", "tit", null).test() observer.assertError(SocketTimeoutException::class.java) val request = mockServer.takeRequest() - val path = "/autocomplete/titanic/search?original_query=tit&tr=search&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-2.44.0&_dt=" + val path = "/autocomplete/titanic/search?original_query=tit&tr=search&analytics_tags%5BappVersion%5D=123&analytics_tags%5BappPlatform%5D=Android&key=copper-key&i=wacko-the-guid&ui=player-three&s=67&c=cioand-2.44.0&_dt=" assert(request.path!!.startsWith(path)) } diff --git a/library/src/test/java/io/constructor/core/ConstructorioSegmentsTest.kt b/library/src/test/java/io/constructor/core/ConstructorioSegmentsTest.kt index 18ccf87c..21752653 100644 --- a/library/src/test/java/io/constructor/core/ConstructorioSegmentsTest.kt +++ b/library/src/test/java/io/constructor/core/ConstructorioSegmentsTest.kt @@ -109,7 +109,7 @@ class ConstructorioSegmentsTest { val observer = ConstructorIo.trackSearchSubmitInternal("titanic", "tit", null).test() observer.assertComplete() val request = mockServer.takeRequest() - val path = "/autocomplete/titanic/search?original_query=tit&tr=search&key=aluminium-key&i=koopa-the-guid&ui=player-two&s=14&us=mobile&us=COUNTRY_US&c=cioand-2.44.0&_dt=" + val path = "/autocomplete/titanic/search?original_query=tit&tr=search&analytics_tags%5BappVersion%5D=123&analytics_tags%5BappPlatform%5D=Android&key=aluminium-key&i=koopa-the-guid&ui=player-two&s=14&us=mobile&us=COUNTRY_US&c=cioand-2.44.0&_dt=" assert(request.path!!.startsWith(path)) } diff --git a/library/src/test/java/io/constructor/core/ConstructorioTestCellTest.kt b/library/src/test/java/io/constructor/core/ConstructorioTestCellTest.kt index c1ad1cf1..5452b991 100644 --- a/library/src/test/java/io/constructor/core/ConstructorioTestCellTest.kt +++ b/library/src/test/java/io/constructor/core/ConstructorioTestCellTest.kt @@ -110,7 +110,7 @@ class ConstructorioTestCellTest { val observer = ConstructorIo.trackSearchSubmitInternal("titanic", "tit", null).test() observer.assertComplete() val request = mockServer.takeRequest() - val path = "/autocomplete/titanic/search?original_query=tit&tr=search&key=aluminium-key&i=koopa-the-guid&ui=player-two&s=14&ef-cellone=vanilla&ef-celltwo=whipped-cream&c=cioand-2.44.0&_dt="; + val path = "/autocomplete/titanic/search?original_query=tit&tr=search&analytics_tags%5BappVersion%5D=123&analytics_tags%5BappPlatform%5D=Android&key=aluminium-key&i=koopa-the-guid&ui=player-two&s=14&ef-cellone=vanilla&ef-celltwo=whipped-cream&c=cioand-2.44.0&_dt="; assert(request.path!!.startsWith(path)) }