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
This commit is contained in:
Chad Scharf 2022-06-20 10:21:50 -04:00 committed by GitHub
parent 98152fee54
commit b28c07790d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 601 additions and 7 deletions

View File

@ -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<any>;
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<ListResponse<UserType>>;
abstract deleteUser(id: string): Promise<any>;
abstract deactivateUser(id: string): Promise<any>;
abstract activateUser(id: string): Promise<any>;
abstract reinviteUser(id: string): Promise<any>;
abstract confirmUser(user: UserType, publicKey: Uint8Array): Promise<any>;
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);
}

View File

@ -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,

View File

@ -0,0 +1,102 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="bulkTitle">
<div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title" id="bulkTitle">
{{ bulkTitle }}
</h2>
<button
type="button"
class="close"
data-dismiss="modal"
appA11yTitle="{{ 'close' | i18n }}"
>
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<app-callout type="danger" *ngIf="users.length <= 0">
{{ "noSelectedUsersApplicable" | i18n }}
</app-callout>
<app-callout type="error" *ngIf="error">
{{ error }}
</app-callout>
<ng-container *ngIf="!done">
<app-callout type="warning" *ngIf="users.length > 0 && !error">
{{ usersWarning }}
</app-callout>
<table class="table table-hover table-list">
<thead>
<tr>
<th colspan="2">{{ "user" | i18n }}</th>
</tr>
</thead>
<tr *ngFor="let user of users">
<td width="30">
<app-avatar
[data]="user | userName"
[email]="user.email"
size="25"
[circle]="true"
[fontSize]="14"
>
</app-avatar>
</td>
<td>
{{ user.email }}
<small class="text-muted d-block" *ngIf="user.name">{{ user.name }}</small>
</td>
</tr>
</table>
</ng-container>
<ng-container *ngIf="done">
<table class="table table-hover table-list">
<thead>
<tr>
<th colspan="2">{{ "user" | i18n }}</th>
<th>{{ "status" | i18n }}</th>
</tr>
</thead>
<tr *ngFor="let user of users">
<td width="30">
<app-avatar
[data]="user | userName"
[email]="user.email"
size="25"
[circle]="true"
[fontSize]="14"
>
</app-avatar>
</td>
<td>
{{ user.email }}
<small class="text-muted d-block" *ngIf="user.name">{{ user.name }}</small>
</td>
<td *ngIf="statuses.has(user.id)">
{{ statuses.get(user.id) }}
</td>
<td *ngIf="!statuses.has(user.id)">
{{ "bulkFilteredMessage" | i18n }}
</td>
</tr>
</table>
</ng-container>
</div>
<div class="modal-footer">
<button
type="submit"
class="btn btn-primary btn-submit"
*ngIf="!done && users.length > 0"
[disabled]="loading"
(click)="submit()"
>
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ bulkTitle }}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{ "close" | i18n }}
</button>
</div>
</div>
</div>
</div>

View File

@ -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<string, string> = 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);
}
}
}

View File

@ -1,6 +1,6 @@
<div class="page-header d-flex">
<div class="page-header">
<h1>{{ "people" | i18n }}</h1>
<div class="ml-auto d-flex">
<div class="mt-2 d-flex">
<div class="btn-group btn-group-sm" role="group">
<button
type="button"
@ -31,6 +31,17 @@
acceptedCount
}}</span>
</button>
<button
type="button"
class="btn btn-outline-secondary"
[ngClass]="{ active: status == userStatusType.Deactivated }"
(click)="filter(userStatusType.Deactivated)"
>
{{ "deactivated" | i18n }}
<span class="badge badge-pill badge-info" *ngIf="deactivatedCount">{{
deactivatedCount
}}</span>
</button>
</div>
<div class="ml-3">
<label class="sr-only" for="search">{{ "search" | i18n }}</label>
@ -68,6 +79,14 @@
<i class="bwi bwi-fw bwi-check" aria-hidden="true"></i>
{{ "confirmSelected" | i18n }}
</button>
<button class="dropdown-item" appStopClick (click)="bulkActivate()">
<i class="bwi bwi-fw bwi-plus-circle" aria-hidden="true"></i>
{{ "activate" | i18n }}
</button>
<button class="dropdown-item" appStopClick (click)="bulkDeactivate()">
<i class="bwi bwi-fw bwi-minus-circle" aria-hidden="true"></i>
{{ "deactivate" | i18n }}
</button>
<button class="dropdown-item text-danger" appStopClick (click)="bulkRemove()">
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
{{ "remove" | i18n }}
@ -143,6 +162,9 @@
<span class="badge badge-warning" *ngIf="u.status === userStatusType.Accepted">{{
"accepted" | i18n
}}</span>
<span class="badge badge-secondary" *ngIf="u.status === userStatusType.Deactivated">{{
"deactivated" | i18n
}}</span>
<small class="text-muted d-block" *ngIf="u.name">{{ u.name }}</small>
</td>
<td>
@ -233,6 +255,26 @@
<i class="bwi bwi-fw bwi-key" aria-hidden="true"></i>
{{ "resetPassword" | i18n }}
</a>
<a
class="dropdown-item"
href="#"
appStopClick
(click)="activate(u)"
*ngIf="u.status === userStatusType.Deactivated"
>
<i class="bwi bwi-fw bwi-plus-circle" aria-hidden="true"></i>
{{ "activate" | i18n }}
</a>
<a
class="dropdown-item"
href="#"
appStopClick
(click)="deactivate(u)"
*ngIf="u.status !== userStatusType.Deactivated"
>
<i class="bwi bwi-fw bwi-minus-circle" aria-hidden="true"></i>
{{ "deactivate" | i18n }}
</a>
<a class="dropdown-item text-danger" href="#" appStopClick (click)="remove(u)">
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
{{ "remove" | i18n }}

