Add super properties to posthog (plateformCode)

This commit is contained in:
Valere 2024-05-28 09:25:24 +02:00
parent 4acbe4e582
commit 08c124e13b
5 changed files with 164 additions and 5 deletions

View file

@ -160,7 +160,7 @@ dependencies {
api 'com.facebook.stetho:stetho:1.6.0'
// Analytics
api 'com.github.matrix-org:matrix-analytics-events:0.15.0'
api 'com.github.matrix-org:matrix-analytics-events:0.22.0'
api libs.google.phonenumber

View file

@ -22,7 +22,8 @@ import im.vector.app.core.dispatchers.CoroutineDispatchers
import im.vector.app.core.pushers.UnregisterUnifiedPushUseCase
import im.vector.app.core.services.GuardServiceStarter
import im.vector.app.core.session.ConfigureAndStartSessionUseCase
import im.vector.app.features.analytics.DecryptionFailureTracker
import im.vector.app.features.analytics.VectorAnalytics
import im.vector.app.features.analytics.plan.SuperProperties
import im.vector.app.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.crypto.keysrequest.KeyRequestHandler
import im.vector.app.features.crypto.verification.IncomingVerificationRequestHandler
@ -57,7 +58,7 @@ class ActiveSessionHolder @Inject constructor(
private val unregisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase,
private val applicationCoroutineScope: CoroutineScope,
private val coroutineDispatchers: CoroutineDispatchers,
private val decryptionFailureTracker: DecryptionFailureTracker,
private val vectorAnalytics: VectorAnalytics,
) {
private var activeSessionReference: AtomicReference<Session?> = AtomicReference()
@ -74,6 +75,13 @@ class ActiveSessionHolder @Inject constructor(
session.callSignalingService().addCallListener(callManager)
imageManager.onSessionStarted(session)
guardServiceStarter.start()
vectorAnalytics.updateSuperProperties(
SuperProperties(
platformCodeName = SuperProperties.PlatformCodeName.EA,
cryptoSDK = SuperProperties.CryptoSDK.Rust,
cryptoSDKVersion = session.cryptoService().getCryptoVersion(applicationContext, false)
)
)
}
suspend fun clearActiveSession() {

View file

@ -18,6 +18,7 @@ package im.vector.app.features.analytics
import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
import im.vector.app.features.analytics.plan.SuperProperties
import im.vector.app.features.analytics.plan.UserProperties
interface AnalyticsTracker {
@ -35,4 +36,10 @@ interface AnalyticsTracker {
* Update user specific properties.
*/
fun updateUserProperties(userProperties: UserProperties)
/**
* Update the super properties.
* Super properties are added to any tracked event automatically.
*/
fun updateSuperProperties(updatedProperties: SuperProperties)
}

View file

@ -23,6 +23,7 @@ import im.vector.app.features.analytics.VectorAnalytics
import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
import im.vector.app.features.analytics.log.analyticsTag
import im.vector.app.features.analytics.plan.SuperProperties
import im.vector.app.features.analytics.plan.UserProperties
import im.vector.app.features.analytics.store.AnalyticsStore
import kotlinx.coroutines.CoroutineScope
@ -63,6 +64,8 @@ class DefaultVectorAnalytics @Inject constructor(
// Cache for the properties to send
private var pendingUserProperties: UserProperties? = null
private var superProperties: SuperProperties? = null
override fun init() {
observeUserConsent()
observeAnalyticsId()
@ -173,7 +176,7 @@ class DefaultVectorAnalytics @Inject constructor(
?.capture(
event.getName(),
analyticsId,
event.getProperties()?.toPostHogProperties()
event.getProperties()?.toPostHogProperties().orEmpty().withSuperProperties()
)
}
@ -181,7 +184,7 @@ class DefaultVectorAnalytics @Inject constructor(
Timber.tag(analyticsTag.value).d("screen($screen)")
posthog
?.takeIf { userConsent == true }
?.screen(screen.getName(), screen.getProperties()?.toPostHogProperties())
?.screen(screen.getName(), screen.getProperties()?.toPostHogProperties().orEmpty().withSuperProperties())
}
override fun updateUserProperties(userProperties: UserProperties) {
@ -226,9 +229,38 @@ class DefaultVectorAnalytics @Inject constructor(
return nonNulls
}
/**
* Adds super properties to the actual property set.
* If a property of the same name is already on the reported event it will not be overwritten.
*/
private fun Map<String, Any>.withSuperProperties(): Map<String, Any> {
val withSuperProperties = this.toMutableMap()
val superProperties = this@DefaultVectorAnalytics.superProperties?.getProperties()
superProperties?.forEach {
if (!withSuperProperties.containsKey(it.key)) {
withSuperProperties[it.key] = it.value
}
}
return withSuperProperties
}
override fun trackError(throwable: Throwable) {
sentryAnalytics
.takeIf { userConsent == true }
?.trackError(throwable)
}
override fun updateSuperProperties(updatedProperties: SuperProperties) {
if (this.superProperties == null) {
this.superProperties = updatedProperties
return
}
this.superProperties = SuperProperties(
platformCodeName = updatedProperties.platformCodeName ?: this.superProperties?.platformCodeName,
cryptoSDK = updatedProperties.cryptoSDK ?: this.superProperties?.cryptoSDK,
appPlatform = updatedProperties.appPlatform ?: this.superProperties?.appPlatform,
cryptoSDKVersion = updatedProperties.cryptoSDKVersion ?: superProperties?.cryptoSDKVersion
)
}
}

View file

@ -16,6 +16,7 @@
package im.vector.app.features.analytics.impl
import im.vector.app.features.analytics.plan.SuperProperties
import im.vector.app.test.fakes.FakeAnalyticsStore
import im.vector.app.test.fakes.FakeLateInitUserPropertiesFactory
import im.vector.app.test.fakes.FakePostHog
@ -174,6 +175,117 @@ class DefaultVectorAnalyticsTest {
fakeSentryAnalytics.verifyNoErrorTracking()
}
@Test
fun `Super properties should be added to all captured events`() = runTest {
fakeAnalyticsStore.givenUserContent(consent = true)
val updatedProperties = SuperProperties(
platformCodeName = SuperProperties.PlatformCodeName.EA,
cryptoSDKVersion = "0.0",
cryptoSDK = SuperProperties.CryptoSDK.Rust
)
defaultVectorAnalytics.updateSuperProperties(updatedProperties)
val fakeEvent = aVectorAnalyticsEvent("THE_NAME", mutableMapOf("foo" to "bar"))
defaultVectorAnalytics.capture(fakeEvent)
fakePostHog.verifyEventTracked(
"THE_NAME",
fakeEvent.getProperties().clearNulls()?.toMutableMap()?.apply {
updatedProperties.getProperties()?.let { putAll(it) }
}
)
// Check with a screen event
val fakeScreen = aVectorAnalyticsScreen("Screen", mutableMapOf("foo" to "bar"))
defaultVectorAnalytics.screen(fakeScreen)
fakePostHog.verifyScreenTracked(
"Screen",
fakeScreen.getProperties().clearNulls()?.toMutableMap()?.apply {
updatedProperties.getProperties()?.let { putAll(it) }
}
)
}
@Test
fun `Super properties can be updated`() = runTest {
fakeAnalyticsStore.givenUserContent(consent = true)
val superProperties = SuperProperties(
platformCodeName = SuperProperties.PlatformCodeName.EA,
cryptoSDKVersion = "0.0",
cryptoSDK = SuperProperties.CryptoSDK.Rust
)
defaultVectorAnalytics.updateSuperProperties(superProperties)
val fakeEvent = aVectorAnalyticsEvent("THE_NAME", mutableMapOf("foo" to "bar"))
defaultVectorAnalytics.capture(fakeEvent)
fakePostHog.verifyEventTracked(
"THE_NAME",
fakeEvent.getProperties().clearNulls()?.toMutableMap()?.apply {
superProperties.getProperties()?.let { putAll(it) }
}
)
val superPropertiesUpdate = superProperties.copy(cryptoSDKVersion = "1.0")
defaultVectorAnalytics.updateSuperProperties(superPropertiesUpdate)
defaultVectorAnalytics.capture(fakeEvent)
fakePostHog.verifyEventTracked(
"THE_NAME",
fakeEvent.getProperties().clearNulls()?.toMutableMap()?.apply {
superPropertiesUpdate.getProperties()?.let { putAll(it) }
}
)
}
@Test
fun `Super properties should not override event property`() = runTest {
fakeAnalyticsStore.givenUserContent(consent = true)
val superProperties = SuperProperties(
cryptoSDKVersion = "0.0",
)
defaultVectorAnalytics.updateSuperProperties(superProperties)
val fakeEvent = aVectorAnalyticsEvent("THE_NAME", mutableMapOf("cryptoSDKVersion" to "XXX"))
defaultVectorAnalytics.capture(fakeEvent)
fakePostHog.verifyEventTracked(
"THE_NAME",
mapOf(
"cryptoSDKVersion" to "XXX"
)
)
}
@Test
fun `Super properties should be added to event with no properties`() = runTest {
fakeAnalyticsStore.givenUserContent(consent = true)
val superProperties = SuperProperties(
cryptoSDKVersion = "0.0",
)
defaultVectorAnalytics.updateSuperProperties(superProperties)
val fakeEvent = aVectorAnalyticsEvent("THE_NAME", null)
defaultVectorAnalytics.capture(fakeEvent)
fakePostHog.verifyEventTracked(
"THE_NAME",
mapOf(
"cryptoSDKVersion" to "0.0"
)
)
}
private fun Map<String, Any?>?.clearNulls(): Map<String, Any>? {
if (this == null) return null