Analytics : add analytics on read status and favorite toggles

This commit is contained in:
ganfra 2024-02-19 17:36:23 +01:00
parent 22ab4e716b
commit 66030aeb64
8 changed files with 91 additions and 30 deletions

View file

@ -29,6 +29,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import im.vector.app.features.analytics.plan.Interaction
import io.element.android.features.leaveroom.api.LeaveRoomEvent
import io.element.android.features.leaveroom.api.LeaveRoomPresenter
import io.element.android.features.networkmonitor.api.NetworkMonitor
@ -45,12 +46,15 @@ import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.indicator.api.IndicatorService
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.encryption.EncryptionService
import io.element.android.libraries.matrix.api.encryption.RecoveryState
import io.element.android.libraries.matrix.api.timeline.ReceiptType
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.api.user.getCurrentUser
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import io.element.android.services.analytics.api.AnalyticsService
import io.element.android.services.analyticsproviders.api.trackers.captureInteraction
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collect
@ -78,6 +82,7 @@ class RoomListPresenter @Inject constructor(
private val indicatorService: IndicatorService,
private val migrationScreenPresenter: MigrationScreenPresenter,
private val sessionPreferencesStore: SessionPreferencesStore,
private val analyticsService: AnalyticsService,
) : Presenter<RoomListState> {
@Composable
override fun present(): RoomListState {
@ -145,27 +150,9 @@ class RoomListPresenter @Inject constructor(
}
is RoomListEvents.LeaveRoom -> leaveRoomState.eventSink(LeaveRoomEvent.ShowConfirmation(event.roomId))
is RoomListEvents.SetRoomIsFavorite -> coroutineScope.launch {
client.getRoom(event.roomId)?.use { room ->
room.setIsFavorite(event.isFavorite)
}
}
is RoomListEvents.MarkAsRead -> coroutineScope.launch {
client.getRoom(event.roomId)?.use { room ->
room.setUnreadFlag(isUnread = false)
val receiptType = if (sessionPreferencesStore.isSendPublicReadReceiptsEnabled().first()) {
ReceiptType.READ
} else {
ReceiptType.READ_PRIVATE
}
room.markAsRead(receiptType)
}
}
is RoomListEvents.MarkAsUnread -> coroutineScope.launch {
client.getRoom(event.roomId)?.use { room ->
room.setUnreadFlag(isUnread = true)
}
}
is RoomListEvents.SetRoomIsFavorite -> coroutineScope.setRoomIsFavorite(event.roomId, event.isFavorite)
is RoomListEvents.MarkAsRead -> coroutineScope.markAsRead(event.roomId)
is RoomListEvents.MarkAsUnread -> coroutineScope.markAsUnread(event.roomId)
}
}
@ -225,6 +212,39 @@ class RoomListPresenter @Inject constructor(
}
}
private fun CoroutineScope.setRoomIsFavorite(roomId: RoomId, isFavorite: Boolean) = launch {
client.getRoom(roomId)?.use { room ->
room.setIsFavorite(isFavorite)
.onSuccess {
analyticsService.captureInteraction(name = Interaction.Name.MobileRoomListRoomContextMenuFavouriteToggle)
}
}
}
private fun CoroutineScope.markAsRead(roomId: RoomId) = launch {
client.getRoom(roomId)?.use { room ->
room.setUnreadFlag(isUnread = false)
val receiptType = if (sessionPreferencesStore.isSendPublicReadReceiptsEnabled().first()) {
ReceiptType.READ
} else {
ReceiptType.READ_PRIVATE
}
room.markAsRead(receiptType)
.onSuccess {
analyticsService.captureInteraction(name = Interaction.Name.MobileRoomListRoomContextMenuUnreadToggle)
}
}
}
private fun CoroutineScope.markAsUnread(roomId: RoomId) = launch {
client.getRoom(roomId)?.use { room ->
room.setUnreadFlag(isUnread = true)
.onSuccess {
analyticsService.captureInteraction(name = Interaction.Name.MobileRoomListRoomContextMenuUnreadToggle)
}
}
}
private fun updateVisibleRange(range: IntRange) {
if (range.isEmpty()) return
val midExtendedRangeSize = EXTENDED_RANGE_SIZE / 2

View file

@ -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.Interaction
import io.element.android.features.leaveroom.api.LeaveRoomEvent
import io.element.android.features.leaveroom.api.LeaveRoomPresenter
import io.element.android.features.leaveroom.fake.FakeLeaveRoomPresenter
@ -66,6 +67,8 @@ import io.element.android.libraries.matrix.test.room.aRoomInfo
import io.element.android.libraries.matrix.test.room.aRoomSummaryFilled
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
import io.element.android.services.analytics.api.AnalyticsService
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.consumeItemsUntilPredicate
import io.element.android.tests.testutils.testCoroutineDispatchers
@ -474,10 +477,11 @@ class RoomListPresenterTests {
fun `present - when set is favorite event is emitted, then the action is called`() = runTest {
val scope = CoroutineScope(coroutineContext + SupervisorJob())
val room = FakeMatrixRoom()
val analyticsService = FakeAnalyticsService()
val client = FakeMatrixClient().apply {
givenGetRoomResult(A_ROOM_ID, room)
}
val presenter = createRoomListPresenter(client = client, coroutineScope = scope)
val presenter = createRoomListPresenter(client = client, coroutineScope = scope, analyticsService = analyticsService)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@ -486,6 +490,10 @@ class RoomListPresenterTests {
assertThat(room.setIsFavoriteCalls).isEqualTo(listOf(true))
initialState.eventSink(RoomListEvents.SetRoomIsFavorite(A_ROOM_ID, false))
assertThat(room.setIsFavoriteCalls).isEqualTo(listOf(true, false))
assertThat(analyticsService.capturedEvents).containsExactly(
Interaction(name = Interaction.Name.MobileRoomListRoomContextMenuFavouriteToggle),
Interaction(name = Interaction.Name.MobileRoomListRoomContextMenuFavouriteToggle)
)
cancelAndIgnoreRemainingEvents()
scope.cancel()
}
@ -527,11 +535,13 @@ class RoomListPresenterTests {
val matrixClient = FakeMatrixClient().apply {
givenGetRoomResult(A_ROOM_ID, room)
}
val analyticsService = FakeAnalyticsService()
val scope = CoroutineScope(coroutineContext + SupervisorJob())
val presenter = createRoomListPresenter(
client = matrixClient,
coroutineScope = scope,
sessionPreferencesStore = sessionPreferencesStore,
analyticsService = analyticsService,
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@ -550,6 +560,11 @@ class RoomListPresenterTests {
initialState.eventSink.invoke(RoomListEvents.MarkAsRead(A_ROOM_ID))
assertThat(room.markAsReadCalls).isEqualTo(listOf(ReceiptType.READ, ReceiptType.READ_PRIVATE))
assertThat(room.setUnreadFlagCalls).isEqualTo(listOf(false, true, false))
assertThat(analyticsService.capturedEvents).containsExactly(
Interaction(name = Interaction.Name.MobileRoomListRoomContextMenuUnreadToggle),
Interaction(name = Interaction.Name.MobileRoomListRoomContextMenuUnreadToggle),
Interaction(name = Interaction.Name.MobileRoomListRoomContextMenuUnreadToggle),
)
cancelAndIgnoreRemainingEvents()
scope.cancel()
}
@ -572,7 +587,8 @@ class RoomListPresenterTests {
migrationScreenPresenter: MigrationScreenPresenter = MigrationScreenPresenter(
matrixClient = client,
migrationScreenStore = InMemoryMigrationScreenStore(),
)
),
analyticsService: AnalyticsService = FakeAnalyticsService(),
) = RoomListPresenter(
client = client,
sessionVerificationService = sessionVerificationService,
@ -599,5 +615,6 @@ class RoomListPresenterTests {
),
migrationScreenPresenter = migrationScreenPresenter,
sessionPreferencesStore = sessionPreferencesStore,
analyticsService = analyticsService,
)
}