Skip to content

Commit

Permalink
feat: delete custom user attributes from events (#41)
Browse files Browse the repository at this point in the history
  • Loading branch information
zhu-xiaowei authored Dec 12, 2023
1 parent 87a0e7f commit eb441ae
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 37 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
4 changes: 2 additions & 2 deletions Sources/Clickstream/AWSClickstreamPlugin+ClientBehavior.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ extension AWSClickstreamPlugin {

func enable() {
if isEnabled { return }
self.autoFlushEventsTimer?.resume()
autoFlushEventsTimer?.resume()
clickstream.isEnable = true
isEnabled = true
}
Expand All @@ -104,6 +104,6 @@ extension AWSClickstreamPlugin {
if !isEnabled { return }
isEnabled = false
clickstream.isEnable = false
self.autoFlushEventsTimer?.suspend()
autoFlushEventsTimer?.suspend()
}
}
1 change: 0 additions & 1 deletion Sources/Clickstream/ClickstreamObjc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ import Foundation
try ClickstreamAnalytics.getClickstreamConfiguration()
}


private static func getItems(_ items: [NSDictionary]) -> [ClickstreamAttribute] {
var resultItems: [ClickstreamAttribute] = []
for item in items {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand All @@ -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
}

Expand All @@ -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 {
Expand All @@ -70,7 +72,7 @@ class AnalyticsClient: AnalyticsClientBehaviour {
userAttribute["value"] = attribute
}
userAttribute["set_timestamp"] = Date().millisecondsSince1970
userAttributes[key] = userAttribute
allUserAttributes[key] = userAttribute
}
}

Expand All @@ -79,15 +81,15 @@ class AnalyticsClient: AnalyticsClientBehaviour {
}

func removeUserAttribute(forKey key: String) {
userAttributes[key] = nil
allUserAttributes[key] = nil
}

func updateUserId(_ id: String?) {
if userId != id {
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
Expand All @@ -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
Expand Down Expand Up @@ -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)
}

Expand All @@ -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 {}
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
35 changes: 21 additions & 14 deletions Tests/ClickstreamTests/Clickstream/AnalyticsClientTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down Expand Up @@ -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)
}
Expand All @@ -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)
}

Expand All @@ -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)
}

Expand All @@ -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))
}

Expand All @@ -220,19 +220,26 @@ 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"
let userUniqueId = clickstream.userUniqueId
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)
}
Expand Down Expand Up @@ -284,7 +291,7 @@ class AnalyticsClientTest: XCTestCase {
}
}

func testRecordRecordEventWithUserAttribute() async {
func testRecordRecordEventWithoutCustomUserAttribute() async {
let event = analyticsClient.createEvent(withEventType: "testEvent")
XCTAssertTrue(event.attributes.isEmpty)

Expand All @@ -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")
Expand Down
21 changes: 13 additions & 8 deletions Tests/ClickstreamTests/IntegrationTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}

Expand Down

0 comments on commit eb441ae

Please sign in to comment.