1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-12-21 16:18:28 +01:00

[AC-2243] Refactor member modal loading logic (#8087)

* Move loadOrganizationUser into own method

* Simplify logic and use observables more

* Move init logic into ctor instead of ngOnInit
This commit is contained in:
Thomas Rittson 2024-03-12 13:32:21 +10:00 committed by GitHub
parent 2181a6d91a
commit 7ced8d5cc9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 136 additions and 122 deletions

View File

@ -16,7 +16,10 @@
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</ng-container>
<bit-tab-group *ngIf="!loading" [(selectedIndex)]="tabIndex">
<bit-tab-group
*ngIf="!loading && organization$ | async as organization"
[(selectedIndex)]="tabIndex"
>
<bit-tab [label]="'role' | i18n">
<ng-container *ngIf="!editMode">
<p>{{ "inviteUserDesc" | i18n }}</p>
@ -60,7 +63,10 @@
</div>
</label>
</div>
<div *ngIf="!flexibleCollectionsEnabled" class="tw-mb-2 tw-flex tw-items-baseline">
<div
*ngIf="!organization.flexibleCollections"
class="tw-mb-2 tw-flex tw-items-baseline"
>
<input
type="radio"
id="userTypeManager"
@ -116,11 +122,11 @@
formControlName="type"
name="type"
class="tw-relative tw-bottom-[-1px] tw-mr-2"
[attr.disabled]="!canUseCustomPermissions || null"
[attr.disabled]="!organization.useCustomPermissions || null"
/>
<label class="tw-m-0" for="userTypeCustom">
{{ "custom" | i18n }}
<ng-container *ngIf="!canUseCustomPermissions; else enterprise">
<ng-container *ngIf="!organization.useCustomPermissions; else enterprise">
<div class="text-base tw-block tw-font-normal tw-text-muted">
{{ "customDescNonEnterpriseStart" | i18n
}}<a href="https://bitwarden.com/contact/" target="_blank" rel="noreferrer">{{
@ -138,7 +144,7 @@
</div>
</fieldset>
<ng-container *ngIf="customUserTypeSelected">
<ng-container *ngIf="!flexibleCollectionsEnabled; else customPermissionsFC">
<ng-container *ngIf="!organization.flexibleCollections; else customPermissionsFC">
<h3 class="mt-4 d-flex tw-font-semibold">
{{ "permissions" | i18n }}
</h3>
@ -365,7 +371,7 @@
</div>
</ng-template>
</ng-container>
<ng-container *ngIf="canUseSecretsManager">
<ng-container *ngIf="organization.useSecretsManager">
<h3 class="mt-4">
{{ "secretsManager" | i18n }}
<a
@ -401,14 +407,14 @@
[columnHeader]="'groups' | i18n"
[selectorLabelText]="'selectGroups' | i18n"
[emptySelectionText]="'noGroupsAdded' | i18n"
[flexibleCollectionsEnabled]="flexibleCollectionsEnabled"
[flexibleCollectionsEnabled]="organization.flexibleCollections"
></bit-access-selector>
</bit-tab>
<bit-tab [label]="'collections' | i18n">
<div *ngIf="organization.useGroups" class="tw-mb-6">
{{ "userPermissionOverrideHelper" | i18n }}
</div>
<div *ngIf="!flexibleCollectionsEnabled" class="tw-mb-6">
<div *ngIf="!organization.flexibleCollections" class="tw-mb-6">
<bit-form-control>
<input type="checkbox" bitCheckbox formControlName="accessAllCollections" />
<bit-label>
@ -434,7 +440,7 @@
[columnHeader]="'collection' | i18n"
[selectorLabelText]="'selectCollections' | i18n"
[emptySelectionText]="'noCollectionsAdded' | i18n"
[flexibleCollectionsEnabled]="flexibleCollectionsEnabled"
[flexibleCollectionsEnabled]="organization.flexibleCollections"
></bit-access-selector
></bit-tab>
</bit-tab-group>

View File

@ -1,7 +1,16 @@
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
import { Component, Inject, OnDestroy, OnInit } from "@angular/core";
import { Component, Inject, OnDestroy } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { combineLatest, of, shareReplay, Subject, switchMap, takeUntil } from "rxjs";
import {
combineLatest,
firstValueFrom,
Observable,
of,
shareReplay,
Subject,
switchMap,
takeUntil,
} from "rxjs";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
@ -18,7 +27,6 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
import { DialogService } from "@bitwarden/components";
import { flagEnabled } from "../../../../../../utils/flags";
import { CollectionAdminService } from "../../../../../vault/core/collection-admin.service";
import {
CollectionAccessSelectionView,
@ -66,7 +74,7 @@ export enum MemberDialogResult {
@Component({
templateUrl: "member-dialog.component.html",
})
export class MemberDialogComponent implements OnInit, OnDestroy {
export class MemberDialogComponent implements OnDestroy {
loading = true;
editMode = false;
isRevoked = false;
@ -74,12 +82,10 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
access: "all" | "selected" = "selected";
collections: CollectionView[] = [];
organizationUserType = OrganizationUserType;
canUseCustomPermissions: boolean;
PermissionMode = PermissionMode;
canUseSecretsManager: boolean;
showNoMasterPasswordWarning = false;
protected organization: Organization;
protected organization$: Observable<Organization>;
protected collectionAccessItems: AccessItemView[] = [];
protected groupAccessItems: AccessItemView[] = [];
protected tabIndex: MemberDialogTab;
@ -130,7 +136,6 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
private dialogRef: DialogRef<MemberDialogResult>,
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private organizationService: OrganizationService,
private formBuilder: FormBuilder,
// TODO: We should really look into consolidating naming conventions for these services
private collectionAdminService: CollectionAdminService,
@ -139,28 +144,26 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
private organizationUserService: OrganizationUserService,
private dialogService: DialogService,
private configService: ConfigServiceAbstraction,
) {}
organizationService: OrganizationService,
) {
this.organization$ = organizationService
.get$(this.params.organizationId)
.pipe(shareReplay({ refCount: true, bufferSize: 1 }));
async ngOnInit() {
this.editMode = this.params.organizationUserId != null;
this.tabIndex = this.params.initialTab ?? MemberDialogTab.Role;
this.title = this.i18nService.t(this.editMode ? "editMember" : "inviteMember");
const organization$ = of(this.organizationService.get(this.params.organizationId)).pipe(
shareReplay({ refCount: true, bufferSize: 1 }),
);
const groups$ = organization$.pipe(
switchMap((organization) => {
if (!organization.useGroups) {
return of([] as GroupView[]);
}
return this.groupService.getAll(this.params.organizationId);
}),
const groups$ = this.organization$.pipe(
switchMap((organization) =>
organization.useGroups
? this.groupService.getAll(this.params.organizationId)
: of([] as GroupView[]),
),
);
combineLatest({
organization: organization$,
organization: this.organization$,
collections: this.collectionAdminService.getAll(this.params.organizationId),
userDetails: this.params.organizationUserId
? this.userService.get(this.params.organizationId, this.params.organizationUserId)
@ -169,23 +172,7 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
})
.pipe(takeUntil(this.destroy$))
.subscribe(({ organization, collections, userDetails, groups }) => {
this.organization = organization;
this.canUseCustomPermissions = organization.useCustomPermissions;
this.canUseSecretsManager = organization.useSecretsManager && flagEnabled("secretsManager");
const emailsControlValidators = [
Validators.required,
commaSeparatedEmails,
orgSeatLimitReachedValidator(
this.organization,
this.params.allOrganizationUserEmails,
this.i18nService.t("subscriptionUpgrade", organization.seats),
),
];
const emailsControl = this.formGroup.get("emails");
emailsControl.setValidators(emailsControlValidators);
emailsControl.updateValueAndValidity();
this.setFormValidators(organization);
this.collectionAccessItems = [].concat(
collections.map((c) => mapCollectionToAccessItemView(c)),
@ -196,6 +183,34 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
);
if (this.params.organizationUserId) {
this.loadOrganizationUser(userDetails, groups, collections);
}
this.loading = false;
});
}
private setFormValidators(organization: Organization) {
const emailsControlValidators = [
Validators.required,
commaSeparatedEmails,
orgSeatLimitReachedValidator(
organization,
this.params.allOrganizationUserEmails,
this.i18nService.t("subscriptionUpgrade", organization.seats),
),
];
const emailsControl = this.formGroup.get("emails");
emailsControl.setValidators(emailsControlValidators);
emailsControl.updateValueAndValidity();
}
private loadOrganizationUser(
userDetails: OrganizationUserAdminView,
groups: GroupView[],
collections: CollectionView[],
) {
if (!userDetails) {
throw new Error("Could not find user to edit.");
}
@ -263,10 +278,6 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
});
}
this.loading = false;
});
}
check(c: CollectionView, select?: boolean) {
(c as any).checked = select == null ? !(c as any).checked : select;
if (!(c as any).checked) {
@ -335,7 +346,9 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
return;
}
if (!this.canUseCustomPermissions && this.customUserTypeSelected) {
const organization = await firstValueFrom(this.organization$);
if (!organization.useCustomPermissions && this.customUserTypeSelected) {
this.platformUtilsService.showToast(
"error",
null,
@ -363,8 +376,7 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
await this.userService.save(userView);
} else {
userView.id = this.params.organizationUserId;
const maxEmailsCount =
this.organization.planProductType === ProductType.TeamsStarter ? 10 : 20;
const maxEmailsCount = organization.planProductType === ProductType.TeamsStarter ? 10 : 20;
const emails = [...new Set(this.formGroup.value.emails.trim().split(/\s*,\s*/))];
if (emails.length > maxEmailsCount) {
this.formGroup.controls.emails.setErrors({
@ -373,8 +385,8 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
return;
}
if (
this.organization.hasReseller &&
this.params.numConfirmedMembers + emails.length > this.organization.seats
organization.hasReseller &&
this.params.numConfirmedMembers + emails.length > organization.seats
) {
this.formGroup.controls.emails.setErrors({
tooManyEmails: { message: this.i18nService.t("seatLimitReachedContactYourProvider") },
@ -515,10 +527,6 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
});
}
protected get flexibleCollectionsEnabled() {
return this.organization?.flexibleCollections;
}
protected readonly ProductType = ProductType;
}