Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: delete custom user attributes from events #41

Merged
merged 5 commits into from
Dec 12, 2023
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
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