From b28c07790ded729df12ec792568501a73aec232d Mon Sep 17 00:00:00 2001 From: Chad Scharf <3904944+cscharf@users.noreply.github.com> Date: Mon, 20 Jun 2022 10:21:50 -0400 Subject: [PATCH] EC-263 - Deactivate/activate in user management (#2893) * SM-48 - Disable/enable in user management * SM-48 - Disabled badge added to edit user * SM-48 - Fix linter issues * SM-48 - Color adjustments to badging * SM-48 - Fix prettier formatting * EC-263 - Rename disable to deactivate * EC-263 - lint errors and cleanup * EC-263 - Fix build and importer errors * EC-263 - import grouping order fix * EC-263 - PR review feedback and cleanup * EC-263 - Fix build error in loose components * EC-263 - Fix build error on formPromise in user edit * EC-263 - Fix a11y bindings and modal handling --- .../src/app/common/base.people.component.ts | 85 ++++++++++++++- .../app/modules/loose-components.module.ts | 3 + .../bulk/bulk-deactivate.component.html | 102 ++++++++++++++++++ .../manage/bulk/bulk-deactivate.component.ts | 74 +++++++++++++ .../manage/people.component.html | 46 +++++++- .../organizations/manage/people.component.ts | 43 ++++++++ .../manage/user-add-edit.component.html | 41 +++++++ .../manage/user-add-edit.component.ts | 73 +++++++++++++ apps/web/src/locales/en/messages.json | 71 +++++++++++- .../app/providers/manage/people.component.ts | 10 ++ libs/common/src/abstractions/api.service.ts | 10 ++ .../src/enums/organizationUserStatusType.ts | 1 + .../src/enums/providerUserStatusType.ts | 1 + libs/common/src/services/api.service.ts | 48 +++++++++ 14 files changed, 601 insertions(+), 7 deletions(-) create mode 100644 apps/web/src/app/organizations/manage/bulk/bulk-deactivate.component.html create mode 100644 apps/web/src/app/organizations/manage/bulk/bulk-deactivate.component.ts diff --git a/apps/web/src/app/common/base.people.component.ts b/apps/web/src/app/common/base.people.component.ts index ac33e5509d..af65fef96b 100644 --- a/apps/web/src/app/common/base.people.component.ts +++ b/apps/web/src/app/common/base.people.component.ts @@ -34,7 +34,7 @@ export abstract class BasePeopleComponent< confirmModalRef: ViewContainerRef; get allCount() { - return this.allUsers != null ? this.allUsers.length : 0; + return this.activeUsers != null ? this.activeUsers.length : 0; } get invitedCount() { @@ -55,11 +55,17 @@ export abstract class BasePeopleComponent< : 0; } + get deactivatedCount() { + return this.statusMap.has(this.userStatusType.Deactivated) + ? this.statusMap.get(this.userStatusType.Deactivated).length + : 0; + } + get showConfirmUsers(): boolean { return ( - this.allUsers != null && + this.activeUsers != null && this.statusMap != null && - this.allUsers.length > 1 && + this.activeUsers.length > 1 && this.confirmedCount > 0 && this.confirmedCount < 3 && this.acceptedCount > 0 @@ -82,6 +88,7 @@ export abstract class BasePeopleComponent< actionPromise: Promise; protected allUsers: UserType[] = []; + protected activeUsers: UserType[] = []; protected didScroll = false; protected pageSize = 100; @@ -105,12 +112,15 @@ export abstract class BasePeopleComponent< abstract edit(user: UserType): void; abstract getUsers(): Promise>; abstract deleteUser(id: string): Promise; + abstract deactivateUser(id: string): Promise; + abstract activateUser(id: string): Promise; abstract reinviteUser(id: string): Promise; abstract confirmUser(user: UserType, publicKey: Uint8Array): Promise; async load() { const response = await this.getUsers(); this.statusMap.clear(); + this.activeUsers = []; for (const status of Utils.iterateEnum(this.userStatusType)) { this.statusMap.set(status, []); } @@ -123,6 +133,9 @@ export abstract class BasePeopleComponent< } else { this.statusMap.get(u.status).push(u); } + if (u.status !== this.userStatusType.Deactivated) { + this.activeUsers.push(u); + } }); this.filter(this.status); this.loading = false; @@ -133,7 +146,7 @@ export abstract class BasePeopleComponent< if (this.status != null) { this.users = this.statusMap.get(this.status); } else { - this.users = this.allUsers; + this.users = this.activeUsers; } // Reset checkbox selecton this.selectAll(false); @@ -219,6 +232,62 @@ export abstract class BasePeopleComponent< this.actionPromise = null; } + async deactivate(user: UserType) { + const confirmed = await this.platformUtilsService.showDialog( + this.deactivateWarningMessage(), + this.i18nService.t("deactivateUserId", this.userNamePipe.transform(user)), + this.i18nService.t("deactivate"), + this.i18nService.t("cancel"), + "warning" + ); + + if (!confirmed) { + return false; + } + + this.actionPromise = this.deactivateUser(user.id); + try { + await this.actionPromise; + this.platformUtilsService.showToast( + "success", + null, + this.i18nService.t("deactivatedUserId", this.userNamePipe.transform(user)) + ); + await this.load(); + } catch (e) { + this.validationService.showError(e); + } + this.actionPromise = null; + } + + async activate(user: UserType) { + const confirmed = await this.platformUtilsService.showDialog( + this.activateWarningMessage(), + this.i18nService.t("activateUserId", this.userNamePipe.transform(user)), + this.i18nService.t("activate"), + this.i18nService.t("cancel"), + "warning" + ); + + if (!confirmed) { + return false; + } + + this.actionPromise = this.activateUser(user.id); + try { + await this.actionPromise; + this.platformUtilsService.showToast( + "success", + null, + this.i18nService.t("activatedUserId", this.userNamePipe.transform(user)) + ); + await this.load(); + } catch (e) { + this.validationService.showError(e); + } + this.actionPromise = null; + } + async reinvite(user: UserType) { if (this.actionPromise != null) { return; @@ -325,6 +394,14 @@ export abstract class BasePeopleComponent< return this.i18nService.t("removeUserConfirmation"); } + protected deactivateWarningMessage(): string { + return this.i18nService.t("deactivateUserConfirmation"); + } + + protected activateWarningMessage(): string { + return this.i18nService.t("activateUserConfirmation"); + } + protected getCheckedUsers() { return this.users.filter((u) => (u as any).checked); } diff --git a/apps/web/src/app/modules/loose-components.module.ts b/apps/web/src/app/modules/loose-components.module.ts index 3fa418f6c5..45bb482036 100644 --- a/apps/web/src/app/modules/loose-components.module.ts +++ b/apps/web/src/app/modules/loose-components.module.ts @@ -30,6 +30,7 @@ import { NavbarComponent } from "../layouts/navbar.component"; import { UserLayoutComponent } from "../layouts/user-layout.component"; import { OrganizationLayoutComponent } from "../organizations/layouts/organization-layout.component"; import { BulkConfirmComponent as OrgBulkConfirmComponent } from "../organizations/manage/bulk/bulk-confirm.component"; +import { BulkDeactivateComponent as OrgBulkDeactivateomponent } from "../organizations/manage/bulk/bulk-deactivate.component"; import { BulkRemoveComponent as OrgBulkRemoveComponent } from "../organizations/manage/bulk/bulk-remove.component"; import { BulkStatusComponent as OrgBulkStatusComponent } from "../organizations/manage/bulk/bulk-status.component"; import { CollectionAddEditComponent as OrgCollectionAddEditComponent } from "../organizations/manage/collection-add-edit.component"; @@ -236,6 +237,7 @@ import { OrganizationBadgeModule } from "./vault/modules/organization-badge/orga OrganizationSubscriptionComponent, OrgAttachmentsComponent, OrgBulkConfirmComponent, + OrgBulkDeactivateomponent, OrgBulkRemoveComponent, OrgBulkStatusComponent, OrgCiphersComponent, @@ -395,6 +397,7 @@ import { OrganizationBadgeModule } from "./vault/modules/organization-badge/orga OrganizationSubscriptionComponent, OrgAttachmentsComponent, OrgBulkConfirmComponent, + OrgBulkDeactivateomponent, OrgBulkRemoveComponent, OrgBulkStatusComponent, OrgCiphersComponent, diff --git a/apps/web/src/app/organizations/manage/bulk/bulk-deactivate.component.html b/apps/web/src/app/organizations/manage/bulk/bulk-deactivate.component.html new file mode 100644 index 0000000000..b8bebcaa6f --- /dev/null +++ b/apps/web/src/app/organizations/manage/bulk/bulk-deactivate.component.html @@ -0,0 +1,102 @@ + diff --git a/apps/web/src/app/organizations/manage/bulk/bulk-deactivate.component.ts b/apps/web/src/app/organizations/manage/bulk/bulk-deactivate.component.ts new file mode 100644 index 0000000000..a6c63765cc --- /dev/null +++ b/apps/web/src/app/organizations/manage/bulk/bulk-deactivate.component.ts @@ -0,0 +1,74 @@ +import { Component } from "@angular/core"; + +import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref"; +import { ModalConfig } from "@bitwarden/angular/services/modal.service"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; +import { OrganizationUserBulkRequest } from "@bitwarden/common/models/request/organizationUserBulkRequest"; + +import { BulkUserDetails } from "./bulk-status.component"; + +@Component({ + selector: "app-bulk-deactivate", + templateUrl: "bulk-deactivate.component.html", +}) +export class BulkDeactivateComponent { + isDeactivating: boolean; + organizationId: string; + users: BulkUserDetails[]; + + statuses: Map = new Map(); + + loading = false; + done = false; + error: string; + + constructor( + protected apiService: ApiService, + protected i18nService: I18nService, + private modalRef: ModalRef, + config: ModalConfig + ) { + this.isDeactivating = config.data.isDeactivating; + this.organizationId = config.data.organizationId; + this.users = config.data.users; + } + + get bulkTitle() { + const titleKey = this.isDeactivating ? "deactivateUsers" : "activateUsers"; + return this.i18nService.t(titleKey); + } + + get usersWarning() { + const warningKey = this.isDeactivating ? "deactivateUsersWarning" : "activateUsersWarning"; + return this.i18nService.t(warningKey); + } + + async submit() { + this.loading = true; + try { + const response = await this.performBulkUserAction(); + + const bulkMessage = this.isDeactivating ? "bulkDeactivatedMessage" : "bulkActivatedMessage"; + response.data.forEach((entry) => { + const error = entry.error !== "" ? entry.error : this.i18nService.t(bulkMessage); + this.statuses.set(entry.id, error); + }); + this.done = true; + } catch (e) { + this.error = e.message; + } + + this.loading = false; + this.modalRef.close(); + } + + protected async performBulkUserAction() { + const request = new OrganizationUserBulkRequest(this.users.map((user) => user.id)); + if (this.isDeactivating) { + return await this.apiService.deactivateManyOrganizationUsers(this.organizationId, request); + } else { + return await this.apiService.activateManyOrganizationUsers(this.organizationId, request); + } + } +} diff --git a/apps/web/src/app/organizations/manage/people.component.html b/apps/web/src/app/organizations/manage/people.component.html index 49be829e5f..902dd85d6e 100644 --- a/apps/web/src/app/organizations/manage/people.component.html +++ b/apps/web/src/app/organizations/manage/people.component.html @@ -1,6 +1,6 @@ -