Skip to content

Commit f38a422

Browse files
authored
Merge pull request #7801 from element-hq/valere/super_properties
Analytics | Add support for super properties and appPlatform
2 parents fddd6e2 + fb60f87 commit f38a422

File tree

10 files changed

+376
-17
lines changed

10 files changed

+376
-17
lines changed

Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Riot/Modules/Analytics/Analytics.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ import AnalyticsEvents
9494

9595
guard let session = session else { return }
9696
useAnalyticsSettings(from: session)
97+
client.updateSuperProperties(.init(appPlatform: .EI,
98+
cryptoSDK: .Rust,
99+
cryptoSDKVersion: session.crypto.version))
97100
}
98101

99102
/// Stops analytics tracking and calls `reset` to clear any IDs and event queues.
@@ -148,6 +151,13 @@ import AnalyticsEvents
148151
switch result {
149152
case .success(let settings):
150153
self.identify(with: settings)
154+
self.client.updateSuperProperties(
155+
AnalyticsEvent.SuperProperties(
156+
appPlatform: .EI,
157+
cryptoSDK: .Rust,
158+
cryptoSDKVersion: session.crypto.version
159+
)
160+
)
151161
self.service = nil
152162
case .failure:
153163
MXLog.error("[Analytics] Failed to use analytics settings. Will continue to run without analytics ID.")
@@ -242,7 +252,9 @@ extension Analytics {
242252
let userProperties = AnalyticsEvent.UserProperties(allChatsActiveFilter: allChatsActiveFilter?.analyticsName,
243253
ftueUseCaseSelection: ftueUseCase?.analyticsName,
244254
numFavouriteRooms: numFavouriteRooms,
245-
numSpaces: numSpaces)
255+
numSpaces: numSpaces,
256+
recoveryState: nil,
257+
verificationState: nil)
246258
client.updateUserProperties(userProperties)
247259
}
248260

Riot/Modules/Analytics/AnalyticsClientProtocol.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,11 @@ protocol AnalyticsClientProtocol {
5353
/// be a delay when updating user properties as these are cached to be included
5454
/// as part of the next event that gets captured.
5555
func updateUserProperties(_ userProperties: AnalyticsEvent.UserProperties)
56+
57+
58+
/// Updates the super properties.
59+
/// Super properties added to all captured events and screen.
60+
/// - Parameter superProperties: The properties event to capture.
61+
func updateSuperProperties(_ event: AnalyticsEvent.SuperProperties)
62+
5663
}

Riot/Modules/Analytics/PostHogAnalyticsClient.swift

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,39 @@ import AnalyticsEvents
1919

