Skip to content

Conversation

@aj-rosado
Copy link
Contributor

🎟️ Tracking

https://bitwarden.atlassian.net/browse/PM-28468

📔 Objective

Added an observable flow that will be updated after a sync and if the conditions are met will trigger the RootNavViewModel to display the MigrateToVaultItems flow.
Only after a sync in order to ensure we are not displaying the migration screen with stale data.

The conditions for the navigation are

  • VAULT_OWNERSHIP policy is active
  • User has vault items (no organizationId)
  • FeatureFlag is active
  • User hasInternetConnection

This is only in draft, missing all the unit tests and some manual tests

⏰ Reminders before review

  • Contributor guidelines followed
  • All formatters and local linters executed and passed
  • Written new unit and / or integration tests where applicable
  • Protected functional changes with optionality (feature flags)
  • Used internationalization (i18n) for all UI strings
  • CI builds passed
  • Communicated to DevOps any deployment requirements
  • Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team

🦮 Reviewer guidelines

  • 👍 (:+1:) or similar for great changes
  • 📝 (:memo:) or ℹ️ (:information_source:) for notes or general info
  • ❓ (:question:) for questions
  • 🤔 (:thinking:) or 💭 (:thought_balloon:) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion
  • 🎨 (:art:) for suggestions / improvements
  • ❌ (:x:) or ⚠️ (:warning:) for more significant problems or concerns needing attention
  • 🌱 (:seedling:) or ♻️ (:recycle:) for future improvements or indications of technical debt
  • ⛏ (:pick:) for minor or nitpick changes

@github-actions
Copy link
Contributor

github-actions bot commented Dec 17, 2025

Logo
Checkmarx One – Scan Summary & Details25ea982a-4957-4b06-b9bf-a9c163f99db0

Great job! No new security vulnerabilities introduced in this pull request

@codecov
Copy link

codecov bot commented Dec 18, 2025

Codecov Report

❌ Patch coverage is 72.41379% with 16 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.49%. Comparing base (7c929c3) to head (ff3693a).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
...twarden/data/vault/manager/VaultSyncManagerImpl.kt 73.91% 4 Missing and 2 partials ⚠️
...arden/ui/platform/feature/rootnav/RootNavScreen.kt 0.00% 2 Missing and 1 partial ⚠️
...en/ui/platform/feature/rootnav/RootNavViewModel.kt 85.71% 2 Missing and 1 partial ⚠️
...ture/migratetomyitems/MigrateToMyItemsViewModel.kt 70.00% 0 Missing and 3 partials ⚠️
...feature/migratetomyitems/MigrateToMyItemsScreen.kt 0.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6279      +/-   ##
==========================================
- Coverage   85.53%   85.49%   -0.05%     
==========================================
  Files         761      761              
  Lines       54522    54569      +47     
  Branches     7833     7856      +23     
==========================================
+ Hits        46636    46653      +17     
- Misses       5169     5192      +23     
- Partials     2717     2724       +7     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@aj-rosado aj-rosado marked this pull request as ready for review December 18, 2025 12:26
@claude
Copy link
Contributor

claude bot commented Dec 18, 2025

Claude finished @aj-rosado's task in 4m 40s —— View job


Code Review Complete

I've reviewed PR #6279 with a focus on the validation and navigation logic for the MigrateToMyItems flow.


Overall Assessment: REQUEST CHANGES

The implementation follows proper architectural patterns with excellent test coverage, but requires addressing a critical user experience issue before merge.

Critical Finding

Missing Migration State Persistence (VaultSyncManagerImpl.kt:565-575)

The shouldMigratePersonalVaultFlow will emit true on every sync when conditions are met, but there's no mechanism to track whether the user has already seen or dismissed the migration screen. This creates a poor UX where:

  1. User syncs → migration screen shows
  2. User dismisses or completes migration
  3. User syncs again → migration screen shows again (endless loop)

Recommendation: Add persistence tracking before removing draft status:

