PM-2053 Update Bulk Status dialog (#8928)

* PM-2053 Update Bulk Status Dialog

* PM-2053 Upadate bulk status dialog

* PM-2053 Updated type issues in Bulk status dialog
This commit is contained in:
KiruthigaManivannan 2024-05-23 19:21:07 +05:30 committed by GitHub
parent dd53a1c5ce
commit 92b70d983d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 125 additions and 181 deletions

View File

@ -1,57 +1,38 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="bulkTitle"> <bit-dialog dialogSize="large" [title]="'bulkConfirmStatus' | i18n">
<div class="modal-dialog modal-dialog-scrollable modal-lg" role="document"> <ng-container bitDialogContent>
<div class="modal-content"> <div class="tw-text-center" *ngIf="loading">
<div class="modal-header"> <i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<h1 class="modal-title" id="bulkTitle"> {{ "loading" | i18n }}
{{ "bulkConfirmStatus" | i18n }}
</h1>
<button
type="button"
class="close"
data-dismiss="modal"
appA11yTitle="{{ 'close' | i18n }}"
>
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="card-body text-center" *ngIf="loading">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
{{ "loading" | i18n }}
</div>
<table class="table table-hover table-list" *ngIf="!loading">
<thead>
<tr>
<th colspan="2">{{ "user" | i18n }}</th>
<th>{{ "status" | i18n }}</th>
</tr>
</thead>
<tr *ngFor="let item of users">
<td width="30">
<bit-avatar
[text]="item.user | userName"
[id]="item.user.id"
size="small"
></bit-avatar>
</td>
<td>
{{ item.user.email }}
<small class="text-muted d-block" *ngIf="item.user.name">{{ item.user.name }}</small>
</td>
<td class="text-danger" *ngIf="item.error">
{{ item.message }}
</td>
<td *ngIf="!item.error">
{{ item.message }}
</td>
</tr>
</table>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{ "close" | i18n }}
</button>
</div>
</div> </div>
</div> <bit-table *ngIf="!loading">
</div> <ng-container header>
<tr>
<th colspan="2" bitCell>{{ "user" | i18n }}</th>
<th bitCell>{{ "status" | i18n }}</th>
</tr>
</ng-container>
<ng-template body>
<tr bitRow *ngFor="let item of users">
<td width="30" bitCell>
<bit-avatar [text]="item.user | userName" [id]="item.user.id" size="small"></bit-avatar>
</td>
<td bitCell>
{{ item.user.email }}
<small class="text-muted d-block" *ngIf="item.user.name">{{ item.user.name }}</small>
</td>
<td class="tw-text-danger" *ngIf="item.error" bitCell>
{{ item.message }}
</td>
<td *ngIf="!item.error" bitCell>
{{ item.message }}
</td>
</tr>
</ng-template>
</bit-table>
</ng-container>
<ng-container bitDialogFooter>
<button bitButton type="button" buttonType="secondary" bitDialogClose>
{{ "close" | i18n }}
</button>
</ng-container>
</bit-dialog>

View File

