1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-09-28 04:08:47 +02:00

[EC-859] update billing routes for owners of Managed orgs (#4611)

* [EC-859] update billing routes for owners of Managed orgs

* [EC-859] fix observable in billing tab

* [EC-859] update observable name

* [EC-859] update reporting and settings observables

* [EC-859] add startsWith to reporting observable

* [EC-859] async pipe once in settings

* [EC-859] create get$ in org service

* [EC-859] transition remaining components

* [EC-859] add as org to template

* [EC-859] add shareReplay to observable to prevent multicasting
- future proof get$ on org service

* [AC-859] fix missed org
This commit is contained in:
Jake Fink 2023-02-27 16:31:55 -05:00 committed by GitHub
parent ff89d86d40
commit c160827272
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 66 additions and 63 deletions

View File

@ -2,6 +2,7 @@ import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router"; import { RouterModule, Routes } from "@angular/router";
import { canAccessBillingTab } from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; import { canAccessBillingTab } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/models/domain/organization";
import { WebPlatformUtilsService } from "../../core/web-platform-utils.service"; import { WebPlatformUtilsService } from "../../core/web-platform-utils.service";
import { PaymentMethodComponent } from "../../settings/payment-method.component"; import { PaymentMethodComponent } from "../../settings/payment-method.component";
@ -30,15 +31,19 @@ const routes: Routes = [
{ {
path: "payment-method", path: "payment-method",
component: PaymentMethodComponent, component: PaymentMethodComponent,
canActivate: [OrganizationPermissionsGuard],
data: { data: {
titleId: "paymentMethod", titleId: "paymentMethod",
organizationPermissions: (org: Organization) => org.canManageBilling,
}, },
}, },
{ {
path: "history", path: "history",
component: OrgBillingHistoryViewComponent, component: OrgBillingHistoryViewComponent,
canActivate: [OrganizationPermissionsGuard],
data: { data: {
titleId: "billingHistory", titleId: "billingHistory",
organizationPermissions: (org: Organization) => org.canManageBilling,
}, },
}, },
], ],

View File

@ -8,7 +8,7 @@
{{ "subscription" | i18n }} {{ "subscription" | i18n }}
</a> </a>
<a <a
*ngIf="showPaymentAndHistory" *ngIf="showPaymentAndHistory$ | async"
routerLink="payment-method" routerLink="payment-method"
class="list-group-item" class="list-group-item"
routerLinkActive="active" routerLinkActive="active"
@ -16,7 +16,7 @@
{{ "paymentMethod" | i18n }} {{ "paymentMethod" | i18n }}
</a> </a>
<a <a
*ngIf="showPaymentAndHistory" *ngIf="showPaymentAndHistory$ | async"
routerLink="history" routerLink="history"
class="list-group-item" class="list-group-item"
routerLinkActive="active" routerLinkActive="active"

View File

@ -1,14 +1,27 @@
import { Component } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { map, Observable, switchMap } from "rxjs";
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
@Component({ @Component({
selector: "app-org-billing-tab", selector: "app-org-billing-tab",
templateUrl: "organization-billing-tab.component.html", templateUrl: "organization-billing-tab.component.html",
}) })
export class OrganizationBillingTabComponent { export class OrganizationBillingTabComponent implements OnInit {
showPaymentAndHistory: boolean; showPaymentAndHistory$: Observable<boolean>;
constructor(private platformUtilsService: PlatformUtilsService) {
this.showPaymentAndHistory = !this.platformUtilsService.isSelfHost(); constructor(
private route: ActivatedRoute,
private organizationService: OrganizationService,
private platformUtilsService: PlatformUtilsService
) {}
ngOnInit() {
this.showPaymentAndHistory$ = this.route.params.pipe(
switchMap((params) => this.organizationService.get$(params.organizationId)),
map((org) => !this.platformUtilsService.isSelfHost() && org.canManageBilling)
);
} }
} }

View File

@ -1,14 +1,14 @@
<div class="container page-content"> <div class="container page-content">
<div class="row"> <div class="row">
<div class="col-3" *ngIf="showLeftNav"> <div class="col-3" *ngIf="showLeftNav$ | async">
<div class="card" *ngIf="organization"> <div class="card" *ngIf="organization$ | async as org">
<div class="card-header">{{ "reporting" | i18n }}</div> <div class="card-header">{{ "reporting" | i18n }}</div>
<div class="list-group list-group-flush"> <div class="list-group list-group-flush">
<a <a
routerLink="events" routerLink="events"
class="list-group-item" class="list-group-item"
routerLinkActive="active" routerLinkActive="active"
*ngIf="organization.canAccessEventLogs" *ngIf="org.canAccessEventLogs"
> >
{{ "eventLogs" | i18n }} {{ "eventLogs" | i18n }}
</a> </a>
@ -16,14 +16,14 @@
routerLink="reports" routerLink="reports"
class="list-group-item" class="list-group-item"
routerLinkActive="active" routerLinkActive="active"
*ngIf="organization.canAccessReports" *ngIf="org.canAccessReports"
> >
{{ "reports" | i18n }} {{ "reports" | i18n }}
</a> </a>
</div> </div>
</div> </div>
</div> </div>
<div class="col-9" [ngClass]="showLeftNav ? 'col-9' : 'col-12'"> <div class="col-9" [ngClass]="(showLeftNav$ | async) ? 'col-9' : 'col-12'">
<router-outlet></router-outlet> <router-outlet></router-outlet>
</div> </div>
</div> </div>

