-
Notifications
You must be signed in to change notification settings - Fork 926
[PM-28468] Updated validation and navigation for MigrateToMyItems #6279
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
[PM-28468] Updated validation and navigation for MigrateToMyItems #6279
Conversation
|
Great job! No new security vulnerabilities introduced in this pull request |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #6279 +/- ##
==========================================
+ Coverage 84.93% 85.42% +0.49%
==========================================
Files 876 776 -100
Lines 56942 54996 -1946
Branches 7900 7874 -26
==========================================
- Hits 48366 46983 -1383
+ Misses 5833 5286 -547
+ Partials 2743 2727 -16 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
Claude finished @aj-rosado's task in 4m 40s —— View job Code Review CompleteI've reviewed PR #6279 with a focus on the validation and navigation logic for the MigrateToMyItems flow. Overall Assessment: REQUEST CHANGESThe implementation follows proper architectural patterns with excellent test coverage, but requires addressing a critical user experience issue before merge. Critical FindingMissing Migration State Persistence ( The
Recommendation: Add persistence tracking before removing draft status: // In SettingsDiskSource or AuthDiskSource
fun storeMigrationAttempted(userId: String, organizationId: String)
fun hasMigrationBeenAttempted(userId: String, organizationId: String): BooleanCheck this in Questions Requiring Clarification
Positive Findings
RecommendationAddress the persistence issue (Finding 1) before removing draft status. The current TODO at |
| cipherListView.any { it.organizationId == null } | ||
| } | ||
|
|
||
| // Updates [shouldMigratePersonalVaultFlow]. |
There was a problem hiding this comment.
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.
| val orgId = policyManager.getPersonalOwnershipPolicyOrganizationId() | ||
| val orgName = authRepository.userStateFlow.value | ||
| ?.activeAccount | ||
| ?.organizations | ||
| ?.firstOrNull { it.id == orgId } | ||
| ?.name |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
| val shouldMigrate = policyManager | ||
| .getActivePolicies(PolicyTypeJson.PERSONAL_OWNERSHIP) | ||
| .any() && | ||
| featureFlagManager.getFeatureFlag(FlagKey.MigrateMyVaultToMyItems) && | ||
| connectionManager.isNetworkConnected && | ||
| cipherList.any { it.organizationId == null } |
There was a problem hiding this comment.
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 }
}
}
}There was a problem hiding this comment.
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.
…arameter to MigrateToMyItems
…ation # Conflicts: # app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/migratetomyitems/MigrateToMyItemsViewModelTest.kt
| * Flow that emits when conditions are met for the user to migrate their personal vault. | ||
| * Updated after each sync to reflect current policy and vault state. | ||
| */ | ||
| val shouldMigratePersonalVaultFlow: StateFlow<VaultMigrationData> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⛏️ since this is no longer a simple Boolean value, can we rename it to something like vaultMigrationDataStateFlow? That would align with the naming of other flows defined above.
| // We need to be sure the data on device is updated | ||
| // before sending the user to the migration screen | ||
| if (userShouldMigrateVault { | ||
| result.successes.any { it.organizationId == null } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is being shadowed by the outer map { } arg.
| result.successes.any { it.organizationId == null } | |
| result.successes.any { cipher -> cipher.organizationId == null } |
| result.successes.any { it.organizationId == null } | ||
| } | ||
| ) { | ||
| sync(forced = true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IDK how I feel about having side-affects in this flow 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not feel totally comfortable with it as well. Any idea of a better approach?
Otherwise we will display the takeover to the user until a sync is forced even if the user does not need to do the migration anymore
| } | ||
|
|
||
| private fun verifyAndUpdateIfUserShouldMigrateVaultToMyItems(cipherList: List<Cipher>) { | ||
| val userId = activeUserId ?: return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we just pass in the userId to avoid any conflicts
| if (orgId != null && orgName != null) { | ||
| VaultMigrationData.MigrationRequired( | ||
| organizationId = orgId, | ||
| organizationName = orgName, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The VaultSyncManager handling the migration feels a little odd to me. It make it responsible for more scope than it maybe should be responsible for.
What would you think of a VaultMigrationManager that consumed the cipher data to handle the migration?
| result.successes.any { it.organizationId == null } | ||
| } | ||
| ) { | ||
| sync(forced = true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓ Is there a specific scenario or issue you're encountering that requires this additional sync() call before navigating? Asking because this feels out of place. I would expect the disk source data to already be up-to-date here. If not, the next sync (manual or automatic) should trigger migration if it's needed.
If the concern is navigating to migration screens with stale data, could we perform sync or syncForResult in the MigateToMyItemsViewModel immediately before triggering the migration?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes the issues is displaying the Migrate screen when is not necessary.
So we navigate to the screen, sync and check again if the user still needs the migration and dismiss it if he does not? Looks good to me actually, this should be an edge case

🎟️ 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
MigrateToVaultItemsflow.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_OWNERSHIPpolicy is activeorganizationId)This is only in draft, missing all the unit tests and some manual tests
⏰ Reminders before review
🦮 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