[SM-1168] Update access policy selector to disable on submit (#8519)

* Add loading and disabled on all inputs

* Add proper spinner and form disable on submit
This commit is contained in:
Thomas Avery 2024-04-29 17:30:18 -05:00 committed by GitHub
parent 20de053770
commit 61d079cc34
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 43 additions and 27 deletions

View File

@ -1,4 +1,4 @@
<form [formGroup]="formGroup" [bitSubmit]="submit"> <form [formGroup]="formGroup" [bitSubmit]="submit" *ngIf="!loading; else spinner">
<div class="tw-w-2/5"> <div class="tw-w-2/5">
<p class="tw-mt-8" *ngIf="!loading"> <p class="tw-mt-8" *ngIf="!loading">
{{ "projectPeopleDescription" | i18n }} {{ "projectPeopleDescription" | i18n }}
@ -19,3 +19,9 @@
</button> </button>
</div> </div>
</form> </form>
<ng-template #spinner>
<div class="tw-items-center tw-justify-center tw-pt-64 tw-text-center">
<i class="bwi bwi-spinner bwi-spin bwi-3x"></i>
</div>
</ng-template>

View File

@ -1,7 +1,7 @@
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from "@angular/core"; import { ChangeDetectorRef, Component, OnDestroy, OnInit } from "@angular/core";
import { FormControl, FormGroup } from "@angular/forms"; import { FormControl, FormGroup } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { combineLatest, Subject, switchMap, takeUntil, catchError, EMPTY } from "rxjs"; import { combineLatest, Subject, switchMap, takeUntil, catchError } from "rxjs";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@ -37,11 +37,9 @@ export class ProjectPeopleComponent implements OnInit, OnDestroy {
return convertToAccessPolicyItemViews(policies); return convertToAccessPolicyItemViews(policies);
}), }),
), ),
catchError(() => { catchError(async () => {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. await this.router.navigate(["/sm", this.organizationId, "projects"]);
// eslint-disable-next-line @typescript-eslint/no-floating-promises return [];
this.router.navigate(["/sm", this.organizationId, "projects"]);
return EMPTY;
}), }),
); );
@ -99,17 +97,20 @@ export class ProjectPeopleComponent implements OnInit, OnDestroy {
if (this.formGroup.invalid) { if (this.formGroup.invalid) {
return; return;
} }
const formValues = this.formGroup.value.accessPolicies;
this.formGroup.disable();
const showAccessRemovalWarning = const showAccessRemovalWarning =
await this.accessPolicySelectorService.showAccessRemovalWarning( await this.accessPolicySelectorService.showAccessRemovalWarning(
this.organizationId, this.organizationId,
this.formGroup.value.accessPolicies, formValues,
); );
if (showAccessRemovalWarning) { if (showAccessRemovalWarning) {
const confirmed = await this.showWarning(); const confirmed = await this.showWarning();
if (!confirmed) { if (!confirmed) {
this.setSelected(this.currentAccessPolicies); this.setSelected(this.currentAccessPolicies);
this.formGroup.enable();
return; return;
} }
} }
@ -117,7 +118,7 @@ export class ProjectPeopleComponent implements OnInit, OnDestroy {
try { try {
const projectPeopleView = convertToProjectPeopleAccessPoliciesView( const projectPeopleView = convertToProjectPeopleAccessPoliciesView(
this.projectId, this.projectId,
this.formGroup.value.accessPolicies, formValues,
); );
const peoplePoliciesViews = await this.accessPolicyService.putProjectPeopleAccessPolicies( const peoplePoliciesViews = await this.accessPolicyService.putProjectPeopleAccessPolicies(
this.projectId, this.projectId,
@ -126,9 +127,7 @@ export class ProjectPeopleComponent implements OnInit, OnDestroy {
this.currentAccessPolicies = convertToAccessPolicyItemViews(peoplePoliciesViews); this.currentAccessPolicies = convertToAccessPolicyItemViews(peoplePoliciesViews);
if (showAccessRemovalWarning) { if (showAccessRemovalWarning) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. await this.router.navigate(["sm", this.organizationId, "projects"]);
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(["sm", this.organizationId, "projects"]);
} }
this.platformUtilsService.showToast( this.platformUtilsService.showToast(
"success", "success",
@ -139,6 +138,7 @@ export class ProjectPeopleComponent implements OnInit, OnDestroy {
this.validationService.showError(e); this.validationService.showError(e);
this.setSelected(this.currentAccessPolicies); this.setSelected(this.currentAccessPolicies);
} }
this.formGroup.enable();
}; };
private setSelected(policiesToSelect: ApItemViewType[]) { private setSelected(policiesToSelect: ApItemViewType[]) {

View File

@ -1,4 +1,4 @@
<form [formGroup]="formGroup" [bitSubmit]="submit"> <form [formGroup]="formGroup" [bitSubmit]="submit" *ngIf="!loading; else spinner">
<div class="tw-w-2/5"> <div class="tw-w-2/5">
<p class="tw-mt-8" *ngIf="!loading"> <p class="tw-mt-8" *ngIf="!loading">
{{ "machineAccountPeopleDescription" | i18n }} {{ "machineAccountPeopleDescription" | i18n }}
@ -20,3 +20,9 @@
</button> </button>
</div> </div>
</form> </form>
<ng-template #spinner>
<div class="tw-items-center tw-justify-center tw-pt-64 tw-text-center">
<i class="bwi bwi-spinner bwi-spin bwi-3x"></i>
</div>
</ng-template>

View File

@ -1,7 +1,7 @@
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from "@angular/core"; import { ChangeDetectorRef, Component, OnDestroy, OnInit } from "@angular/core";
import { FormControl, FormGroup } from "@angular/forms"; import { FormControl, FormGroup } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { catchError, combineLatest, EMPTY, Subject, switchMap, takeUntil } from "rxjs"; import { combineLatest, Subject, switchMap, takeUntil } from "rxjs";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@ -40,12 +40,6 @@ export class ServiceAccountPeopleComponent implements OnInit, OnDestroy {
return convertToAccessPolicyItemViews(policies); return convertToAccessPolicyItemViews(policies);
}), }),
), ),
catchError(() => {
// 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
this.router.navigate(["/sm", this.organizationId, "machine-accounts"]);
return EMPTY;
}),
); );
private potentialGrantees$ = combineLatest([this.route.params]).pipe( private potentialGrantees$ = combineLatest([this.route.params]).pipe(
@ -101,29 +95,32 @@ export class ServiceAccountPeopleComponent implements OnInit, OnDestroy {
if (this.isFormInvalid()) { if (this.isFormInvalid()) {
return; return;
} }
const formValues = this.formGroup.value.accessPolicies;
this.formGroup.disable();
const showAccessRemovalWarning = const showAccessRemovalWarning =
await this.accessPolicySelectorService.showAccessRemovalWarning( await this.accessPolicySelectorService.showAccessRemovalWarning(
this.organizationId, this.organizationId,
this.formGroup.value.accessPolicies, formValues,
); );
if ( if (
await this.handleAccessRemovalWarning(showAccessRemovalWarning, this.currentAccessPolicies) await this.handleAccessRemovalWarning(showAccessRemovalWarning, this.currentAccessPolicies)
) { ) {
this.formGroup.enable();
return; return;
} }
try { try {
const peoplePoliciesViews = await this.updateServiceAccountPeopleAccessPolicies( const peoplePoliciesViews = await this.updateServiceAccountPeopleAccessPolicies(
this.serviceAccountId, this.serviceAccountId,
this.formGroup.value.accessPolicies, formValues,
); );
await this.handleAccessTokenAvailableWarning( await this.handleAccessTokenAvailableWarning(
showAccessRemovalWarning, showAccessRemovalWarning,
this.currentAccessPolicies, this.currentAccessPolicies,
this.formGroup.value.accessPolicies, formValues,
); );
this.currentAccessPolicies = convertToAccessPolicyItemViews(peoplePoliciesViews); this.currentAccessPolicies = convertToAccessPolicyItemViews(peoplePoliciesViews);
@ -137,6 +134,7 @@ export class ServiceAccountPeopleComponent implements OnInit, OnDestroy {
this.validationService.showError(e); this.validationService.showError(e);
this.setSelected(this.currentAccessPolicies); this.setSelected(this.currentAccessPolicies);
} }
this.formGroup.enable();
}; };
private setSelected(policiesToSelect: ApItemViewType[]) { private setSelected(policiesToSelect: ApItemViewType[]) {
@ -198,9 +196,7 @@ export class ServiceAccountPeopleComponent implements OnInit, OnDestroy {
selectedPolicies: ApItemValueType[], selectedPolicies: ApItemValueType[],
): Promise<void> { ): Promise<void> {
if (showAccessRemovalWarning) { if (showAccessRemovalWarning) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. await this.router.navigate(["sm", this.organizationId, "machine-accounts"]);
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(["sm", this.organizationId, "machine-accounts"]);
} else if ( } else if (
this.accessPolicySelectorService.isAccessRemoval(currentAccessPolicies, selectedPolicies) this.accessPolicySelectorService.isAccessRemoval(currentAccessPolicies, selectedPolicies)
) { ) {

View File

@ -55,6 +55,7 @@
bitIconButton="bwi-close" bitIconButton="bwi-close"
buttonType="main" buttonType="main"
size="default" size="default"
[disabled]="disabled"
[attr.title]="'remove' | i18n" [attr.title]="'remove' | i18n"
[attr.aria-label]="'remove' | i18n" [attr.aria-label]="'remove' | i18n"
(click)="selectionList.deselectItem(item.id); handleBlur()" (click)="selectionList.deselectItem(item.id); handleBlur()"
@ -84,7 +85,14 @@
</bit-form-field> </bit-form-field>
<div class="tw-ml-3 tw-mt-7 tw-shrink-0"> <div class="tw-ml-3 tw-mt-7 tw-shrink-0">
<button type="button" bitButton buttonType="secondary" (click)="addButton()"> <button
type="button"
bitButton
buttonType="secondary"
[loading]="loading"
[disabled]="disabled"
(click)="addButton()"
>
{{ "add" | i18n }} {{ "add" | i18n }}
</button> </button>
</div> </div>