Skip to content

Commit a7b6d98

Browse files
authored
[Ch3511] Purchase Event (#66)
* Added purchase tracking event * Removed swiftlint from shell scripts (temporarily), added request builder tests * Updated tracking tests * Updated tracking event * Added a test for multiple ids
1 parent b27b486 commit a7b6d98

File tree

9 files changed

+203
-13
lines changed

9 files changed

+203
-13
lines changed

AutocompleteClient.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
089399DF2161B92600BFE3D9 /* TrackInputFocusRequestBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089399DE2161B92600BFE3D9 /* TrackInputFocusRequestBuilderTests.swift */; };
2020
089399E12161C51800BFE3D9 /* TrackSearchSubmitRequestBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089399E02161C51800BFE3D9 /* TrackSearchSubmitRequestBuilder.swift */; };
2121
089399E32161C67D00BFE3D9 /* TrackSearchResultsLoadedRequestBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089399E22161C67D00BFE3D9 /* TrackSearchResultsLoadedRequestBuilder.swift */; };
22+
08C1E6052193BE3600A2E24E /* CIOTrackPurchaseData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08C1E6042193BE3600A2E24E /* CIOTrackPurchaseData.swift */; };
23+
08C1E6072193C29C00A2E24E /* TrackPurchaseRequestBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08C1E6062193C29C00A2E24E /* TrackPurchaseRequestBuilderTests.swift */; };
2224
08CCD3EF216675DD00C3F234 /* CIOABTestCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CCD3EE216675DD00C3F234 /* CIOABTestCell.swift */; };
2325
08FFB76C215EBBF8008CAA7D /* CIOTrackAutocompleteSelectData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08FFB76B215EBBF8008CAA7D /* CIOTrackAutocompleteSelectData.swift */; };
2426
08FFB76E215EC1E2008CAA7D /* CIOTrackSearchSubmitData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08FFB76D215EC1DF008CAA7D /* CIOTrackSearchSubmitData.swift */; };
@@ -207,6 +209,8 @@
207209
089399DE2161B92600BFE3D9 /* TrackInputFocusRequestBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackInputFocusRequestBuilderTests.swift; sourceTree = "<group>"; };
208210
089399E02161C51800BFE3D9 /* TrackSearchSubmitRequestBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackSearchSubmitRequestBuilder.swift; sourceTree = "<group>"; };
209211
089399E22161C67D00BFE3D9 /* TrackSearchResultsLoadedRequestBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackSearchResultsLoadedRequestBuilder.swift; sourceTree = "<group>"; };
212+
08C1E6042193BE3600A2E24E /* CIOTrackPurchaseData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIOTrackPurchaseData.swift; sourceTree = "<group>"; };
213+
08C1E6062193C29C00A2E24E /* TrackPurchaseRequestBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackPurchaseRequestBuilderTests.swift; sourceTree = "<group>"; };
210214
08CCD3EE216675DD00C3F234 /* CIOABTestCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CIOABTestCell.swift; path = AutocompleteClient/FW/Logic/ABTesting/CIOABTestCell.swift; sourceTree = SOURCE_ROOT; };
211215
08FFB76B215EBBF8008CAA7D /* CIOTrackAutocompleteSelectData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIOTrackAutocompleteSelectData.swift; sourceTree = "<group>"; };
212216
08FFB76D215EC1DF008CAA7D /* CIOTrackSearchSubmitData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CIOTrackSearchSubmitData.swift; sourceTree = "<group>"; };
@@ -472,6 +476,7 @@
472476
F685255A21413D4D00A27FAA /* CIORequestData.swift */,
473477
08FFB76B215EBBF8008CAA7D /* CIOTrackAutocompleteSelectData.swift */,
474478
F19E28A61F5F44C30022C814 /* CIOTrackConversionData.swift */,
479+
08C1E6042193BE3600A2E24E /* CIOTrackPurchaseData.swift */,
475480
088F7D1D210FA3C6005B9FB4 /* CIOTrackInputFocusData.swift */,
476481
F6DF7B9320849E8D00A7CDAD /* CIOTrackSearchResultClickData.swift */,
477482
F6D2E9CB20E21022007F8761 /* CIOTrackSearchResultsLoadedData.swift */,
@@ -593,6 +598,7 @@
593598
F60FE9091F5C5B9D0037A0AB /* AutocompleteQueryRequestBuilderTests.swift */,
594599
0879E93E215F290D00018BBA /* TrackAutocompleteSelectRequestBuilderTests.swift */,
595600
F1C557441F605C9D0040D6AD /* TrackConversionRequestBuilderTests.swift */,
601+
08C1E6062193C29C00A2E24E /* TrackPurchaseRequestBuilderTests.swift */,
596602
089399DE2161B92600BFE3D9 /* TrackInputFocusRequestBuilderTests.swift */,
597603
08465EC92161C96900AEBD76 /* TrackSearchResultClickRequestBuilder.swift */,
598604
089399E22161C67D00BFE3D9 /* TrackSearchResultsLoadedRequestBuilder.swift */,
@@ -1523,6 +1529,7 @@
15231529
F69C2C9D207D1C9C0060B2B9 /* String+SearchSuggestionsSection.swift in Sources */,
15241530
F63808FB1F5D49AF00C3B322 /* TaskResponse.swift in Sources */,
15251531
F685255B21413D4D00A27FAA /* CIORequestData.swift in Sources */,
1532+
08C1E6052193BE3600A2E24E /* CIOTrackPurchaseData.swift in Sources */,
15261533
F1F68DE31F58B8BB00B42602 /* CIOError.swift in Sources */,
15271534
F6EC29AF2176014C00DCFA07 /* SessionLoader.swift in Sources */,
15281535
088F7D1E210FA3C6005B9FB4 /* CIOTrackInputFocusData.swift in Sources */,
@@ -1556,6 +1563,7 @@
15561563
isa = PBXSourcesBuildPhase;
15571564
buildActionMask = 2147483647;
15581565
files = (
1566+
08C1E6072193C29C00A2E24E /* TrackPurchaseRequestBuilderTests.swift in Sources */,
15591567
08689F0121897914000D3565 /* ConstructorIOUserIDTests.swift in Sources */,
15601568
F64F46B51F5975070094C697 /* AutocompleteResultTests.swift in Sources */,
15611569
F1C557451F605C9D0040D6AD /* TrackConversionRequestBuilderTests.swift in Sources */,

AutocompleteClient/Constants/Constants.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ struct Constants {
5858
static let baseURLString = "https://ac.cnstrc.com"
5959
static let httpMethod = "GET"
6060
static let queryStringFormat = "%@/%@/%@"
61-
6261
static let sessionIncrementTimeoutInSeconds: TimeInterval = 1800 // 30 mins
6362
}
6463

@@ -110,6 +109,7 @@ struct Constants {
110109
static let groupID = "group[group_id]"
111110
static let name = "name"
112111
static let customerID = "customer_id"
112+
static let customerIDs = "customer_ids"
113113
static let revenue = "revenue"
114114
static let dateTime = "_dt"
115115
static let defaultItemSectionName = "Products"
@@ -141,13 +141,15 @@ struct Constants {
141141

142142
struct TrackConversion {
143143
static let format = "%@/autocomplete/%@/conversion"
144+
}
144145

146+
struct TrackPurchase {
147+
static let format = "%@/autocomplete/TERM_UNKNOWN/purchase"
145148
}
146149

147150
struct Logging{
148151
private static let prefix = "[ConstructorIO]:"
149152
private static let format: (_ message: String) -> String = { message in return "\(Logging.prefix) \(message)" }
150-
151153
static let performURLRequest: (_ request: URLRequest) -> String = { request in return Logging.format("Performing URL Request \(request)") }
152154
}
153155

AutocompleteClient/FW/Logic/Request/Builder/QueryItemCollection.swift

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,21 @@ struct QueryItemCollection{
1414
mutating func add(_ item: URLQueryItem){
1515
self[item.name] = item
1616
}
17-
18-
mutating func remove(key: String){
19-
self[key] = nil
17+
18+
mutating func addMultiple(index: Int, item: URLQueryItem) {
19+
self[item.name + String(index)] = item
2020
}
21-
22-
private subscript(value: String) -> URLQueryItem?{
23-
get{
24-
return queryItems[value]
21+
22+
mutating func remove(name: String) {
23+
self[name] = nil
24+
}
25+
26+
private subscript(name: String) -> URLQueryItem? {
27+
get {
28+
return queryItems[name]
2529
}
26-
set{
27-
queryItems[value] = newValue
30+
set {
31+
queryItems[name] = newValue
2832
}
2933
}
3034

AutocompleteClient/FW/Logic/Request/Builder/RequestBuilder+QueryItems.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ extension RequestBuilder{
4040
queryItems.add(URLQueryItem(name: Constants.Track.customerID, value: customerID))
4141
}
4242

43+
func set(customerIDs: [String]?) {
44+
guard let customerIDs = customerIDs else { return }
45+
for (index, customerID) in customerIDs.enumerated() {
46+
queryItems.addMultiple(index: index, item: URLQueryItem(name: Constants.Track.customerIDs, value: customerID))
47+
}
48+
}
49+
4350
func set(autocompleteSection: String?) {
4451
guard let sectionName = autocompleteSection else { return }
4552
queryItems.add(URLQueryItem(name: Constants.Track.autocompleteSection, value: sectionName))
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//
2+
// CIOTrackPurchaseData.swift
3+
// Constructor.io
4+
//
5+
// Copyright © Constructor.io. All rights reserved.
6+
// http://constructor.io/
7+
//
8+
9+
import Foundation
10+
11+
/**
12+
Struct encapsulating the parameters that must/can be set set in order to track a purchase for an item.
13+
*/
14+
public struct CIOTrackPurchaseData: CIORequestData {
15+
16+
public let customerIDs: [String]
17+
public var sectionName: String?
18+
19+
public var url: String {
20+
return String(format: Constants.TrackPurchase.format, Constants.Track.baseURLString)
21+
}
22+
23+
public init(customerIDs: [String], sectionName: String? = nil) {
24+
self.customerIDs = customerIDs
25+
self.sectionName = sectionName
26+
}
27+
28+
public func decorateRequest(requestBuilder: RequestBuilder) {
29+
requestBuilder.set(customerIDs: self.customerIDs)
30+
requestBuilder.set(autocompleteSection: self.sectionName)
31+
}
32+
}

AutocompleteClient/FW/Logic/Worker/ConstructorIO.swift

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,20 @@ public class ConstructorIO: CIOSessionManagerDelegate {
137137
let request = self.buildRequest(data: data)
138138
execute(request, completionHandler: completionHandler)
139139
}
140-
140+
141+
/// Track a purchase.
142+
///
143+
/// - Parameters:
144+
/// - customerIDs: customer IDs.
145+
/// - sectionName The name of the autocomplete section the term came from
146+
/// - completionHandler: The callback to execute on completion.
147+
public func trackPurchase(customerIDs: [String], sectionName: String? = nil, completionHandler: TrackingCompletionHandler? = nil) {
148+
let section = sectionName ?? self.config.defaultItemSectionName ?? Constants.Track.defaultItemSectionName
149+
let data = CIOTrackPurchaseData(customerIDs: customerIDs, sectionName: section)
150+
let request = self.buildRequest(data: data)
151+
execute(request, completionHandler: completionHandler)
152+
}
153+
141154
private func trackSessionStart(session: Int, completionHandler: TrackingCompletionHandler? = nil) {
142155
let request = self.buildSessionStartRequest(session: session)
143156
execute(request, completionHandler: completionHandler)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//
2+
// TrackPurchaseRequestBuilderTests.swift
3+
// AutocompleteClientTests
4+
//
5+
// Copyright © Constructor.io. All rights reserved.
6+
// http://constructor.io/
7+
//
8+
9+
import XCTest
10+
@testable import ConstructorAutocomplete
11+
12+
class TrackPurchaseRequestBuilderTests: XCTestCase {
13+
14+
fileprivate let testACKey = "testKey123213"
15+
fileprivate let customerIDs = ["custIDq3éû qd", "womp womp"]
16+
fileprivate let sectionName = "some section name@"
17+
18+
fileprivate var encodedCustomerID1: String = ""
19+
fileprivate var encodedCustomerID2: String = ""
20+
fileprivate var encodedSectionName: String = ""
21+
22+
fileprivate var builder: RequestBuilder!
23+
24+
override func setUp() {
25+
super.setUp()
26+
self.encodedCustomerID1 = "customer_ids=" + customerIDs[0].addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!;
27+
self.encodedCustomerID2 = "customer_ids=" + customerIDs[1].addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!;
28+
self.encodedSectionName = self.sectionName.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
29+
self.builder = RequestBuilder(apiKey: testACKey)
30+
}
31+
32+
func testTrackPurchaseBuilder() {
33+
let tracker = CIOTrackPurchaseData(customerIDs: self.customerIDs)
34+
builder.build(trackData: tracker)
35+
let request = builder.getRequest()
36+
let url = request.url!.absoluteString
37+
38+
XCTAssertEqual(request.httpMethod, "GET")
39+
XCTAssertTrue(url.hasPrefix("https://ac.cnstrc.com/autocomplete/TERM_UNKNOWN/purchase?"))
40+
XCTAssertTrue(url.contains(encodedCustomerID1), "URL should contain the customer ID[1].")
41+
XCTAssertTrue(url.contains(encodedCustomerID2), "URL should contain the customer ID[2].")
42+
XCTAssertTrue(url.contains("c=cioios-"), "URL should contain the version string.")
43+
XCTAssertTrue(url.contains("key=\(testACKey)"), "URL should contain the api key.")
44+
}
45+
46+
func testTrackPurchaseBuilder_WithSectionName() {
47+
let tracker = CIOTrackPurchaseData(customerIDs: self.customerIDs, sectionName: self.sectionName)
48+
builder.build(trackData: tracker)
49+
let request = builder.getRequest()
50+
let url = request.url!.absoluteString
51+
52+
XCTAssertEqual(request.httpMethod, "GET")
53+
XCTAssertTrue(url.hasPrefix("https://ac.cnstrc.com/autocomplete/TERM_UNKNOWN/purchase?"))
54+
XCTAssertTrue(url.contains(encodedCustomerID1), "URL should contain the customer ID[1].")
55+
XCTAssertTrue(url.contains(encodedCustomerID2), "URL should contain the customer ID[2].")
56+
XCTAssertTrue(url.contains("autocomplete_section=\(encodedSectionName)"), "URL should contain the autocomplete section name.")
57+
XCTAssertTrue(url.contains("c=cioios-"), "URL should contain the version string.")
58+
XCTAssertTrue(url.contains("key=\(testACKey)"), "URL should contain the api key.")
59+
}
60+
}

AutocompleteClientTests/FW/Logic/Worker/ConstructorIOTrackingTests.swift

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,4 +411,68 @@ class ConstructorIOTrackingTests: XCTestCase {
411411
})
412412
self.wait(for: expectation)
413413
}
414+
415+
func testTrackPurchase() {
416+
let customerIDs = ["customer_id_q2ew"]
417+
let builder = CIOBuilder(expectation: "Calling trackPurchase should send a valid request with a default section name.", builder: http(200))
418+
stub(regex("https://ac.cnstrc.com/autocomplete/TERM_UNKNOWN/purchase?i=\(kRegexClientID)&key=key_OucJxxrfiTVUQx0C&c=cioios-&s=1&customer_ids=customer_id_q2ew&autocomplete_section=Products&_dt=\(kRegexTimestamp)"), builder.create())
419+
self.constructor.trackPurchase(customerIDs: customerIDs, sectionName: nil)
420+
self.wait(for: builder.expectation)
421+
}
422+
423+
func testTrackPurchase_WithMultipleIDs() {
424+
let customerIDs = ["bumble", "bee", "autobot"]
425+
let builder = CIOBuilder(expectation: "Calling trackPurchase should send a valid request with a default section name.", builder: http(200))
426+
stub(regex("https://ac.cnstrc.com/autocomplete/TERM_UNKNOWN/purchase?_dt=\(kRegexTimestamp)&i=\(kRegexClientID)&key=key_OucJxxrfiTVUQx0C&customer_ids=autobot&c=cioios-&s=1&customer_ids=bumble&autocomplete_section=Products&customer_ids=bee"), builder.create())
427+
self.constructor.trackPurchase(customerIDs: customerIDs, sectionName: nil)
428+
self.wait(for: builder.expectation)
429+
}
430+
431+
func testTrackPurchase_WithSection() {
432+
let customerIDs = ["customer_id_q2ew"]
433+
let sectionName = "Search Suggestions"
434+
let builder = CIOBuilder(expectation: "Calling trackPurchase should send a valid request with a section name.", builder: http(200))
435+
stub(regex("https://ac.cnstrc.com/autocomplete/TERM_UNKNOWN/purchase?i=\(kRegexClientID)&key=key_OucJxxrfiTVUQx0C&c=cioios-&s=1&customer_ids=customer_id_q2ew&autocomplete_section=Search%20Suggestions&_dt=\(kRegexTimestamp)"), builder.create())
436+
self.constructor.trackPurchase(customerIDs: customerIDs, sectionName: sectionName)
437+
self.wait(for: builder.expectation)
438+
}
439+
440+
func testTrackPurchase_With400() {
441+
let expectation = self.expectation(description: "Calling trackPurchase with 400 should return badRequest CIOError.")
442+
let customerIDs = ["customer_id_q2ew"]
443+
stub(regex("https://ac.cnstrc.com/autocomplete/TERM_UNKNOWN/purchase?i=\(kRegexClientID)&key=key_OucJxxrfiTVUQx0C&c=cioios-&s=1&customer_ids=customer_id_q2ew&autocomplete_section=Products&_dt=\(kRegexTimestamp)"), http(400))
444+
self.constructor.trackPurchase(customerIDs: customerIDs, sectionName: nil, completionHandler: { error in
445+
if let cioError = error as? CIOError {
446+
XCTAssertEqual(cioError, .badRequest, "If tracking call returns status code 400, the error should be delegated to the completion handler")
447+
expectation.fulfill()
448+
}
449+
})
450+
self.wait(for: expectation)
451+
}
452+
453+
func testTrackPurchase_With500() {
454+
let expectation = self.expectation(description: "Calling trackPurchase with 500 should return internalServerError CIOError.")
455+
let customerIDs = ["customer_id_q2ew"]
456+
stub(regex("https://ac.cnstrc.com/autocomplete/TERM_UNKNOWN/purchase?i=\(kRegexClientID)&key=key_OucJxxrfiTVUQx0C&c=cioios-&s=1&customer_ids=customer_id_q2ew&autocomplete_section=Products&_dt=\(kRegexTimestamp)"), http(500))
457+
self.constructor.trackPurchase(customerIDs: customerIDs, sectionName: nil, completionHandler: { error in
458+
if let cioError = error as? CIOError {
459+
XCTAssertEqual(cioError, .internalServerError, "If tracking call returns status code 500, the error should be delegated to the completion handler")
460+
expectation.fulfill()
461+
}
462+
})
463+
self.wait(for: expectation)
464+
}
465+
466+
func testTrackPurchase_WithNoConnectivity() {
467+
let expectation = self.expectation(description: "Calling trackPurchase with no connectvity should return noConnectivity CIOError.")
468+
let customerIDs = ["customer_id_q2ew"]
469+
stub(regex("https://ac.cnstrc.com/autocomplete/TERM_UNKNOWN/purchase?i=\(kRegexClientID)&key=key_OucJxxrfiTVUQx0C&c=cioios-&s=1&customer_ids=customer_id_q2ew&autocomplete_section=Products&_dt=\(kRegexTimestamp)"), noConnectivity())
470+
self.constructor.trackPurchase(customerIDs: customerIDs, sectionName: nil, completionHandler: { error in
471+
if let cioError = error as? CIOError {
472+
XCTAssertEqual(cioError, CIOError.noConnection, "If tracking call returns no connectivity, the error should be delegated to the completion handler")
473+
expectation.fulfill()
474+
}
475+
})
476+
self.wait(for: expectation)
477+
}
414478
}

AutocompleteClientTests/Test Utils/Constants/TestConstants.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ let kRegexTimestamp = "[1-9][0-9]*"
1313
let kRegexClientID = "([A-Z0-9-])*"
1414

1515
struct TestConstants {
16-
static let defaultExpectationTimeout: TimeInterval = 10.0
16+
static let defaultExpectationTimeout: TimeInterval = 1.0
1717
static let testApiKey = "key_OucJxxrfiTVUQx0C"
1818
static let testConfig = ConstructorIOConfig(apiKey: "key_OucJxxrfiTVUQx0C")
1919

0 commit comments

Comments
 (0)