Bulk re-invite of org users (#961)
* Add support for bulk re-invite of org users * Add selectAll, resolve review comments
This commit is contained in:
parent
3ac2ce079a
commit
51f3fee75d
2
jslib
2
jslib
|
@ -1 +1 @@
|
||||||
Subproject commit 8244971026ffefb962e235a79c5cb219163bead9
|
Subproject commit 1e2c56cacf975eab4527cb3c1a63cf8136b58bd4
|
|
@ -25,6 +25,27 @@
|
||||||
<input type="search" class="form-control form-control-sm" id="search" placeholder="{{'search' | i18n}}"
|
<input type="search" class="form-control form-control-sm" id="search" placeholder="{{'search' | i18n}}"
|
||||||
[(ngModel)]="searchText">
|
[(ngModel)]="searchText">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="dropdown ml-3" appListDropdown>
|
||||||
|
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button" id="bulkActionsButton"
|
||||||
|
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" appA11yTitle="{{'options' | i18n}}">
|
||||||
|
<i class="fa fa-cog" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="bulkActionsButton">
|
||||||
|
<button class="dropdown-item" appStopClick (click)="bulkReinvite()">
|
||||||
|
<i class="fa fa-fw fa-envelope-o" aria-hidden="true"></i>
|
||||||
|
{{'reinviteSelected' | i18n}}
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<button class="dropdown-item" appStopClick (click)="selectAll(true)">
|
||||||
|
<i class="fa fa-fw fa-check-square-o" aria-hidden="true"></i>
|
||||||
|
{{'selectAll' | i18n}}
|
||||||
|
</button>
|
||||||
|
<button class="dropdown-item" appStopClick (click)="selectAll(false)">
|
||||||
|
<i class="fa fa-fw fa-minus-square-o" aria-hidden="true"></i>
|
||||||
|
{{'unselectAll' | i18n}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<button type="button" class="btn btn-sm btn-outline-primary ml-3" (click)="invite()">
|
<button type="button" class="btn btn-sm btn-outline-primary ml-3" (click)="invite()">
|
||||||
<i class="fa fa-plus fa-fw" aria-hidden="true"></i>
|
<i class="fa fa-plus fa-fw" aria-hidden="true"></i>
|
||||||
{{'inviteUser' | i18n}}
|
{{'inviteUser' | i18n}}
|
||||||
|
@ -46,6 +67,9 @@
|
||||||
[infiniteScrollDisabled]="!isPaging()" (scrolled)="loadMore()">
|
[infiniteScrollDisabled]="!isPaging()" (scrolled)="loadMore()">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let u of searchedUsers">
|
<tr *ngFor="let u of searchedUsers">
|
||||||
|
<td (click)="checkUser(u)" class="table-list-checkbox">
|
||||||
|
<input type="checkbox" [(ngModel)]="u.checked" appStopProp>
|
||||||
|
</td>
|
||||||
<td width="30">
|
<td width="30">
|
||||||
<app-avatar [data]="u.name || u.email" [email]="u.email" size="25" [circle]="true"
|
<app-avatar [data]="u.name || u.email" [email]="u.email" size="25" [circle]="true"
|
||||||
[fontSize]="14"></app-avatar>
|
[fontSize]="14"></app-avatar>
|
||||||
|
|
|
@ -25,6 +25,7 @@ import { UserService } from 'jslib/abstractions/user.service';
|
||||||
|
|
||||||
import { OrganizationUserConfirmRequest } from 'jslib/models/request/organizationUserConfirmRequest';
|
import { OrganizationUserConfirmRequest } from 'jslib/models/request/organizationUserConfirmRequest';
|
||||||
|
|
||||||
|
import { UserBulkReinviteRequest } from 'jslib/models/request/userBulkReinviteRequest';
|
||||||
import { OrganizationUserUserDetailsResponse } from 'jslib/models/response/organizationUserResponse';
|
import { OrganizationUserUserDetailsResponse } from 'jslib/models/response/organizationUserResponse';
|
||||||
|
|
||||||
import { OrganizationUserStatusType } from 'jslib/enums/organizationUserStatusType';
|
import { OrganizationUserStatusType } from 'jslib/enums/organizationUserStatusType';
|
||||||
|
@ -38,6 +39,8 @@ import { UserAddEditComponent } from './user-add-edit.component';
|
||||||
import { UserConfirmComponent } from './user-confirm.component';
|
import { UserConfirmComponent } from './user-confirm.component';
|
||||||
import { UserGroupsComponent } from './user-groups.component';
|
import { UserGroupsComponent } from './user-groups.component';
|
||||||
|
|
||||||
|
const MaxCheckedCount = 500;
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-org-people',
|
selector: 'app-org-people',
|
||||||
templateUrl: 'people.component.html',
|
templateUrl: 'people.component.html',
|
||||||
|
@ -125,6 +128,8 @@ export class PeopleComponent implements OnInit {
|
||||||
} else {
|
} else {
|
||||||
this.users = this.allUsers;
|
this.users = this.allUsers;
|
||||||
}
|
}
|
||||||
|
// Reset checkbox selecton
|
||||||
|
this.selectAll(false);
|
||||||
this.resetPaging();
|
this.resetPaging();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,6 +251,30 @@ export class PeopleComponent implements OnInit {
|
||||||
this.actionPromise = null;
|
this.actionPromise = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async bulkReinvite() {
|
||||||
|
if (this.actionPromise != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const users = this.getCheckedUsers().filter(u => u.status === OrganizationUserStatusType.Invited);
|
||||||
|
|
||||||
|
if (users.length <= 0) {
|
||||||
|
this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'),
|
||||||
|
this.i18nService.t('noSelectedUsersApplicable'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const request = new UserBulkReinviteRequest(users.map(user => user.id));
|
||||||
|
this.actionPromise = this.apiService.postManyOrganizationUserReinvite(this.organizationId, request);
|
||||||
|
try {
|
||||||
|
await this.actionPromise;
|
||||||
|
this.toasterService.popAsync('success', null, this.i18nService.t('usersHasBeenReinvited'));
|
||||||
|
} catch (e) {
|
||||||
|
this.validationService.showError(e);
|
||||||
|
}
|
||||||
|
this.actionPromise = null;
|
||||||
|
}
|
||||||
|
|
||||||
async confirm(user: OrganizationUserUserDetailsResponse) {
|
async confirm(user: OrganizationUserUserDetailsResponse) {
|
||||||
function updateUser(self: PeopleComponent) {
|
function updateUser(self: PeopleComponent) {
|
||||||
user.status = OrganizationUserStatusType.Confirmed;
|
user.status = OrganizationUserStatusType.Confirmed;
|
||||||
|
@ -358,6 +387,22 @@ export class PeopleComponent implements OnInit {
|
||||||
return !searching && this.users && this.users.length > this.pageSize;
|
return !searching && this.users && this.users.length > this.pageSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkUser(user: OrganizationUserUserDetailsResponse, select?: boolean) {
|
||||||
|
(user as any).checked = select == null ? !(user as any).checked : select;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectAll(select: boolean) {
|
||||||
|
if (select) {
|
||||||
|
this.selectAll(false);
|
||||||
|
}
|
||||||
|
const selectCount = select && this.users.length > MaxCheckedCount
|
||||||
|
? MaxCheckedCount
|
||||||
|
: this.users.length;
|
||||||
|
for (let i = 0; i < selectCount; i++) {
|
||||||
|
this.checkUser(this.users[i], select);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async doConfirmation(user: OrganizationUserUserDetailsResponse, publicKey: Uint8Array) {
|
private async doConfirmation(user: OrganizationUserUserDetailsResponse, publicKey: Uint8Array) {
|
||||||
const orgKey = await this.cryptoService.getOrgKey(this.organizationId);
|
const orgKey = await this.cryptoService.getOrgKey(this.organizationId);
|
||||||
const key = await this.cryptoService.rsaEncrypt(orgKey.key, publicKey.buffer);
|
const key = await this.cryptoService.rsaEncrypt(orgKey.key, publicKey.buffer);
|
||||||
|
@ -391,4 +436,8 @@ export class PeopleComponent implements OnInit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getCheckedUsers() {
|
||||||
|
return this.users.filter(u => (u as any).checked);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3890,5 +3890,14 @@
|
||||||
},
|
},
|
||||||
"passwordConfirmationDesc": {
|
"passwordConfirmationDesc": {
|
||||||
"message": "This action is protected. To continue, please re-enter your master password to verify your identity."
|
"message": "This action is protected. To continue, please re-enter your master password to verify your identity."
|
||||||
|
},
|
||||||
|
"reinviteSelected": {
|
||||||
|
"message": "Resend Invitations"
|
||||||
|
},
|
||||||
|
"noSelectedUsersApplicable": {
|
||||||
|
"message": "This action is not applicable to any of the selected users."
|
||||||
|
},
|
||||||
|
"usersHasBeenReinvited": {
|
||||||
|
"message": "The selected users have been reinvited."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue