PM-29843: Record item org migration events (#6275)

This commit is contained in:
Patrick Honkonen 2025-12-29 09:18:10 -05:00 committed by GitHub
parent 0975144342
commit 7cd0e2c176
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 44 additions and 2 deletions

View File

@ -11,6 +11,8 @@ import com.bitwarden.ui.util.Text
import com.bitwarden.ui.util.asText
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.auth.repository.model.LeaveOrganizationResult
import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager
import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent
import com.x8bit.bitwarden.ui.platform.model.SnackbarRelay
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.launchIn
@ -29,6 +31,7 @@ private const val KEY_STATE = "state"
class LeaveOrganizationViewModel @Inject constructor(
private val authRepository: AuthRepository,
private val snackbarRelayManager: SnackbarRelayManager<SnackbarRelay>,
private val organizationEventManager: OrganizationEventManager,
savedStateHandle: SavedStateHandle,
) : BaseViewModel<LeaveOrganizationState, LeaveOrganizationEvent, LeaveOrganizationAction>(
initialState = savedStateHandle[KEY_STATE] ?: run {
@ -94,6 +97,9 @@ class LeaveOrganizationViewModel @Inject constructor(
) {
when (val result = action.result) {
is LeaveOrganizationResult.Success -> {
organizationEventManager.trackEvent(
event = OrganizationEvent.ItemOrganizationDeclined,
)
mutableStateFlow.update {
it.copy(dialogState = null)
}

View File

@ -7,6 +7,8 @@ import com.bitwarden.ui.platform.base.BaseViewModel
import com.bitwarden.ui.platform.resource.BitwardenString
import com.bitwarden.ui.util.Text
import com.bitwarden.ui.util.asText
import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager
import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.launchIn
@ -23,6 +25,7 @@ private const val KEY_STATE = "state"
*/
@HiltViewModel
class MigrateToMyItemsViewModel @Inject constructor(
private val organizationEventManager: OrganizationEventManager,
savedStateHandle: SavedStateHandle,
) : BaseViewModel<MigrateToMyItemsState, MigrateToMyItemsEvent, MigrateToMyItemsAction>(
initialState = savedStateHandle[KEY_STATE] ?: run {
@ -101,6 +104,9 @@ class MigrateToMyItemsViewModel @Inject constructor(
action: MigrateToMyItemsAction.Internal.MigrateToMyItemsResultReceived,
) {
if (action.success) {
organizationEventManager.trackEvent(
event = OrganizationEvent.ItemOrganizationAccepted,
)
clearDialog()
sendEvent(MigrateToMyItemsEvent.NavigateToVault)
} else {

View File

@ -15,7 +15,9 @@ import com.x8bit.bitwarden.data.auth.repository.model.LeaveOrganizationResult
import com.x8bit.bitwarden.data.auth.repository.model.Organization
import com.x8bit.bitwarden.data.auth.repository.model.UserState
import com.x8bit.bitwarden.data.auth.repository.model.VaultUnlockType
import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager
import com.x8bit.bitwarden.data.platform.manager.model.FirstTimeState
import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent
import com.x8bit.bitwarden.ui.platform.model.SnackbarRelay
import io.mockk.coEvery
import io.mockk.every
@ -43,6 +45,10 @@ class LeaveOrganizationViewModelTest : BaseViewModelTest() {
every { sendSnackbarData(data = any(), relay = any()) } just runs
}
private val mockOrganizationEventManager: OrganizationEventManager = mockk {
every { trackEvent(any()) } just runs
}
@BeforeEach
fun setup() {
mockkStatic(SavedStateHandle::toLeaveOrganizationArgs)
@ -108,8 +114,9 @@ class LeaveOrganizationViewModelTest : BaseViewModelTest() {
}
}
@Suppress("MaxLineLength")
@Test
fun `LeaveOrganizationClick with Success should send snackbar and navigate to vault`() =
fun `LeaveOrganizationClick with Success should track ItemOrganizationDeclined event, send snackbar, and navigate to vault`() =
runTest {
coEvery {
mockAuthRepository.leaveOrganization(ORGANIZATION_ID)
@ -128,6 +135,9 @@ class LeaveOrganizationViewModelTest : BaseViewModelTest() {
message = BitwardenString.you_left_the_organization.asText(),
),
)
mockOrganizationEventManager.trackEvent(
event = OrganizationEvent.ItemOrganizationDeclined,
)
}
}
@ -176,6 +186,7 @@ class LeaveOrganizationViewModelTest : BaseViewModelTest() {
val viewModel = LeaveOrganizationViewModel(
authRepository = mockAuthRepository,
snackbarRelayManager = mockSnackbarRelayManager,
organizationEventManager = mockOrganizationEventManager,
savedStateHandle = savedStateHandle,
)
@ -192,6 +203,7 @@ class LeaveOrganizationViewModelTest : BaseViewModelTest() {
return LeaveOrganizationViewModel(
authRepository = mockAuthRepository,
snackbarRelayManager = mockSnackbarRelayManager,
organizationEventManager = mockOrganizationEventManager,
savedStateHandle = savedStateHandle,
)
}

View File

@ -5,9 +5,15 @@ import app.cash.turbine.test
import com.bitwarden.ui.platform.base.BaseViewModelTest
import com.bitwarden.ui.platform.resource.BitwardenString
import com.bitwarden.ui.util.asText
import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager
import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.runs
import io.mockk.unmockkStatic
import io.mockk.verify
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertEquals
@ -17,6 +23,10 @@ import org.junit.jupiter.api.Test
class MigrateToMyItemsViewModelTest : BaseViewModelTest() {
private val mockOrganizationEventManager: OrganizationEventManager = mockk {
every { trackEvent(any()) } just runs
}
@BeforeEach
fun setup() {
mockkStatic(SavedStateHandle::toMigrateToMyItemsArgs)
@ -60,8 +70,9 @@ class MigrateToMyItemsViewModelTest : BaseViewModelTest() {
}
}
@Suppress("MaxLineLength")
@Test
fun `MigrateToMyItemsResultReceived with success should clear dialog and navigate to vault`() =
fun `MigrateToMyItemsResultReceived with success should track ItemOrganizationAccepted event, clear dialog, and navigate to vault`() =
runTest {
val viewModel = createViewModel()
@ -78,6 +89,12 @@ class MigrateToMyItemsViewModelTest : BaseViewModelTest() {
}
assertNull(viewModel.stateFlow.value.dialog)
verify {
mockOrganizationEventManager.trackEvent(
event = OrganizationEvent.ItemOrganizationAccepted,
)
}
}
@Test
@ -158,6 +175,7 @@ class MigrateToMyItemsViewModelTest : BaseViewModelTest() {
organizationName = ORGANIZATION_NAME,
)
return MigrateToMyItemsViewModel(
organizationEventManager = mockOrganizationEventManager,
savedStateHandle = savedStateHandle,
)
}