View File

@ -1,6 +1,6 @@
import { Component, OnDestroy, OnInit } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router"; import { ActivatedRoute } from "@angular/router";
import { concatMap, Subject, takeUntil } from "rxjs"; import { map, Observable, shareReplay, startWith, switchMap } from "rxjs";
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/models/domain/organization"; import { Organization } from "@bitwarden/common/models/domain/organization";
@ -9,29 +9,21 @@ import { Organization } from "@bitwarden/common/models/domain/organization";
selector: "app-org-reporting", selector: "app-org-reporting",
templateUrl: "reporting.component.html", templateUrl: "reporting.component.html",
}) })
export class ReportingComponent implements OnInit, OnDestroy { export class ReportingComponent implements OnInit {
organization: Organization; organization$: Observable<Organization>;
showLeftNav = true; showLeftNav$: Observable<boolean>;
private destroy$ = new Subject<void>();
constructor(private route: ActivatedRoute, private organizationService: OrganizationService) {} constructor(private route: ActivatedRoute, private organizationService: OrganizationService) {}
ngOnInit() { ngOnInit() {
this.route.params this.organization$ = this.route.params.pipe(
.pipe( switchMap((params) => this.organizationService.get$(params.organizationId)),
concatMap(async (params) => { shareReplay({ refCount: true, bufferSize: 1 })
this.organization = await this.organizationService.get(params.organizationId); );
this.showLeftNav =
this.organization.canAccessEventLogs && this.organization.canAccessReports;
}),
takeUntil(this.destroy$)
)
.subscribe();
}
ngOnDestroy(): void { this.showLeftNav$ = this.organization$.pipe(
this.destroy$.next(); map((o) => o.canAccessEventLogs && o.canAccessReports),
this.destroy$.complete(); startWith(true)
);
} }
} }

View File

@ -3,12 +3,12 @@
<div class="col-3"> <div class="col-3">
<div class="card"> <div class="card">
<div class="card-header">{{ "settings" | i18n }}</div> <div class="card-header">{{ "settings" | i18n }}</div>
<div class="list-group list-group-flush"> <div class="list-group list-group-flush" *ngIf="organization$ | async as org">
<a <a
routerLink="account" routerLink="account"
class="list-group-item" class="list-group-item"
routerLinkActive="active" routerLinkActive="active"
*ngIf="organization?.isOwner" *ngIf="org.isOwner"
> >
{{ "organizationInfo" | i18n }} {{ "organizationInfo" | i18n }}
</a> </a>
@ -16,7 +16,7 @@
routerLink="policies" routerLink="policies"
class="list-group-item" class="list-group-item"
routerLinkActive="active" routerLinkActive="active"
*ngIf="organization?.canManagePolicies" *ngIf="org.canManagePolicies"
> >
{{ "policies" | i18n }} {{ "policies" | i18n }}
</a> </a>
@ -24,7 +24,7 @@
routerLink="two-factor" routerLink="two-factor"
class="list-group-item" class="list-group-item"
routerLinkActive="active" routerLinkActive="active"
*ngIf="organization?.use2fa && organization?.isOwner" *ngIf="org.use2fa && org.isOwner"
> >
{{ "twoStepLogin" | i18n }} {{ "twoStepLogin" | i18n }}
</a> </a>
@ -32,7 +32,7 @@
routerLink="tools/import" routerLink="tools/import"
class="list-group-item" class="list-group-item"
routerLinkActive="active" routerLinkActive="active"
*ngIf="organization?.canAccessImportExport" *ngIf="org.canAccessImportExport"
> >
{{ "importData" | i18n }} {{ "importData" | i18n }}
</a> </a>
@ -40,7 +40,7 @@
routerLink="tools/export" routerLink="tools/export"
class="list-group-item" class="list-group-item"
routerLinkActive="active" routerLinkActive="active"
*ngIf="organization?.canAccessImportExport" *ngIf="org.canAccessImportExport"
> >
{{ "exportVault" | i18n }} {{ "exportVault" | i18n }}
</a> </a>
@ -48,7 +48,7 @@
routerLink="domain-verification" routerLink="domain-verification"
class="list-group-item" class="list-group-item"
routerLinkActive="active" routerLinkActive="active"
*ngIf="organization?.canManageDomainVerification" *ngIf="org?.canManageDomainVerification"
> >
{{ "domainVerification" | i18n }} {{ "domainVerification" | i18n }}
</a> </a>
@ -56,7 +56,7 @@
routerLink="sso" routerLink="sso"
class="list-group-item" class="list-group-item"
routerLinkActive="active" routerLinkActive="active"
*ngIf="organization?.canManageSso" *ngIf="org.canManageSso"
> >
{{ "singleSignOn" | i18n }} {{ "singleSignOn" | i18n }}
</a> </a>
@ -64,7 +64,7 @@
routerLink="scim" routerLink="scim"
class="list-group-item" class="list-group-item"
routerLinkActive="active" routerLinkActive="active"
*ngIf="organization?.canManageScim" *ngIf="org.canManageScim"
> >
{{ "scim" | i18n }} {{ "scim" | i18n }}
</a> </a>

