mirror of
https://github.com/bitwarden/browser.git
synced 2024-12-22 16:29:09 +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:
parent
2181a6d91a
commit
7ced8d5cc9
@ -16,7 +16,10 @@
|
|||||||
></i>
|
></i>
|
||||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||||
</ng-container>
|
</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">
|
<bit-tab [label]="'role' | i18n">
|
||||||
<ng-container *ngIf="!editMode">
|
<ng-container *ngIf="!editMode">
|
||||||
<p>{{ "inviteUserDesc" | i18n }}</p>
|
<p>{{ "inviteUserDesc" | i18n }}</p>
|
||||||
@ -60,7 +63,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</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
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
id="userTypeManager"
|
id="userTypeManager"
|
||||||
@ -116,11 +122,11 @@
|
|||||||
formControlName="type"
|
formControlName="type"
|
||||||
name="type"
|
name="type"
|
||||||
class="tw-relative tw-bottom-[-1px] tw-mr-2"
|
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">
|
<label class="tw-m-0" for="userTypeCustom">
|
||||||
{{ "custom" | i18n }}
|
{{ "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">
|
<div class="text-base tw-block tw-font-normal tw-text-muted">
|
||||||
{{ "customDescNonEnterpriseStart" | i18n
|
{{ "customDescNonEnterpriseStart" | i18n
|
||||||
}}<a href="https://bitwarden.com/contact/" target="_blank" rel="noreferrer">{{
|
}}<a href="https://bitwarden.com/contact/" target="_blank" rel="noreferrer">{{
|
||||||
@ -138,7 +144,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<ng-container *ngIf="customUserTypeSelected">
|
<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">
|
<h3 class="mt-4 d-flex tw-font-semibold">
|
||||||
{{ "permissions" | i18n }}
|
{{ "permissions" | i18n }}
|
||||||
</h3>
|
</h3>
|
||||||
@ -365,7 +371,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="canUseSecretsManager">
|
<ng-container *ngIf="organization.useSecretsManager">
|
||||||
<h3 class="mt-4">
|
<h3 class="mt-4">
|
||||||
{{ "secretsManager" | i18n }}
|
{{ "secretsManager" | i18n }}
|
||||||
<a
|
<a
|
||||||
@ -401,14 +407,14 @@
|
|||||||
[columnHeader]="'groups' | i18n"
|
[columnHeader]="'groups' | i18n"
|
||||||
[selectorLabelText]="'selectGroups' | i18n"
|
[selectorLabelText]="'selectGroups' | i18n"
|
||||||
[emptySelectionText]="'noGroupsAdded' | i18n"
|
[emptySelectionText]="'noGroupsAdded' | i18n"
|
||||||
[flexibleCollectionsEnabled]="flexibleCollectionsEnabled"
|
[flexibleCollectionsEnabled]="organization.flexibleCollections"
|
||||||
></bit-access-selector>
|
></bit-access-selector>
|
||||||
</bit-tab>
|
</bit-tab>
|
||||||
<bit-tab [label]="'collections' | i18n">
|
<bit-tab [label]="'collections' | i18n">
|
||||||
<div *ngIf="organization.useGroups" class="tw-mb-6">
|
<div *ngIf="organization.useGroups" class="tw-mb-6">
|
||||||
{{ "userPermissionOverrideHelper" | i18n }}
|
{{ "userPermissionOverrideHelper" | i18n }}
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="!flexibleCollectionsEnabled" class="tw-mb-6">
|
<div *ngIf="!organization.flexibleCollections" class="tw-mb-6">
|
||||||
<bit-form-control>
|
<bit-form-control>
|
||||||
<input type="checkbox" bitCheckbox formControlName="accessAllCollections" />
|
<input type="checkbox" bitCheckbox formControlName="accessAllCollections" />
|
||||||
<bit-label>
|
<bit-label>
|
||||||
@ -434,7 +440,7 @@
|
|||||||
[columnHeader]="'collection' | i18n"
|
[columnHeader]="'collection' | i18n"
|
||||||
[selectorLabelText]="'selectCollections' | i18n"
|
[selectorLabelText]="'selectCollections' | i18n"
|
||||||
[emptySelectionText]="'noCollectionsAdded' | i18n"
|
[emptySelectionText]="'noCollectionsAdded' | i18n"
|
||||||
[flexibleCollectionsEnabled]="flexibleCollectionsEnabled"
|
[flexibleCollectionsEnabled]="organization.flexibleCollections"
|
||||||
></bit-access-selector
|
></bit-access-selector
|
||||||
></bit-tab>
|
></bit-tab>
|
||||||
</bit-tab-group>
|
</bit-tab-group>
|
||||||
|
@ -1,7 +1,16 @@
|
|||||||
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
|
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 { 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 { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
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";
|
||||||
@ -18,7 +27,6 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
|||||||
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
|
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
|
||||||
import { DialogService } from "@bitwarden/components";
|
import { DialogService } from "@bitwarden/components";
|
||||||
|
|
||||||
import { flagEnabled } from "../../../../../../utils/flags";
|
|
||||||
import { CollectionAdminService } from "../../../../../vault/core/collection-admin.service";
|
import { CollectionAdminService } from "../../../../../vault/core/collection-admin.service";
|
||||||
import {
|
import {
|
||||||
CollectionAccessSelectionView,
|
CollectionAccessSelectionView,
|
||||||
@ -66,7 +74,7 @@ export enum MemberDialogResult {
|
|||||||
@Component({
|
@Component({
|
||||||
templateUrl: "member-dialog.component.html",
|
templateUrl: "member-dialog.component.html",
|
||||||
})
|
})
|
||||||
export class MemberDialogComponent implements OnInit, OnDestroy {
|
export class MemberDialogComponent implements OnDestroy {
|
||||||
loading = true;
|
loading = true;
|
||||||
editMode = false;
|
editMode = false;
|
||||||
isRevoked = false;
|
isRevoked = false;
|
||||||
@ -74,12 +82,10 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
|||||||
access: "all" | "selected" = "selected";
|
access: "all" | "selected" = "selected";
|
||||||
collections: CollectionView[] = [];
|
collections: CollectionView[] = [];
|
||||||
organizationUserType = OrganizationUserType;
|
organizationUserType = OrganizationUserType;
|
||||||
canUseCustomPermissions: boolean;
|
|
||||||
PermissionMode = PermissionMode;
|
PermissionMode = PermissionMode;
|
||||||
canUseSecretsManager: boolean;
|
|
||||||
showNoMasterPasswordWarning = false;
|
showNoMasterPasswordWarning = false;
|
||||||
|
|
||||||
protected organization: Organization;
|
protected organization$: Observable<Organization>;
|
||||||
protected collectionAccessItems: AccessItemView[] = [];
|
protected collectionAccessItems: AccessItemView[] = [];
|
||||||
protected groupAccessItems: AccessItemView[] = [];
|
protected groupAccessItems: AccessItemView[] = [];
|
||||||
protected tabIndex: MemberDialogTab;
|
protected tabIndex: MemberDialogTab;
|
||||||
@ -130,7 +136,6 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
|||||||
private dialogRef: DialogRef<MemberDialogResult>,
|
private dialogRef: DialogRef<MemberDialogResult>,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private organizationService: OrganizationService,
|
|
||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
// TODO: We should really look into consolidating naming conventions for these services
|
// TODO: We should really look into consolidating naming conventions for these services
|
||||||
private collectionAdminService: CollectionAdminService,
|
private collectionAdminService: CollectionAdminService,
|
||||||
@ -139,28 +144,26 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
|||||||
private organizationUserService: OrganizationUserService,
|
private organizationUserService: OrganizationUserService,
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
private configService: ConfigServiceAbstraction,
|
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.editMode = this.params.organizationUserId != null;
|
||||||
this.tabIndex = this.params.initialTab ?? MemberDialogTab.Role;
|
this.tabIndex = this.params.initialTab ?? MemberDialogTab.Role;
|
||||||
this.title = this.i18nService.t(this.editMode ? "editMember" : "inviteMember");
|
this.title = this.i18nService.t(this.editMode ? "editMember" : "inviteMember");
|
||||||
|
|
||||||
const organization$ = of(this.organizationService.get(this.params.organizationId)).pipe(
|
const groups$ = this.organization$.pipe(
|
||||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
switchMap((organization) =>
|
||||||
);
|
organization.useGroups
|
||||||
const groups$ = organization$.pipe(
|
? this.groupService.getAll(this.params.organizationId)
|
||||||
switchMap((organization) => {
|
: of([] as GroupView[]),
|
||||||
if (!organization.useGroups) {
|
),
|
||||||
return of([] as GroupView[]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.groupService.getAll(this.params.organizationId);
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
combineLatest({
|
combineLatest({
|
||||||
organization: organization$,
|
organization: this.organization$,
|
||||||
collections: this.collectionAdminService.getAll(this.params.organizationId),
|
collections: this.collectionAdminService.getAll(this.params.organizationId),
|
||||||
userDetails: this.params.organizationUserId
|
userDetails: this.params.organizationUserId
|
||||||
? this.userService.get(this.params.organizationId, 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$))
|
.pipe(takeUntil(this.destroy$))
|
||||||
.subscribe(({ organization, collections, userDetails, groups }) => {
|
.subscribe(({ organization, collections, userDetails, groups }) => {
|
||||||
this.organization = organization;
|
this.setFormValidators(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.collectionAccessItems = [].concat(
|
this.collectionAccessItems = [].concat(
|
||||||
collections.map((c) => mapCollectionToAccessItemView(c)),
|
collections.map((c) => mapCollectionToAccessItemView(c)),
|
||||||
@ -196,6 +183,34 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (this.params.organizationUserId) {
|
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) {
|
if (!userDetails) {
|
||||||
throw new Error("Could not find user to edit.");
|
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) {
|
check(c: CollectionView, select?: boolean) {
|
||||||
(c as any).checked = select == null ? !(c as any).checked : select;
|
(c as any).checked = select == null ? !(c as any).checked : select;
|
||||||
if (!(c as any).checked) {
|
if (!(c as any).checked) {
|
||||||
@ -335,7 +346,9 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.canUseCustomPermissions && this.customUserTypeSelected) {
|
const organization = await firstValueFrom(this.organization$);
|
||||||
|
|
||||||
|
if (!organization.useCustomPermissions && this.customUserTypeSelected) {
|
||||||
this.platformUtilsService.showToast(
|
this.platformUtilsService.showToast(
|
||||||
"error",
|
"error",
|
||||||
null,
|
null,
|
||||||
@ -363,8 +376,7 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
|||||||
await this.userService.save(userView);
|
await this.userService.save(userView);
|
||||||
} else {
|
} else {
|
||||||
userView.id = this.params.organizationUserId;
|
userView.id = this.params.organizationUserId;
|
||||||
const maxEmailsCount =
|
const maxEmailsCount = organization.planProductType === ProductType.TeamsStarter ? 10 : 20;
|
||||||
this.organization.planProductType === ProductType.TeamsStarter ? 10 : 20;
|
|
||||||
const emails = [...new Set(this.formGroup.value.emails.trim().split(/\s*,\s*/))];
|
const emails = [...new Set(this.formGroup.value.emails.trim().split(/\s*,\s*/))];
|
||||||
if (emails.length > maxEmailsCount) {
|
if (emails.length > maxEmailsCount) {
|
||||||
this.formGroup.controls.emails.setErrors({
|
this.formGroup.controls.emails.setErrors({
|
||||||
@ -373,8 +385,8 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
this.organization.hasReseller &&
|
organization.hasReseller &&
|
||||||
this.params.numConfirmedMembers + emails.length > this.organization.seats
|
this.params.numConfirmedMembers + emails.length > organization.seats
|
||||||
) {
|
) {
|
||||||
this.formGroup.controls.emails.setErrors({
|
this.formGroup.controls.emails.setErrors({
|
||||||
tooManyEmails: { message: this.i18nService.t("seatLimitReachedContactYourProvider") },
|
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;
|
protected readonly ProductType = ProductType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user