@ -1,9 +1,19 @@
import { Component } from "@angular/core"; import { DIALOG_DATA, DialogConfig } from "@angular/cdk/dialog";
import { Component, Inject, OnInit } from "@angular/core";
import { OrganizationUserBulkResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses";
import { import {
OrganizationUserStatusType, OrganizationUserStatusType,
ProviderUserStatusType, ProviderUserStatusType,
} from "@bitwarden/common/admin-console/enums"; } from "@bitwarden/common/admin-console/enums";
import { ProviderUserBulkResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user-bulk.response";
import { ProviderUserUserDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user.response";
import { ListResponse } from "@bitwarden/common/models/response/list.response";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { DialogService } from "@bitwarden/components";
import { OrganizationUserView } from "../../../core/views/organization-user.view";
export interface BulkUserDetails { export interface BulkUserDetails {
id: string; id: string;
@ -19,11 +29,63 @@ type BulkStatusEntry = {
message: string; message: string;
}; };
type BulkStatusDialogData = {
users: Array<OrganizationUserView | ProviderUserUserDetailsResponse>;
filteredUsers: Array<OrganizationUserView | ProviderUserUserDetailsResponse>;
request: Promise<ListResponse<OrganizationUserBulkResponse | ProviderUserBulkResponse>>;
successfullMessage: string;
};
@Component({ @Component({
selector: "app-bulk-status", selector: "app-bulk-status",
templateUrl: "bulk-status.component.html", templateUrl: "bulk-status.component.html",
}) })
export class BulkStatusComponent { export class BulkStatusComponent implements OnInit {
users: BulkStatusEntry[]; users: BulkStatusEntry[];
loading = false; loading = false;
constructor(
@Inject(DIALOG_DATA) protected data: BulkStatusDialogData,
private i18nService: I18nService,
private logService: LogService,
) {}
async ngOnInit() {
this.loading = true;
await this.showBulkStatus(this.data);
}
async showBulkStatus(data: BulkStatusDialogData) {
try {
const response = await data.request;
const keyedErrors: any = response.data
.filter((r) => r.error !== "")
.reduce((a, x) => ({ ...a, [x.id]: x.error }), {});
const keyedFilteredUsers: any = data.filteredUsers.reduce(
(a, x) => ({ ...a, [x.id]: x }),
{},
);
this.users = data.users.map((user) => {
let message = keyedErrors[user.id] ?? data.successfullMessage;
// eslint-disable-next-line
if (!keyedFilteredUsers.hasOwnProperty(user.id)) {
message = this.i18nService.t("bulkFilteredMessage");
}
return {
user: user,
error: keyedErrors.hasOwnProperty(user.id), // eslint-disable-line
message: message,
};
});
this.loading = false;
} catch (e) {
this.logService.error(e);
}
}
static open(dialogService: DialogService, config: DialogConfig<BulkStatusDialogData>) {
return dialogService.open(BulkStatusComponent, config);
}
} }

View File

@ -23,10 +23,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction
import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service"; import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service";
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
import { OrganizationUserConfirmRequest } from "@bitwarden/common/admin-console/abstractions/organization-user/requests"; import { OrganizationUserConfirmRequest } from "@bitwarden/common/admin-console/abstractions/organization-user/requests";
import { import { OrganizationUserUserDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses";
OrganizationUserBulkResponse,
OrganizationUserUserDetailsResponse,
} from "@bitwarden/common/admin-console/abstractions/organization-user/responses";
import { PolicyApiServiceAbstraction as PolicyApiService } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { PolicyApiServiceAbstraction as PolicyApiService } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { import {
@ -39,7 +36,6 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request"; import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request";
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction";
import { ProductType } from "@bitwarden/common/enums"; import { ProductType } from "@bitwarden/common/enums";
import { ListResponse } from "@bitwarden/common/models/response/list.response";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@ -538,12 +534,17 @@ export class PeopleComponent extends BasePeopleComponent<OrganizationUserView> {
); );
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
this.showBulkStatus(
users, // Bulk Status component open
filteredUsers, const dialogRef = BulkStatusComponent.open(this.dialogService, {
response, data: {
this.i18nService.t("bulkReinviteMessage"), users: users,
); filteredUsers: filteredUsers,
request: response,
successfullMessage: this.i18nService.t("bulkReinviteMessage"),
},
});
await lastValueFrom(dialogRef.closed);
} catch (e) { } catch (e) {
this.validationService.showError(e); this.validationService.showError(e);
} }
@ -665,59 +666,6 @@ export class PeopleComponent extends BasePeopleComponent<OrganizationUserView> {
return true; return true;
} }
private async showBulkStatus(
users: OrganizationUserView[],
filteredUsers: OrganizationUserView[],
request: Promise<ListResponse<OrganizationUserBulkResponse>>,
successfullMessage: string,
) {
const [modal, childComponent] = await this.modalService.openViewRef(
BulkStatusComponent,
this.bulkStatusModalRef,
(comp) => {
comp.loading = true;
},
);
// Workaround to handle closing the modal shortly after it has been opened
let close = false;
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
modal.onShown.subscribe(() => {
if (close) {
modal.close();
}
});
try {
const response = await request;
if (modal) {
const keyedErrors: any = response.data
.filter((r) => r.error !== "")
.reduce((a, x) => ({ ...a, [x.id]: x.error }), {});
const keyedFilteredUsers: any = filteredUsers.reduce((a, x) => ({ ...a, [x.id]: x }), {});
childComponent.users = users.map((user) => {
let message = keyedErrors[user.id] ?? successfullMessage;
// eslint-disable-next-line
if (!keyedFilteredUsers.hasOwnProperty(user.id)) {
message = this.i18nService.t("bulkFilteredMessage");
}
return {
user: user,
error: keyedErrors.hasOwnProperty(user.id), // eslint-disable-line
message: message,
};
});
childComponent.loading = false;
}
} catch {
close = true;
modal.close();
}
}
private async noMasterPasswordConfirmationDialog(user: OrganizationUserView) { private async noMasterPasswordConfirmationDialog(user: OrganizationUserView) {
return this.dialogService.openSimpleDialog({ return this.dialogService.openSimpleDialog({
title: { title: {

View File

@ -1,5 +1,6 @@
import { Component, ViewChild, ViewContainerRef } from "@angular/core"; import { Component, ViewChild, ViewContainerRef } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { lastValueFrom } from "rxjs";
import { first } from "rxjs/operators"; import { first } from "rxjs/operators";
import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe"; import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe";
@ -12,7 +13,6 @@ import { ProviderService } from "@bitwarden/common/admin-console/abstractions/pr
import { ProviderUserStatusType, ProviderUserType } from "@bitwarden/common/admin-console/enums"; import { ProviderUserStatusType, ProviderUserType } from "@bitwarden/common/admin-console/enums";
import { ProviderUserBulkRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-user-bulk.request"; import { ProviderUserBulkRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-user-bulk.request";
import { ProviderUserConfirmRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-user-confirm.request"; import { ProviderUserConfirmRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-user-confirm.request";
import { ProviderUserBulkResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user-bulk.response";
import { ProviderUserUserDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user.response"; import { ProviderUserUserDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user.response";
import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { ListResponse } from "@bitwarden/common/models/response/list.response";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
@ -222,12 +222,17 @@ export class PeopleComponent extends BasePeopleComponent<ProviderUserUserDetails
const response = this.apiService.postManyProviderUserReinvite(this.providerId, request); const response = this.apiService.postManyProviderUserReinvite(this.providerId, request);
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
this.showBulkStatus(
users, // Bulk Status component open
filteredUsers, const dialogRef = BulkStatusComponent.open(this.dialogService, {
response, data: {
this.i18nService.t("bulkReinviteMessage"), users: users,
); filteredUsers: filteredUsers,
request: response,
successfullMessage: this.i18nService.t("bulkReinviteMessage"),
},
});
await lastValueFrom(dialogRef.closed);
} catch (e) { } catch (e) {
this.validationService.showError(e); this.validationService.showError(e);
} }
@ -251,56 +256,4 @@ export class PeopleComponent extends BasePeopleComponent<ProviderUserUserDetails
await modal.onClosedPromise(); await modal.onClosedPromise();
await this.load(); await this.load();
} }
private async showBulkStatus(
users: ProviderUserUserDetailsResponse[],
filteredUsers: ProviderUserUserDetailsResponse[],
request: Promise<ListResponse<ProviderUserBulkResponse>>,
successfullMessage: string,
) {
const [modal, childComponent] = await this.modalService.openViewRef(
BulkStatusComponent,
this.bulkStatusModalRef,
(comp) => {
comp.loading = true;
},
);
// Workaround to handle closing the modal shortly after it has been opened
let close = false;
modal.onShown.subscribe(() => {
if (close) {
modal.close();
}
});
try {
const response = await request;
if (modal) {
const keyedErrors: any = response.data
.filter((r) => r.error !== "")
.reduce((a, x) => ({ ...a, [x.id]: x.error }), {});
const keyedFilteredUsers: any = filteredUsers.reduce((a, x) => ({ ...a, [x.id]: x }), {});
childComponent.users = users.map((user) => {
let message = keyedErrors[user.id] ?? successfullMessage;
// eslint-disable-next-line
if (!keyedFilteredUsers.hasOwnProperty(user.id)) {
message = this.i18nService.t("bulkFilteredMessage");
}
return {
user: user,
error: keyedErrors.hasOwnProperty(user.id), // eslint-disable-line
message: message,
};
});
childComponent.loading = false;
}
} catch {
close = true;
modal.close();
}
}
} }