Browse Source

Analytics: track screen `MobileScreen.ScreenName.RoomCall`

pull/2970/head
Benoit Marty 4 months ago
parent
commit
6fc4450c56
  1. 2
      features/call/build.gradle.kts
  2. 12
      features/call/src/main/kotlin/io/element/android/features/call/ui/CallScreenPresenter.kt
  3. 49
      features/call/src/test/kotlin/io/element/android/features/call/ui/CallScreenPresenterTest.kt
  4. 2
      gradle/libs.versions.toml
  5. 3
      services/analytics/test/build.gradle.kts
  6. 31
      services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/FakeScreenTracker.kt

2
features/call/build.gradle.kts

@ -43,6 +43,7 @@ dependencies { @@ -43,6 +43,7 @@ dependencies {
implementation(projects.libraries.matrix.impl)
implementation(projects.libraries.network)
implementation(projects.libraries.preferences.api)
implementation(projects.services.analytics.api)
implementation(projects.services.toolbox.api)
implementation(libs.androidx.webkit)
implementation(libs.serialization.json)
@ -56,5 +57,6 @@ dependencies { @@ -56,5 +57,6 @@ dependencies {
testImplementation(projects.libraries.featureflag.test)
testImplementation(projects.libraries.preferences.test)
testImplementation(projects.libraries.matrix.test)
testImplementation(projects.services.analytics.test)
testImplementation(projects.tests.testutils)
}

12
features/call/src/main/kotlin/io/element/android/features/call/ui/CallScreenPresenter.kt

@ -29,6 +29,7 @@ import androidx.compose.runtime.setValue @@ -29,6 +29,7 @@ import androidx.compose.runtime.setValue
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.features.analytics.plan.MobileScreen
import io.element.android.features.call.CallType
import io.element.android.features.call.data.WidgetMessage
import io.element.android.features.call.utils.CallWidgetProvider
@ -42,6 +43,7 @@ import io.element.android.libraries.matrix.api.MatrixClientProvider @@ -42,6 +43,7 @@ import io.element.android.libraries.matrix.api.MatrixClientProvider
import io.element.android.libraries.matrix.api.sync.SyncState
import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver
import io.element.android.libraries.network.useragent.UserAgentProvider
import io.element.android.services.analytics.api.ScreenTracker
import io.element.android.services.toolbox.api.systemclock.SystemClock
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collect
@ -61,6 +63,7 @@ class CallScreenPresenter @AssistedInject constructor( @@ -61,6 +63,7 @@ class CallScreenPresenter @AssistedInject constructor(
private val clock: SystemClock,
private val dispatchers: CoroutineDispatchers,
private val matrixClientsProvider: MatrixClientProvider,
private val screenTracker: ScreenTracker,
private val appCoroutineScope: CoroutineScope,
) : Presenter<CallScreenState> {
@AssistedFactory
@ -83,6 +86,15 @@ class CallScreenPresenter @AssistedInject constructor( @@ -83,6 +86,15 @@ class CallScreenPresenter @AssistedInject constructor(
loadUrl(callType, urlState, callWidgetDriver)
}
when (callType) {
is CallType.ExternalUrl -> {
// No analytics yet for external calls
}
is CallType.RoomCall -> {
screenTracker.TrackScreen(screen = MobileScreen.ScreenName.RoomCall)
}
}
HandleMatrixClientSyncState()
callWidgetDriver.value?.let { driver ->

49
features/call/src/test/kotlin/io/element/android/features/call/ui/CallScreenPresenterTest.kt

@ -20,6 +20,7 @@ import app.cash.molecule.RecompositionMode @@ -20,6 +20,7 @@ import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import im.vector.app.features.analytics.plan.MobileScreen
import io.element.android.features.call.CallType
import io.element.android.features.call.utils.FakeCallWidgetProvider
import io.element.android.features.call.utils.FakeWidgetMessageInterceptor
@ -32,9 +33,13 @@ import io.element.android.libraries.matrix.test.FakeMatrixClient @@ -32,9 +33,13 @@ import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
import io.element.android.libraries.matrix.test.widget.FakeMatrixWidgetDriver
import io.element.android.libraries.network.useragent.UserAgentProvider
import io.element.android.services.analytics.api.ScreenTracker
import io.element.android.services.analytics.test.FakeScreenTracker
import io.element.android.services.toolbox.api.systemclock.SystemClock
import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.consumeItemsUntilTimeout
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancelAndJoin
@ -53,16 +58,20 @@ class CallScreenPresenterTest { @@ -53,16 +58,20 @@ class CallScreenPresenterTest {
@Test
fun `present - with CallType ExternalUrl just loads the URL`() = runTest {
val presenter = createCallScreenPresenter(CallType.ExternalUrl("https://call.element.io"))
val analyticsLambda = lambdaRecorder<MobileScreen.ScreenName, Unit> { }
val presenter = createCallScreenPresenter(
callType = CallType.ExternalUrl("https://call.element.io"),
screenTracker = FakeScreenTracker(analyticsLambda)
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
// Wait until the URL is loaded
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.urlState).isEqualTo(AsyncData.Success("https://call.element.io"))
assertThat(initialState.isInWidgetMode).isFalse()
analyticsLambda.assertions().isNeverCalled()
}
}
@ -70,22 +79,29 @@ class CallScreenPresenterTest { @@ -70,22 +79,29 @@ class CallScreenPresenterTest {
fun `present - with CallType RoomCall loads URL and runs WidgetDriver`() = runTest {
val widgetDriver = FakeMatrixWidgetDriver()
val widgetProvider = FakeCallWidgetProvider(widgetDriver)
val analyticsLambda = lambdaRecorder<MobileScreen.ScreenName, Unit> { }
val presenter = createCallScreenPresenter(
callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID),
widgetDriver = widgetDriver,
widgetProvider = widgetProvider,
screenTracker = FakeScreenTracker(analyticsLambda)
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
// Wait until the URL is loaded
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.urlState).isInstanceOf(AsyncData.Success::class.java)
assertThat(initialState.isInWidgetMode).isTrue()
assertThat(widgetProvider.getWidgetCalled).isTrue()
assertThat(widgetDriver.runCalledCount).isEqualTo(1)
// Called several times because of the recomposition
analyticsLambda.assertions().isCalledExactly(2)
.withSequence(
listOf(value(MobileScreen.ScreenName.RoomCall)),
listOf(value(MobileScreen.ScreenName.RoomCall))
)
}
}
@ -95,6 +111,7 @@ class CallScreenPresenterTest { @@ -95,6 +111,7 @@ class CallScreenPresenterTest {
val presenter = createCallScreenPresenter(
callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID),
widgetDriver = widgetDriver,
screenTracker = FakeScreenTracker {},
)
val messageInterceptor = FakeWidgetMessageInterceptor()
moleculeFlow(RecompositionMode.Immediate) {
@ -125,6 +142,7 @@ class CallScreenPresenterTest { @@ -125,6 +142,7 @@ class CallScreenPresenterTest {
widgetDriver = widgetDriver,
navigator = navigator,
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
screenTracker = FakeScreenTracker {},
)
val messageInterceptor = FakeWidgetMessageInterceptor()
moleculeFlow(RecompositionMode.Immediate) {
@ -155,6 +173,7 @@ class CallScreenPresenterTest { @@ -155,6 +173,7 @@ class CallScreenPresenterTest {
widgetDriver = widgetDriver,
navigator = navigator,
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
screenTracker = FakeScreenTracker {},
)
val messageInterceptor = FakeWidgetMessageInterceptor()
moleculeFlow(RecompositionMode.Immediate) {
@ -185,7 +204,8 @@ class CallScreenPresenterTest { @@ -185,7 +204,8 @@ class CallScreenPresenterTest {
widgetDriver = widgetDriver,
navigator = navigator,
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
matrixClientsProvider = FakeMatrixClientProvider(getClient = { Result.success(matrixClient) })
matrixClientsProvider = FakeMatrixClientProvider(getClient = { Result.success(matrixClient) }),
screenTracker = FakeScreenTracker {},
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@ -208,7 +228,8 @@ class CallScreenPresenterTest { @@ -208,7 +228,8 @@ class CallScreenPresenterTest {
widgetDriver = widgetDriver,
navigator = navigator,
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
matrixClientsProvider = FakeMatrixClientProvider(getClient = { Result.success(matrixClient) })
matrixClientsProvider = FakeMatrixClientProvider(getClient = { Result.success(matrixClient) }),
screenTracker = FakeScreenTracker {},
)
val hasRun = Mutex(true)
val job = launch {
@ -233,6 +254,7 @@ class CallScreenPresenterTest { @@ -233,6 +254,7 @@ class CallScreenPresenterTest {
widgetProvider: FakeCallWidgetProvider = FakeCallWidgetProvider(widgetDriver),
dispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
matrixClientsProvider: FakeMatrixClientProvider = FakeMatrixClientProvider(),
screenTracker: ScreenTracker = FakeScreenTracker(),
): CallScreenPresenter {
val userAgentProvider = object : UserAgentProvider {
override fun provide(): String {
@ -241,14 +263,15 @@ class CallScreenPresenterTest { @@ -241,14 +263,15 @@ class CallScreenPresenterTest {
}
val clock = SystemClock { 0 }
return CallScreenPresenter(
callType,
navigator,
widgetProvider,
userAgentProvider,
clock,
dispatchers,
matrixClientsProvider,
this,
callType = callType,
navigator = navigator,
callWidgetProvider = widgetProvider,
userAgentProvider = userAgentProvider,
clock = clock,
dispatchers = dispatchers,
matrixClientsProvider = matrixClientsProvider,
screenTracker = screenTracker,
appCoroutineScope = this,
)
}
}

2
gradle/libs.versions.toml

@ -187,7 +187,7 @@ zxing_cpp = "io.github.zxing-cpp:android:2.2.0" @@ -187,7 +187,7 @@ zxing_cpp = "io.github.zxing-cpp:android:2.2.0"
posthog = "com.posthog:posthog-android:3.3.0"
sentry = "io.sentry:sentry-android:7.9.0"
# main branch can be tested replacing the version with main-SNAPSHOT
matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.23.0"
matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.23.1"
# Emojibase
matrix_emojibase_bindings = "io.element.android:emojibase-bindings:1.1.3"

3
services/analytics/test/build.gradle.kts

@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
* limitations under the License.
*/
plugins {
id("io.element.android-library")
id("io.element.android-compose-library")
}
android {
@ -24,5 +24,6 @@ android { @@ -24,5 +24,6 @@ android {
dependencies {
implementation(projects.services.analytics.api)
implementation(projects.libraries.core)
implementation(projects.tests.testutils)
implementation(libs.coroutines.core)
}

31
services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/FakeScreenTracker.kt

@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
/*
* 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.services.analytics.test
import androidx.compose.runtime.Composable
import im.vector.app.features.analytics.plan.MobileScreen
import io.element.android.services.analytics.api.ScreenTracker
import io.element.android.tests.testutils.lambda.lambdaError
class FakeScreenTracker(
private val trackScreenLambda: (MobileScreen.ScreenName) -> Unit = { lambdaError() }
) : ScreenTracker {
@Composable
override fun TrackScreen(screen: MobileScreen.ScreenName) {
trackScreenLambda(screen)
}
}
Loading…
Cancel
Save