Skip to content

Commit

Permalink
feat: add call rating analytics (WPB-11082) (#3446)
Browse files Browse the repository at this point in the history
  • Loading branch information
yamilmedina authored Sep 16, 2024
1 parent 149442a commit e5614ee
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 111 deletions.
2 changes: 1 addition & 1 deletion app/src/main/kotlin/com/wire/android/WireApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ class WireApplication : BaseApp() {
.isAppVisibleFlow()
.filter { isVisible -> isVisible }
.collect {
AnonymousAnalyticsManagerImpl.sendEvent(AnalyticsEvent.AppOpen())
AnonymousAnalyticsManagerImpl.sendEvent(AnalyticsEvent.AppOpen)
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/kotlin/com/wire/android/ui/WireActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,8 @@ class WireActivity : AppCompatActivity() {
@Composable
private fun HandleDialogs(navigate: (NavigationCommand) -> Unit) {
val context = LocalContext.current
val callFeedbackSheetState = rememberWireModalSheetState<Unit>()
val callFeedbackSheetState =
rememberWireModalSheetState<Unit>(onDismissAction = { featureFlagNotificationViewModel.skipCallFeedback(false) })
with(featureFlagNotificationViewModel.featureFlagState) {
if (shouldShowTeamAppLockDialog) {
TeamAppLockFeatureFlagDialog(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ fun ConversationScreen(
getOngoingCallIntent(activity, it.toString()).run {
activity.startActivity(this)
}
AnonymousAnalyticsManagerImpl.sendEvent(event = AnalyticsEvent.CallJoined())
AnonymousAnalyticsManagerImpl.sendEvent(event = AnalyticsEvent.CallJoined)
}
}
)
Expand All @@ -325,7 +325,7 @@ fun ConversationScreen(
getOutgoingCallIntent(activity, conversationListCallViewModel.conversationId.toString()).run {
activity.startActivity(this)
}
AnonymousAnalyticsManagerImpl.sendEvent(event = AnalyticsEvent.CallJoined())
AnonymousAnalyticsManagerImpl.sendEvent(event = AnalyticsEvent.CallJoined)
}
showDialog.value = ConversationScreenDialogType.NONE
}, onDialogDismiss = {
Expand Down Expand Up @@ -493,7 +493,7 @@ fun ConversationScreen(
},
onJoinCall = {
conversationListCallViewModel.joinOngoingCall {
AnonymousAnalyticsManagerImpl.sendEvent(event = AnalyticsEvent.CallJoined())
AnonymousAnalyticsManagerImpl.sendEvent(event = AnalyticsEvent.CallJoined)
getOngoingCallIntent(activity, it.toString()).run {
activity.startActivity(this)
}
Expand Down Expand Up @@ -751,15 +751,15 @@ private fun startCallIfPossible(
} else {
conversationListCallViewModel.endEstablishedCallIfAny {
onOpenOutgoingCallScreen(conversationListCallViewModel.conversationId)
AnonymousAnalyticsManagerImpl.sendEvent(event = AnalyticsEvent.CallInitiated())
AnonymousAnalyticsManagerImpl.sendEvent(event = AnalyticsEvent.CallInitiated)
}
ConversationScreenDialogType.NONE
}
}

ConferenceCallingResult.Disabled.Established -> {
onOpenOngoingCallScreen(conversationListCallViewModel.conversationId)
AnonymousAnalyticsManagerImpl.sendEvent(event = AnalyticsEvent.CallJoined())
AnonymousAnalyticsManagerImpl.sendEvent(event = AnalyticsEvent.CallJoined)
ConversationScreenDialogType.NONE
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,10 +343,10 @@ class SendMessageViewModel @Inject constructor(
) = also {
onSuccess {
val event = when (assetType) {
AttachmentType.IMAGE -> AnalyticsEvent.Contributed.Photo()
AttachmentType.VIDEO -> AnalyticsEvent.Contributed.Video()
AttachmentType.GENERIC_FILE -> AnalyticsEvent.Contributed.File()
AttachmentType.AUDIO -> AnalyticsEvent.Contributed.Audio()
AttachmentType.IMAGE -> AnalyticsEvent.Contributed.Photo
AttachmentType.VIDEO -> AnalyticsEvent.Contributed.Video
AttachmentType.GENERIC_FILE -> AnalyticsEvent.Contributed.File
AttachmentType.AUDIO -> AnalyticsEvent.Contributed.Audio
}
analyticsManager.sendEvent(event)
}
Expand All @@ -360,10 +360,10 @@ class SendMessageViewModel @Inject constructor(
is ComposableMessageBundle.AudioMessageBundle,
is ComposableMessageBundle.AttachmentPickedBundle -> return@also

is ComposableMessageBundle.LocationBundle -> AnalyticsEvent.Contributed.Location()
is Ping -> AnalyticsEvent.Contributed.Ping()
is ComposableMessageBundle.LocationBundle -> AnalyticsEvent.Contributed.Location
is Ping -> AnalyticsEvent.Contributed.Ping
is ComposableMessageBundle.EditMessageBundle,
is ComposableMessageBundle.SendTextMessageBundle -> AnalyticsEvent.Contributed.Text()
is ComposableMessageBundle.SendTextMessageBundle -> AnalyticsEvent.Contributed.Text
}
analyticsManager.sendEvent(event)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ fun ConversationRouterHomeBridge(
}
val onJoinedCall: (ConversationId) -> Unit = remember(navigator) {
{
AnonymousAnalyticsManagerImpl.sendEvent(event = AnalyticsEvent.CallJoined())
AnonymousAnalyticsManagerImpl.sendEvent(event = AnalyticsEvent.CallJoined)
getOngoingCallIntent(activity, it.toString()).run {
activity.startActivity(this)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ class BackupAndRestoreViewModel
is CreateBackupResult.Failure -> {
state = state.copy(backupCreationProgress = BackupCreationProgress.Failed)
appLogger.e("Failed to create backup: ${result.coreFailure}")
AnonymousAnalyticsManagerImpl.sendEvent(event = AnalyticsEvent.BackupExportFailed())
AnonymousAnalyticsManagerImpl.sendEvent(event = AnalyticsEvent.BackupExportFailed)
}
}
}
Expand Down Expand Up @@ -188,7 +188,7 @@ class BackupAndRestoreViewModel
importDatabase(importedBackupPath)
} else {
state = state.copy(restoreFileValidation = RestoreFileValidation.IncompatibleBackup)
AnonymousAnalyticsManagerImpl.sendEvent(event = AnalyticsEvent.BackupRestoreFailed())
AnonymousAnalyticsManagerImpl.sendEvent(event = AnalyticsEvent.BackupRestoreFailed)
}
}
}
Expand All @@ -201,7 +201,7 @@ class BackupAndRestoreViewModel
VerifyBackupResult.Failure.InvalidBackupFile -> "No valid files found in the backup"
}

AnonymousAnalyticsManagerImpl.sendEvent(event = AnalyticsEvent.BackupRestoreFailed())
AnonymousAnalyticsManagerImpl.sendEvent(event = AnalyticsEvent.BackupRestoreFailed)
appLogger.e("Failed to extract backup files: $errorMessage")
}
}
Expand All @@ -217,7 +217,7 @@ class BackupAndRestoreViewModel
updateCreationProgress(PROGRESS_75)
delay(SMALL_DELAY)
state = state.copy(backupRestoreProgress = BackupRestoreProgress.Finished)
AnonymousAnalyticsManagerImpl.sendEvent(event = AnalyticsEvent.BackupRestoreSucceeded())
AnonymousAnalyticsManagerImpl.sendEvent(event = AnalyticsEvent.BackupRestoreSucceeded)
}

is RestoreBackupResult.Failure -> {
Expand All @@ -229,7 +229,7 @@ class BackupAndRestoreViewModel
restoreFileValidation = RestoreFileValidation.IncompatibleBackup,
backupRestoreProgress = BackupRestoreProgress.Failed
)
AnonymousAnalyticsManagerImpl.sendEvent(event = AnalyticsEvent.BackupRestoreFailed())
AnonymousAnalyticsManagerImpl.sendEvent(event = AnalyticsEvent.BackupRestoreFailed)
}
}
}
Expand All @@ -250,17 +250,17 @@ class BackupAndRestoreViewModel
restorePasswordValidation = PasswordValidation.Valid
)
restoreBackupPasswordState.clearText()
AnonymousAnalyticsManagerImpl.sendEvent(event = AnalyticsEvent.BackupRestoreSucceeded())
AnonymousAnalyticsManagerImpl.sendEvent(event = AnalyticsEvent.BackupRestoreSucceeded)
}

