From 070200546843f7d06b5983a3e7cb711add3436e4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 14 Mar 2024 16:17:54 +0100 Subject: [PATCH 1/7] Track UTD errors. --- changelog.d/2544.misc | 1 + libraries/matrix/impl/build.gradle.kts | 1 + .../matrix/impl/RustMatrixClientFactory.kt | 3 ++ .../matrix/impl/analytics/UtdTracker.kt | 41 +++++++++++++++++++ 4 files changed, 46 insertions(+) create mode 100644 changelog.d/2544.misc create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTracker.kt diff --git a/changelog.d/2544.misc b/changelog.d/2544.misc new file mode 100644 index 0000000000..2d64ba871e --- /dev/null +++ b/changelog.d/2544.misc @@ -0,0 +1 @@ + Track UTD errors. diff --git a/libraries/matrix/impl/build.gradle.kts b/libraries/matrix/impl/build.gradle.kts index 8815214dd6..1f807a0120 100644 --- a/libraries/matrix/impl/build.gradle.kts +++ b/libraries/matrix/impl/build.gradle.kts @@ -39,6 +39,7 @@ dependencies { implementation(projects.libraries.di) implementation(projects.libraries.androidutils) implementation(projects.libraries.network) + implementation(projects.services.analytics.api) implementation(projects.services.toolbox.api) implementation(projects.libraries.featureflag.api) api(projects.libraries.matrix.api) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt index 64f152cd9c..80302933d5 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt @@ -18,6 +18,7 @@ package io.element.android.libraries.matrix.impl import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.di.CacheDirectory +import io.element.android.libraries.matrix.impl.analytics.UtdTracker import io.element.android.libraries.matrix.impl.certificates.UserCertificatesProvider import io.element.android.libraries.matrix.impl.proxy.ProxyProvider import io.element.android.libraries.network.useragent.UserAgentProvider @@ -42,6 +43,7 @@ class RustMatrixClientFactory @Inject constructor( private val userCertificatesProvider: UserCertificatesProvider, private val proxyProvider: ProxyProvider, private val clock: SystemClock, + private val utdTracker: UtdTracker, ) { suspend fun create(sessionData: SessionData): RustMatrixClient = withContext(coroutineDispatchers.io) { val client = ClientBuilder() @@ -68,6 +70,7 @@ class RustMatrixClientFactory @Inject constructor( client.restoreSession(sessionData.toSession()) val syncService = client.syncService() + .withUtdHook(utdTracker) .finish() RustMatrixClient( diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTracker.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTracker.kt new file mode 100644 index 0000000000..8c9180a8ee --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTracker.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.impl.analytics + +import im.vector.app.features.analytics.plan.Error +import io.element.android.services.analytics.api.AnalyticsService +import org.matrix.rustcomponents.sdk.UnableToDecryptDelegate +import org.matrix.rustcomponents.sdk.UnableToDecryptInfo +import timber.log.Timber +import javax.inject.Inject + +class UtdTracker @Inject constructor( + private val analyticsService: AnalyticsService, +) : UnableToDecryptDelegate { + override fun onUtd(info: UnableToDecryptInfo) { + Timber.d("onUtd for event ${info.eventId}, timeToDecryptMs: ${info.timeToDecryptMs}") + // TODO info will contain more information in the future, so that the app can report more precise data to the analytics. + val event = Error( + context = null, + cryptoModule = Error.CryptoModule.Rust, + domain = Error.Domain.E2EE, + // TODO get a more specific error name from `info` + name = Error.Name.OlmKeysNotSentError, + ) + analyticsService.capture(event) + } +} From a61b7c91f477c18c3eb702be68365564772a218f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 15 Mar 2024 09:36:20 +0100 Subject: [PATCH 2/7] Bump analytics plan to 0.14.0 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 13a16b4acf..46d946fe19 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -176,7 +176,7 @@ kotlinpoet = "com.squareup:kotlinpoet:1.16.0" # Analytics posthog = "com.posthog:posthog-android:3.1.15" sentry = "io.sentry:sentry-android:7.6.0" -matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.11.0" +matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.14.0" # Emojibase matrix_emojibase_bindings = "io.element.android:emojibase-bindings:1.1.3" From cf36ce3cecd2e59568c32702e1e105b669d167a4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 15 Mar 2024 09:41:43 +0100 Subject: [PATCH 3/7] Improve crypto error content. --- .../android/libraries/matrix/impl/analytics/UtdTracker.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTracker.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTracker.kt index 8c9180a8ee..578782d6c2 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTracker.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTracker.kt @@ -28,10 +28,12 @@ class UtdTracker @Inject constructor( ) : UnableToDecryptDelegate { override fun onUtd(info: UnableToDecryptInfo) { Timber.d("onUtd for event ${info.eventId}, timeToDecryptMs: ${info.timeToDecryptMs}") - // TODO info will contain more information in the future, so that the app can report more precise data to the analytics. val event = Error( context = null, + // Keep cryptoModule for compatibility. cryptoModule = Error.CryptoModule.Rust, + cryptoSDK = Error.CryptoSDK.Rust, + timeToDecryptMillis = info.timeToDecryptMs?.toInt() ?: -1, domain = Error.Domain.E2EE, // TODO get a more specific error name from `info` name = Error.Name.OlmKeysNotSentError, From b8e8578fad4013223aaf9660e71b0ed44f28e4e6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 15 Mar 2024 11:27:53 +0100 Subject: [PATCH 4/7] Add extra properties `"cryptoSDK" to Error.CryptoSDK.Rust` to all events and screen sent to PostHog. --- .../posthog/PosthogAnalyticsProvider.kt | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogAnalyticsProvider.kt b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogAnalyticsProvider.kt index 16cac5722d..fb542b7f28 100644 --- a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogAnalyticsProvider.kt +++ b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogAnalyticsProvider.kt @@ -20,6 +20,7 @@ import com.posthog.PostHogInterface import com.squareup.anvil.annotations.ContributesMultibinding import im.vector.app.features.analytics.itf.VectorAnalyticsEvent import im.vector.app.features.analytics.itf.VectorAnalyticsScreen +import im.vector.app.features.analytics.plan.Error import im.vector.app.features.analytics.plan.UserProperties import io.element.android.libraries.di.AppScope import io.element.android.services.analyticsproviders.api.AnalyticsProvider @@ -56,11 +57,17 @@ class PosthogAnalyticsProvider @Inject constructor( } override fun capture(event: VectorAnalyticsEvent) { - posthog?.capture(event.getName(), properties = event.getProperties()?.keepOnlyNonNullValues()) + posthog?.capture( + event = event.getName(), + properties = event.getProperties()?.keepOnlyNonNullValues().withExtraProperties(), + ) } override fun screen(screen: VectorAnalyticsScreen) { - posthog?.screen(screen.getName(), properties = screen.getProperties()) + posthog?.screen( + screenTitle = screen.getName(), + properties = screen.getProperties().withExtraProperties(), + ) } override fun updateUserProperties(userProperties: UserProperties) { @@ -98,3 +105,14 @@ private fun Map.keepOnlyNonNullValues(): Map { } return result } + +/** + * Properties which will be added to all Events and Screens. + */ +private val extraProperties: Map = mapOf( + "cryptoSDK" to Error.CryptoSDK.Rust +) + +private fun Map?.withExtraProperties(): Map { + return orEmpty() + extraProperties +} From c1dfa4806cac1e2444d5e967d73224701f975d4d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 15 Mar 2024 12:00:16 +0100 Subject: [PATCH 5/7] Fix compilation of minimal. --- .../kotlin/io/element/android/samples/minimal/MainActivity.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt index 1e5d1e7fd9..687a4e1c50 100644 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt +++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt @@ -29,10 +29,12 @@ import androidx.core.view.WindowCompat import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.impl.RustMatrixClientFactory +import io.element.android.libraries.matrix.impl.analytics.UtdTracker import io.element.android.libraries.matrix.impl.auth.RustMatrixAuthenticationService import io.element.android.libraries.network.useragent.SimpleUserAgentProvider import io.element.android.libraries.sessionstorage.api.LoggedInState import io.element.android.libraries.sessionstorage.impl.memory.InMemorySessionStore +import io.element.android.services.analytics.noop.NoopAnalyticsService import io.element.android.services.toolbox.impl.systemclock.DefaultSystemClock import kotlinx.coroutines.runBlocking import java.io.File @@ -59,6 +61,7 @@ class MainActivity : ComponentActivity() { userCertificatesProvider = userCertificatesProvider, proxyProvider = proxyProvider, clock = DefaultSystemClock(), + utdTracker = UtdTracker(NoopAnalyticsService()), ), passphraseGenerator = NullPassphraseGenerator(), buildMeta = Singleton.buildMeta, From dce57a72348b5eda469d5313886f3ade8e73b79b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 15 Mar 2024 12:29:38 +0100 Subject: [PATCH 6/7] Add unit test on UtdTracker --- libraries/matrix/impl/build.gradle.kts | 1 + .../matrix/impl/analytics/UtdTrackerTest.kt | 60 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTrackerTest.kt diff --git a/libraries/matrix/impl/build.gradle.kts b/libraries/matrix/impl/build.gradle.kts index 1f807a0120..d50bad6696 100644 --- a/libraries/matrix/impl/build.gradle.kts +++ b/libraries/matrix/impl/build.gradle.kts @@ -53,6 +53,7 @@ dependencies { testImplementation(libs.test.junit) testImplementation(libs.test.truth) testImplementation(projects.libraries.matrix.test) + testImplementation(projects.services.analytics.test) testImplementation(libs.coroutines.test) testImplementation(libs.test.turbine) } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTrackerTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTrackerTest.kt new file mode 100644 index 0000000000..1fbbefa6e4 --- /dev/null +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTrackerTest.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.impl.analytics + +import com.google.common.truth.Truth.assertThat +import im.vector.app.features.analytics.plan.Error +import io.element.android.libraries.matrix.test.AN_EVENT_ID +import io.element.android.services.analytics.test.FakeAnalyticsService +import org.junit.Test +import org.matrix.rustcomponents.sdk.UnableToDecryptInfo + +class UtdTrackerTest { + @Test + fun `when onUtd is called with null timeToDecryptMs, the expected analytics Event is sent`() { + val fakeAnalyticsService = FakeAnalyticsService() + val sut = UtdTracker(fakeAnalyticsService) + sut.onUtd(UnableToDecryptInfo(eventId = AN_EVENT_ID.value, timeToDecryptMs = null)) + assertThat(fakeAnalyticsService.capturedEvents).containsExactly( + Error( + context = null, + cryptoModule = Error.CryptoModule.Rust, + cryptoSDK = Error.CryptoSDK.Rust, + timeToDecryptMillis = -1, + domain = Error.Domain.E2EE, + name = Error.Name.OlmKeysNotSentError + ) + ) + } + + @Test + fun `when onUtd is called with timeToDecryptMs, the expected analytics Event is sent`() { + val fakeAnalyticsService = FakeAnalyticsService() + val sut = UtdTracker(fakeAnalyticsService) + sut.onUtd(UnableToDecryptInfo(eventId = AN_EVENT_ID.value, timeToDecryptMs = 123.toULong())) + assertThat(fakeAnalyticsService.capturedEvents).containsExactly( + Error( + context = null, + cryptoModule = Error.CryptoModule.Rust, + cryptoSDK = Error.CryptoSDK.Rust, + timeToDecryptMillis = 123, + domain = Error.Domain.E2EE, + name = Error.Name.OlmKeysNotSentError + ) + ) + } +} From 55cbb992013df2d9361e75f20dfa44ed65a46dca Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 15 Mar 2024 12:33:54 +0100 Subject: [PATCH 7/7] Improve UtdTracker test. --- .../android/libraries/matrix/impl/analytics/UtdTrackerTest.kt | 4 ++++ .../android/services/analytics/test/FakeAnalyticsService.kt | 2 ++ 2 files changed, 6 insertions(+) diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTrackerTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTrackerTest.kt index 1fbbefa6e4..18ccdbb3e8 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTrackerTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTrackerTest.kt @@ -39,6 +39,8 @@ class UtdTrackerTest { name = Error.Name.OlmKeysNotSentError ) ) + assertThat(fakeAnalyticsService.screenEvents).isEmpty() + assertThat(fakeAnalyticsService.trackedErrors).isEmpty() } @Test @@ -56,5 +58,7 @@ class UtdTrackerTest { name = Error.Name.OlmKeysNotSentError ) ) + assertThat(fakeAnalyticsService.screenEvents).isEmpty() + assertThat(fakeAnalyticsService.trackedErrors).isEmpty() } } diff --git a/services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/FakeAnalyticsService.kt b/services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/FakeAnalyticsService.kt index 65b4a78b9a..14ef329ae4 100644 --- a/services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/FakeAnalyticsService.kt +++ b/services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/FakeAnalyticsService.kt @@ -31,6 +31,7 @@ class FakeAnalyticsService( private val isEnabledFlow = MutableStateFlow(isEnabled) private val didAskUserConsentFlow = MutableStateFlow(didAskUserConsent) val capturedEvents = mutableListOf() + val screenEvents = mutableListOf() val trackedErrors = mutableListOf() override fun getAvailableAnalyticsProviders(): Set = emptySet() @@ -60,6 +61,7 @@ class FakeAnalyticsService( } override fun screen(screen: VectorAnalyticsScreen) { + screenEvents += screen } override fun updateUserProperties(userProperties: UserProperties) {