-
Notifications
You must be signed in to change notification settings - Fork 121
[Woo POS][Local Catalog] Add Better Analytics to Background Task Refresh System #16011
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
Changes from 4 commits
b154c9a
919594c
91837a6
c05ec92
adf6881
9a05702
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,16 +1,58 @@ | ||
| import Foundation | ||
| import WooFoundation | ||
|
|
||
| extension WooAnalyticsEvent { | ||
| enum BackgroundUpdates { | ||
|
|
||
| private enum Keys { | ||
| static let timeTaken = "time_taken" | ||
| static let backgroundTimeGranted = "background_time_granted" | ||
| static let networkType = "network_type" | ||
| static let isExpensiveConnection = "is_expensive_connection" | ||
| static let isLowDataMode = "is_low_data_mode" | ||
| static let isPowered = "is_powered" | ||
| static let batteryLevel = "battery_level" | ||
| static let isLowPowerMode = "is_low_power_mode" | ||
| static let timeSinceLastRun = "time_since_last_run" | ||
| static let completionStatus = "completion_status" | ||
| } | ||
|
|
||
| static func dataSynced(timeTaken: TimeInterval) -> WooAnalyticsEvent { | ||
| WooAnalyticsEvent(statName: .backgroundDataSynced, properties: [Keys.timeTaken: timeTaken]) | ||
| } | ||
|
|
||
| static func dataSyncedDetailed( | ||
|
||
| timeTaken: TimeInterval, | ||
| backgroundTimeGranted: TimeInterval?, | ||
| networkType: String, | ||
| isExpensiveConnection: Bool, | ||
| isLowDataMode: Bool, | ||
| isPowered: Bool, | ||
| batteryLevel: Float, | ||
| isLowPowerMode: Bool, | ||
| timeSinceLastRun: TimeInterval? | ||
| ) -> WooAnalyticsEvent { | ||
| var properties: [String: WooAnalyticsEventPropertyType] = [ | ||
| Keys.timeTaken: Int64(timeTaken), | ||
| Keys.networkType: networkType, | ||
| Keys.isExpensiveConnection: isExpensiveConnection, | ||
| Keys.isLowDataMode: isLowDataMode, | ||
| Keys.isPowered: isPowered, | ||
| Keys.batteryLevel: Float64(batteryLevel), | ||
| Keys.isLowPowerMode: isLowPowerMode | ||
| ] | ||
|
|
||
| if let backgroundTimeGranted = backgroundTimeGranted { | ||
| properties[Keys.backgroundTimeGranted] = Int64(backgroundTimeGranted) | ||
| } | ||
|
|
||
| if let timeSinceLastRun = timeSinceLastRun { | ||
| properties[Keys.timeSinceLastRun] = Int64(timeSinceLastRun) | ||
| } | ||
|
|
||
| return WooAnalyticsEvent(statName: .backgroundDataSynced, properties: properties) | ||
| } | ||
|
|
||
| static func dataSyncError(_ error: Error) -> WooAnalyticsEvent { | ||
| WooAnalyticsEvent(statName: .backgroundDataSyncError, properties: [:], error: error) | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -50,6 +50,7 @@ extension UserDefaults { | |||||
|
|
||||||
| // Background Task Refresh | ||||||
| case latestBackgroundOrderSyncDate | ||||||
| case lastBackgroundRefreshTime | ||||||
|
||||||
| case lastBackgroundRefreshTime | |
| case lastBackgroundRefreshCompletionTime |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| import UIKit | ||
| import Foundation | ||
| import BackgroundTasks | ||
| import Network | ||
|
|
||
| final class BackgroundTaskRefreshDispatcher { | ||
|
|
||
|
|
@@ -60,6 +61,8 @@ final class BackgroundTaskRefreshDispatcher { | |
| // Launch all refresh tasks in parallel. | ||
| let refreshTasks = Task { | ||
| do { | ||
| let systemInfo = await BackgroundTaskSystemInfo() | ||
|
||
| let lastRunTime = UserDefaults.standard[.lastBackgroundRefreshTime] as? Date | ||
|
||
|
|
||
| let startTime = Date.now | ||
|
|
||
|
|
@@ -78,7 +81,23 @@ final class BackgroundTaskRefreshDispatcher { | |
| } | ||
|
|
||
| let timeTaken = round(Date.now.timeIntervalSince(startTime)) | ||
| ServiceLocator.analytics.track(event: .BackgroundUpdates.dataSynced(timeTaken: timeTaken)) | ||
| let timeSinceLastRun = lastRunTime?.timeIntervalSinceNow.magnitude | ||
|
||
|
|
||
| ServiceLocator.analytics.track(event: .BackgroundUpdates.dataSyncedDetailed( | ||
| timeTaken: timeTaken, | ||
| backgroundTimeGranted: systemInfo.backgroundTimeGranted, | ||
| networkType: systemInfo.networkType, | ||
| isExpensiveConnection: systemInfo.isExpensiveConnection, | ||
| isLowDataMode: systemInfo.isLowDataMode, | ||
| isPowered: systemInfo.isPowered, | ||
| batteryLevel: systemInfo.batteryLevel, | ||
| isLowPowerMode: systemInfo.isLowPowerMode, | ||
| timeSinceLastRun: timeSinceLastRun | ||
| )) | ||
|
|
||
| // Save date, for use in analytics next time we refresh | ||
| UserDefaults.standard[.lastBackgroundRefreshTime] = Date.now | ||
|
|
||
| backgroundTask.setTaskCompleted(success: true) | ||
|
|
||
| } catch { | ||
|
|
@@ -93,7 +112,7 @@ final class BackgroundTaskRefreshDispatcher { | |
| ServiceLocator.analytics.track(event: .BackgroundUpdates.dataSyncError(BackgroundError.expired)) | ||
| refreshTasks.cancel() | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private extension BackgroundTaskRefreshDispatcher { | ||
|
|
@@ -109,3 +128,87 @@ extension BackgroundTaskRefreshDispatcher { | |
| case expired | ||
| } | ||
| } | ||
|
|
||
| // MARK: - System Information Helper | ||
|
|
||
| private struct NetworkInfo { | ||
| let type: String | ||
| let isExpensive: Bool | ||
| let isLowDataMode: Bool | ||
| } | ||
|
|
||
| private struct BackgroundTaskSystemInfo { | ||
| let backgroundTimeGranted: TimeInterval? | ||
| let networkInfo: NetworkInfo | ||
|
||
| let isPowered: Bool | ||
| let batteryLevel: Float | ||
| let isLowPowerMode: Bool | ||
|
|
||
| // Computed properties for clean external access | ||
| var networkType: String { networkInfo.type } | ||
| var isExpensiveConnection: Bool { networkInfo.isExpensive } | ||
| var isLowDataMode: Bool { networkInfo.isLowDataMode } | ||
|
|
||
| @MainActor | ||
| init() async { | ||
| // Background time granted (nil if foreground/unlimited) | ||
| let backgroundTime = UIApplication.shared.backgroundTimeRemaining | ||
| self.backgroundTimeGranted = backgroundTime < Double.greatestFiniteMagnitude ? backgroundTime : nil | ||
|
|
||
| // Network info | ||
| self.networkInfo = await Self.getNetworkInfo() | ||
|
|
||
| // Power and battery info | ||
| let device = UIDevice.current | ||
| device.isBatteryMonitoringEnabled = true | ||
|
|
||
| self.isPowered = device.batteryState == .charging || device.batteryState == .full | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: how about just logging the battery state?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that will just log an int, not a useful string. Also during analysis, we'd have to check for both values which indicate that it's plugged in. |
||
| self.batteryLevel = device.batteryLevel | ||
| self.isLowPowerMode = ProcessInfo.processInfo.isLowPowerModeEnabled | ||
|
|
||
| device.isBatteryMonitoringEnabled = false | ||
| } | ||
|
|
||
| private static func getNetworkInfo() async -> NetworkInfo { | ||
| return await withCheckedContinuation { continuation in | ||
| let monitor = NWPathMonitor() | ||
|
|
||
| monitor.pathUpdateHandler = { path in | ||
| continuation.resume(returning: NetworkInfo(path: path)) | ||
| monitor.cancel() | ||
| } | ||
|
|
||
| let queue = DispatchQueue(label: "network.monitor.queue") | ||
| monitor.start(queue: queue) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private extension NetworkInfo { | ||
| init(path: NWPath) { | ||
| guard path.status == .satisfied else { | ||
| self.type = "no_connection" | ||
| self.isExpensive = false | ||
| self.isLowDataMode = false | ||
| return | ||
| } | ||
|
|
||
| self.type = Self.networkType(from: path) | ||
| self.isExpensive = path.isExpensive | ||
| self.isLowDataMode = path.isConstrained | ||
| } | ||
|
|
||
| private static func networkType(from path: NWPath) -> String { | ||
| if path.usesInterfaceType(.wifi) { | ||
| return "wifi" | ||
| } else if path.usesInterfaceType(.cellular) { | ||
| return "cellular" | ||
| } else if path.usesInterfaceType(.wiredEthernet) { | ||
| return "ethernet" | ||
| } else if path.usesInterfaceType(.loopback) { | ||
| return "loopback" | ||
| } else { | ||
| return "other" | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: this isn't used, also noted in the CI lint error