diff --git a/README.md b/README.md index 52cadd3..8e96d05 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,13 @@ Clickstream Swift SDK can help you easily collect and report in-app events from The SDK relies on the Amplify for Swift Core Library and is developed according to the Amplify Swift SDK plug-in specification. In addition, we've added features that automatically collect common user events and attributes (e.g., screen view, first open) to simplify data collection for users. +Visit our [Documentation site](https://awslabs.github.io/clickstream-analytics-on-aws/en/latest/sdk-manual/swift/) and to learn more about Clickstream Swift SDK. + ### Platform Support The Clickstream SDK supports iOS 13+. -[**API Documentation**](https://awslabs.github.io/clickstream-swift/) +[**API Documentation**](https://awslabs.github.io/clickstream-swift/) - [Objective-C API Reference](https://awslabs.github.io/clickstream-swift/Classes/ClickstreamObjc.html) diff --git a/Sources/Clickstream/AWSClickstreamPlugin+ClientBehavior.swift b/Sources/Clickstream/AWSClickstreamPlugin+ClientBehavior.swift index 81909c8..2208072 100644 --- a/Sources/Clickstream/AWSClickstreamPlugin+ClientBehavior.swift +++ b/Sources/Clickstream/AWSClickstreamPlugin+ClientBehavior.swift @@ -95,7 +95,7 @@ extension AWSClickstreamPlugin { func enable() { if isEnabled { return } - self.autoFlushEventsTimer?.resume() + autoFlushEventsTimer?.resume() clickstream.isEnable = true isEnabled = true } @@ -104,6 +104,6 @@ extension AWSClickstreamPlugin { if !isEnabled { return } isEnabled = false clickstream.isEnable = false - self.autoFlushEventsTimer?.suspend() + autoFlushEventsTimer?.suspend() } } diff --git a/Sources/Clickstream/ClickstreamObjc.swift b/Sources/Clickstream/ClickstreamObjc.swift index b14b55e..5f10dbb 100644 --- a/Sources/Clickstream/ClickstreamObjc.swift +++ b/Sources/Clickstream/ClickstreamObjc.swift @@ -71,7 +71,6 @@ import Foundation try ClickstreamAnalytics.getClickstreamConfiguration() } - private static func getItems(_ items: [NSDictionary]) -> [ClickstreamAttribute] { var resultItems: [ClickstreamAttribute] = [] for item in items { diff --git a/Sources/Clickstream/Dependency/Clickstream/Analytics/AnalyticsClient.swift b/Sources/Clickstream/Dependency/Clickstream/Analytics/AnalyticsClient.swift index 78c0758..853345f 100644 --- a/Sources/Clickstream/Dependency/Clickstream/Analytics/AnalyticsClient.swift +++ b/Sources/Clickstream/Dependency/Clickstream/Analytics/AnalyticsClient.swift @@ -27,7 +27,8 @@ class AnalyticsClient: AnalyticsClientBehaviour { private(set) var eventRecorder: AnalyticsEventRecording private let sessionProvider: SessionProvider private(set) lazy var globalAttributes: [String: AttributeValue] = [:] - private(set) var userAttributes: [String: Any] = [:] + private(set) var allUserAttributes: [String: Any] = [:] + private(set) var simpleUserAttributes: [String: Any] = [:] private let clickstream: ClickstreamContext private(set) var userId: String? var autoRecordClient: AutoRecordEventClient? @@ -40,7 +41,8 @@ class AnalyticsClient: AnalyticsClientBehaviour { self.eventRecorder = eventRecorder self.sessionProvider = sessionProvider self.userId = UserDefaultsUtil.getCurrentUserId(storage: clickstream.storage) - self.userAttributes = UserDefaultsUtil.getUserAttributes(storage: clickstream.storage) + self.allUserAttributes = UserDefaultsUtil.getUserAttributes(storage: clickstream.storage) + self.simpleUserAttributes = getSimpleUserAttributes() self.autoRecordClient = (clickstream.sessionClient as? SessionClient)?.autoRecordClient } @@ -57,9 +59,9 @@ class AnalyticsClient: AnalyticsClientBehaviour { } func addUserAttribute(_ attribute: AttributeValue, forKey key: String) { - let eventError = EventChecker.checkUserAttribute( - currentNumber: userAttributes.count, - key: key, value: attribute) + let eventError = EventChecker.checkUserAttribute(currentNumber: allUserAttributes.count, + key: key, + value: attribute) if eventError.errorCode > 0 { recordEventError(eventError) } else { @@ -70,7 +72,7 @@ class AnalyticsClient: AnalyticsClientBehaviour { userAttribute["value"] = attribute } userAttribute["set_timestamp"] = Date().millisecondsSince1970 - userAttributes[key] = userAttribute + allUserAttributes[key] = userAttribute } } @@ -79,7 +81,7 @@ class AnalyticsClient: AnalyticsClientBehaviour { } func removeUserAttribute(forKey key: String) { - userAttributes[key] = nil + allUserAttributes[key] = nil } func updateUserId(_ id: String?) { @@ -87,7 +89,7 @@ class AnalyticsClient: AnalyticsClientBehaviour { userId = id UserDefaultsUtil.saveCurrentUserId(storage: clickstream.storage, userId: userId) if let newUserId = id, !newUserId.isEmpty { - userAttributes = JsonObject() + allUserAttributes = JsonObject() let userInfo = UserDefaultsUtil.getNewUserInfo(storage: clickstream.storage, userId: newUserId) // swiftlint:disable force_cast clickstream.userUniqueId = userInfo["user_unique_id"] as! String @@ -100,11 +102,12 @@ class AnalyticsClient: AnalyticsClientBehaviour { } else { addUserAttribute(id!, forKey: Event.ReservedAttribute.USER_ID) } + simpleUserAttributes = getSimpleUserAttributes() } } func updateUserAttributes() { - UserDefaultsUtil.updateUserAttributes(storage: clickstream.storage, userAttributes: userAttributes) + UserDefaultsUtil.updateUserAttributes(storage: clickstream.storage, userAttributes: allUserAttributes) } // MARK: - Event recording @@ -140,7 +143,11 @@ class AnalyticsClient: AnalyticsClientBehaviour { forKey: Event.ReservedAttribute.SCREEN_UNIQUEID) } } - event.setUserAttribute(userAttributes) + if event.eventType == Event.PresetEvent.PROFILE_SET { + event.setUserAttribute(allUserAttributes) + } else { + event.setUserAttribute(simpleUserAttributes) + } try eventRecorder.save(event) } @@ -160,6 +167,17 @@ class AnalyticsClient: AnalyticsClientBehaviour { func submitEvents(isBackgroundMode: Bool = false) { eventRecorder.submitEvents(isBackgroundMode: isBackgroundMode) } + + func getSimpleUserAttributes() -> [String: Any] { + simpleUserAttributes = [:] + simpleUserAttributes[Event.ReservedAttribute.USER_FIRST_TOUCH_TIMESTAMP] + = allUserAttributes[Event.ReservedAttribute.USER_FIRST_TOUCH_TIMESTAMP] + if allUserAttributes.keys.contains(Event.ReservedAttribute.USER_ID) { + simpleUserAttributes[Event.ReservedAttribute.USER_ID] + = allUserAttributes[Event.ReservedAttribute.USER_ID] + } + return simpleUserAttributes + } } extension AnalyticsClient: ClickstreamLogger {} diff --git a/Sources/Clickstream/Dependency/Clickstream/Event/EventChecker.swift b/Sources/Clickstream/Dependency/Clickstream/Event/EventChecker.swift index 937bf9d..feb6fbf 100644 --- a/Sources/Clickstream/Dependency/Clickstream/Event/EventChecker.swift +++ b/Sources/Clickstream/Dependency/Clickstream/Event/EventChecker.swift @@ -211,7 +211,9 @@ class EventChecker { error.errorMessage = getErrorMessage(key) } } - if error.errorCode == Event.ErrorCode.NO_ERROR, valueString.utf8.count > Event.Limit.MAX_LENGTH_OF_ITEM_VALUE { + if error.errorCode == Event.ErrorCode.NO_ERROR, + valueString.utf8.count > Event.Limit.MAX_LENGTH_OF_ITEM_VALUE + { errorMsg = """ item attribute : \(key), reached the max length of item attribute value limit ( \(Event.Limit.MAX_LENGTH_OF_ITEM_VALUE). current length is: (\(valueString.utf8.count)) diff --git a/Tests/ClickstreamTests/Clickstream/AnalyticsClientTest.swift b/Tests/ClickstreamTests/Clickstream/AnalyticsClientTest.swift index ca779e1..c76d320 100644 --- a/Tests/ClickstreamTests/Clickstream/AnalyticsClientTest.swift +++ b/Tests/ClickstreamTests/Clickstream/AnalyticsClientTest.swift @@ -126,8 +126,8 @@ class AnalyticsClientTest: XCTestCase { func testAddUserAttributeSuccess() { analyticsClient.addUserAttribute("appStore", forKey: "userChannel") - let userAttributeCount = analyticsClient.userAttributes.count - let attributeValue = (analyticsClient.userAttributes["userChannel"] as! JsonObject)["value"] as? String + let userAttributeCount = analyticsClient.allUserAttributes.count + let attributeValue = (analyticsClient.allUserAttributes["userChannel"] as! JsonObject)["value"] as? String XCTAssertEqual(userAttributeCount, 2) XCTAssertEqual(attributeValue, "appStore") } @@ -169,8 +169,8 @@ class AnalyticsClientTest: XCTestCase { analyticsClient.addUserAttribute("value1", forKey: "name01") analyticsClient.addUserAttribute("value2", forKey: "name02") analyticsClient.removeUserAttribute(forKey: "name01") - let value1 = analyticsClient.userAttributes["name01"] - let value2 = analyticsClient.userAttributes["name02"] + let value1 = analyticsClient.allUserAttributes["name01"] + let value2 = analyticsClient.allUserAttributes["name02"] XCTAssertNil(value1) XCTAssertNotNil(value2) } @@ -180,7 +180,7 @@ class AnalyticsClientTest: XCTestCase { analyticsClient.addUserAttribute("value", forKey: "name\(i)") } analyticsClient.removeUserAttribute(forKey: "name1000") - let userAttributeCount = analyticsClient.userAttributes.count + let userAttributeCount = analyticsClient.allUserAttributes.count XCTAssertEqual(100, userAttributeCount) } @@ -201,7 +201,7 @@ class AnalyticsClientTest: XCTestCase { } Thread.sleep(forTimeInterval: 0.02) XCTAssertEqual(0, eventRecorder.saveCount) - let userAttributeCount = analyticsClient.userAttributes.count + let userAttributeCount = analyticsClient.allUserAttributes.count XCTAssertEqual(2, userAttributeCount) } @@ -210,7 +210,7 @@ class AnalyticsClientTest: XCTestCase { let userUniqueId = clickstream.userUniqueId XCTAssertNil(userId) XCTAssertNotNil(userUniqueId) - let userAttribute = analyticsClient.userAttributes + let userAttribute = analyticsClient.allUserAttributes XCTAssertTrue(userAttribute.keys.contains(Event.ReservedAttribute.USER_FIRST_TOUCH_TIMESTAMP)) } @@ -220,11 +220,18 @@ class AnalyticsClientTest: XCTestCase { analyticsClient.updateUserId(userIdForA) analyticsClient.addUserAttribute(12, forKey: "user_age") analyticsClient.updateUserId(userIdForA) - let userAttribute = analyticsClient.userAttributes + let userAttribute = analyticsClient.allUserAttributes XCTAssertTrue(userAttribute.keys.contains("user_age")) XCTAssertEqual(userUniqueId, clickstream.userUniqueId) } + func testGetSimpleUserAttributeWithUserId() { + analyticsClient.updateUserId("123") + let simpleUserAttributes = analyticsClient.getSimpleUserAttributes() + XCTAssertTrue(simpleUserAttributes.keys.contains(Event.ReservedAttribute.USER_FIRST_TOUCH_TIMESTAMP)) + XCTAssertTrue(simpleUserAttributes.keys.contains(Event.ReservedAttribute.USER_ID)) + } + func testUpdateDifferentUserId() { let userIdForA = "aaa" let userIdForB = "bbb" @@ -232,7 +239,7 @@ class AnalyticsClientTest: XCTestCase { analyticsClient.updateUserId(userIdForA) analyticsClient.addUserAttribute(12, forKey: "user_age") analyticsClient.updateUserId(userIdForB) - let userAttribute = analyticsClient.userAttributes + let userAttribute = analyticsClient.allUserAttributes XCTAssertFalse(userAttribute.keys.contains("user_age")) XCTAssertNotEqual(userUniqueId, clickstream.userUniqueId) } @@ -284,7 +291,7 @@ class AnalyticsClientTest: XCTestCase { } } - func testRecordRecordEventWithUserAttribute() async { + func testRecordRecordEventWithoutCustomUserAttribute() async { let event = analyticsClient.createEvent(withEventType: "testEvent") XCTAssertTrue(event.attributes.isEmpty) @@ -300,10 +307,10 @@ class AnalyticsClientTest: XCTestCase { return } - XCTAssertEqual(savedEvent.userAttributes.count, 4) - XCTAssertEqual((savedEvent.userAttributes["attribute_0"] as! JsonObject)["value"] as? String, "test_0") - XCTAssertEqual((savedEvent.userAttributes["metric_0"] as! JsonObject)["value"] as? Int, 0) - XCTAssertEqual((savedEvent.userAttributes["metric_1"] as! JsonObject)["value"] as? Int, 1) + XCTAssertEqual(savedEvent.userAttributes.count, 1) + XCTAssertFalse(savedEvent.userAttributes.keys.contains("test_0")) + XCTAssertFalse(savedEvent.userAttributes.keys.contains("metric_0")) + XCTAssertFalse(savedEvent.userAttributes.keys.contains("metric_1")) } catch { XCTFail("Unexpected exception while attempting to record event") diff --git a/Tests/ClickstreamTests/IntegrationTest.swift b/Tests/ClickstreamTests/IntegrationTest.swift index eba8e2b..1c14367 100644 --- a/Tests/ClickstreamTests/IntegrationTest.swift +++ b/Tests/ClickstreamTests/IntegrationTest.swift @@ -156,11 +156,16 @@ class IntegrationTest: XCTestCase { Thread.sleep(forTimeInterval: 0.1) let testEvent = try getTestEvent() let userInfo = testEvent["user"] as! [String: Any] - XCTAssertEqual(21, (userInfo["_user_age"] as! JsonObject)["value"] as! Int) - XCTAssertEqual(true, (userInfo["isFirstOpen"] as! JsonObject)["value"] as! Bool) - XCTAssertEqual(85.2, (userInfo["score"] as! JsonObject)["value"] as! Double) - XCTAssertEqual("carl", (userInfo["_user_name"] as! JsonObject)["value"] as! String) XCTAssertEqual("13232", (userInfo[Event.ReservedAttribute.USER_ID] as! JsonObject)["value"] as! String) + XCTAssertFalse(userInfo.keys.contains("_user_age")) + XCTAssertFalse(userInfo.keys.contains("isFirstOpen")) + XCTAssertFalse(userInfo.keys.contains("score")) + XCTAssertFalse(userInfo.keys.contains("sc_user_nameore")) + + XCTAssertEqual(21, (analyticsClient.allUserAttributes["_user_age"] as! JsonObject)["value"] as! Int) + XCTAssertEqual(true, (analyticsClient.allUserAttributes["isFirstOpen"] as! JsonObject)["value"] as! Bool) + XCTAssertEqual(85.2, ((analyticsClient.allUserAttributes["score"] as! JsonObject)["value"] as! NSDecimalNumber).doubleValue) + XCTAssertEqual("carl", (analyticsClient.allUserAttributes["_user_name"] as! JsonObject)["value"] as! String) } func testSetUserIdString() throws { @@ -354,10 +359,10 @@ class IntegrationTest: XCTestCase { Thread.sleep(forTimeInterval: 0.1) let testEvent = try getTestEvent() let userInfo = testEvent["user"] as! [String: Any] - XCTAssertEqual(21, (userInfo["_user_age"] as! JsonObject)["value"] as! Int) - XCTAssertEqual(true, (userInfo["isFirstOpen"] as! JsonObject)["value"] as! Bool) - XCTAssertEqual(85.2, (userInfo["score"] as! JsonObject)["value"] as! Double) - XCTAssertEqual("carl", (userInfo["_user_name"] as! JsonObject)["value"] as! String) + XCTAssertFalse(userInfo.keys.contains("_user_age")) + XCTAssertFalse(userInfo.keys.contains("isFirstOpen")) + XCTAssertFalse(userInfo.keys.contains("score")) + XCTAssertFalse(userInfo.keys.contains("sc_user_nameore")) XCTAssertEqual("3231", (userInfo[Event.ReservedAttribute.USER_ID] as! JsonObject)["value"] as! String) }