View File

@ -1,6 +1,6 @@
import { Component, OnDestroy, OnInit } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router"; import { ActivatedRoute } from "@angular/router";
import { Subject, switchMap, takeUntil } from "rxjs"; import { Observable, switchMap } from "rxjs";
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/models/domain/organization"; import { Organization } from "@bitwarden/common/models/domain/organization";
@ -9,26 +9,14 @@ import { Organization } from "@bitwarden/common/models/domain/organization";
selector: "app-org-settings", selector: "app-org-settings",
templateUrl: "settings.component.html", templateUrl: "settings.component.html",
}) })
export class SettingsComponent implements OnInit, OnDestroy { export class SettingsComponent implements OnInit {
organization: Organization; organization$: Observable<Organization>;
private destroy$ = new Subject<void>();
constructor(private route: ActivatedRoute, private organizationService: OrganizationService) {} constructor(private route: ActivatedRoute, private organizationService: OrganizationService) {}
ngOnInit() { ngOnInit() {
this.route.params this.organization$ = this.route.params.pipe(
.pipe( switchMap((params) => this.organizationService.get$(params.organizationId))
switchMap(async (params) => await this.organizationService.get(params.organizationId)), );
takeUntil(this.destroy$)
)
.subscribe((organization) => {
this.organization = organization;
});
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
} }
} }

View File

@ -32,7 +32,7 @@ export function canAccessReportingTab(org: Organization): boolean {
} }
export function canAccessBillingTab(org: Organization): boolean { export function canAccessBillingTab(org: Organization): boolean {
return org.canManageBilling; return org.isOwner;
} }
export function canAccessOrgAdmin(org: Organization): boolean { export function canAccessOrgAdmin(org: Organization): boolean {
@ -63,6 +63,7 @@ export function isNotProviderUser(org: Organization): boolean {
export abstract class OrganizationService { export abstract class OrganizationService {
organizations$: Observable<Organization[]>; organizations$: Observable<Organization[]>;
get$: (id: string) => Observable<Organization | undefined>;
get: (id: string) => Organization; get: (id: string) => Organization;
getByIdentifier: (identifier: string) => Organization; getByIdentifier: (identifier: string) => Organization;
getAll: (userId?: string) => Promise<Organization[]>; getAll: (userId?: string) => Promise<Organization[]>;

View File

@ -1,4 +1,4 @@
import { BehaviorSubject, concatMap } from "rxjs"; import { BehaviorSubject, concatMap, map, Observable } from "rxjs";
import { InternalOrganizationService as InternalOrganizationServiceAbstraction } from "../../abstractions/organization/organization.service.abstraction"; import { InternalOrganizationService as InternalOrganizationServiceAbstraction } from "../../abstractions/organization/organization.service.abstraction";
import { StateService } from "../../abstractions/state.service"; import { StateService } from "../../abstractions/state.service";
@ -26,6 +26,10 @@ export class OrganizationService implements InternalOrganizationServiceAbstracti
.subscribe(); .subscribe();
} }
get$(id: string): Observable<Organization | undefined> {
return this.organizations$.pipe(map((orgs) => orgs.find((o) => o.id === id)));
}
async getAll(userId?: string): Promise<Organization[]> { async getAll(userId?: string): Promise<Organization[]> {
const organizationsMap = await this.stateService.getOrganizations({ userId: userId }); const organizationsMap = await this.stateService.getOrganizations({ userId: userId });
return Object.values(organizationsMap || {}).map((o) => new Organization(o)); return Object.values(organizationsMap || {}).map((o) => new Organization(o));