Skip to content

Commit c09e346

Browse files
zhu-xiaoweixiaoweii
andauthored
fix: change user engagement event and engagement time calculate rule (#25)
Co-authored-by: xiaoweii <xiaoweii@amazom.com>
1 parent ceecc65 commit c09e346

File tree

9 files changed

+173
-57
lines changed

9 files changed

+173
-57
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ do {
107107
configuration.authCookie = "your authentication cookie"
108108
configuration.sessionTimeoutDuration = 1800000
109109
configuration.isTrackScreenViewEvents = true
110+
configuration.isTrackUserEngagementEvents = true
110111
configuration.isLogEvents = true
111112
configuration.isCompressEvents = true
112113
} catch {

Sources/Clickstream/Dependency/Clickstream/ActivityTracking/UIViewController+Swizzle.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import UIKit
1010

1111
private var hasSwizzled = false
12-
private var viewDidAppearFunc: ((String, String) -> Void)?
12+
private var viewDidAppearFunc: ((String, String, String) -> Void)?
1313
extension UIViewController: ClickstreamLogger {}
1414

1515
extension UIViewController {
@@ -19,7 +19,8 @@ extension UIViewController {
1919

2020
let screenName = NSStringFromClass(type(of: self))
2121
let screenPath = getPath()
22-
viewDidAppearFunc?(screenName, screenPath)
22+
let screenHashValue = String(describing: hashValue)
23+
viewDidAppearFunc?(screenName, screenPath, screenHashValue)
2324
}
2425

2526
func getPath() -> String {
@@ -31,7 +32,7 @@ extension UIViewController {
3132
return path
3233
}
3334

34-
static func swizzle(viewDidAppear: @escaping (String, String) -> Void) {
35+
static func swizzle(viewDidAppear: @escaping (String, String, String) -> Void) {
3536
viewDidAppearFunc = viewDidAppear
3637
guard !hasSwizzled else { return }
3738

Sources/Clickstream/Dependency/Clickstream/AutoRecord/AutoRecordEventClient.swift

Lines changed: 83 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ class AutoRecordEventClient {
1515
private var isEntrances = false
1616
private var isFirstOpen: Bool
1717
private var isFirstTime = true
18-
private var startEngageTimestamp: Int64!
18+
private var lastEngageTime: Int64 = 0
1919
private var lastScreenName: String?
2020
private var lastScreenPath: String?
21-
private var lastScreenStartTime: Int64?
21+
private var lastScreenUniqueId: String?
22+
private var lastScreenStartTimestamp: Int64 = 0
2223

2324
init(clickstream: ClickstreamContext) {
2425
self.clickstream = clickstream
@@ -33,6 +34,84 @@ class AutoRecordEventClient {
3334
}
3435
}
3536

37+
func onViewDidAppear(screenName: String, screenPath: String, screenHashValue: String) {
38+
let screenUniqueId = getScreenUniqueId(screenHashValue)
39+
if !isSameScreen(screenName, screenPath, screenUniqueId) {
40+
recordUserEngagement()
41+
recordScreenView(screenName, screenPath, screenUniqueId)
42+
}
43+
}
44+
45+
func recordScreenView(_ screenName: String, _ screenPath: String, _ screenUniqueId: String) {
46+
if !clickstream.configuration.isTrackScreenViewEvents {
47+
return
48+
}
49+
let event = clickstream.analyticsClient.createEvent(withEventType: Event.PresetEvent.SCREEN_VIEW)
50+
let eventTimestamp = event.timestamp
51+
event.addAttribute(screenName, forKey: Event.ReservedAttribute.SCREEN_NAME)
52+
event.addAttribute(screenPath, forKey: Event.ReservedAttribute.SCREEN_ID)
53+
event.addAttribute(screenUniqueId, forKey: Event.ReservedAttribute.SCREEN_UNIQUEID)
54+
if lastScreenName != nil, lastScreenPath != nil, lastScreenUniqueId != nil {
55+
event.addAttribute(lastScreenName!, forKey: Event.ReservedAttribute.PREVIOUS_SCREEN_NAME)
56+
event.addAttribute(lastScreenPath!, forKey: Event.ReservedAttribute.PREVIOUS_SCREEN_ID)
57+
event.addAttribute(lastScreenUniqueId!, forKey: Event.ReservedAttribute.PREVIOUS_SCREEN_UNIQUEID)
58+
}
59+
let previousTimestamp = getPreviousScreenViewTimestamp()
60+
if previousTimestamp > 0 {
61+
event.addAttribute(previousTimestamp, forKey: Event.ReservedAttribute.PREVIOUS_TIMESTAMP)
62+
}
63+
event.addAttribute(isEntrances ? 1 : 0, forKey: Event.ReservedAttribute.ENTRANCES)
64+
if lastEngageTime > 0 {
65+
event.addAttribute(lastEngageTime, forKey: Event.ReservedAttribute.ENGAGEMENT_TIMESTAMP)
66+
}
67+
recordEvent(event)
68+
69+
isEntrances = false
70+
lastScreenName = screenName
71+
lastScreenPath = screenPath
72+
lastScreenUniqueId = screenUniqueId
73+
lastScreenStartTimestamp = eventTimestamp
74+
UserDefaultsUtil.savePreviousScreenViewTimestamp(storage: clickstream.storage, timestamp: eventTimestamp)
75+
}
76+
77+
func recordUserEngagement() {
78+
if lastScreenStartTimestamp == 0 { return }
79+
lastEngageTime = Date().millisecondsSince1970 - lastScreenStartTimestamp
80+
if clickstream.configuration.isTrackUserEngagementEvents, lastEngageTime > Constants.minEngagementTime {
81+
let event = clickstream.analyticsClient.createEvent(withEventType: Event.PresetEvent.USER_ENGAGEMENT)
82+
event.addAttribute(lastEngageTime, forKey: Event.ReservedAttribute.ENGAGEMENT_TIMESTAMP)
83+
if lastScreenName != nil, lastScreenPath != nil, lastScreenUniqueId != nil {
84+
event.addAttribute(lastScreenName!, forKey: Event.ReservedAttribute.SCREEN_NAME)
85+
event.addAttribute(lastScreenPath!, forKey: Event.ReservedAttribute.SCREEN_ID)
86+
event.addAttribute(lastScreenUniqueId!, forKey: Event.ReservedAttribute.SCREEN_UNIQUEID)
87+
}
88+
recordEvent(event)
89+
}
90+
}
91+
92+
func getPreviousScreenViewTimestamp() -> Int64 {
93+
if lastScreenStartTimestamp > 0 {
94+
return lastScreenStartTimestamp
95+
} else {
96+
return UserDefaultsUtil.getPreviousScreenViewTimestamp(storage: clickstream.storage)
97+
}
98+
}
99+
100+
func getScreenUniqueId(_ screenHashValue: String) -> String {
101+
let shortDeviceId = clickstream.systemInfo.deviceId.padding(toLength: Constants.maxDeviceIdLength,
102+
withPad: Constants.paddingChar,
103+
startingAt: 0)
104+
return "\(shortDeviceId)-\(screenHashValue)"
105+
}
106+
107+
func isSameScreen(_ screenName: String, _ screenPath: String, _ screenUniqueId: String) -> Bool {
108+
lastScreenName != nil
109+
&& lastScreenPath != nil
110+
&& screenName == lastScreenName
111+
&& screenPath == lastScreenPath
112+
&& screenUniqueId == lastScreenUniqueId
113+
}
114+
36115
func checkAppVersionUpdate(clickstream: ClickstreamContext) {
37116
let appVersion = UserDefaultsUtil.getAppVersion(storage: clickstream.storage)
38117
if appVersion != nil {
@@ -82,24 +161,6 @@ class AutoRecordEventClient {
82161
isFirstTime = false
83162
}
84163

85-
func recordUserEngagement() {
86-
let engagementTime = Date().millisecondsSince1970 - startEngageTimestamp
87-
if engagementTime > Constants.minEngagementTime {
88-
let event = clickstream.analyticsClient.createEvent(withEventType: Event.PresetEvent.USER_ENGAGEMENT)
89-
event.addAttribute(engagementTime, forKey: Event.ReservedAttribute.ENGAGEMENT_TIMESTAMP)
90-
if lastScreenName != nil, lastScreenPath != nil {
91-
event.addAttribute(lastScreenName!, forKey: Event.ReservedAttribute.SCREEN_NAME)
92-
event.addAttribute(lastScreenPath!, forKey: Event.ReservedAttribute.SCREEN_ID)
93-
}
94-
recordEvent(event)
95-
}
96-
clickstream.analyticsClient.submitEvents(isBackgroundMode: true)
97-
}
98-
99-
func updateEngageTimestamp() {
100-
startEngageTimestamp = Date().millisecondsSince1970
101-
}
102-
103164
func recordSessionStartEvent() {
104165
let event = clickstream.analyticsClient.createEvent(withEventType: Event.PresetEvent.SESSION_START)
105166
recordEvent(event)
@@ -109,31 +170,6 @@ class AutoRecordEventClient {
109170
isEntrances = true
110171
}
111172

112-
func onViewDidAppear(screenName: String, screenPath: String) {
113-
if !clickstream.configuration.isTrackScreenViewEvents {
114-
return
115-
}
116-
let currentTimestamp = Date().millisecondsSince1970
117-
let event = clickstream.analyticsClient.createEvent(withEventType: Event.PresetEvent.SCREEN_VIEW)
118-
event.addAttribute(screenName, forKey: Event.ReservedAttribute.SCREEN_NAME)
119-
event.addAttribute(screenPath, forKey: Event.ReservedAttribute.SCREEN_ID)
120-
if lastScreenName != nil, lastScreenPath != nil {
121-
event.addAttribute(lastScreenName!, forKey: Event.ReservedAttribute.PREVIOUS_SCREEN_NAME)
122-
event.addAttribute(lastScreenPath!, forKey: Event.ReservedAttribute.PREVIOUS_SCREEN_ID)
123-
}
124-
event.addAttribute(isEntrances ? 1 : 0, forKey: Event.ReservedAttribute.ENTRANCES)
125-
if !isEntrances, lastScreenStartTime != nil {
126-
event.addAttribute(currentTimestamp - lastScreenStartTime!,
127-
forKey: Event.ReservedAttribute.ENGAGEMENT_TIMESTAMP)
128-
}
129-
recordEvent(event)
130-
131-
isEntrances = false
132-
lastScreenName = screenName
133-
lastScreenPath = screenPath
134-
lastScreenStartTime = currentTimestamp
135-
}
136-
137173
func setupExceptionHandler() {
138174
NSSetUncaughtExceptionHandler { exception in
139175
AutoRecordEventClient.handleException(exception)
@@ -168,6 +204,8 @@ class AutoRecordEventClient {
168204
extension AutoRecordEventClient {
169205
enum Constants {
170206
static let minEngagementTime = 1_000
207+
static let maxDeviceIdLength = 8
208+
static let paddingChar = "_"
171209
}
172210
}
173211

Sources/Clickstream/Dependency/Clickstream/ClickstreamContext.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ extension UserDefaults: UserDefaultsBehaviour {
4545
var isTrackAppExceptionEvents: Bool
4646
/// Whether to track app scren view events automatically
4747
public var isTrackScreenViewEvents: Bool
48+
public var isTrackUserEngagementEvents: Bool
4849
/// Whether to compress events when send to server
4950
public var isCompressEvents: Bool
5051
/// Whether to log events json in console when debug
@@ -59,6 +60,7 @@ extension UserDefaults: UserDefaultsBehaviour {
5960
sendEventsInterval: Int,
6061
isTrackAppExceptionEvents: Bool = true,
6162
isTrackScreenViewEvents: Bool = true,
63+
isTrackUserEngagementEvents: Bool = true,
6264
isCompressEvents: Bool = true,
6365
isLogEvents: Bool = false,
6466
sessionTimeoutDuration: Int64 = 1_800_000)
@@ -68,6 +70,7 @@ extension UserDefaults: UserDefaultsBehaviour {
6870
self.sendEventsInterval = sendEventsInterval
6971
self.isTrackAppExceptionEvents = isTrackAppExceptionEvents
7072
self.isTrackScreenViewEvents = isTrackScreenViewEvents
73+
self.isTrackUserEngagementEvents = isTrackUserEngagementEvents
7174
self.isCompressEvents = isCompressEvents
7275
self.isLogEvents = isLogEvents
7376
self.sessionTimeoutDuration = sessionTimeoutDuration

Sources/Clickstream/Dependency/Clickstream/Event/Event.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,11 @@ enum Event {
164164
static let ENTRANCES = "_entrances"
165165
static let PREVIOUS_SCREEN_ID = "_previous_screen_id"
166166
static let PREVIOUS_SCREEN_NAME = "_previous_screen_name"
167+
static let PREVIOUS_SCREEN_UNIQUEID = "_previous_screen_unique_id"
168+
static let PREVIOUS_TIMESTAMP = "_previous_timestamp"
167169
static let SCREEN_ID = "_screen_id"
168170
static let SCREEN_NAME = "_screen_name"
171+
static let SCREEN_UNIQUEID = "_screen_unique_id"
169172
static let IS_FIRST_TIME = "_is_first_time"
170173
static let EXCEPTION_NAME = "_exception_name"
171174
static let EXCEPTION_REASON = "_exception_reason"

Sources/Clickstream/Dependency/Clickstream/Session/SessionClient.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ class SessionClient: SessionClientBehaviour {
5757

5858
private func handleAppEnterForeground() {
5959
log.debug("Application entered the foreground.")
60-
autoRecordClient.updateEngageTimestamp()
6160
autoRecordClient.handleAppStart()
6261
let isNewSession = initialSession()
6362
if isNewSession {
@@ -69,6 +68,7 @@ class SessionClient: SessionClientBehaviour {
6968
log.debug("Application entered the background.")
7069
storeSession()
7170
autoRecordClient.recordUserEngagement()
71+
clickstream.analyticsClient.submitEvents(isBackgroundMode: true)
7272
}
7373

7474
private func respond(to newState: ApplicationState) {

Sources/Clickstream/Support/Utils/UserDefaultsUtil.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,16 @@ enum UserDefaultsUtil {
173173
static func saveBundleSequenceId(storage: ClickstreamContextStorage, bundleSequenceId: Int) {
174174
storage.userDefaults.save(key: Constants.isFirstOpenKey, value: String(describing: bundleSequenceId))
175175
}
176+
177+
static func getPreviousScreenViewTimestamp(storage: ClickstreamContextStorage) -> Int64 {
178+
Int64(storage.userDefaults.string(forKey: Constants.previousScreenViewTimestampKey) ?? "0")!
179+
}
180+
181+
static func savePreviousScreenViewTimestamp(storage: ClickstreamContextStorage, timestamp: Int64) {
182+
storage.userDefaults.save(key: Constants.previousScreenViewTimestampKey, value: String(describing: timestamp))
183+
}
176184
}
185+
177186
// swiftlint:enable force_cast
178187

179188
extension UserDefaultsUtil {
@@ -190,6 +199,7 @@ extension UserDefaultsUtil {
190199
static let sessionKey = prefix + "sessionKey"
191200
static let isFirstOpenKey = prefix + "isFirstOpenKey"
192201
static let bundleSequenceIdKey = prefix + "bundleSequenceIdKey"
202+
static let previousScreenViewTimestampKey = prefix + "previousScreenViewTimestampKey"
193203
}
194204
}
195205

0 commit comments

Comments
 (0)