Skip to content

Commit

Permalink
✨ Add _device auto properties
Browse files Browse the repository at this point in the history
  • Loading branch information
mmaatttt committed May 6, 2024
1 parent f13e9c7 commit 52ed5a3
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 18 deletions.
62 changes: 46 additions & 16 deletions Sources/AppcuesKit/Data/Analytics/AutoPropertyDecorator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,23 @@ internal class AutoPropertyDecorator: AnalyticsDecorating {
private let config: Appcues.Config

// these are the fixed values for the duration of the app runtime
private var applicationProperties: [String: Any] = [:]
private lazy var applicationProperties: [String: Any] = {
[
"_appId": config.applicationID,
"_operatingSystem": "iOS",
"_bundlePackageId": Bundle.main.identifier,
"_appName": Bundle.main.displayName,
"_appVersion": Bundle.main.version,
"_appBuild": Bundle.main.build,
"_sdkVersion": __appcues_version,
"_sdkName": "appcues-ios",
"_osVersion": UIDevice.current.systemVersion,
"_deviceType": UIDevice.current.userInterfaceIdiom.analyticsName,
"_deviceModel": UIDevice.current.modelName
]
}()

var deviceLanguage: String? { Bundle.main.preferredLocalizations.first }

// these may be redundant with _identity auto props in some cases, but backend
// systems requested that we send in both spots on requests
Expand All @@ -35,7 +51,6 @@ internal class AutoPropertyDecorator: AnalyticsDecorating {
self.appcues = container.owner
self.storage = container.resolve(DataStoring.self)
self.config = container.resolve(Appcues.Config.self)
configureApplicationProperties()
}

func decorate(_ tracking: TrackingUpdate) -> TrackingUpdate {
Expand Down Expand Up @@ -82,7 +97,7 @@ internal class AutoPropertyDecorator: AnalyticsDecorating {
"_sessionRandomizer": sessionRandomizer,
"_currentScreenTitle": currentScreen,
"_lastScreenTitle": previousScreen,
"_lastBrowserLanguage": Bundle.main.preferredLocalizations.first,
"_lastBrowserLanguage": deviceLanguage,
// _lastSeenAt deprecates _updatedAt which can't be entirely removed since it's used for targeting
"_lastSeenAt": now,
"_updatedAt": now,
Expand All @@ -107,25 +122,27 @@ internal class AutoPropertyDecorator: AnalyticsDecorating {
decorated.identityAutoProperties = merged
}

// TODO: add _device to new events
if case .event(Events.Session.sessionStarted.rawValue, _) = tracking.type {
decorated.deviceAutoProperties = deviceAutoProperties().merging(applicationProperties)
}

decorated.context = context

return decorated
}

private func configureApplicationProperties() {
applicationProperties = [
"_appId": config.applicationID,
"_operatingSystem": "iOS",
"_bundlePackageId": Bundle.main.identifier,
"_appName": Bundle.main.displayName,
"_appVersion": Bundle.main.version,
"_appBuild": Bundle.main.build,
"_sdkVersion": __appcues_version,
"_sdkName": "appcues-ios",
"_osVersion": UIDevice.current.systemVersion,
"_deviceType": UIDevice.current.userInterfaceIdiom.analyticsName,
"_deviceModel": UIDevice.current.modelName
private func deviceAutoProperties() -> [String: Any] {
[
"_deviceId": storage.deviceID,
"_language": deviceLanguage,
// TODO: more properties
// _pushToken
// _pushSubscriptionStatus
// _pushEnabled
// _pushEnabledBackground
]
.compactMapValues { $0 }
}
}

Expand Down Expand Up @@ -162,4 +179,17 @@ extension TrackingUpdate {
properties = newProps
}
}

// some events have device-related auto props nested inside a _device object
var deviceAutoProperties: [String: Any]? {
get {
return properties?["_device"] as? [String: Any]
}
set {
var newProps = properties ?? [:]
newProps["_device"] = newValue
properties = newProps
}
}

}
4 changes: 2 additions & 2 deletions Sources/AppcuesKit/Data/Networking/DynamicCodingKeys.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ extension KeyedEncodingContainer where K == DynamicCodingKeys {
try dict?.forEach { key, value in
let codingKey = DynamicCodingKeys(key: key)

if (key == "_identity" || key == "interactionData" || key == "_sdkMetrics"), let nestedProps = value as? [String: Any] {
// "_identity" is a special case - the Appcues auto-properties that supply app/user/session data
if Set(["_identity", "_device", "interactionData", "_sdkMetrics"]).contains(key), let nestedProps = value as? [String: Any] {
// "_identity" and "_device" are special cases - the Appcues auto-properties that supply app/device/user/session data
// "interactionData" is a special case where a nested object is expected
// "_sdkMetrics" is a special case - the Experience rendering timing metrics
var autopropContainer = self.nestedContainer(keyedBy: DynamicCodingKeys.self, forKey: codingKey)
Expand Down
10 changes: 10 additions & 0 deletions Sources/AppcuesKit/Presentation/Debugger/Panel/LoggedEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ internal struct LoggedEvent: Identifiable {
.map { ($0.key, String(describing: $0.value)) }
properties["_identity"] = nil

// flatten the nested `_device` auto-properties into individual top level items.
let deviceAutoProps = (properties["_device"] as? [String: Any] ?? [:])
.sortedWithAutoProperties()
.map { ($0.key, String(describing: $0.value)) }
properties["_device"] = nil

// flatten the nested `_sdkMetrics` properties into individual top level items.
let metricProps = (properties["_sdkMetrics"] as? [String: Any] ?? [:])
.sortedWithAutoProperties()
Expand Down Expand Up @@ -75,6 +81,10 @@ internal struct LoggedEvent: Identifiable {
groups.append(("Identity Auto-properties", identityAutoProps))
}

if !deviceAutoProps.isEmpty {
groups.append(("Device Auto-properties", deviceAutoProps))
}

if !metricProps.isEmpty {
groups.append(("SDK Metrics", metricProps))
}
Expand Down

0 comments on commit 52ed5a3

Please sign in to comment.