View File

@ -29,6 +29,7 @@ import { OrganizationUserUserDetailsResponse } from "@bitwarden/common/models/re
import { BasePeopleComponent } from "../../common/base.people.component";
import { BulkConfirmComponent } from "./bulk/bulk-confirm.component";
import { BulkDeactivateComponent } from "./bulk/bulk-deactivate.component";
import { BulkRemoveComponent } from "./bulk/bulk-remove.component";
import { BulkStatusComponent } from "./bulk/bulk-status.component";
import { EntityEventsComponent } from "./entity-events.component";
@ -166,6 +167,14 @@ export class PeopleComponent
return this.apiService.deleteOrganizationUser(this.organizationId, id);
}
deactivateUser(id: string): Promise<any> {
return this.apiService.deactivateOrganizationUser(this.organizationId, id);
}
activateUser(id: string): Promise<any> {
return this.apiService.activateOrganizationUser(this.organizationId, id);
}
reinviteUser(id: string): Promise<any> {
return this.apiService.postOrganizationUserReinvite(this.organizationId, id);
}
@ -236,6 +245,14 @@ export class PeopleComponent
modal.close();
this.removeUser(user);
});
comp.onDeactivatedUser.subscribe(() => {
modal.close();
this.load();
});
comp.onActivatedUser.subscribe(() => {
modal.close();
this.load();
});
}
);
}
@ -273,6 +290,32 @@ export class PeopleComponent
await this.load();
}
async bulkDeactivate() {
await this.bulkActivateOrDeactivate(true);
}
async bulkActivate() {
await this.bulkActivateOrDeactivate(false);
}
async bulkActivateOrDeactivate(isDeactivating: boolean) {
if (this.actionPromise != null) {
return;
}
const ref = this.modalService.open(BulkDeactivateComponent, {
allowMultipleModals: true,
data: {
organizationId: this.organizationId,
users: this.getCheckedUsers(),
isDeactivating: isDeactivating,
},
});
await ref.onClosedPromise();
await this.load();
}
async bulkReinvite() {
if (this.actionPromise != null) {
return;

View File

@ -11,6 +11,7 @@
<h2 class="modal-title" id="userAddEditTitle">
{{ title }}
<small class="text-muted" *ngIf="name">{{ name }}</small>
<span class="badge badge-dark" *ngIf="isDeactivated">{{ "deactivated" | i18n }}</span>
</h2>
<button
type="button"
@ -378,6 +379,46 @@
{{ "cancel" | i18n }}
</button>
<div class="ml-auto">
<button
type="button"
(click)="activate()"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'activate' | i18n }}"
*ngIf="editMode && isDeactivated"
[disabled]="form.loading"
>
<i
class="bwi bwi-plus-circle bwi-lg bwi-fw"
[hidden]="form.loading"
aria-hidden="true"
></i>
<i
class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw"
[hidden]="!form.loading"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
</button>
<button
type="button"
(click)="deactivate()"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'deactivate' | i18n }}"
*ngIf="editMode && !isDeactivated"
[disabled]="form.loading"
>
<i
class="bwi bwi-minus-circle bwi-lg bwi-fw"
[hidden]="form.loading"
aria-hidden="true"
></i>
<i
class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw"
[hidden]="!form.loading"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
</button>
<button
#deleteBtn
type="button"