// In SettingsDiskSource or AuthDiskSource
fun storeMigrationAttempted(userId: String, organizationId: String)
fun hasMigrationBeenAttempted(userId: String, organizationId: String): Boolean

Check this in verifyAndUpdateIfUserShouldMigrateVaultToMyItems to prevent repeated prompts.


Questions Requiring Clarification

  1. Forced Sync Behavior (VaultSyncManagerImpl.kt:432-434): Why trigger a forced sync when decrypting ciphers? Could this cause unnecessary sync loops? The comment mentions ensuring fresh data, but the sync flow already validates at line 360.

  2. Navigation Priority (RootNavViewModel.kt:133-136): Migration intercepts vault unlocked state - is blocking vault access until migration is addressed the intended behavior?

  3. Logic Duplication (VaultSyncManagerImpl.kt:553-575): Two methods contain nearly identical validation logic differing only in input type. Consider extracting shared policy/flag/network checks.


Positive Findings

  • ✅ Proper MVVM + UDF patterns with StateFlow
  • ✅ Correct Hilt dependency injection throughout
  • ✅ Comprehensive test coverage (261 lines added)
  • ✅ Well-documented public APIs with KDoc
  • ✅ Clean architecture with appropriate module organization
  • ✅ No security concerns identified

Recommendation

Address the persistence issue (Finding 1) before removing draft status. The current TODO at MigrateToMyItemsViewModel.kt:77-84 combined with the missing state tracking will result in poor UX when the migration logic is fully implemented.


cipherListView.any { it.organizationId == null }
}

// Updates [shouldMigratePersonalVaultFlow].
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⛏️ Don't really need this comment. The function name is descriptive enough.

Comment on lines 34 to 39
val orgId = policyManager.getPersonalOwnershipPolicyOrganizationId()
val orgName = authRepository.userStateFlow.value
?.activeAccount
?.organizations
?.firstOrNull { it.id == orgId }
?.name
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Passing args makes for a simpler ViewModel; specifically in regards to handling scenarios when either orgId or orgName are null. Is there a reason why this approach is taken instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have changed this quite a bit from original idea. RootNavViewModel only verifies if it should navigate from the shouldMigratePersonalVaultFlow, not having any info regarding the org that should do the migration.

This was changed because if the vault was updated on a different client, the migration flow would still be displayed as it would not check with the most synced data. Now we are syncing before displaying the screen to make sure we are not displaying the screen unless it is needed.

This being said, I agree that if we could pass the parameters would simplify the VM, not sure about a good way to achieve it, only that I can think about is the flow having the Organization object?

Comment on lines 565 to 570
val shouldMigrate = policyManager
.getActivePolicies(PolicyTypeJson.PERSONAL_OWNERSHIP)
.any() &&
featureFlagManager.getFeatureFlag(FlagKey.MigrateMyVaultToMyItems) &&
connectionManager.isNetworkConnected &&
cipherList.any { it.organizationId == null }
Copy link
Contributor

@SaintPatrck SaintPatrck Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⛏️ 🎨 Not a mandatory change request, but some of this duplication could be eliminated if userShouldMigrate() is changed to accept hasPersonalItems as a lambda.

private fun userShouldMigrateVault(
    hasPersonalItems: () -> Boolean,
) : Boolean {
    return connectionManager.isNetworkConnected &&
        featureFlagManager.getFeatureFlag(...) &&
        policyManager.getActivePolicies(...).any() &&
        hasPersonalItems
}

private fun verifyAndUpdateIfUserShouldMigrateVaultToMyItems(
    cipherList: List<Cipher>,
) {
    mutableShouldMIgratePersonalVaultFlow.update {
        userShouldMigrateVault { 
            cipherList.any { it.organizationId == null } 
        }
    }
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would also be a performance improvement since it would short-circuit before attempting to iterate over the entire cipher collection when any of the preceding conditions are false.

…ation

# Conflicts:
#	app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/migratetomyitems/MigrateToMyItemsViewModelTest.kt
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants