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' api 'com.facebook.stetho:stetho:1.6.0'
// Analytics // 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 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.pushers.UnregisterUnifiedPushUseCase
import im.vector.app.core.services.GuardServiceStarter import im.vector.app.core.services.GuardServiceStarter
import im.vector.app.core.session.ConfigureAndStartSessionUseCase 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.call.webrtc.WebRtcCallManager
import im.vector.app.features.crypto.keysrequest.KeyRequestHandler import im.vector.app.features.crypto.keysrequest.KeyRequestHandler
import im.vector.app.features.crypto.verification.IncomingVerificationRequestHandler import im.vector.app.features.crypto.verification.IncomingVerificationRequestHandler
@ -57,7 +58,7 @@ class ActiveSessionHolder @Inject constructor(
private val unregisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase, private val unregisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase,
private val applicationCoroutineScope: CoroutineScope, private val applicationCoroutineScope: CoroutineScope,
private val coroutineDispatchers: CoroutineDispatchers, private val coroutineDispatchers: CoroutineDispatchers,
private val decryptionFailureTracker: DecryptionFailureTracker, private val vectorAnalytics: VectorAnalytics,
) { ) {
private var activeSessionReference: AtomicReference<Session?> = AtomicReference() private var activeSessionReference: AtomicReference<Session?> = AtomicReference()
@ -74,6 +75,13 @@ class ActiveSessionHolder @Inject constructor(
session.callSignalingService().addCallListener(callManager) session.callSignalingService().addCallListener(callManager)
imageManager.onSessionStarted(session) imageManager.onSessionStarted(session)
guardServiceStarter.start() guardServiceStarter.start()
vectorAnalytics.updateSuperProperties(
SuperProperties(
platformCodeName = SuperProperties.PlatformCodeName.EA,
cryptoSDK = SuperProperties.CryptoSDK.Rust,
cryptoSDKVersion = session.cryptoService().getCryptoVersion(applicationContext, false)
)
)
} }
suspend fun clearActiveSession() { 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.VectorAnalyticsEvent
import im.vector.app.features.analytics.itf.VectorAnalyticsScreen import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
import im.vector.app.features.analytics.plan.SuperProperties
import im.vector.app.features.analytics.plan.UserProperties import im.vector.app.features.analytics.plan.UserProperties
interface AnalyticsTracker { interface AnalyticsTracker {
@ -35,4 +36,10 @@ interface AnalyticsTracker {
* Update user specific properties. * Update user specific properties.
*/ */
fun updateUserProperties(userProperties: UserProperties) 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.VectorAnalyticsEvent
import im.vector.app.features.analytics.itf.VectorAnalyticsScreen import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
import im.vector.app.features.analytics.log.analyticsTag 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.plan.UserProperties
import im.vector.app.features.analytics.store.AnalyticsStore import im.vector.app.features.analytics.store.AnalyticsStore
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -63,6 +64,8 @@ class DefaultVectorAnalytics @Inject constructor(
// Cache for the properties to send // Cache for the properties to send
private var pendingUserProperties: UserProperties? = null private var pendingUserProperties: UserProperties? = null
private var superProperties: SuperProperties? = null
override fun init() { override fun init() {
observeUserConsent() observeUserConsent()
observeAnalyticsId() observeAnalyticsId()
@ -173,7 +176,7 @@ class DefaultVectorAnalytics @Inject constructor(
?.capture( ?.capture(
event.getName(), event.getName(),
analyticsId, analyticsId,
event.getProperties()?.toPostHogProperties() event.getProperties()?.toPostHogProperties().orEmpty().withSuperProperties()
) )
} }
@ -181,7 +184,7 @@ class DefaultVectorAnalytics @Inject constructor(
Timber.tag(analyticsTag.value).d("screen($screen)") Timber.tag(analyticsTag.value).d("screen($screen)")
posthog posthog
?.takeIf { userConsent == true } ?.takeIf { userConsent == true }
?.screen(screen.getName(), screen.getProperties()?.toPostHogProperties()) ?.screen(screen.getName(), screen.getProperties()?.toPostHogProperties().orEmpty().withSuperProperties())
} }
override fun updateUserProperties(userProperties: UserProperties) { override fun updateUserProperties(userProperties: UserProperties) {
@ -226,9 +229,38 @@ class DefaultVectorAnalytics @Inject constructor(
return nonNulls 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) { override fun trackError(throwable: Throwable) {
sentryAnalytics sentryAnalytics
.takeIf { userConsent == true } .takeIf { userConsent == true }
?.trackError(throwable) ?.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 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.FakeAnalyticsStore
import im.vector.app.test.fakes.FakeLateInitUserPropertiesFactory import im.vector.app.test.fakes.FakeLateInitUserPropertiesFactory
import im.vector.app.test.fakes.FakePostHog import im.vector.app.test.fakes.FakePostHog
@ -174,6 +175,117 @@ class DefaultVectorAnalyticsTest {
fakeSentryAnalytics.verifyNoErrorTracking() 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>? { private fun Map<String, Any?>?.clearNulls(): Map<String, Any>? {
if (this == null) return null if (this == null) return null