-
Notifications
You must be signed in to change notification settings - Fork 122
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Splitting responsibilities of analytics provider (#1884)
# Summary Splits analytics provider responsibilities into two by moving the conditional follow up events on its own class `EventAnalyticsProvider` with the timer/batching logic. This object is only created by the context if the configuration is turned on. The original analytics provider is now only responsible for the initial call, is not aware of any of the events provider's lifetime and only passes the calls to it. # Ticket <ticket> COIOS-815 </ticket> --------- Co-authored-by: Alex Guretzki <[email protected]>
- Loading branch information
Showing
11 changed files
with
433 additions
and
397 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
32 changes: 32 additions & 0 deletions
32
Adyen/Analytics/AnalyticsProvider/AnalyticsProviderProtocol.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// | ||
// Copyright (c) 2024 Adyen N.V. | ||
// | ||
// This file is open source and available under the MIT license. See the LICENSE file for more info. | ||
// | ||
|
||
import Foundation | ||
|
||
@_spi(AdyenInternal) | ||
public protocol AnyInitialAnalyticsProvider { | ||
|
||
/// Sends the initial data and retrieves the checkout attempt id as a response. | ||
func sendInitialAnalytics(with flavor: AnalyticsFlavor, additionalFields: AdditionalAnalyticsFields?) | ||
} | ||
|
||
@_spi(AdyenInternal) | ||
public protocol AnyEventAnalyticsProvider { | ||
|
||
var checkoutAttemptId: String? { get set } | ||
|
||
/// Adds an info event to be sent. | ||
func add(info: AnalyticsEventInfo) | ||
|
||
/// Adds a log event to be sent. | ||
func add(log: AnalyticsEventLog) | ||
|
||
/// Adds an error event to be sent. | ||
func add(error: AnalyticsEventError) | ||
} | ||
|
||
@_spi(AdyenInternal) | ||
public protocol AnyAnalyticsProvider: AnyInitialAnalyticsProvider, AnyEventAnalyticsProvider {} |
111 changes: 111 additions & 0 deletions
111
Adyen/Analytics/AnalyticsProvider/EventAnalyticsProvider.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
// | ||
// Copyright (c) 2024 Adyen N.V. | ||
// | ||
// This file is open source and available under the MIT license. See the LICENSE file for more info. | ||
// | ||
|
||
import AdyenNetworking | ||
import Foundation | ||
|
||
internal final class EventAnalyticsProvider: AnyEventAnalyticsProvider { | ||
|
||
private enum Constants { | ||
static let batchInterval: TimeInterval = 10 | ||
static let infoLimit = 50 | ||
static let logLimit = 5 | ||
static let errorLimit = 5 | ||
} | ||
|
||
internal var checkoutAttemptId: String? | ||
internal let apiClient: APIClientProtocol | ||
internal let eventDataSource: AnyAnalyticsEventDataSource | ||
|
||
private let context: AnalyticsContext | ||
private var batchTimer: Timer? | ||
private let batchInterval: TimeInterval | ||
|
||
internal init( | ||
apiClient: APIClientProtocol, | ||
context: AnalyticsContext, | ||
eventDataSource: AnyAnalyticsEventDataSource, | ||
batchInterval: TimeInterval = Constants.batchInterval | ||
) { | ||
self.apiClient = apiClient | ||
self.eventDataSource = eventDataSource | ||
self.context = context | ||
self.batchInterval = batchInterval | ||
startNextTimer() | ||
} | ||
|
||
deinit { | ||
// attempt to send remaining events on deallocation | ||
batchTimer?.invalidate() | ||
sendEventsIfNeeded() | ||
} | ||
|
||
internal func add(info: AnalyticsEventInfo) { | ||
eventDataSource.add(info: info) | ||
} | ||
|
||
internal func add(log: AnalyticsEventLog) { | ||
eventDataSource.add(log: log) | ||
sendEventsIfNeeded() | ||
} | ||
|
||
internal func add(error: AnalyticsEventError) { | ||
eventDataSource.add(error: error) | ||
sendEventsIfNeeded() | ||
} | ||
|
||
internal func sendEventsIfNeeded() { | ||
guard let request = requestWithAllEvents() else { return } | ||
|
||
apiClient.perform(request) { [weak self] result in | ||
guard let self else { return } | ||
// clear the sent events on successful send | ||
switch result { | ||
case .success: | ||
self.removeEvents(sentBy: request) | ||
self.startNextTimer() | ||
case .failure: | ||
break | ||
} | ||
} | ||
} | ||
|
||
// MARK: - Private | ||
|
||
/// Checks the event arrays safely and creates the request with them if there is any to send. | ||
private func requestWithAllEvents() -> AnalyticsRequest? { | ||
guard let checkoutAttemptId, | ||
let events = eventDataSource.allEvents() else { return nil } | ||
|
||
// as per this call's limitation, we only send up to the | ||
// limit of each event and discard the older ones | ||
let platform = context.platform.rawValue | ||
var request = AnalyticsRequest( | ||
checkoutAttemptId: checkoutAttemptId, | ||
platform: platform | ||
) | ||
request.infos = events.infos.suffix(Constants.infoLimit) | ||
request.logs = events.logs.suffix(Constants.logLimit) | ||
request.errors = events.errors.suffix(Constants.errorLimit) | ||
return request | ||
} | ||
|
||
private func removeEvents(sentBy request: AnalyticsRequest) { | ||
let collection = AnalyticsEventWrapper( | ||
infos: request.infos, | ||
logs: request.logs, | ||
errors: request.errors | ||
) | ||
eventDataSource.removeEvents(matching: collection) | ||
} | ||
|
||
private func startNextTimer() { | ||
batchTimer?.invalidate() | ||
batchTimer = Timer.scheduledTimer(withTimeInterval: batchInterval, repeats: true) { [weak self] _ in | ||
self?.sendEventsIfNeeded() | ||
} | ||
} | ||
} |
Oops, something went wrong.