Skip to content

Commit

Permalink
fix(Analytics): Updating session stop time for cached events.
Browse files Browse the repository at this point in the history
Also improving how sessions are handled in SessionClient
  • Loading branch information
sebaland committed Jan 9, 2024
1 parent c11781f commit 49e5692
Show file tree
Hide file tree
Showing 11 changed files with 188 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public protocol AnalyticsClientBehaviour: Actor {
onSubmit: SubmitResult?)

@discardableResult func submitEvents() async throws -> [PinpointEvent]
func update(_ session: PinpointSession) async throws

nonisolated func createAppleMonetizationEvent(with transaction: SKPaymentTransaction,
with product: SKProduct) -> PinpointEvent
Expand Down Expand Up @@ -262,6 +263,10 @@ actor AnalyticsClient: AnalyticsClientBehaviour {
func submitEvents() async throws -> [PinpointEvent] {
return try await eventRecorder.submitAllEvents()
}

func update(_ session: PinpointSession) async throws {
try await eventRecorder.update(session)
}

func setAutomaticSubmitEventsInterval(_ interval: TimeInterval,
onSubmit: SubmitResult?) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ protocol AnalyticsEventRecording: Actor {
func updateAttributesOfEvents(ofType: String,
withSessionId: PinpointSession.SessionId,
setAttributes: [String: String]) throws

/// Updates the session information of the events that match the same sessionId.
/// - Parameter session: The session to update
func update(_ session: PinpointSession) throws

/// Submit all locally stored events
/// - Returns: A collection of events submitted to Pinpoint
Expand Down Expand Up @@ -78,6 +82,10 @@ actor EventRecorder: AnalyticsEventRecording {
setAttributes: attributes)
}

func update(_ session: PinpointSession) throws {
try storage.updateSession(session)
}

/// Submit all locally stored events in batches. If a previous submission is in progress, it waits until it's completed before proceeding.
/// When the submission for an event is accepted, the event is removed from local storage
/// When the submission for an event is rejected, the event retry count is incremented in the local storage. Events that exceed the maximum retry count (3) are purged.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,22 @@ class AnalyticsEventSQLStorage: AnalyticsEventStorage {
sessionId,
eventType])
}

func updateSession(_ session: PinpointSession) throws {
let updateStatement = """
UPDATE Event
SET sessionStartTime = ?, sessionStopTime = ?
WHERE sessionId = ?
"""
_ = try dbAdapter.executeQuery(
updateStatement,
[
session.startTime.asISO8601String,
session.stopTime?.asISO8601String,
session.sessionId
]
)
}

/// Get the oldest event with limit
/// - Parameter limit: The number of query result to limit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ protocol AnalyticsEventStorage {
func updateEvents(ofType: String,
withSessionId: PinpointSession.SessionId,
setAttributes: [String: String]) throws

/// Updates the session information of the events that match the same sessionId.
/// - Parameter session: The session to update
func updateSession(_ session: PinpointSession) throws

/// Get the oldest event with limit
/// - Parameter limit: The number of query result to limit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,36 @@ import AWSPluginsCore
import Foundation

extension PinpointEvent {
var clientTypeSession: PinpointClientTypes.Session? {
#if os(watchOS)
// If the session duration cannot be represented by Int, return a nil session instead.
// This is extremely unlikely to happen since a session's stopTime is set when the app is closed
if let duration = session.duration, duration > Int.max {
return nil
private var clientTypeSession: PinpointClientTypes.Session? {
var sessionDuration: Int? = nil
if let duration = session.duration {
// If the session duration cannot be represented by Int, return a nil session instead.
// This is extremely unlikely to happen since a session's stopTime is set when the app is closed
guard let intDuration = Int(exactly: duration) else { return nil }
sessionDuration = intDuration
}
#endif
return PinpointClientTypes.Session(duration: Int(session.duration),
id: session.sessionId,
startTimestamp: session.startTime.asISO8601String,
stopTimestamp: session.stopTime?.asISO8601String)

return .init(
duration: sessionDuration,
id: session.sessionId,
startTimestamp: session.startTime.asISO8601String,
stopTimestamp: session.stopTime?.asISO8601String
)
}

var clientTypeEvent: PinpointClientTypes.Event {
return PinpointClientTypes.Event(appPackageName: Bundle.main.appPackageName,
appTitle: Bundle.main.appName,
appVersionCode: Bundle.main.appVersion,
attributes: attributes,
clientSdkVersion: AmplifyAWSServiceConfiguration.amplifyVersion,
eventType: eventType,
metrics: metrics,
sdkName: AmplifyAWSServiceConfiguration.platformName,
session: clientTypeSession,
timestamp: eventDate.asISO8601String)
return .init(
appPackageName: Bundle.main.appPackageName,
appTitle: Bundle.main.appName,
appVersionCode: Bundle.main.appVersion,
attributes: attributes,
clientSdkVersion: AmplifyAWSServiceConfiguration.amplifyVersion,
eventType: eventType,
metrics: metrics,
sdkName: AmplifyAWSServiceConfiguration.platformName,
session: clientTypeSession,
timestamp: eventDate.asISO8601String
)
}
}

Expand All @@ -55,12 +60,3 @@ extension Bundle {
object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? ""
}
}

private extension Int {
init?(_ value: Int64?) {
guard let value = value else {
return nil
}
self.init(value)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,18 @@
import Foundation

@_spi(InternalAWSPinpoint)
public class PinpointSession: Codable {
public struct PinpointSession: Codable {
private enum State: Codable {
case active
case paused
case stopped
}
typealias SessionId = String

let sessionId: SessionId
let startTime: Date
private(set) var stopTime: Date?
private var state: State = .active

init(appId: String,
uniqueId: String) {
Expand All @@ -29,32 +35,40 @@ public class PinpointSession: Codable {
self.sessionId = sessionId
self.startTime = startTime
self.stopTime = stopTime
if stopTime != nil {
state = .stopped
}
}

var isPaused: Bool {
return stopTime != nil
return stopTime != nil && state == .paused
}

var isStopped: Bool {
return stopTime != nil && state == .stopped
}

var duration: Date.Millisecond? {
/// According to Pinpoint's documentation, `duration` is only required if `stopTime` is not nil.
guard let endTime = stopTime else {
return nil
}
return endTime.millisecondsSince1970 - startTime.millisecondsSince1970
guard let stopTime else { return nil }
return stopTime.millisecondsSince1970 - startTime.millisecondsSince1970
}

func stop() {
guard stopTime == nil else { return }
stopTime = Date()
mutating func stop() {
guard !isStopped else { return }
stopTime = stopTime ?? Date()
state = .stopped
}

func pause() {
mutating func pause() {
guard !isPaused else { return }
stopTime = Date()
state = .paused
}

func resume() {
mutating func resume() {
stopTime = nil
state = .active
}

private static func generateSessionId(appId: String,
Expand Down
Loading

0 comments on commit 49e5692

Please sign in to comment.