is RestoreBackupResult.Failure -> {
mapBackupRestoreFailure(result.failure)
AnonymousAnalyticsManagerImpl.sendEvent(event = AnalyticsEvent.BackupRestoreFailed())
AnonymousAnalyticsManagerImpl.sendEvent(event = AnalyticsEvent.BackupRestoreFailed)
}
}
} else {
state = state.copy(backupRestoreProgress = BackupRestoreProgress.Failed)
AnonymousAnalyticsManagerImpl.sendEvent(event = AnalyticsEvent.BackupRestoreFailed())
AnonymousAnalyticsManagerImpl.sendEvent(event = AnalyticsEvent.BackupRestoreFailed)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import com.wire.android.datastore.GlobalDataStore
import com.wire.android.di.KaliumCoreLogic
import com.wire.android.feature.AppLockSource
import com.wire.android.feature.DisableAppLockUseCase
import com.wire.android.feature.analytics.AnonymousAnalyticsManager
import com.wire.android.feature.analytics.model.AnalyticsEvent
import com.wire.android.ui.analytics.IsAnalyticsAvailableUseCase
import com.wire.android.ui.home.FeatureFlagState
import com.wire.android.ui.home.conversations.selfdeletion.SelfDeletionMapper.toSelfDeletionDuration
Expand Down Expand Up @@ -60,7 +62,8 @@ class FeatureFlagNotificationViewModel @Inject constructor(
private val currentSessionFlow: CurrentSessionFlowUseCase,
private val globalDataStore: GlobalDataStore,
private val disableAppLockUseCase: DisableAppLockUseCase,
private val isAnalyticsAvailable: IsAnalyticsAvailableUseCase
private val isAnalyticsAvailable: IsAnalyticsAvailableUseCase,
private val analyticsManager: AnonymousAnalyticsManager
) : ViewModel() {

var featureFlagState by mutableStateOf(FeatureFlagState())
Expand Down Expand Up @@ -232,7 +235,7 @@ class FeatureFlagNotificationViewModel @Inject constructor(
} else if (shouldAskFeedback) {
showCallFeedbackFlow.emit(Unit)
} else {
// TODO Analytics: send feedback event with "label: not-displayed"
analyticsManager.sendEvent(AnalyticsEvent.CallQualityFeedback.NotDisplayed)
}
}

Expand Down Expand Up @@ -351,11 +354,10 @@ class FeatureFlagNotificationViewModel @Inject constructor(
featureFlagState = featureFlagState.copy(e2EIResult = null)
}

@Suppress("UnusedParameter")
fun rateCall(rate: Int, doNotAsk: Boolean) {
currentUserId?.let {
viewModelScope.launch {
// TODO Analytics: send feedback event
analyticsManager.sendEvent(AnalyticsEvent.CallQualityFeedback.Answered(rate))
coreLogic.getSessionScope(it).calls.updateNextTimeCallFeedback(doNotAsk)
}
}
Expand All @@ -365,7 +367,7 @@ class FeatureFlagNotificationViewModel @Inject constructor(
currentUserId?.let {
viewModelScope.launch {
coreLogic.getSessionScope(it).calls.updateNextTimeCallFeedback(doNotAsk)
// TODO Analytics: send feedback was skipped event
analyticsManager.sendEvent(AnalyticsEvent.CallQualityFeedback.Dismissed)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ import com.wire.android.config.CoroutineTestExtension
import com.wire.android.datastore.GlobalDataStore
import com.wire.android.feature.AppLockSource
import com.wire.android.feature.DisableAppLockUseCase
import com.wire.android.feature.analytics.AnonymousAnalyticsManager
import com.wire.android.feature.analytics.model.AnalyticsEvent
import com.wire.android.feature.analytics.model.AnalyticsEventConstants
import com.wire.android.framework.TestUser
import com.wire.android.ui.analytics.IsAnalyticsAvailableUseCase
import com.wire.android.ui.home.FeatureFlagState
Expand Down Expand Up @@ -298,6 +301,49 @@ class FeatureFlagNotificationViewModelTest {
coVerify(exactly = 1) { arrangement.markNotifyForRevokedCertificateAsNotified() }
}

@Test
fun givenARateCallIsDisplayed_whenSendingScore_thenInvokeEventForScoreWithValue() = runTest {
val (arrangement, viewModel) = Arrangement()
.withCurrentSessionsFlow(flowOf(CurrentSessionResult.Success(AccountInfo.Valid(UserId("value", "domain")))))
.arrange()

viewModel.rateCall(5, false)

coVerify(exactly = 1) {
arrangement.analyticsManager.sendEvent(
match {
it is AnalyticsEvent.CallQualityFeedback.Answered && it.score == 5
}
)
}
coVerify(exactly = 1) {
arrangement.analyticsManager.sendEvent(
match {
it is AnalyticsEvent.CallQualityFeedback && it.label ==
AnalyticsEventConstants.CALLING_QUALITY_REVIEW_LABEL_ANSWERED
}
)
}
}

@Test
fun givenARateCallIsDisplayed_whenDismissingIt_thenInvokeEventForDismiss() = runTest {
val (arrangement, viewModel) = Arrangement()
.withCurrentSessionsFlow(flowOf(CurrentSessionResult.Success(AccountInfo.Valid(UserId("value", "domain")))))
.arrange()

viewModel.skipCallFeedback(false)

coVerify(exactly = 1) {
arrangement.analyticsManager.sendEvent(
match {
it is AnalyticsEvent.CallQualityFeedback && it.label ==
AnalyticsEventConstants.CALLING_QUALITY_REVIEW_LABEL_DISMISSED
}
)
}
}

private inner class Arrangement {

@MockK
Expand Down Expand Up @@ -327,6 +373,9 @@ class FeatureFlagNotificationViewModelTest {
@MockK
lateinit var isAnalyticsAvailable: IsAnalyticsAvailableUseCase

@MockK
lateinit var analyticsManager: AnonymousAnalyticsManager

@MockK
lateinit var markNotifyForRevokedCertificateAsNotified: MarkNotifyForRevokedCertificateAsNotifiedUseCase

Expand All @@ -336,7 +385,8 @@ class FeatureFlagNotificationViewModelTest {
currentSessionFlow = currentSessionFlow,
globalDataStore = globalDataStore,
disableAppLockUseCase = disableAppLockUseCase,
isAnalyticsAvailable = isAnalyticsAvailable
isAnalyticsAvailable = isAnalyticsAvailable,
analyticsManager = analyticsManager
)
}

Expand All @@ -355,6 +405,7 @@ class FeatureFlagNotificationViewModelTest {
coEvery { coreLogic.getSessionScope(any()).calls.observeEndCallDueToDegradationDialog() } returns flowOf()
coEvery { coreLogic.getSessionScope(any()).calls.observeAskCallFeedbackUseCase() } returns flowOf()
coEvery { coreLogic.getSessionScope(any()).observeShouldNotifyForRevokedCertificate() } returns flowOf()
coEvery { coreLogic.getSessionScope(any()).calls.updateNextTimeCallFeedback(any()) } returns Unit
every { coreLogic.getSessionScope(any()).markNotifyForRevokedCertificateAsNotified } returns
markNotifyForRevokedCertificateAsNotified
coEvery { ppLockTeamFeatureConfigObserver() } returns flowOf(null)
Expand Down
Loading

0 comments on commit e5614ee

Please sign in to comment.