diff --git a/apps/wallet/src/components/requests/operations/RemoveUserGroupOperation.vue b/apps/wallet/src/components/requests/operations/RemoveUserGroupOperation.vue index bc72404e8..c2a771b80 100644 --- a/apps/wallet/src/components/requests/operations/RemoveUserGroupOperation.vue +++ b/apps/wallet/src/components/requests/operations/RemoveUserGroupOperation.vue @@ -8,15 +8,24 @@ - +
+ + +
diff --git a/apps/wallet/src/locales/en.locale.ts b/apps/wallet/src/locales/en.locale.ts index f8b25e398..ebd0af182 100644 --- a/apps/wallet/src/locales/en.locale.ts +++ b/apps/wallet/src/locales/en.locale.ts @@ -894,6 +894,14 @@ export default { external_canisters: 'Canisters', approval_rules: 'Approval Rules', }, + user_groups: { + members_removal_warning: + 'Removing this group will affect user {users} who is still a member of it. | Removing this group will affect users {users} who are still members of it.', + members_removal_warning_more: + 'Removing this group will affect users {users} and {count} more who are still members of it.', + members_removal_warning_unknown: + 'Removing this group will also remove it from any users who are still members of it.', + }, pages: { dashboard: { title: 'Dashboard', diff --git a/apps/wallet/src/locales/fr.locale.ts b/apps/wallet/src/locales/fr.locale.ts index dc05ebf91..20aff362c 100644 --- a/apps/wallet/src/locales/fr.locale.ts +++ b/apps/wallet/src/locales/fr.locale.ts @@ -904,6 +904,14 @@ export default { external_canisters: 'Canisters', approval_rules: 'Règles d approbation', }, + user_groups: { + members_removal_warning: + "La suppression de ce groupe affectera l'usager {users} qui en est encore membre. | La suppression de ce groupe affectera les usagers {users} qui en sont encore membres.", + members_removal_warning_more: + 'La suppression de ce groupe affectera les usagers {users} et {count} autre(s) qui en sont encore membres.', + members_removal_warning_unknown: + 'La suppression de ce groupe le retirera également de tous les usagers qui en sont encore membres.', + }, pages: { dashboard: { title: 'Tableau de Bord', diff --git a/apps/wallet/src/locales/pt.locale.ts b/apps/wallet/src/locales/pt.locale.ts index 896d83671..b703c6d49 100644 --- a/apps/wallet/src/locales/pt.locale.ts +++ b/apps/wallet/src/locales/pt.locale.ts @@ -900,6 +900,14 @@ export default { external_canisters: 'Canisters', approval_rules: 'Regras de aprovação', }, + user_groups: { + members_removal_warning: + 'Remover este grupo afetará o usuário {users}, que ainda é membro dele. | Remover este grupo afetará os usuários {users}, que ainda são membros dele.', + members_removal_warning_more: + 'Remover este grupo afetará os usuários {users} e mais {count}, que ainda são membros dele.', + members_removal_warning_unknown: + 'Remover este grupo também o removerá de todos os usuários que ainda são membros dele.', + }, pages: { dashboard: { title: 'Dashboard', diff --git a/apps/wallet/src/pages/UserGroupsPage.vue b/apps/wallet/src/pages/UserGroupsPage.vue index 797dbc536..1ace00cdc 100644 --- a/apps/wallet/src/pages/UserGroupsPage.vue +++ b/apps/wallet/src/pages/UserGroupsPage.vue @@ -76,7 +76,10 @@ :submit="id => station.service.removeUserGroup({ user_group_id: id })" @failed="useOnFailedOperation" @submitted="useOnSuccessfulOperation" - /> + > +

{{ $t('app.dialog_confirmation_question') }}

+ + , user_group_repository: Arc, + user_repository: Arc, } impl UserGroupService { @@ -155,6 +158,21 @@ impl UserGroupService { } } + // Removes the group from all of its members, so no user record is left referencing a + // group that no longer exists. + let members = self.user_repository.find_where(UserWhereClause { + search_term: None, + statuses: None, + groups: Some(vec![user_group.id]), + }); + + for mut user in members { + user.groups.retain(|group_id| *group_id != user_group.id); + user.last_modification_timestamp = next_time(); + + self.user_repository.insert(user.to_key(), user); + } + self.user_group_repository.remove(&user_group.id); Ok(()) @@ -165,8 +183,10 @@ impl UserGroupService { mod test { use crate::{ core::write_system_info, - models::{AddUserGroupOperationInput, SystemInfo}, + models::{user_test_utils::mock_user, AddUserGroupOperationInput, SystemInfo, UserStatus}, + repositories::USER_REPOSITORY, }; + use orbit_essentials::repository::Repository; use super::USER_GROUP_SERVICE; @@ -198,4 +218,49 @@ mod test { assert_eq!(error.code, "CANNOT_DELETE_DISASTER_RECOVERY_COMMITTEE"); } + + #[tokio::test] + async fn test_deleting_user_group_removes_it_from_members() { + let user_group = USER_GROUP_SERVICE + .create(AddUserGroupOperationInput { + name: "Test".to_string(), + }) + .await + .expect("Failed to create user group"); + let other_group = USER_GROUP_SERVICE + .create(AddUserGroupOperationInput { + name: "Other".to_string(), + }) + .await + .expect("Failed to create user group"); + + write_system_info(SystemInfo::default()); + + // Inactive members are cleaned up as well. + let mut active_member = mock_user(); + active_member.groups = vec![user_group.id, other_group.id]; + USER_REPOSITORY.insert(active_member.to_key(), active_member.to_owned()); + + let mut inactive_member = mock_user(); + inactive_member.groups = vec![user_group.id]; + inactive_member.status = UserStatus::Inactive; + USER_REPOSITORY.insert(inactive_member.to_key(), inactive_member.to_owned()); + + USER_GROUP_SERVICE + .remove(&user_group.id) + .await + .expect("Failed to remove user group"); + + let active_member = USER_REPOSITORY.get(&active_member.to_key()).unwrap(); + let inactive_member = USER_REPOSITORY.get(&inactive_member.to_key()).unwrap(); + + assert_eq!(active_member.groups, vec![other_group.id]); + assert!(inactive_member.groups.is_empty()); + + for status in [UserStatus::Active, UserStatus::Inactive] { + assert!(USER_REPOSITORY + .find_by_group_and_status(&user_group.id, &status) + .is_empty()); + } + } }