View File

@ -5,6 +5,7 @@ import { CollectionService } from "@bitwarden/common/abstractions/collection.ser
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { OrganizationUserStatusType } from "@bitwarden/common/enums/organizationUserStatusType";
import { OrganizationUserType } from "@bitwarden/common/enums/organizationUserType";
import { PermissionsApi } from "@bitwarden/common/models/api/permissionsApi";
import { CollectionData } from "@bitwarden/common/models/data/collectionData";
@ -26,9 +27,12 @@ export class UserAddEditComponent implements OnInit {
@Input() usesKeyConnector = false;
@Output() onSavedUser = new EventEmitter();
@Output() onDeletedUser = new EventEmitter();
@Output() onDeactivatedUser = new EventEmitter();
@Output() onActivatedUser = new EventEmitter();
loading = true;
editMode = false;
isDeactivated = false;
title: string;
emails: string;
type: OrganizationUserType = OrganizationUserType.User;
@ -97,6 +101,7 @@ export class UserAddEditComponent implements OnInit {
);
this.access = user.accessAll ? "all" : "selected";
this.type = user.type;
this.isDeactivated = user.status === OrganizationUserStatusType.Deactivated;
if (user.type === OrganizationUserType.Custom) {
this.permissions = user.permissions;
}
@ -239,4 +244,72 @@ export class UserAddEditComponent implements OnInit {
this.logService.error(e);
}
}
async deactivate() {
if (!this.editMode) {
return;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("deactivateUserConfirmation"),
this.i18nService.t("deactivateUserId", this.name),
this.i18nService.t("deactivate"),
this.i18nService.t("cancel"),
"warning"
);
if (!confirmed) {
return false;
}
try {
this.formPromise = this.apiService.deactivateOrganizationUser(
this.organizationId,
this.organizationUserId
);
await this.formPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("deactivatedUserId", this.name)
);
this.isDeactivated = true;
this.onDeactivatedUser.emit();
} catch (e) {
this.logService.error(e);
}
}
async activate() {
if (!this.editMode) {
return;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("activateUserConfirmation"),
this.i18nService.t("activateUserId", this.name),
this.i18nService.t("activate"),
this.i18nService.t("cancel"),
"warning"
);
if (!confirmed) {
return false;
}
try {
this.formPromise = this.apiService.activateOrganizationUser(
this.organizationId,
this.organizationUserId
);
await this.formPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("activatedUserId", this.name)
);
this.isDeactivated = false;
this.onActivatedUser.emit();
} catch (e) {
this.logService.error(e);
}
}
}

View File

@ -1238,6 +1238,9 @@
"enabled": {
"message": "Enabled"
},
"activate": {
"message": "Activate"
},
"premium": {
"message": "Premium",
"description": "Premium Membership"
@ -1263,6 +1266,9 @@
"disable": {
"message": "Disable"
},
"deactivate": {
"message": "Deactivate"
},
"twoStepLoginProviderEnabled": {
"message": "This two-step login provider is enabled on your account."
},
@ -2226,6 +2232,12 @@
"removeUserConfirmation": {
"message": "Are you sure you want to remove this user?"
},
"deactivateUserConfirmation": {
"message": "The member will no longer have access to the organization, but will still have access to their individual vault."
},
"activateUserConfirmation": {
"message": "Are you sure you want to activate this user, granting them access to the organization?"
},
"removeUserConfirmationKeyConnector": {
"message": "Warning! This user requires Key Connector to manage their encryption. Removing this user from your organization will permanently disable their account. This action cannot be undone. Do you want to proceed?"
},
@ -2571,6 +2583,42 @@
}
}
},
"deactivatedUserId": {
"message": "Deactivated user $ID$.",
"placeholders": {
"id": {
"content": "$1",
"example": "John Smith"
}
}
},
"activatedUserId": {
"message": "Activated user $ID$.",
"placeholders": {
"id": {
"content": "$1",
"example": "John Smith"
}
}
},
"deactivateUserId": {
"message": "Deactivate user $ID$?",
"placeholders": {
"id": {
"content": "$1",
"example": "John Smith"
}
}
},
"activateUserId": {
"message": "Activate user $ID$?",
"placeholders": {
"id": {
"content": "$1",
"example": "John Smith"
}
}
},
"createdAttachmentForItem": {
"message": "Created attachment for item $ID$.",
"placeholders": {
@ -3618,6 +3666,9 @@
"disabled": {
"message": "Disabled"
},
"deactivated": {
"message": "Deactivated"
},
"sendLink": {
"message": "Send link",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
@ -4219,6 +4270,12 @@
"removeUsersWarning": {
"message": "Are you sure you want to remove the following users? The process may take a few seconds to complete and cannot be interrupted or canceled."
},
"deactivateUsersWarning": {
"message": "Are you sure you want to deactivate the following members? They will no longer have access to the organization, but will still have access to their individual vaults. The process may take a few seconds to complete and cannot be interrupted or canceled."
},
"activateUsersWarning": {
"message": "Are you sure you want to activate the following members, granting them access to the organization? The process may take a few seconds to complete and cannot be interrupted or canceled."
},
"theme": {
"message": "Theme"
},
@ -4249,6 +4306,12 @@
"bulkRemovedMessage": {
"message": "Removed successfully"
},
"bulkDeactivatedMessage": {
"message": "Deactivated successfully"
},
"bulkActivatedMessage": {
"message": "Activated successfully"
},
"bulkFilteredMessage": {
"message": "Excluded, not applicable for this action."
},
@ -4258,6 +4321,12 @@
"removeUsers": {
"message": "Remove Users"
},
"deactivateUsers": {
"message": "Deactivate Users"
},
"activateUsers": {
"message": "Activate Users"
},
"error": {
"message": "Error"
},
@ -5097,4 +5166,4 @@
}
}
}
}
}