2020
/// An analytics client that reports events to a PostHog server.
2121
class PostHogAnalyticsClient: AnalyticsClientProtocol {
22+
23+
private var posthogFactory: PostHogFactory = DefaultPostHogFactory()
24+
25+
init(posthogFactory: PostHogFactory? = nil) {
26+
if let factory = posthogFactory {
27+
self.posthogFactory = factory
28+
}
29+
}
30+
2231
/// The PHGPostHog object used to report events.
23-
private var postHog: PostHogSDK?
32+
private var postHog: PostHogProtocol?
2433

2534
/// Any user properties to be included with the next captured event.
2635
private(set) var pendingUserProperties: AnalyticsEvent.UserProperties?
2736

37+
/// Super Properties are properties associated with events that are set once and then sent with every capture call, be it a $screen, an autocaptured button click, or anything else.
38+
/// It is different from user properties that will be attached to the user and not events.
39+
/// Not persisted for now, should be set on start.
40+
private var superProperties: AnalyticsEvent.SuperProperties?
41+
2842
static let shared = PostHogAnalyticsClient()
2943

30-
var isRunning: Bool { postHog != nil && !postHog!.isOptOut() }
44+
var isRunning: Bool {
45+
guard let postHog else { return false }
46+
return !postHog.isOptOut()
47+
}
3148

3249
func start() {
3350
// Only start if analytics have been configured in BuildSettings
3451
guard let configuration = PostHogConfig.standard else { return }
3552

3653
if postHog == nil {
37-
PostHogSDK.shared.setup(configuration)
38-
postHog = PostHogSDK.shared
54+
postHog = posthogFactory.createPostHog(config: configuration)
3955
}
4056

4157
postHog?.optIn()
@@ -67,13 +83,13 @@ class PostHogAnalyticsClient: AnalyticsClientProtocol {
6783
}
6884

6985
func capture(_ event: AnalyticsEventProtocol) {
70-
postHog?.capture(event.eventName, properties: event.properties, userProperties: pendingUserProperties?.properties.compactMapValues { $0 })
86+
postHog?.capture(event.eventName, properties: attachSuperProperties(to: event.properties), userProperties: pendingUserProperties?.properties.compactMapValues { $0 })
7187
// Pending user properties have been added
7288
self.pendingUserProperties = nil
7389
}
7490

7591
func screen(_ event: AnalyticsScreenProtocol) {
76-
postHog?.screen(event.screenName.rawValue, properties: event.properties)
92+
postHog?.screen(event.screenName.rawValue, properties: attachSuperProperties(to: event.properties))
7793
}
7894

7995
func updateUserProperties(_ userProperties: AnalyticsEvent.UserProperties) {
@@ -86,9 +102,35 @@ class PostHogAnalyticsClient: AnalyticsClientProtocol {
86102
self.pendingUserProperties = AnalyticsEvent.UserProperties(allChatsActiveFilter: userProperties.allChatsActiveFilter ?? pendingUserProperties.allChatsActiveFilter,
87103
ftueUseCaseSelection: userProperties.ftueUseCaseSelection ?? pendingUserProperties.ftueUseCaseSelection,
88104
numFavouriteRooms: userProperties.numFavouriteRooms ?? pendingUserProperties.numFavouriteRooms,
89-
numSpaces: userProperties.numSpaces ?? pendingUserProperties.numSpaces)
105+
numSpaces: userProperties.numSpaces ?? pendingUserProperties.numSpaces,
106+
// Not yet supported
107+
recoveryState: nil, verificationState: nil)
90108
}
91109

110+
func updateSuperProperties(_ updatedProperties: AnalyticsEvent.SuperProperties) {
111+
self.superProperties = AnalyticsEvent.SuperProperties(
112+
appPlatform: updatedProperties.appPlatform ?? superProperties?.appPlatform,
113+
cryptoSDK: updatedProperties.cryptoSDK ?? superProperties?.cryptoSDK,
114+
cryptoSDKVersion: updatedProperties.cryptoSDKVersion ?? superProperties?.cryptoSDKVersion
115+
)
116+
}
117+
118+
/// Attach super properties to events.
119+
/// If the property is already set on the event, the already set value will be kept.
120+
private func attachSuperProperties(to properties: [String: Any]) -> [String: Any] {
121+
guard isRunning, let superProperties else { return properties }
122+
123+
var properties = properties
124+
125+
superProperties.properties.forEach { (key: String, value: Any) in
126+
if properties[key] == nil {
127+
properties[key] = value
128+
}
129+
}
130+
return properties
131+
}
132+
133+
92134
}
93135

94136
extension PostHogAnalyticsClient: RemoteFeaturesClientProtocol {
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//
2+
// Copyright 2024 New Vector Ltd
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
17+
import Foundation
18+
import PostHog
19+
20+
protocol PostHogProtocol {
21+
func optIn()
22+
23+
func optOut()
24+
25+
func reset()
26+
27+
func flush()
28+
29+
func capture(_ event: String, properties: [String: Any]?, userProperties: [String: Any]?)
30+
31+
func screen(_ screenTitle: String, properties: [String: Any]?)
32+
33+
func isFeatureEnabled(_ feature: String) -> Bool
34+
35+
func identify(_ distinctId: String)
36+
37+
func identify(_ distinctId: String, userProperties: [String: Any]?)
38+
39+
func isOptOut() -> Bool
40+
}
41+
42+
protocol PostHogFactory {
43+
func createPostHog(config: PostHogConfig) -> PostHogProtocol
44+
}
45+
46+
class DefaultPostHogFactory: PostHogFactory {
47+
func createPostHog(config: PostHogConfig) -> PostHogProtocol {
48+
PostHogSDK.shared.setup(config)
49+
return PostHogSDK.shared
50+
}
51+
}
52+
53+
extension PostHogSDK: PostHogProtocol { }

RiotTests/AnalyticsTests.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ class AnalyticsTests: XCTestCase {
7878
XCTAssertNil(client.pendingUserProperties, "No user properties should have been set yet.")
7979

8080
// When updating the user properties
81-
client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: nil, ftueUseCaseSelection: .PersonalMessaging, numFavouriteRooms: 4, numSpaces: 5))
81+
client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: nil, ftueUseCaseSelection: .PersonalMessaging, numFavouriteRooms: 4, numSpaces: 5, recoveryState: nil, verificationState: nil))
8282

