Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions AutocompleteClient.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@
08C1E6052193BE3600A2E24E /* CIOTrackPurchaseData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08C1E6042193BE3600A2E24E /* CIOTrackPurchaseData.swift */; };
08C1E6072193C29C00A2E24E /* TrackPurchaseRequestBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08C1E6062193C29C00A2E24E /* TrackPurchaseRequestBuilderTests.swift */; };
08FFB76C215EBBF8008CAA7D /* CIOTrackAutocompleteSelectData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08FFB76B215EBBF8008CAA7D /* CIOTrackAutocompleteSelectData.swift */; };
B7C3E1205D2B4F6A9E8C1B02 /* CIOTrackSearchSubmitFilters.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7C3E1215D2B4F6A9E8C1B02 /* CIOTrackSearchSubmitFilters.swift */; };
B7C3E1225D2B4F6A9E8C1B03 /* CIOTrackSearchSubmitRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7C3E1235D2B4F6A9E8C1B03 /* CIOTrackSearchSubmitRequest.swift */; };
08FFB76E215EC1E2008CAA7D /* CIOTrackSearchSubmitData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08FFB76D215EC1DF008CAA7D /* CIOTrackSearchSubmitData.swift */; };
0D6F0DA342504F8D1DB657B5 /* TrackAgentResultViewRequestBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD016CFF1317FE8714751737 /* TrackAgentResultViewRequestBuilderTests.swift */; };
120264B1D888913DB32E9BDD /* ConstructorIOTrackAgentResultLoadStartedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B2AB45915473F18E0232C00 /* ConstructorIOTrackAgentResultLoadStartedTests.swift */; };
Expand Down Expand Up @@ -450,6 +452,8 @@
08C1E6042193BE3600A2E24E /* CIOTrackPurchaseData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIOTrackPurchaseData.swift; sourceTree = "<group>"; };
08C1E6062193C29C00A2E24E /* TrackPurchaseRequestBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackPurchaseRequestBuilderTests.swift; sourceTree = "<group>"; };
08FFB76B215EBBF8008CAA7D /* CIOTrackAutocompleteSelectData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIOTrackAutocompleteSelectData.swift; sourceTree = "<group>"; };
B7C3E1215D2B4F6A9E8C1B02 /* CIOTrackSearchSubmitFilters.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CIOTrackSearchSubmitFilters.swift; sourceTree = "<group>"; };
B7C3E1235D2B4F6A9E8C1B03 /* CIOTrackSearchSubmitRequest.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CIOTrackSearchSubmitRequest.swift; sourceTree = "<group>"; };
08FFB76D215EC1DF008CAA7D /* CIOTrackSearchSubmitData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CIOTrackSearchSubmitData.swift; sourceTree = "<group>"; };
14F639EF2F5B572DE847C09B /* ConstructorIOTrackAgentResultViewTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ConstructorIOTrackAgentResultViewTests.swift; sourceTree = "<group>"; };
2B2AB45915473F18E0232C00 /* ConstructorIOTrackAgentResultLoadStartedTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ConstructorIOTrackAgentResultLoadStartedTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -955,6 +959,8 @@
F6DF7B9320849E8D00A7CDAD /* CIOTrackSearchResultClickData.swift */,
F6D2E9CB20E21022007F8761 /* CIOTrackSearchResultsLoadedData.swift */,
08FFB76D215EC1DF008CAA7D /* CIOTrackSearchSubmitData.swift */,
B7C3E1235D2B4F6A9E8C1B03 /* CIOTrackSearchSubmitRequest.swift */,
B7C3E1215D2B4F6A9E8C1B02 /* CIOTrackSearchSubmitFilters.swift */,
F685255C21414E1D00A27FAA /* CIOTrackSessionStartData.swift */,
7D45383928DC085600490BFE /* CIOQuizQuery.swift */,
BF02E29E28B5739C0062B44F /* CIOItem.swift */,
Expand Down Expand Up @@ -2594,6 +2600,8 @@
BF380F1627E12623005E01F5 /* CIOSearchQueryBuilder.swift in Sources */,
F6852559213D604900A27FAA /* AutocompleteResultCount.swift in Sources */,
08FFB76E215EC1E2008CAA7D /* CIOTrackSearchSubmitData.swift in Sources */,
B7C3E1225D2B4F6A9E8C1B03 /* CIOTrackSearchSubmitRequest.swift in Sources */,
B7C3E1205D2B4F6A9E8C1B02 /* CIOTrackSearchSubmitFilters.swift in Sources */,
0828954D256C5A85009A00BC /* CIOAutocompleteResponse.swift in Sources */,
0828954B256C5A85009A00BC /* CIOSearchRedirectInfo.swift in Sources */,
08289544256C5A85009A00BC /* CIOSortOption.swift in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion AutocompleteClient/Constants/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ struct Constants {
}

struct TrackSearchSubmit {
static let format = "%@/autocomplete/%@/search"
static let format = "%@/v2/behavioral_action/search_submit"
}

struct TrackSearchResultsLoaded {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,46 @@ import Foundation
struct CIOTrackSearchSubmitData: CIORequestData {

let searchTerm: String
let originalQuery: String
let group: CIOGroup?
let userInput: String
let filters: CIOTrackSearchSubmitFilters?
let analyticsTags: [String: String]?
let section: String?
Comment thread
constructor-claude-bedrock[bot] marked this conversation as resolved.
Comment thread
evanyan13 marked this conversation as resolved.

func url(with baseURL: String) -> String {
return String(format: Constants.TrackSearchSubmit.format, baseURL, self.searchTerm.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed)!)
return String(format: Constants.TrackSearchSubmit.format, baseURL)
}

init(searchTerm: String, originalQuery: String, group: CIOGroup? = nil, analyticsTags: [String: String]? = nil) {
init(searchTerm: String, userInput: String, filters: CIOTrackSearchSubmitFilters? = nil, analyticsTags: [String: String]? = nil, section: String? = nil) {
self.searchTerm = searchTerm
self.originalQuery = originalQuery
self.group = group
self.userInput = userInput
self.filters = filters
self.analyticsTags = analyticsTags
self.section = section
}

func decorateRequest(requestBuilder: RequestBuilder) {
requestBuilder.set(originalQuery: self.originalQuery)
if let group = self.group {
requestBuilder.set(groupName: group.displayName)
requestBuilder.set(groupID: group.groupID)
requestBuilder.set(autocompleteSection: self.section)
}

func httpMethod() -> String {
return "POST"
}

// The endpoint rejects unknown body parameters, so fields in the baseParams are not included in the body
func httpBody(baseParams _: [String: Any]) -> Data? {
var dict = [
"search_term": self.searchTerm,
"user_input": self.userInput
] as [String: Any]

if let filters = self.filters {
dict["filters"] = ["group_id": filters.groupID]
}

if let analyticsTags = self.analyticsTags {
dict["analytics_tags"] = analyticsTags
}
requestBuilder.set(analyticsTags: self.analyticsTags)

return try? JSONSerialization.data(withJSONObject: dict)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important Issue: Every other v2 POST data struct (e.g. CIOTrackAgentResultViewData, CIOTrackPurchaseData, CIOTrackConversionData) calls dict.merge(baseParams) { current, _ in current } before serialising, which injects the base query params (key, i, s, c, _dt, etc.) into the body as well.

This struct deliberately skips that merge and explains the reason in the comment above (httpBody). That is correct behaviour for this particular endpoint. However, the comment only appears on the function, not at the call site. Consider adding a brief note in the doc-comment of CIOTrackSearchSubmitData (or at minimum keeping the existing function-level comment) so the next maintainer doesn't "fix" this to match the other structs.

More importantly, the existing comment (// The endpoint rejects unknown body properties, so base params are sent only as query items) is accurate and should be kept — just make sure it isn't accidentally removed in a future refactor.

Comment thread
constructor-claude-bedrock[bot] marked this conversation as resolved.
Comment thread
evanyan13 marked this conversation as resolved.
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// CIOTrackSearchSubmitFilters.swift
// AutocompleteClient
//
// Copyright (c) Constructor.io Corporation. All rights reserved.
// http://constructor.io/
//

import Foundation

/**
Struct encapsulating the filters that can be sent when tracking a search submission.
Only `group_id` is supported for now. Using a struct keeps the API forward-compatible if more filters are added.
*/
public struct CIOTrackSearchSubmitFilters {
Comment thread
evanyan13 marked this conversation as resolved.
/**
The id of the group applied by the search. Should be present when the user selects a search suggestion that automatically applies a group filter. Must not be empty, the endpoint rejects empty values
*/
public let groupID: String
Comment thread
evanyan13 marked this conversation as resolved.

/**
Create search submit filters.

- Parameters:
- groupID: The id of the group applied by the search
*/
public init(groupID: String) {
self.groupID = groupID
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//
// CIOTrackSearchSubmitRequest.swift
// AutocompleteClient
//
// Copyright (c) Constructor.io Corporation. All rights reserved.
// http://constructor.io/
//

import Foundation

/**
Struct encapsulating the parameters that can be passed to constructorIO.trackSearchSubmit() function.
*/
public struct CIOTrackSearchSubmitRequest {

/**
The term that the user searched for
*/
public let searchTerm: String

/**
The current text in the input field
*/
public let userInput: String

/**
The filters applied by the search. Only a group id is supported, i.e. "Pumpkin in Canned Goods"
*/
public let filters: CIOTrackSearchSubmitFilters?

/**
Additional custom analytics tags to be sent with the event. Merged with the default analytics tags
*/
public let analyticsTags: [String: String]?

/**
The section of the index to use (defaults to the configured default section, or "Products")
*/
public let section: String?

/**
Create a search submit request

- Parameters:
- searchTerm: The term that the user searched for
- userInput: The current text in the input field
- filters: The filters applied by the search
- analyticsTags: Additional custom analytics tags to be sent with the event
- section: The section of the index to use
*/
public init(searchTerm: String, userInput: String, filters: CIOTrackSearchSubmitFilters? = nil, analyticsTags: [String: String]? = nil, section: String? = nil) {
self.searchTerm = searchTerm
self.userInput = userInput
self.filters = filters
self.analyticsTags = analyticsTags
self.section = section
}
}
29 changes: 25 additions & 4 deletions AutocompleteClient/FW/Logic/Worker/ConstructorIO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,27 @@ public class ConstructorIO: CIOSessionManagerDelegate {
}

/**
Track when a user submits a search (pressing enter within input element, or clicking submit element)
Track when a user submits a search (pressing enter within input element, or clicking submit button)

- Parameters:
- request: The search submit request. See `CIOTrackSearchSubmitRequest` for the available fields
- completionHandler: The callback to execute on completion.

### Usage Example: ###
```
constructorIO.trackSearchSubmit(CIOTrackSearchSubmitRequest(searchTerm: "apple", userInput: "app"))
```
*/
public func trackSearchSubmit(_ request: CIOTrackSearchSubmitRequest, completionHandler: TrackingCompletionHandler? = nil) {
let sectionName = request.section ?? self.config.defaultItemSectionName ?? Constants.Track.defaultItemSectionName
Comment thread
evanyan13 marked this conversation as resolved.
let mergedTags = mergeAnalyticsTags(defaultAnalyticsTags: self.config.defaultAnalyticsTags, newAnalyticsTags: request.analyticsTags)
let data = CIOTrackSearchSubmitData(searchTerm: request.searchTerm, userInput: request.userInput, filters: request.filters, analyticsTags: mergedTags, section: sectionName)
let urlRequest = self.buildRequest(data: data)
executeTracking(urlRequest, completionHandler: completionHandler)
}

/**
Track when a user submits a search (pressing enter within input element, or clicking submit button)

- Parameters:
- searchTerm: The term that the user searched for
Expand All @@ -448,10 +468,11 @@ public class ConstructorIO: CIOSessionManagerDelegate {
constructorIO.trackSearchSubmit(searchTerm: "apple", originalQuery: "app")
```
*/
@available(*, deprecated, message: "Use trackSearchSubmit(_:completionHandler:) with CIOTrackSearchSubmitRequest instead")
public func trackSearchSubmit(searchTerm: String, originalQuery: String, group: CIOGroup? = nil, analyticsTags: [String: String]? = nil, completionHandler: TrackingCompletionHandler? = nil) {
let data = CIOTrackSearchSubmitData(searchTerm: searchTerm, originalQuery: originalQuery, group: group, analyticsTags: mergeAnalyticsTags(defaultAnalyticsTags: self.config.defaultAnalyticsTags, newAnalyticsTags: analyticsTags))
let request = self.buildRequest(data: data)
executeTracking(request, completionHandler: completionHandler)
// The v2 endpoint rejects an empty group_id, omit filters if groupID is empty
let filters = group.flatMap { $0.groupID.isEmpty ? nil : CIOTrackSearchSubmitFilters(groupID: $0.groupID) }
trackSearchSubmit(CIOTrackSearchSubmitRequest(searchTerm: searchTerm, userInput: originalQuery, filters: filters, analyticsTags: analyticsTags), completionHandler: completionHandler)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,8 @@ extension CIOAutocompleteViewController: UITableViewDelegate, UITableViewDataSou
constructorIO.trackAutocompleteSelect(searchTerm: result.result.value, originalQuery: viewModel.searchTerm, sectionName: sectionName, group: result.group)

// Track search
constructorIO.trackSearchSubmit(searchTerm: result.result.value, originalQuery: viewModel.searchTerm, group: result.group)
let filters = result.group.map { CIOTrackSearchSubmitFilters(groupID: $0.groupID) }
Comment thread
evanyan13 marked this conversation as resolved.
constructorIO.trackSearchSubmit(CIOTrackSearchSubmitRequest(searchTerm: result.result.value, userInput: viewModel.searchTerm, filters: filters))

self.delegate?.autocompleteController?(controller: self, didSelectResult: result)
}
Expand Down Expand Up @@ -404,7 +405,7 @@ extension CIOAutocompleteViewController: UISearchBarDelegate {

public func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
// Track search
self.constructorIO.trackSearchSubmit(searchTerm: viewModel.searchTerm, originalQuery: viewModel.searchTerm)
self.constructorIO.trackSearchSubmit(CIOTrackSearchSubmitRequest(searchTerm: viewModel.searchTerm, userInput: viewModel.searchTerm))
}

public func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
Expand Down
Loading
Loading