Skip to content

Commit 08c124e

Browse files
committed
Add super properties to posthog (plateformCode)
1 parent 4acbe4e commit 08c124e

File tree

5 files changed

+164
-5
lines changed

5 files changed

+164
-5
lines changed

vector/build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ dependencies {
160160
api 'com.facebook.stetho:stetho:1.6.0'
161161

162162
// Analytics
163-
api 'com.github.matrix-org:matrix-analytics-events:0.15.0'
163+
api 'com.github.matrix-org:matrix-analytics-events:0.22.0'
164164

165165
api libs.google.phonenumber
166166

vector/src/main/java/im/vector/app/core/di/ActiveSessionHolder.kt

+10-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ import im.vector.app.core.dispatchers.CoroutineDispatchers
2222
import im.vector.app.core.pushers.UnregisterUnifiedPushUseCase
2323
import im.vector.app.core.services.GuardServiceStarter
2424
import im.vector.app.core.session.ConfigureAndStartSessionUseCase
25-
import im.vector.app.features.analytics.DecryptionFailureTracker
25+
import im.vector.app.features.analytics.VectorAnalytics
26+
import im.vector.app.features.analytics.plan.SuperProperties
2627
import im.vector.app.features.call.webrtc.WebRtcCallManager
2728
import im.vector.app.features.crypto.keysrequest.KeyRequestHandler
2829
import im.vector.app.features.crypto.verification.IncomingVerificationRequestHandler
@@ -57,7 +58,7 @@ class ActiveSessionHolder @Inject constructor(
5758
private val unregisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase,
5859
private val applicationCoroutineScope: CoroutineScope,
5960
private val coroutineDispatchers: CoroutineDispatchers,
60-
private val decryptionFailureTracker: DecryptionFailureTracker,
61+
private val vectorAnalytics: VectorAnalytics,
6162
) {
6263

6364
private var activeSessionReference: AtomicReference<Session?> = AtomicReference()
@@ -74,6 +75,13 @@ class ActiveSessionHolder @Inject constructor(
7475
session.callSignalingService().addCallListener(callManager)
7576
imageManager.onSessionStarted(session)
7677
guardServiceStarter.start()
78+
vectorAnalytics.updateSuperProperties(
79+
SuperProperties(
80+
platformCodeName = SuperProperties.PlatformCodeName.EA,
81+
cryptoSDK = SuperProperties.CryptoSDK.Rust,
82+
cryptoSDKVersion = session.cryptoService().getCryptoVersion(applicationContext, false)
83+
)
84+
)
7785
}
7886

7987
suspend fun clearActiveSession() {

vector/src/main/java/im/vector/app/features/analytics/AnalyticsTracker.kt

+7
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package im.vector.app.features.analytics
1818

1919
import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
2020
import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
21+
import im.vector.app.features.analytics.plan.SuperProperties
2122
import im.vector.app.features.analytics.plan.UserProperties
2223

2324
interface AnalyticsTracker {
@@ -35,4 +36,10 @@ interface AnalyticsTracker {
3536
* Update user specific properties.
3637
*/
3738
fun updateUserProperties(userProperties: UserProperties)
39+
40+
/**
41+
* Update the super properties.
42+
* Super properties are added to any tracked event automatically.
43+
*/
44+
fun updateSuperProperties(updatedProperties: SuperProperties)
3845
}

vector/src/main/java/im/vector/app/features/analytics/impl/DefaultVectorAnalytics.kt

+34-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import im.vector.app.features.analytics.VectorAnalytics
2323
import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
2424
import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
2525
import im.vector.app.features.analytics.log.analyticsTag
26+
import im.vector.app.features.analytics.plan.SuperProperties
2627
import im.vector.app.features.analytics.plan.UserProperties
2728
import im.vector.app.features.analytics.store.AnalyticsStore
2829
import kotlinx.coroutines.CoroutineScope
@@ -63,6 +64,8 @@ class DefaultVectorAnalytics @Inject constructor(
6364
// Cache for the properties to send
6465
private var pendingUserProperties: UserProperties? = null
6566

67+
private var superProperties: SuperProperties? = null
68+
6669
override fun init() {
6770
observeUserConsent()
6871
observeAnalyticsId()
@@ -173,15 +176,15 @@ class DefaultVectorAnalytics @Inject constructor(
173176
?.capture(
174177
event.getName(),
175178
analyticsId,
176-
event.getProperties()?.toPostHogProperties()
179+
event.getProperties()?.toPostHogProperties().orEmpty().withSuperProperties()
177180
)
178181
}
179182

180183
override fun screen(screen: VectorAnalyticsScreen) {
181184
Timber.tag(analyticsTag.value).d("screen($screen)")
182185
posthog
183186
?.takeIf { userConsent == true }
184-
?.screen(screen.getName(), screen.getProperties()?.toPostHogProperties())
187+
?.screen(screen.getName(), screen.getProperties()?.toPostHogProperties().orEmpty().withSuperProperties())
185188
}
186189

187190
override fun updateUserProperties(userProperties: UserProperties) {
@@ -226,9 +229,38 @@ class DefaultVectorAnalytics @Inject constructor(
226229
return nonNulls
227230
}
228231

232+
/**
233+
* Adds super properties to the actual property set.
234+
* If a property of the same name is already on the reported event it will not be overwritten.
235+
*/
236+
private fun Map<String, Any>.withSuperProperties(): Map<String, Any> {
237+
val withSuperProperties = this.toMutableMap()
238+
val superProperties = this@DefaultVectorAnalytics.superProperties?.getProperties()
239+
superProperties?.forEach {
240+
if (!withSuperProperties.containsKey(it.key)) {
241+
withSuperProperties[it.key] = it.value
242+
}
243+
}
244+
return withSuperProperties
245+
}
246+
229247
override fun trackError(throwable: Throwable) {
230248
sentryAnalytics
231249
.takeIf { userConsent == true }
232250
?.trackError(throwable)
233251
}
252+
253+
override fun updateSuperProperties(updatedProperties: SuperProperties) {
254+
if (this.superProperties == null) {
255+
this.superProperties = updatedProperties
256+
return
257+
}
258+
259+
this.superProperties = SuperProperties(
260+
platformCodeName = updatedProperties.platformCodeName ?: this.superProperties?.platformCodeName,
261+
cryptoSDK = updatedProperties.cryptoSDK ?: this.superProperties?.cryptoSDK,
262+
appPlatform = updatedProperties.appPlatform ?: this.superProperties?.appPlatform,
263+
cryptoSDKVersion = updatedProperties.cryptoSDKVersion ?: superProperties?.cryptoSDKVersion
264+
)
265+
}
234266
}

vector/src/test/java/im/vector/app/features/analytics/impl/DefaultVectorAnalyticsTest.kt

+112
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package im.vector.app.features.analytics.impl
1818

19+
import im.vector.app.features.analytics.plan.SuperProperties
1920
import im.vector.app.test.fakes.FakeAnalyticsStore
2021
import im.vector.app.test.fakes.FakeLateInitUserPropertiesFactory
2122
import im.vector.app.test.fakes.FakePostHog
@@ -174,6 +175,117 @@ class DefaultVectorAnalyticsTest {
174175
fakeSentryAnalytics.verifyNoErrorTracking()
175176
}
176177

178+
@Test
179+
fun `Super properties should be added to all captured events`() = runTest {
180+
fakeAnalyticsStore.givenUserContent(consent = true)
181+
182+
val updatedProperties = SuperProperties(
183+
platformCodeName = SuperProperties.PlatformCodeName.EA,
184+
cryptoSDKVersion = "0.0",
185+
cryptoSDK = SuperProperties.CryptoSDK.Rust
186+
)
187+
188+
defaultVectorAnalytics.updateSuperProperties(updatedProperties)
189+
190+
val fakeEvent = aVectorAnalyticsEvent("THE_NAME", mutableMapOf("foo" to "bar"))
191+
defaultVectorAnalytics.capture(fakeEvent)
192+
193+
fakePostHog.verifyEventTracked(
194+
"THE_NAME",
195+
fakeEvent.getProperties().clearNulls()?.toMutableMap()?.apply {
196+
updatedProperties.getProperties()?.let { putAll(it) }
197+
}
198+
)
199+
200+
// Check with a screen event
201+
val fakeScreen = aVectorAnalyticsScreen("Screen", mutableMapOf("foo" to "bar"))
202+
defaultVectorAnalytics.screen(fakeScreen)
203+
204+
fakePostHog.verifyScreenTracked(
205+
"Screen",
206+
fakeScreen.getProperties().clearNulls()?.toMutableMap()?.apply {
207+
updatedProperties.getProperties()?.let { putAll(it) }
208+
}
209+
)
210+
}
211+
212+
@Test
213+
fun `Super properties can be updated`() = runTest {
214+
fakeAnalyticsStore.givenUserContent(consent = true)
215+
216+
val superProperties = SuperProperties(
217+
platformCodeName = SuperProperties.PlatformCodeName.EA,
218+
cryptoSDKVersion = "0.0",
219+
cryptoSDK = SuperProperties.CryptoSDK.Rust
220+
)
221+
222+
defaultVectorAnalytics.updateSuperProperties(superProperties)
223+
224+
val fakeEvent = aVectorAnalyticsEvent("THE_NAME", mutableMapOf("foo" to "bar"))
225+
defaultVectorAnalytics.capture(fakeEvent)
226+
227+
fakePostHog.verifyEventTracked(
228+
"THE_NAME",
229+
fakeEvent.getProperties().clearNulls()?.toMutableMap()?.apply {
230+
superProperties.getProperties()?.let { putAll(it) }
231+
}
232+
)
233+
234+
val superPropertiesUpdate = superProperties.copy(cryptoSDKVersion = "1.0")
235+
defaultVectorAnalytics.updateSuperProperties(superPropertiesUpdate)
236+
237+
defaultVectorAnalytics.capture(fakeEvent)
238+
239+
fakePostHog.verifyEventTracked(
240+
"THE_NAME",
241+
fakeEvent.getProperties().clearNulls()?.toMutableMap()?.apply {
242+
superPropertiesUpdate.getProperties()?.let { putAll(it) }
243+
}
244+
)
245+
}
246+
247+
@Test
248+
fun `Super properties should not override event property`() = runTest {
249+
fakeAnalyticsStore.givenUserContent(consent = true)
250+
251+
val superProperties = SuperProperties(
252+
cryptoSDKVersion = "0.0",
253+
)
254+
255+
defaultVectorAnalytics.updateSuperProperties(superProperties)
256+
257+
val fakeEvent = aVectorAnalyticsEvent("THE_NAME", mutableMapOf("cryptoSDKVersion" to "XXX"))
258+
defaultVectorAnalytics.capture(fakeEvent)
259+
260+
fakePostHog.verifyEventTracked(
261+
"THE_NAME",
262+
mapOf(
263+
"cryptoSDKVersion" to "XXX"
264+
)
265+
)
266+
}
267+
268+
@Test
269+
fun `Super properties should be added to event with no properties`() = runTest {
270+
fakeAnalyticsStore.givenUserContent(consent = true)
271+
272+
val superProperties = SuperProperties(
273+
cryptoSDKVersion = "0.0",
274+
)
275+
276+
defaultVectorAnalytics.updateSuperProperties(superProperties)
277+
278+
val fakeEvent = aVectorAnalyticsEvent("THE_NAME", null)
279+
defaultVectorAnalytics.capture(fakeEvent)
280+
281+
fakePostHog.verifyEventTracked(
282+
"THE_NAME",
283+
mapOf(
284+
"cryptoSDKVersion" to "0.0"
285+
)
286+
)
287+
}
288+
177289
private fun Map<String, Any?>?.clearNulls(): Map<String, Any>? {
178290
if (this == null) return null
179291

0 commit comments

Comments
 (0)