View File

@ -120,6 +120,16 @@ export class PeopleComponent
return this.apiService.deleteProviderUser(this.providerId, id);
}
deactivateUser(id: string): Promise<any> {
// Not implemented.
return null;
}
activateUser(id: string): Promise<any> {
// Not implemented.
return null;
}
reinviteUser(id: string): Promise<any> {
return this.apiService.postProviderUserReinvite(this.providerId, id);
}

View File

@ -444,6 +444,16 @@ export abstract class ApiService {
organizationId: string,
request: OrganizationUserBulkRequest
) => Promise<ListResponse<OrganizationUserBulkResponse>>;
deactivateOrganizationUser: (organizationId: string, id: string) => Promise<any>;
deactivateManyOrganizationUsers: (
organizationId: string,
request: OrganizationUserBulkRequest
) => Promise<ListResponse<OrganizationUserBulkResponse>>;
activateOrganizationUser: (organizationId: string, id: string) => Promise<any>;
activateManyOrganizationUsers: (
organizationId: string,
request: OrganizationUserBulkRequest
) => Promise<ListResponse<OrganizationUserBulkResponse>>;
getSync: () => Promise<SyncResponse>;
postImportDirectory: (organizationId: string, request: ImportDirectoryRequest) => Promise<any>;

View File

@ -2,4 +2,5 @@ export enum OrganizationUserStatusType {
Invited = 0,
Accepted = 1,
Confirmed = 2,
Deactivated = -1,
}

View File

@ -2,4 +2,5 @@ export enum ProviderUserStatusType {
Invited = 0,
Accepted = 1,
Confirmed = 2,
Deactivated = -1, // Not used, compile-time support only
}

View File

@ -1361,6 +1361,54 @@ export class ApiService implements ApiServiceAbstraction {
return new ListResponse(r, OrganizationUserBulkResponse);
}
deactivateOrganizationUser(organizationId: string, id: string): Promise<any> {
return this.send(
"PUT",
"/organizations/" + organizationId + "/users/" + id + "/deactivate",
null,
true,
false
);
}
async deactivateManyOrganizationUsers(
organizationId: string,
request: OrganizationUserBulkRequest
): Promise<ListResponse<OrganizationUserBulkResponse>> {
const r = await this.send(
"PUT",
"/organizations/" + organizationId + "/users/deactivate",
request,
true,
true
);
return new ListResponse(r, OrganizationUserBulkResponse);
}
activateOrganizationUser(organizationId: string, id: string): Promise<any> {
return this.send(
"PUT",
"/organizations/" + organizationId + "/users/" + id + "/activate",
null,
true,
false
);
}
async activateManyOrganizationUsers(
organizationId: string,
request: OrganizationUserBulkRequest
): Promise<ListResponse<OrganizationUserBulkResponse>> {
const r = await this.send(
"PUT",
"/organizations/" + organizationId + "/users/activate",
request,
true,
true
);
return new ListResponse(r, OrganizationUserBulkResponse);
}
// Plan APIs
async getPlans(): Promise<ListResponse<PlanResponse>> {