Analytics: track screen MobileScreen.ScreenName.RoomCall
This commit is contained in:
parent
5835a025b4
commit
8e6bf997dc
6 changed files with 84 additions and 15 deletions
|
|
@ -43,6 +43,7 @@ dependencies {
|
||||||
implementation(projects.libraries.matrix.impl)
|
implementation(projects.libraries.matrix.impl)
|
||||||
implementation(projects.libraries.network)
|
implementation(projects.libraries.network)
|
||||||
implementation(projects.libraries.preferences.api)
|
implementation(projects.libraries.preferences.api)
|
||||||
|
implementation(projects.services.analytics.api)
|
||||||
implementation(projects.services.toolbox.api)
|
implementation(projects.services.toolbox.api)
|
||||||
implementation(libs.androidx.webkit)
|
implementation(libs.androidx.webkit)
|
||||||
implementation(libs.serialization.json)
|
implementation(libs.serialization.json)
|
||||||
|
|
@ -56,5 +57,6 @@ dependencies {
|
||||||
testImplementation(projects.libraries.featureflag.test)
|
testImplementation(projects.libraries.featureflag.test)
|
||||||
testImplementation(projects.libraries.preferences.test)
|
testImplementation(projects.libraries.preferences.test)
|
||||||
testImplementation(projects.libraries.matrix.test)
|
testImplementation(projects.libraries.matrix.test)
|
||||||
|
testImplementation(projects.services.analytics.test)
|
||||||
testImplementation(projects.tests.testutils)
|
testImplementation(projects.tests.testutils)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import androidx.compose.runtime.setValue
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
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.CallType
|
||||||
import io.element.android.features.call.data.WidgetMessage
|
import io.element.android.features.call.data.WidgetMessage
|
||||||
import io.element.android.features.call.utils.CallWidgetProvider
|
import io.element.android.features.call.utils.CallWidgetProvider
|
||||||
|
|
@ -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.sync.SyncState
|
||||||
import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver
|
import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver
|
||||||
import io.element.android.libraries.network.useragent.UserAgentProvider
|
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 io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
|
|
@ -61,6 +63,7 @@ class CallScreenPresenter @AssistedInject constructor(
|
||||||
private val clock: SystemClock,
|
private val clock: SystemClock,
|
||||||
private val dispatchers: CoroutineDispatchers,
|
private val dispatchers: CoroutineDispatchers,
|
||||||
private val matrixClientsProvider: MatrixClientProvider,
|
private val matrixClientsProvider: MatrixClientProvider,
|
||||||
|
private val screenTracker: ScreenTracker,
|
||||||
private val appCoroutineScope: CoroutineScope,
|
private val appCoroutineScope: CoroutineScope,
|
||||||
) : Presenter<CallScreenState> {
|
) : Presenter<CallScreenState> {
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
|
|
@ -83,6 +86,15 @@ class CallScreenPresenter @AssistedInject constructor(
|
||||||
loadUrl(callType, urlState, callWidgetDriver)
|
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()
|
HandleMatrixClientSyncState()
|
||||||
|
|
||||||
callWidgetDriver.value?.let { driver ->
|
callWidgetDriver.value?.let { driver ->
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import app.cash.molecule.RecompositionMode
|
||||||
import app.cash.molecule.moleculeFlow
|
import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
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.CallType
|
||||||
import io.element.android.features.call.utils.FakeCallWidgetProvider
|
import io.element.android.features.call.utils.FakeCallWidgetProvider
|
||||||
import io.element.android.features.call.utils.FakeWidgetMessageInterceptor
|
import io.element.android.features.call.utils.FakeWidgetMessageInterceptor
|
||||||
|
|
@ -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.FakeMatrixClientProvider
|
||||||
import io.element.android.libraries.matrix.test.widget.FakeMatrixWidgetDriver
|
import io.element.android.libraries.matrix.test.widget.FakeMatrixWidgetDriver
|
||||||
import io.element.android.libraries.network.useragent.UserAgentProvider
|
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.services.toolbox.api.systemclock.SystemClock
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
import io.element.android.tests.testutils.consumeItemsUntilTimeout
|
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 io.element.android.tests.testutils.testCoroutineDispatchers
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.cancelAndJoin
|
import kotlinx.coroutines.cancelAndJoin
|
||||||
|
|
@ -53,16 +58,20 @@ class CallScreenPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - with CallType ExternalUrl just loads the URL`() = runTest {
|
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) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
// Wait until the URL is loaded
|
// Wait until the URL is loaded
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
|
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.urlState).isEqualTo(AsyncData.Success("https://call.element.io"))
|
assertThat(initialState.urlState).isEqualTo(AsyncData.Success("https://call.element.io"))
|
||||||
assertThat(initialState.isInWidgetMode).isFalse()
|
assertThat(initialState.isInWidgetMode).isFalse()
|
||||||
|
analyticsLambda.assertions().isNeverCalled()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,22 +79,29 @@ class CallScreenPresenterTest {
|
||||||
fun `present - with CallType RoomCall loads URL and runs WidgetDriver`() = runTest {
|
fun `present - with CallType RoomCall loads URL and runs WidgetDriver`() = runTest {
|
||||||
val widgetDriver = FakeMatrixWidgetDriver()
|
val widgetDriver = FakeMatrixWidgetDriver()
|
||||||
val widgetProvider = FakeCallWidgetProvider(widgetDriver)
|
val widgetProvider = FakeCallWidgetProvider(widgetDriver)
|
||||||
|
val analyticsLambda = lambdaRecorder<MobileScreen.ScreenName, Unit> { }
|
||||||
val presenter = createCallScreenPresenter(
|
val presenter = createCallScreenPresenter(
|
||||||
callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID),
|
callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID),
|
||||||
widgetDriver = widgetDriver,
|
widgetDriver = widgetDriver,
|
||||||
widgetProvider = widgetProvider,
|
widgetProvider = widgetProvider,
|
||||||
|
screenTracker = FakeScreenTracker(analyticsLambda)
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
// Wait until the URL is loaded
|
// Wait until the URL is loaded
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
|
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.urlState).isInstanceOf(AsyncData.Success::class.java)
|
assertThat(initialState.urlState).isInstanceOf(AsyncData.Success::class.java)
|
||||||
assertThat(initialState.isInWidgetMode).isTrue()
|
assertThat(initialState.isInWidgetMode).isTrue()
|
||||||
assertThat(widgetProvider.getWidgetCalled).isTrue()
|
assertThat(widgetProvider.getWidgetCalled).isTrue()
|
||||||
assertThat(widgetDriver.runCalledCount).isEqualTo(1)
|
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 {
|
||||||
val presenter = createCallScreenPresenter(
|
val presenter = createCallScreenPresenter(
|
||||||
callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID),
|
callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID),
|
||||||
widgetDriver = widgetDriver,
|
widgetDriver = widgetDriver,
|
||||||
|
screenTracker = FakeScreenTracker {},
|
||||||
)
|
)
|
||||||
val messageInterceptor = FakeWidgetMessageInterceptor()
|
val messageInterceptor = FakeWidgetMessageInterceptor()
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
|
@ -125,6 +142,7 @@ class CallScreenPresenterTest {
|
||||||
widgetDriver = widgetDriver,
|
widgetDriver = widgetDriver,
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
|
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
|
||||||
|
screenTracker = FakeScreenTracker {},
|
||||||
)
|
)
|
||||||
val messageInterceptor = FakeWidgetMessageInterceptor()
|
val messageInterceptor = FakeWidgetMessageInterceptor()
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
|
@ -155,6 +173,7 @@ class CallScreenPresenterTest {
|
||||||
widgetDriver = widgetDriver,
|
widgetDriver = widgetDriver,
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
|
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
|
||||||
|
screenTracker = FakeScreenTracker {},
|
||||||
)
|
)
|
||||||
val messageInterceptor = FakeWidgetMessageInterceptor()
|
val messageInterceptor = FakeWidgetMessageInterceptor()
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
|
@ -185,7 +204,8 @@ class CallScreenPresenterTest {
|
||||||
widgetDriver = widgetDriver,
|
widgetDriver = widgetDriver,
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
|
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
|
||||||
matrixClientsProvider = FakeMatrixClientProvider(getClient = { Result.success(matrixClient) })
|
matrixClientsProvider = FakeMatrixClientProvider(getClient = { Result.success(matrixClient) }),
|
||||||
|
screenTracker = FakeScreenTracker {},
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
|
|
@ -208,7 +228,8 @@ class CallScreenPresenterTest {
|
||||||
widgetDriver = widgetDriver,
|
widgetDriver = widgetDriver,
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
|
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
|
||||||
matrixClientsProvider = FakeMatrixClientProvider(getClient = { Result.success(matrixClient) })
|
matrixClientsProvider = FakeMatrixClientProvider(getClient = { Result.success(matrixClient) }),
|
||||||
|
screenTracker = FakeScreenTracker {},
|
||||||
)
|
)
|
||||||
val hasRun = Mutex(true)
|
val hasRun = Mutex(true)
|
||||||
val job = launch {
|
val job = launch {
|
||||||
|
|
@ -233,6 +254,7 @@ class CallScreenPresenterTest {
|
||||||
widgetProvider: FakeCallWidgetProvider = FakeCallWidgetProvider(widgetDriver),
|
widgetProvider: FakeCallWidgetProvider = FakeCallWidgetProvider(widgetDriver),
|
||||||
dispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
|
dispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
|
||||||
matrixClientsProvider: FakeMatrixClientProvider = FakeMatrixClientProvider(),
|
matrixClientsProvider: FakeMatrixClientProvider = FakeMatrixClientProvider(),
|
||||||
|
screenTracker: ScreenTracker = FakeScreenTracker(),
|
||||||
): CallScreenPresenter {
|
): CallScreenPresenter {
|
||||||
val userAgentProvider = object : UserAgentProvider {
|
val userAgentProvider = object : UserAgentProvider {
|
||||||
override fun provide(): String {
|
override fun provide(): String {
|
||||||
|
|
@ -241,14 +263,15 @@ class CallScreenPresenterTest {
|
||||||
}
|
}
|
||||||
val clock = SystemClock { 0 }
|
val clock = SystemClock { 0 }
|
||||||
return CallScreenPresenter(
|
return CallScreenPresenter(
|
||||||
callType,
|
callType = callType,
|
||||||
navigator,
|
navigator = navigator,
|
||||||
widgetProvider,
|
callWidgetProvider = widgetProvider,
|
||||||
userAgentProvider,
|
userAgentProvider = userAgentProvider,
|
||||||
clock,
|
clock = clock,
|
||||||
dispatchers,
|
dispatchers = dispatchers,
|
||||||
matrixClientsProvider,
|
matrixClientsProvider = matrixClientsProvider,
|
||||||
this,
|
screenTracker = screenTracker,
|
||||||
|
appCoroutineScope = this,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -187,7 +187,7 @@ zxing_cpp = "io.github.zxing-cpp:android:2.2.0"
|
||||||
posthog = "com.posthog:posthog-android:3.3.0"
|
posthog = "com.posthog:posthog-android:3.3.0"
|
||||||
sentry = "io.sentry:sentry-android:7.9.0"
|
sentry = "io.sentry:sentry-android:7.9.0"
|
||||||
# main branch can be tested replacing the version with main-SNAPSHOT
|
# 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
|
# Emojibase
|
||||||
matrix_emojibase_bindings = "io.element.android:emojibase-bindings:1.1.3"
|
matrix_emojibase_bindings = "io.element.android:emojibase-bindings:1.1.3"
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-library")
|
id("io.element.android-compose-library")
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|
@ -24,5 +24,6 @@ android {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.services.analytics.api)
|
implementation(projects.services.analytics.api)
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
|
implementation(projects.tests.testutils)
|
||||||
implementation(libs.coroutines.core)
|
implementation(libs.coroutines.core)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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…
Add table
Add a link
Reference in a new issue