Merge pull request #8839 from element-hq/feature/bca/add_platformcode_to_posthog

Support reporting super properties to posthog (appPlatform)
This commit is contained in:
Valere 2024-05-31 14:05:56 +02:00 committed by GitHub
commit 47bb23a654
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 169 additions and 29 deletions

1
changelog.d/8839.misc Normal file
View file

@ -0,0 +1 @@
Posthog | report platform code for EA

View file

@ -147,5 +147,12 @@ class Matrix(context: Context, matrixConfiguration: MatrixConfiguration) {
fun getSdkVersion(): String { fun getSdkVersion(): String {
return BuildConfig.SDK_VERSION + " (" + BuildConfig.GIT_SDK_REVISION + ")" return BuildConfig.SDK_VERSION + " (" + BuildConfig.GIT_SDK_REVISION + ")"
} }
fun getCryptoVersion(longFormat: Boolean): String {
val version = org.matrix.rustcomponents.sdk.crypto.version()
val gitHash = org.matrix.rustcomponents.sdk.crypto.versionInfo().gitSha
val vodozemac = org.matrix.rustcomponents.sdk.crypto.vodozemacVersion()
return if (longFormat) "Rust SDK $version ($gitHash), Vodozemac $vodozemac" else version
}
} }
} }

View file

@ -16,7 +16,6 @@
package org.matrix.android.sdk.api.session.crypto package org.matrix.android.sdk.api.session.crypto
import android.content.Context
import androidx.annotation.Size import androidx.annotation.Size
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.paging.PagedList import androidx.paging.PagedList
@ -61,8 +60,6 @@ interface CryptoService {
suspend fun deleteDevices(@Size(min = 1) deviceIds: List<String>, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) suspend fun deleteDevices(@Size(min = 1) deviceIds: List<String>, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor)
fun getCryptoVersion(context: Context, longFormat: Boolean): String
fun isCryptoEnabled(): Boolean fun isCryptoEnabled(): Boolean
fun isRoomBlacklistUnverifiedDevices(roomId: String?): Boolean fun isRoomBlacklistUnverifiedDevices(roomId: String?): Boolean

View file

@ -16,7 +16,6 @@
package org.matrix.android.sdk.internal.crypto package org.matrix.android.sdk.internal.crypto
import android.content.Context
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.map import androidx.lifecycle.map
import androidx.paging.PagedList import androidx.paging.PagedList
@ -184,13 +183,6 @@ internal class RustCryptoService @Inject constructor(
deleteDevices(listOf(deviceId), userInteractiveAuthInterceptor) deleteDevices(listOf(deviceId), userInteractiveAuthInterceptor)
} }
override fun getCryptoVersion(context: Context, longFormat: Boolean): String {
val version = org.matrix.rustcomponents.sdk.crypto.version()
val gitHash = org.matrix.rustcomponents.sdk.crypto.versionInfo().gitSha
val vodozemac = org.matrix.rustcomponents.sdk.crypto.vodozemacVersion()
return if (longFormat) "Rust SDK $version ($gitHash), Vodozemac $vodozemac" else version
}
override suspend fun getMyCryptoDevice(): CryptoDeviceInfo = withContext(coroutineDispatchers.io) { override suspend fun getMyCryptoDevice(): CryptoDeviceInfo = withContext(coroutineDispatchers.io) {
olmMachine.ownDevice() olmMachine.ownDevice()
} }

View file

@ -53,6 +53,7 @@ import im.vector.app.core.pushers.FcmHelper
import im.vector.app.core.resources.BuildMeta import im.vector.app.core.resources.BuildMeta
import im.vector.app.features.analytics.DecryptionFailureTracker import im.vector.app.features.analytics.DecryptionFailureTracker
import im.vector.app.features.analytics.VectorAnalytics 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.configuration.VectorConfiguration import im.vector.app.features.configuration.VectorConfiguration
import im.vector.app.features.invite.InvitesAcceptor import im.vector.app.features.invite.InvitesAcceptor
@ -130,6 +131,13 @@ class VectorApplication :
appContext = this appContext = this
flipperProxy.init(matrix) flipperProxy.init(matrix)
vectorAnalytics.init() vectorAnalytics.init()
vectorAnalytics.updateSuperProperties(
SuperProperties(
appPlatform = SuperProperties.AppPlatform.EA,
cryptoSDK = SuperProperties.CryptoSDK.Rust,
cryptoSDKVersion = Matrix.getCryptoVersion(longFormat = false)
)
)
invitesAcceptor.initialize() invitesAcceptor.initialize()
autoRageShaker.initialize() autoRageShaker.initialize()
decryptionFailureTracker.start() decryptionFailureTracker.start()

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.23.0'
api libs.google.phonenumber api libs.google.phonenumber

