Add app version/build properties to PostHog DAU events (#256)

This commit is contained in:
Lawrence Chen 2026-02-21 04:30:52 -08:00 committed by GitHub
parent 9c48d18666
commit 75dec40335
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 89 additions and 6 deletions

View file

@ -39,8 +39,9 @@ final class PostHogAnalytics {
PostHogSDK.shared.setup(config)
// Tag every event so PostHog can distinguish desktop from web.
PostHogSDK.shared.register(["platform": "cmuxterm"])
// Tag every event so PostHog can distinguish desktop from web and
// break events down by released app version/build.
PostHogSDK.shared.register(Self.superProperties(infoDictionary: Bundle.main.infoDictionary ?? [:]))
// The SDK automatically generates and persists an anonymous distinct ID.
@ -68,10 +69,14 @@ final class PostHogAnalytics {
defaults.set(today, forKey: lastActiveDayUTCKey)
PostHogSDK.shared.capture("cmux_daily_active", properties: [
"day_utc": today,
"reason": reason,
])
PostHogSDK.shared.capture(
"cmux_daily_active",
properties: Self.dailyActiveProperties(
dayUTC: today,
reason: reason,
infoDictionary: Bundle.main.infoDictionary ?? [:]
)
)
// For DAU we care more about delivery than batching.
PostHogSDK.shared.flush()
@ -90,4 +95,34 @@ final class PostHogAnalytics {
formatter.dateFormat = "yyyy-MM-dd"
return formatter.string(from: date)
}
nonisolated static func superProperties(infoDictionary: [String: Any]) -> [String: Any] {
var properties: [String: Any] = ["platform": "cmuxterm"]
properties.merge(versionProperties(infoDictionary: infoDictionary)) { _, new in new }
return properties
}
nonisolated static func dailyActiveProperties(
dayUTC: String,
reason: String,
infoDictionary: [String: Any]
) -> [String: Any] {
var properties: [String: Any] = [
"day_utc": dayUTC,
"reason": reason,
]
properties.merge(versionProperties(infoDictionary: infoDictionary)) { _, new in new }
return properties
}
nonisolated private static func versionProperties(infoDictionary: [String: Any]) -> [String: Any] {
var properties: [String: Any] = [:]
if let value = infoDictionary["CFBundleShortVersionString"] as? String, !value.isEmpty {
properties["app_version"] = value
}
if let value = infoDictionary["CFBundleVersion"] as? String, !value.isEmpty {
properties["app_build"] = value
}
return properties
}
}

View file

@ -436,3 +436,51 @@ final class SocketControlSettingsTests: XCTestCase {
)
}
}
final class PostHogAnalyticsPropertiesTests: XCTestCase {
func testDailyActivePropertiesIncludeVersionAndBuild() {
let properties = PostHogAnalytics.dailyActiveProperties(
dayUTC: "2026-02-21",
reason: "didBecomeActive",
infoDictionary: [
"CFBundleShortVersionString": "0.31.0",
"CFBundleVersion": "230",
]
)
XCTAssertEqual(properties["day_utc"] as? String, "2026-02-21")
XCTAssertEqual(properties["reason"] as? String, "didBecomeActive")
XCTAssertEqual(properties["app_version"] as? String, "0.31.0")
XCTAssertEqual(properties["app_build"] as? String, "230")
}
func testSuperPropertiesIncludePlatformVersionAndBuild() {
let properties = PostHogAnalytics.superProperties(
infoDictionary: [
"CFBundleShortVersionString": "0.31.0",
"CFBundleVersion": "230",
]
)
XCTAssertEqual(properties["platform"] as? String, "cmuxterm")
XCTAssertEqual(properties["app_version"] as? String, "0.31.0")
XCTAssertEqual(properties["app_build"] as? String, "230")
}
func testPropertiesOmitVersionFieldsWhenUnavailable() {
let superProperties = PostHogAnalytics.superProperties(infoDictionary: [:])
XCTAssertEqual(superProperties["platform"] as? String, "cmuxterm")
XCTAssertNil(superProperties["app_version"])
XCTAssertNil(superProperties["app_build"])
let dailyProperties = PostHogAnalytics.dailyActiveProperties(
dayUTC: "2026-02-21",
reason: "activeTimer",
infoDictionary: [:]
)
XCTAssertEqual(dailyProperties["day_utc"] as? String, "2026-02-21")
XCTAssertEqual(dailyProperties["reason"] as? String, "activeTimer")
XCTAssertNil(dailyProperties["app_version"])
XCTAssertNil(dailyProperties["app_build"])
}
}