8383
// Then the properties should be cached
8484
XCTAssertNotNil(client.pendingUserProperties, "The user properties should be cached.")
@@ -90,15 +90,15 @@ class AnalyticsTests: XCTestCase {
9090
func testMergingUserProperties() {
9191
// Given a client with a cached use case user properties
9292
let client = PostHogAnalyticsClient()
93-
client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: nil, ftueUseCaseSelection: .PersonalMessaging, numFavouriteRooms: nil, numSpaces: nil))
93+
client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: nil, ftueUseCaseSelection: .PersonalMessaging, numFavouriteRooms: nil, numSpaces: nil, recoveryState: nil, verificationState: nil))
9494

9595
XCTAssertNotNil(client.pendingUserProperties, "The user properties should be cached.")
9696
XCTAssertEqual(client.pendingUserProperties?.ftueUseCaseSelection, .PersonalMessaging, "The use case selection should match.")
9797
XCTAssertNil(client.pendingUserProperties?.numFavouriteRooms, "The number of favorite rooms should not be set.")
9898
XCTAssertNil(client.pendingUserProperties?.numSpaces, "The number of spaces should not be set.")
9999

100100
// When updating the number of spaces
101-
client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: nil, ftueUseCaseSelection: nil, numFavouriteRooms: 4, numSpaces: 5))
101+
client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: nil, ftueUseCaseSelection: nil, numFavouriteRooms: 4, numSpaces: 5, recoveryState: nil, verificationState: nil))
102102

103103
// Then the new properties should be updated and the existing properties should remain unchanged
104104
XCTAssertNotNil(client.pendingUserProperties, "The user properties should be cached.")
@@ -107,7 +107,7 @@ class AnalyticsTests: XCTestCase {
107107
XCTAssertEqual(client.pendingUserProperties?.numSpaces, 5, "The number of spaces should have been updated.")
108108

109109
// When updating the number of spaces
110-
client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: .Favourites, ftueUseCaseSelection: nil, numFavouriteRooms: nil, numSpaces: nil))
110+
client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: .Favourites, ftueUseCaseSelection: nil, numFavouriteRooms: nil, numSpaces: nil, recoveryState: nil, verificationState: nil))
111111

112112
// Then the new properties should be updated and the existing properties should remain unchanged
113113
XCTAssertNotNil(client.pendingUserProperties, "The user properties should be cached.")
@@ -120,7 +120,7 @@ class AnalyticsTests: XCTestCase {
120120
func testSendingUserProperties() {
121121
// Given a client with user properties set
122122
let client = PostHogAnalyticsClient()
123-
client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: nil, ftueUseCaseSelection: .PersonalMessaging, numFavouriteRooms: nil, numSpaces: nil))
123+
client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: nil, ftueUseCaseSelection: .PersonalMessaging, numFavouriteRooms: nil, numSpaces: nil, recoveryState: nil, verificationState: nil))
124124
client.start()
125125

126126
XCTAssertNotNil(client.pendingUserProperties, "The user properties should be cached.")
@@ -137,7 +137,7 @@ class AnalyticsTests: XCTestCase {
137137
func testSendingUserPropertiesWithIdentify() {
138138
// Given a client with user properties set
139139
let client = PostHogAnalyticsClient()
140-
client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: nil, ftueUseCaseSelection: .PersonalMessaging, numFavouriteRooms: nil, numSpaces: nil))
140+
client.updateUserProperties(AnalyticsEvent.UserProperties(allChatsActiveFilter: nil, ftueUseCaseSelection: .PersonalMessaging, numFavouriteRooms: nil, numSpaces: nil, recoveryState: nil, verificationState: nil))
141141
client.start()
142142

143143
XCTAssertNotNil(client.pendingUserProperties, "The user properties should be cached.")

0 commit comments

Comments
 (0)