View file

@ -22,7 +22,6 @@ 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.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 +56,6 @@ 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 var activeSessionReference: AtomicReference<Session?> = AtomicReference() private var activeSessionReference: AtomicReference<Session?> = AtomicReference()

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()
@ -168,20 +171,14 @@ class DefaultVectorAnalytics @Inject constructor(
override fun capture(event: VectorAnalyticsEvent) { override fun capture(event: VectorAnalyticsEvent) {
Timber.tag(analyticsTag.value).d("capture($event)") Timber.tag(analyticsTag.value).d("capture($event)")
posthog posthog?.takeIf { userConsent == true }?.capture(
?.takeIf { userConsent == true } event.getName(), analyticsId, event.getProperties()?.toPostHogProperties().orEmpty().withSuperProperties()
?.capture(
event.getName(),
analyticsId,
event.getProperties()?.toPostHogProperties()
) )
} }
override fun screen(screen: VectorAnalyticsScreen) { override fun screen(screen: VectorAnalyticsScreen) {
Timber.tag(analyticsTag.value).d("screen($screen)") Timber.tag(analyticsTag.value).d("screen($screen)")
posthog posthog?.takeIf { userConsent == true }?.screen(screen.getName(), screen.getProperties()?.toPostHogProperties().orEmpty().withSuperProperties())
?.takeIf { userConsent == true }
?.screen(screen.getName(), screen.getProperties()?.toPostHogProperties())
} }
override fun updateUserProperties(userProperties: UserProperties) { override fun updateUserProperties(userProperties: UserProperties) {
@ -195,9 +192,7 @@ class DefaultVectorAnalytics @Inject constructor(
private fun doUpdateUserProperties(userProperties: UserProperties) { private fun doUpdateUserProperties(userProperties: UserProperties) {
// we need a distinct id to set user properties // we need a distinct id to set user properties
val distinctId = analyticsId ?: return val distinctId = analyticsId ?: return
posthog posthog?.takeIf { userConsent == true }?.identify(distinctId, userProperties.getProperties())
?.takeIf { userConsent == true }
?.identify(distinctId, userProperties.getProperties())
} }
private fun Map<String, Any?>?.toPostHogProperties(): Map<String, Any>? { private fun Map<String, Any?>?.toPostHogProperties(): Map<String, Any>? {
@ -226,9 +221,32 @@ 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.takeIf { it.isEmpty().not() }
}
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) {
this.superProperties = SuperProperties(
cryptoSDK = updatedProperties.cryptoSDK ?: this.superProperties?.cryptoSDK,
appPlatform = updatedProperties.appPlatform ?: this.superProperties?.appPlatform,
cryptoSDKVersion = updatedProperties.cryptoSDKVersion ?: superProperties?.cryptoSDKVersion
)
}
} }

View file

@ -265,7 +265,7 @@ class BugReporter @Inject constructor(
activeSessionHolder.getSafeActiveSession()?.let { session -> activeSessionHolder.getSafeActiveSession()?.let { session ->
userId = session.myUserId userId = session.myUserId
deviceId = session.sessionParams.deviceId deviceId = session.sessionParams.deviceId
olmVersion = session.cryptoService().getCryptoVersion(context, true) olmVersion = Matrix.getCryptoVersion(true)
} }
if (!mIsCancelled) { if (!mIsCancelled) {

View file

@ -96,7 +96,7 @@ class VectorSettingsHelpAboutFragment :
// olm version // olm version
findPreference<VectorPreference>(VectorPreferences.SETTINGS_CRYPTO_VERSION_PREFERENCE_KEY)!! findPreference<VectorPreference>(VectorPreferences.SETTINGS_CRYPTO_VERSION_PREFERENCE_KEY)!!
.summary = session.cryptoService().getCryptoVersion(requireContext(), true) .summary = Matrix.getCryptoVersion(true)
} }
companion object { companion object {

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
@ -51,7 +52,7 @@ class DefaultVectorAnalyticsTest {
analyticsStore = fakeAnalyticsStore.instance, analyticsStore = fakeAnalyticsStore.instance,
globalScope = CoroutineScope(Dispatchers.Unconfined), globalScope = CoroutineScope(Dispatchers.Unconfined),
analyticsConfig = anAnalyticsConfig(isEnabled = true), analyticsConfig = anAnalyticsConfig(isEnabled = true),
lateInitUserPropertiesFactory = fakeLateInitUserPropertiesFactory.instance lateInitUserPropertiesFactory = fakeLateInitUserPropertiesFactory.instance,
) )
@Before @Before
@ -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(
appPlatform = SuperProperties.AppPlatform.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(
appPlatform = SuperProperties.AppPlatform.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