mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 13:23:34 +00:00
[PM-24550] Remove Feature Flag Code for PM-12276 (#16173)
* tests: remove feature flag use in tests * tests: remove breadcrumbingPolicyTests and add service tests * refactor: remove event log use of flag from org-layout component * refactor: remove new policy code from org-layout component * refactor: remove event log use of flag from events component * refactor: remove event log use from collection dialog component * refactor: remove event log use from vault-header component * refactor: remove event-log route logic for org-reporting * refactor: remove logic from org-settings routing * refactor: remove breadcrumbing function and from billing service * refactor: remove ConfigService from DI for billing service * refactor: remove new policy code from policy-edit component * refactor: remove new policy code from policies component * refactor: remove feature flag * fix(Admin Console): revert to use of reactive observables pattern * fix(Admin Console): remove type artifact from reversion
This commit is contained in:
@@ -55,10 +55,7 @@
|
||||
<bit-nav-item
|
||||
[text]="'eventLogs' | i18n"
|
||||
route="reporting/events"
|
||||
*ngIf="
|
||||
(organization.canAccessEventLogs && organization.useEvents) ||
|
||||
(organization.isOwner && (isBreadcrumbEventLogsEnabled$ | async))
|
||||
"
|
||||
*ngIf="organization.canAccessEventLogs || organization.isOwner"
|
||||
></bit-nav-item>
|
||||
<bit-nav-item
|
||||
[text]="'reports' | i18n"
|
||||
@@ -102,7 +99,7 @@
|
||||
<bit-nav-item
|
||||
[text]="'policies' | i18n"
|
||||
route="settings/policies"
|
||||
*ngIf="canShowPoliciesTab$ | async"
|
||||
*ngIf="organization.canManagePolicies"
|
||||
></bit-nav-item>
|
||||
<bit-nav-item
|
||||
[text]="'twoStepLogin' | i18n"
|
||||
|
||||
@@ -68,9 +68,7 @@ export class OrganizationLayoutComponent implements OnInit {
|
||||
hideNewOrgButton$: Observable<boolean>;
|
||||
organizationIsUnmanaged$: Observable<boolean>;
|
||||
|
||||
protected isBreadcrumbEventLogsEnabled$: Observable<boolean>;
|
||||
protected showSponsoredFamiliesDropdown$: Observable<boolean>;
|
||||
protected canShowPoliciesTab$: Observable<boolean>;
|
||||
|
||||
protected paymentDetailsPageData$: Observable<{
|
||||
route: string;
|
||||
@@ -94,9 +92,6 @@ export class OrganizationLayoutComponent implements OnInit {
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.isBreadcrumbEventLogsEnabled$ = this.configService.getFeatureFlag$(
|
||||
FeatureFlag.PM12276_BreadcrumbEventLogs,
|
||||
);
|
||||
document.body.classList.remove("layout_frontend");
|
||||
|
||||
this.organization$ = this.route.params.pipe(
|
||||
@@ -141,18 +136,6 @@ export class OrganizationLayoutComponent implements OnInit {
|
||||
|
||||
this.integrationPageEnabled$ = this.organization$.pipe(map((org) => org.canAccessIntegrations));
|
||||
|
||||
this.canShowPoliciesTab$ = this.organization$.pipe(
|
||||
switchMap((organization) =>
|
||||
this.organizationBillingService
|
||||
.isBreadcrumbingPoliciesEnabled$(organization)
|
||||
.pipe(
|
||||
map(
|
||||
(isBreadcrumbingEnabled) => isBreadcrumbingEnabled || organization.canManagePolicies,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
this.paymentDetailsPageData$ = this.configService
|
||||
.getFeatureFlag$(FeatureFlag.PM21881_ManagePaymentDetailsOutsideCheckout)
|
||||
.pipe(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@let usePlaceHolderEvents = !organization?.useEvents && (isBreadcrumbEventLogsEnabled$ | async);
|
||||
@let usePlaceHolderEvents = !organization?.useEvents;
|
||||
<app-header>
|
||||
<span
|
||||
bitBadge
|
||||
|
||||
@@ -19,7 +19,6 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { ProductTierType } from "@bitwarden/common/billing/enums";
|
||||
import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response";
|
||||
import { EventSystemUser } from "@bitwarden/common/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { EventResponse } from "@bitwarden/common/models/response/event.response";
|
||||
import { EventView } from "@bitwarden/common/models/view/event.view";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
@@ -62,10 +61,6 @@ export class EventsComponent extends BaseEventsComponent implements OnInit, OnDe
|
||||
private orgUsersUserIdMap = new Map<string, any>();
|
||||
readonly ProductTierType = ProductTierType;
|
||||
|
||||
protected isBreadcrumbEventLogsEnabled$ = this.configService.getFeatureFlag$(
|
||||
FeatureFlag.PM12276_BreadcrumbEventLogs,
|
||||
);
|
||||
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private route: ActivatedRoute,
|
||||
|
||||
@@ -1,20 +1,7 @@
|
||||
<app-header>
|
||||
@let organization = organization$ | async;
|
||||
@if (isBreadcrumbingEnabled$ | async) {
|
||||
<button
|
||||
bitBadge
|
||||
class="!tw-align-middle"
|
||||
(click)="changePlan(organization)"
|
||||
slot="title-suffix"
|
||||
type="button"
|
||||
variant="primary"
|
||||
>
|
||||
{{ "upgrade" | i18n }}
|
||||
</button>
|
||||
}
|
||||
</app-header>
|
||||
<app-header></app-header>
|
||||
|
||||
<bit-container>
|
||||
@let organization = organization$ | async;
|
||||
@if (loading) {
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin tw-text-muted"
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// @ts-strict-ignore
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { firstValueFrom, lastValueFrom, map, Observable, switchMap } from "rxjs";
|
||||
import { first } from "rxjs/operators";
|
||||
import { firstValueFrom, lastValueFrom, Observable } from "rxjs";
|
||||
import { first, map } from "rxjs/operators";
|
||||
|
||||
import {
|
||||
getOrganizationById,
|
||||
@@ -14,18 +14,11 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import {
|
||||
ChangePlanDialogResultType,
|
||||
openChangePlanDialog,
|
||||
} from "@bitwarden/web-vault/app/billing/organizations/change-plan-dialog.component";
|
||||
import { All } from "@bitwarden/web-vault/app/vault/individual-vault/vault-filter/shared/models/routed-vault-filter.model";
|
||||
|
||||
import { PolicyListService } from "../../core/policy-list.service";
|
||||
import { BasePolicy } from "../policies";
|
||||
import { CollectionDialogTabType } from "../shared/components/collection-dialog";
|
||||
|
||||
import { PolicyEditComponent, PolicyEditDialogResult } from "./policy-edit.component";
|
||||
|
||||
@@ -38,19 +31,17 @@ export class PoliciesComponent implements OnInit {
|
||||
loading = true;
|
||||
organizationId: string;
|
||||
policies: BasePolicy[];
|
||||
protected organization$: Observable<Organization>;
|
||||
organization$: Observable<Organization>;
|
||||
|
||||
private orgPolicies: PolicyResponse[];
|
||||
protected policiesEnabledMap: Map<PolicyType, boolean> = new Map<PolicyType, boolean>();
|
||||
protected isBreadcrumbingEnabled$: Observable<boolean>;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private accountService: AccountService,
|
||||
private organizationService: OrganizationService,
|
||||
private accountService: AccountService,
|
||||
private policyApiService: PolicyApiServiceAbstraction,
|
||||
private policyListService: PolicyListService,
|
||||
private organizationBillingService: OrganizationBillingServiceAbstraction,
|
||||
private dialogService: DialogService,
|
||||
protected configService: ConfigService,
|
||||
) {}
|
||||
@@ -62,9 +53,11 @@ export class PoliciesComponent implements OnInit {
|
||||
const userId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
||||
);
|
||||
|
||||
this.organization$ = this.organizationService
|
||||
.organizations$(userId)
|
||||
.pipe(getOrganizationById(this.organizationId));
|
||||
|
||||
this.policies = this.policyListService.getPolicies();
|
||||
|
||||
await this.load();
|
||||
@@ -100,11 +93,7 @@ export class PoliciesComponent implements OnInit {
|
||||
this.orgPolicies.forEach((op) => {
|
||||
this.policiesEnabledMap.set(op.type, op.enabled);
|
||||
});
|
||||
this.isBreadcrumbingEnabled$ = this.organization$.pipe(
|
||||
switchMap((organization) =>
|
||||
this.organizationBillingService.isBreadcrumbingPoliciesEnabled$(organization),
|
||||
),
|
||||
);
|
||||
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
@@ -117,34 +106,8 @@ export class PoliciesComponent implements OnInit {
|
||||
});
|
||||
|
||||
const result = await lastValueFrom(dialogRef.closed);
|
||||
switch (result) {
|
||||
case PolicyEditDialogResult.Saved:
|
||||
await this.load();
|
||||
break;
|
||||
case PolicyEditDialogResult.UpgradePlan:
|
||||
await this.changePlan(await firstValueFrom(this.organization$));
|
||||
break;
|
||||
if (result === PolicyEditDialogResult.Saved) {
|
||||
await this.load();
|
||||
}
|
||||
}
|
||||
|
||||
protected readonly CollectionDialogTabType = CollectionDialogTabType;
|
||||
protected readonly All = All;
|
||||
|
||||
protected async changePlan(organization: Organization) {
|
||||
const reference = openChangePlanDialog(this.dialogService, {
|
||||
data: {
|
||||
organizationId: organization.id,
|
||||
subscription: null,
|
||||
productTierType: organization.productTierType,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await lastValueFrom(reference.closed);
|
||||
|
||||
if (result === ChangePlanDialogResultType.Closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.load();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,5 @@
|
||||
<form [formGroup]="formGroup" [bitSubmit]="submit">
|
||||
<bit-dialog [loading]="loading" [title]="'editPolicy' | i18n" [subtitle]="policy.name | i18n">
|
||||
<ng-container bitDialogTitle>
|
||||
<button
|
||||
bitBadge
|
||||
class="!tw-align-middle"
|
||||
(click)="upgradePlan()"
|
||||
*ngIf="isBreadcrumbingEnabled$ | async"
|
||||
type="button"
|
||||
variant="primary"
|
||||
>
|
||||
{{ "planNameEnterprise" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
<ng-container bitDialogContent>
|
||||
<div *ngIf="loading">
|
||||
<i
|
||||
@@ -30,7 +18,6 @@
|
||||
</ng-container>
|
||||
<ng-container bitDialogFooter>
|
||||
<button
|
||||
*ngIf="!(isBreadcrumbingEnabled$ | async); else breadcrumbing"
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
[disabled]="saveDisabled$ | async"
|
||||
@@ -39,11 +26,6 @@
|
||||
>
|
||||
{{ "save" | i18n }}
|
||||
</button>
|
||||
<ng-template #breadcrumbing>
|
||||
<button bitButton buttonType="primary" bitFormButton type="button" (click)="upgradePlan()">
|
||||
{{ "upgrade" | i18n }}
|
||||
</button>
|
||||
</ng-template>
|
||||
<button bitButton buttonType="secondary" bitDialogClose type="button">
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
|
||||
@@ -9,20 +9,12 @@ import {
|
||||
ViewContainerRef,
|
||||
} from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { map, Observable, switchMap } from "rxjs";
|
||||
import { Observable, map } from "rxjs";
|
||||
|
||||
import {
|
||||
getOrganizationById,
|
||||
OrganizationService,
|
||||
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { PolicyRequest } from "@bitwarden/common/admin-console/models/request/policy.request";
|
||||
import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import {
|
||||
DIALOG_DATA,
|
||||
@@ -45,7 +37,6 @@ export type PolicyEditDialogData = {
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum PolicyEditDialogResult {
|
||||
Saved = "saved",
|
||||
UpgradePlan = "upgrade-plan",
|
||||
}
|
||||
@Component({
|
||||
selector: "app-policy-edit",
|
||||
@@ -66,22 +57,15 @@ export class PolicyEditComponent implements AfterViewInit {
|
||||
formGroup = this.formBuilder.group({
|
||||
enabled: [this.enabled],
|
||||
});
|
||||
protected organization$: Observable<Organization>;
|
||||
protected isBreadcrumbingEnabled$: Observable<boolean>;
|
||||
|
||||
constructor(
|
||||
@Inject(DIALOG_DATA) protected data: PolicyEditDialogData,
|
||||
private accountService: AccountService,
|
||||
private policyApiService: PolicyApiServiceAbstraction,
|
||||
private organizationService: OrganizationService,
|
||||
private i18nService: I18nService,
|
||||
private cdr: ChangeDetectorRef,
|
||||
private formBuilder: FormBuilder,
|
||||
private dialogRef: DialogRef<PolicyEditDialogResult>,
|
||||
private toastService: ToastService,
|
||||
private organizationBillingService: OrganizationBillingServiceAbstraction,
|
||||
) {}
|
||||
|
||||
get policy(): BasePolicy {
|
||||
return this.data.policy;
|
||||
}
|
||||
@@ -115,16 +99,6 @@ export class PolicyEditComponent implements AfterViewInit {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
this.organization$ = this.accountService.activeAccount$.pipe(
|
||||
getUserId,
|
||||
switchMap((userId) => this.organizationService.organizations$(userId)),
|
||||
getOrganizationById(this.data.organizationId),
|
||||
);
|
||||
this.isBreadcrumbingEnabled$ = this.organization$.pipe(
|
||||
switchMap((organization) =>
|
||||
this.organizationBillingService.isBreadcrumbingPoliciesEnabled$(organization),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
submit = async () => {
|
||||
@@ -154,8 +128,4 @@ export class PolicyEditComponent implements AfterViewInit {
|
||||
static open = (dialogService: DialogService, config: DialogConfig<PolicyEditDialogData>) => {
|
||||
return dialogService.open<PolicyEditDialogResult>(PolicyEditComponent, config);
|
||||
};
|
||||
|
||||
protected upgradePlan(): void {
|
||||
this.dialogRef.close(PolicyEditDialogResult.UpgradePlan);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { inject, NgModule } from "@angular/core";
|
||||
import { CanMatchFn, RouterModule, Routes } from "@angular/router";
|
||||
import { map } from "rxjs";
|
||||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import { canAccessReportingTab } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { ExposedPasswordsReportComponent } from "../../../dirt/reports/pages/organizations/exposed-passwords-report.component";
|
||||
@@ -26,11 +23,6 @@ import { EventsComponent } from "../manage/events.component";
|
||||
|
||||
import { ReportsHomeComponent } from "./reports-home.component";
|
||||
|
||||
const breadcrumbEventLogsPermission$: CanMatchFn = () =>
|
||||
inject(ConfigService)
|
||||
.getFeatureFlag$(FeatureFlag.PM12276_BreadcrumbEventLogs)
|
||||
.pipe(map((breadcrumbEventLogs) => breadcrumbEventLogs === true));
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: "",
|
||||
@@ -92,24 +84,10 @@ const routes: Routes = [
|
||||
},
|
||||
],
|
||||
},
|
||||
// Event routing is temporarily duplicated
|
||||
{
|
||||
path: "events",
|
||||
component: EventsComponent,
|
||||
canMatch: [breadcrumbEventLogsPermission$], // if this matches, the flag is ON
|
||||
canActivate: [
|
||||
organizationPermissionsGuard(
|
||||
(org) => (org.canAccessEventLogs && org.useEvents) || org.isOwner,
|
||||
),
|
||||
],
|
||||
data: {
|
||||
titleId: "eventLogs",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "events",
|
||||
component: EventsComponent,
|
||||
canActivate: [organizationPermissionsGuard((org) => org.canAccessEventLogs)],
|
||||
canActivate: [organizationPermissionsGuard((org) => org.canAccessEventLogs || org.isOwner)],
|
||||
data: {
|
||||
titleId: "eventLogs",
|
||||
},
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { NgModule, inject } from "@angular/core";
|
||||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
import { map } from "rxjs";
|
||||
|
||||
import { canAccessSettingsTab } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions";
|
||||
|
||||
import { organizationPermissionsGuard } from "../../organizations/guards/org-permissions.guard";
|
||||
import { organizationRedirectGuard } from "../../organizations/guards/org-redirect.guard";
|
||||
@@ -43,14 +41,7 @@ const routes: Routes = [
|
||||
{
|
||||
path: "policies",
|
||||
component: PoliciesComponent,
|
||||
canActivate: [
|
||||
organizationPermissionsGuard((o: Organization) => {
|
||||
const organizationBillingService = inject(OrganizationBillingServiceAbstraction);
|
||||
return organizationBillingService
|
||||
.isBreadcrumbingPoliciesEnabled$(o)
|
||||
.pipe(map((isBreadcrumbingEnabled) => o.canManagePolicies || isBreadcrumbingEnabled));
|
||||
}),
|
||||
],
|
||||
canActivate: [organizationPermissionsGuard((org) => org.canManagePolicies)],
|
||||
data: {
|
||||
titleId: "policies",
|
||||
},
|
||||
|
||||
@@ -34,7 +34,6 @@ import {
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { getById } from "@bitwarden/common/platform/misc";
|
||||
@@ -188,22 +187,16 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
|
||||
await this.loadOrg(this.params.organizationId);
|
||||
}
|
||||
|
||||
const isBreadcrumbEventLogsEnabled = await firstValueFrom(
|
||||
this.configService.getFeatureFlag$(FeatureFlag.PM12276_BreadcrumbEventLogs),
|
||||
this.organizationSelected.setAsyncValidators(
|
||||
freeOrgCollectionLimitValidator(
|
||||
this.organizations$,
|
||||
this.collectionService
|
||||
.encryptedCollections$(userId)
|
||||
.pipe(map((collections) => collections ?? [])),
|
||||
this.i18nService,
|
||||
),
|
||||
);
|
||||
|
||||
if (isBreadcrumbEventLogsEnabled) {
|
||||
this.organizationSelected.setAsyncValidators(
|
||||
freeOrgCollectionLimitValidator(
|
||||
this.organizations$,
|
||||
this.collectionService
|
||||
.encryptedCollections$(userId)
|
||||
.pipe(map((collections) => collections ?? [])),
|
||||
this.i18nService,
|
||||
),
|
||||
);
|
||||
this.formGroup.updateValueAndValidity();
|
||||
}
|
||||
this.formGroup.updateValueAndValidity();
|
||||
|
||||
this.organizationSelected.valueChanges
|
||||
.pipe(
|
||||
|
||||
@@ -13,7 +13,6 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { ProductTierType } from "@bitwarden/common/billing/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
@@ -218,28 +217,22 @@ export class VaultHeaderComponent {
|
||||
}
|
||||
|
||||
async addCollection(): Promise<void> {
|
||||
const isBreadcrumbEventLogsEnabled = await firstValueFrom(
|
||||
this.configService.getFeatureFlag$(FeatureFlag.PM12276_BreadcrumbEventLogs),
|
||||
const organization = this.organizations?.find(
|
||||
(org) => org.productTierType === ProductTierType.Free,
|
||||
);
|
||||
|
||||
if (isBreadcrumbEventLogsEnabled) {
|
||||
const organization = this.organizations?.find(
|
||||
(org) => org.productTierType === ProductTierType.Free,
|
||||
);
|
||||
|
||||
if (this.organizations?.length == 1 && !!organization) {
|
||||
const collections = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(
|
||||
getUserId,
|
||||
switchMap((userId) =>
|
||||
this.collectionAdminService.collectionAdminViews$(organization.id, userId),
|
||||
),
|
||||
if (this.organizations?.length == 1 && !!organization) {
|
||||
const collections = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(
|
||||
getUserId,
|
||||
switchMap((userId) =>
|
||||
this.collectionAdminService.collectionAdminViews$(organization.id, userId),
|
||||
),
|
||||
);
|
||||
if (collections.length === organization.maxCollections) {
|
||||
await this.showFreeOrgUpgradeDialog(organization);
|
||||
return;
|
||||
}
|
||||
),
|
||||
);
|
||||
if (collections.length === organization.maxCollections) {
|
||||
await this.showFreeOrgUpgradeDialog(organization);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1332,7 +1332,6 @@ const safeProviders: SafeProvider[] = [
|
||||
I18nServiceAbstraction,
|
||||
OrganizationApiServiceAbstraction,
|
||||
SyncService,
|
||||
ConfigService,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
|
||||
import { OrganizationResponse } from "../../admin-console/models/response/organization.response";
|
||||
import { InitiationPath } from "../../models/request/reference-event.request";
|
||||
import { PaymentMethodType, PlanType } from "../enums";
|
||||
@@ -63,10 +59,4 @@ export abstract class OrganizationBillingServiceAbstraction {
|
||||
organizationId: string,
|
||||
subscription: SubscriptionInformation,
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* Determines if breadcrumbing policies is enabled for the organizations meeting certain criteria.
|
||||
* @param organization
|
||||
*/
|
||||
abstract isBreadcrumbingPoliciesEnabled$(organization: Organization): Observable<boolean>;
|
||||
}
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
import { mock } from "jest-mock-extended";
|
||||
import { firstValueFrom, of } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationApiServiceAbstraction as OrganizationApiService } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions";
|
||||
import { ProductTierType } from "@bitwarden/common/billing/enums";
|
||||
import {
|
||||
BillingApiServiceAbstraction,
|
||||
SubscriptionInformation,
|
||||
} from "@bitwarden/common/billing/abstractions";
|
||||
import { PaymentMethodType, PlanType } from "@bitwarden/common/billing/enums";
|
||||
import { OrganizationBillingService } from "@bitwarden/common/billing/services/organization-billing.service";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { SyncService } from "@bitwarden/common/platform/sync";
|
||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
|
||||
describe("BillingAccountProfileStateService", () => {
|
||||
import { OrganizationResponse } from "../../admin-console/models/response/organization.response";
|
||||
import { EncString } from "../../key-management/crypto/models/enc-string";
|
||||
import { OrgKey } from "../../types/key";
|
||||
import { PaymentMethodResponse } from "../models/response/payment-method.response";
|
||||
|
||||
describe("OrganizationBillingService", () => {
|
||||
let apiService: jest.Mocked<ApiService>;
|
||||
let billingApiService: jest.Mocked<BillingApiServiceAbstraction>;
|
||||
let keyService: jest.Mocked<KeyService>;
|
||||
@@ -24,7 +28,6 @@ describe("BillingAccountProfileStateService", () => {
|
||||
let i18nService: jest.Mocked<I18nService>;
|
||||
let organizationApiService: jest.Mocked<OrganizationApiService>;
|
||||
let syncService: jest.Mocked<SyncService>;
|
||||
let configService: jest.Mocked<ConfigService>;
|
||||
|
||||
let sut: OrganizationBillingService;
|
||||
|
||||
@@ -36,7 +39,6 @@ describe("BillingAccountProfileStateService", () => {
|
||||
i18nService = mock<I18nService>();
|
||||
organizationApiService = mock<OrganizationApiService>();
|
||||
syncService = mock<SyncService>();
|
||||
configService = mock<ConfigService>();
|
||||
|
||||
sut = new OrganizationBillingService(
|
||||
apiService,
|
||||
@@ -46,7 +48,6 @@ describe("BillingAccountProfileStateService", () => {
|
||||
i18nService,
|
||||
organizationApiService,
|
||||
syncService,
|
||||
configService,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -54,98 +55,246 @@ describe("BillingAccountProfileStateService", () => {
|
||||
return jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe("isBreadcrumbingPoliciesEnabled", () => {
|
||||
it("returns false when feature flag is disabled", async () => {
|
||||
configService.getFeatureFlag$.mockReturnValue(of(false));
|
||||
const org = {
|
||||
isProviderUser: false,
|
||||
canEditSubscription: true,
|
||||
productTierType: ProductTierType.Teams,
|
||||
} as Organization;
|
||||
describe("getPaymentSource()", () => {
|
||||
it("given a valid organization id, then it returns a payment source", async () => {
|
||||
//Arrange
|
||||
const orgId = "organization-test";
|
||||
const paymentMethodResponse = {
|
||||
paymentSource: { type: PaymentMethodType.Card },
|
||||
} as PaymentMethodResponse;
|
||||
billingApiService.getOrganizationPaymentMethod.mockResolvedValue(paymentMethodResponse);
|
||||
|
||||
const actual = await firstValueFrom(sut.isBreadcrumbingPoliciesEnabled$(org));
|
||||
expect(actual).toBe(false);
|
||||
expect(configService.getFeatureFlag$).toHaveBeenCalledWith(
|
||||
FeatureFlag.PM12276_BreadcrumbEventLogs,
|
||||
//Act
|
||||
const returnedPaymentSource = await sut.getPaymentSource(orgId);
|
||||
|
||||
//Assert
|
||||
expect(billingApiService.getOrganizationPaymentMethod).toHaveBeenCalledTimes(1);
|
||||
expect(returnedPaymentSource).toEqual(paymentMethodResponse.paymentSource);
|
||||
});
|
||||
|
||||
it("given an invalid organizationId, it should return undefined", async () => {
|
||||
//Arrange
|
||||
const orgId = "invalid-id";
|
||||
billingApiService.getOrganizationPaymentMethod.mockResolvedValue(null);
|
||||
|
||||
//Act
|
||||
const returnedPaymentSource = await sut.getPaymentSource(orgId);
|
||||
|
||||
//Assert
|
||||
expect(billingApiService.getOrganizationPaymentMethod).toHaveBeenCalledTimes(1);
|
||||
expect(returnedPaymentSource).toBeUndefined();
|
||||
});
|
||||
|
||||
it("given an API error occurs, then it throws the error", async () => {
|
||||
// Arrange
|
||||
const orgId = "error-org";
|
||||
billingApiService.getOrganizationPaymentMethod.mockRejectedValue(new Error("API Error"));
|
||||
|
||||
// Act & Assert
|
||||
await expect(sut.getPaymentSource(orgId)).rejects.toThrow("API Error");
|
||||
expect(billingApiService.getOrganizationPaymentMethod).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("purchaseSubscription()", () => {
|
||||
it("given valid subscription information, then it returns successful response", async () => {
|
||||
//Arrange
|
||||
const subscriptionInformation = {
|
||||
organization: { name: "test-business", billingEmail: "test@test.com" },
|
||||
plan: { type: PlanType.EnterpriseAnnually2023 },
|
||||
payment: {
|
||||
paymentMethod: ["card-token", PaymentMethodType.Card],
|
||||
billing: { postalCode: "12345" },
|
||||
},
|
||||
} as SubscriptionInformation;
|
||||
|
||||
const organizationResponse = {
|
||||
name: subscriptionInformation.organization.name,
|
||||
billingEmail: subscriptionInformation.organization.billingEmail,
|
||||
planType: subscriptionInformation.plan.type,
|
||||
} as OrganizationResponse;
|
||||
|
||||
organizationApiService.create.mockResolvedValue(organizationResponse);
|
||||
keyService.makeOrgKey.mockResolvedValue([new EncString("encrypyted-key"), {} as OrgKey]);
|
||||
keyService.makeKeyPair.mockResolvedValue(["key", new EncString("encrypyted-key")]);
|
||||
encryptService.encryptString.mockResolvedValue(new EncString("collection-encrypyted"));
|
||||
|
||||
//Act
|
||||
const response = await sut.purchaseSubscription(subscriptionInformation);
|
||||
|
||||
//Assert
|
||||
expect(organizationApiService.create).toHaveBeenCalledTimes(1);
|
||||
expect(response).toEqual(organizationResponse);
|
||||
});
|
||||
|
||||
it("given organization creation fails, then it throws an error", async () => {
|
||||
// Arrange
|
||||
const subscriptionInformation = {
|
||||
organization: { name: "test-business", billingEmail: "test@test.com" },
|
||||
plan: { type: PlanType.EnterpriseAnnually2023 },
|
||||
payment: {
|
||||
paymentMethod: ["card-token", PaymentMethodType.Card],
|
||||
billing: { postalCode: "12345" },
|
||||
},
|
||||
} as SubscriptionInformation;
|
||||
|
||||
organizationApiService.create.mockRejectedValue(new Error("Failed to create organization"));
|
||||
keyService.makeOrgKey.mockResolvedValue([new EncString("encrypted-key"), {} as OrgKey]);
|
||||
keyService.makeKeyPair.mockResolvedValue(["key", new EncString("encrypted-key")]);
|
||||
encryptService.encryptString.mockResolvedValue(new EncString("collection-encrypyted"));
|
||||
|
||||
// Act & Assert
|
||||
await expect(sut.purchaseSubscription(subscriptionInformation)).rejects.toThrow(
|
||||
"Failed to create organization",
|
||||
);
|
||||
});
|
||||
|
||||
it("returns false when organization belongs to a provider", async () => {
|
||||
configService.getFeatureFlag$.mockReturnValue(of(true));
|
||||
const org = {
|
||||
isProviderUser: true,
|
||||
canEditSubscription: true,
|
||||
productTierType: ProductTierType.Teams,
|
||||
} as Organization;
|
||||
it("given key generation fails, then it throws an error", async () => {
|
||||
// Arrange
|
||||
const subscriptionInformation = {
|
||||
organization: { name: "test-business", billingEmail: "test@test.com" },
|
||||
plan: { type: PlanType.EnterpriseAnnually2023 },
|
||||
payment: {
|
||||
paymentMethod: ["card-token", PaymentMethodType.Card],
|
||||
billing: { postalCode: "12345" },
|
||||
},
|
||||
} as SubscriptionInformation;
|
||||
|
||||
const actual = await firstValueFrom(sut.isBreadcrumbingPoliciesEnabled$(org));
|
||||
expect(actual).toBe(false);
|
||||
});
|
||||
keyService.makeOrgKey.mockRejectedValue(new Error("Key generation failed"));
|
||||
|
||||
it("returns false when cannot edit subscription", async () => {
|
||||
configService.getFeatureFlag$.mockReturnValue(of(true));
|
||||
const org = {
|
||||
isProviderUser: false,
|
||||
canEditSubscription: false,
|
||||
productTierType: ProductTierType.Teams,
|
||||
} as Organization;
|
||||
|
||||
const actual = await firstValueFrom(sut.isBreadcrumbingPoliciesEnabled$(org));
|
||||
expect(actual).toBe(false);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["Teams", ProductTierType.Teams],
|
||||
["TeamsStarter", ProductTierType.TeamsStarter],
|
||||
])("returns true when all conditions are met with %s tier", async (_, productTierType) => {
|
||||
configService.getFeatureFlag$.mockReturnValue(of(true));
|
||||
const org = {
|
||||
isProviderUser: false,
|
||||
canEditSubscription: true,
|
||||
productTierType: productTierType,
|
||||
} as Organization;
|
||||
|
||||
const actual = await firstValueFrom(sut.isBreadcrumbingPoliciesEnabled$(org));
|
||||
expect(actual).toBe(true);
|
||||
expect(configService.getFeatureFlag$).toHaveBeenCalledWith(
|
||||
FeatureFlag.PM12276_BreadcrumbEventLogs,
|
||||
// Act & Assert
|
||||
await expect(sut.purchaseSubscription(subscriptionInformation)).rejects.toThrow(
|
||||
"Key generation failed",
|
||||
);
|
||||
});
|
||||
|
||||
it("returns false when product tier is not supported", async () => {
|
||||
configService.getFeatureFlag$.mockReturnValue(of(true));
|
||||
const org = {
|
||||
isProviderUser: false,
|
||||
canEditSubscription: true,
|
||||
productTierType: ProductTierType.Enterprise,
|
||||
} as Organization;
|
||||
it("given an invalid plan type, then it throws an error", async () => {
|
||||
// Arrange
|
||||
const subscriptionInformation = {
|
||||
organization: { name: "test-business", billingEmail: "test@test.com" },
|
||||
plan: { type: -1 as unknown as PlanType },
|
||||
payment: {
|
||||
paymentMethod: ["card-token", PaymentMethodType.Card],
|
||||
billing: { postalCode: "12345" },
|
||||
},
|
||||
} as SubscriptionInformation;
|
||||
|
||||
const actual = await firstValueFrom(sut.isBreadcrumbingPoliciesEnabled$(org));
|
||||
expect(actual).toBe(false);
|
||||
// Act & Assert
|
||||
await expect(sut.purchaseSubscription(subscriptionInformation)).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("purchaseSubscriptionNoPaymentMethod()", () => {
|
||||
it("given valid subscription information, then it returns successful response", async () => {
|
||||
//Arrange
|
||||
const subscriptionInformation = {
|
||||
organization: { name: "test-business", billingEmail: "test@test.com" },
|
||||
plan: { type: PlanType.EnterpriseAnnually2023 },
|
||||
} as SubscriptionInformation;
|
||||
|
||||
const organizationResponse = {
|
||||
name: subscriptionInformation.organization.name,
|
||||
plan: { type: subscriptionInformation.plan.type },
|
||||
planType: subscriptionInformation.plan.type,
|
||||
} as OrganizationResponse;
|
||||
|
||||
organizationApiService.createWithoutPayment.mockResolvedValue(organizationResponse);
|
||||
keyService.makeOrgKey.mockResolvedValue([new EncString("encrypted-key"), {} as OrgKey]);
|
||||
keyService.makeKeyPair.mockResolvedValue(["key", new EncString("encrypted-key")]);
|
||||
encryptService.encryptString.mockResolvedValue(new EncString("collection-encrypted"));
|
||||
|
||||
//Act
|
||||
const response = await sut.purchaseSubscriptionNoPaymentMethod(subscriptionInformation);
|
||||
|
||||
//Assert
|
||||
expect(organizationApiService.createWithoutPayment).toHaveBeenCalledTimes(1);
|
||||
expect(response).toEqual(organizationResponse);
|
||||
});
|
||||
|
||||
it("handles all conditions false correctly", async () => {
|
||||
configService.getFeatureFlag$.mockReturnValue(of(false));
|
||||
const org = {
|
||||
isProviderUser: true,
|
||||
canEditSubscription: false,
|
||||
productTierType: ProductTierType.Free,
|
||||
} as Organization;
|
||||
it("given organization creation fails without payment method, then it throws an error", async () => {
|
||||
const subscriptionInformation = {
|
||||
organization: { name: "test-business", billingEmail: "test@test.com" },
|
||||
plan: { type: PlanType.EnterpriseAnnually2023 },
|
||||
} as SubscriptionInformation;
|
||||
|
||||
const actual = await firstValueFrom(sut.isBreadcrumbingPoliciesEnabled$(org));
|
||||
expect(actual).toBe(false);
|
||||
organizationApiService.createWithoutPayment.mockRejectedValue(new Error("Creation failed"));
|
||||
keyService.makeOrgKey.mockResolvedValue([new EncString("encrypted-key"), {} as OrgKey]);
|
||||
keyService.makeKeyPair.mockResolvedValue(["key", new EncString("encrypted-key")]);
|
||||
encryptService.encryptString.mockResolvedValue(new EncString("collection-encrypted"));
|
||||
|
||||
await expect(
|
||||
sut.purchaseSubscriptionNoPaymentMethod(subscriptionInformation),
|
||||
).rejects.toThrow("Creation failed");
|
||||
});
|
||||
|
||||
it("verifies feature flag is only called once", async () => {
|
||||
configService.getFeatureFlag$.mockReturnValue(of(false));
|
||||
const org = {
|
||||
isProviderUser: false,
|
||||
canEditSubscription: true,
|
||||
productTierType: ProductTierType.Teams,
|
||||
} as Organization;
|
||||
it("given key generation fails, then it throws an error", async () => {
|
||||
const subscriptionInformation = {
|
||||
organization: { name: "test-business", billingEmail: "test@test.com" },
|
||||
plan: { type: PlanType.EnterpriseAnnually2023 },
|
||||
} as SubscriptionInformation;
|
||||
|
||||
await firstValueFrom(sut.isBreadcrumbingPoliciesEnabled$(org));
|
||||
expect(configService.getFeatureFlag$).toHaveBeenCalledTimes(1);
|
||||
keyService.makeOrgKey.mockRejectedValue(new Error("Key generation failed"));
|
||||
keyService.makeKeyPair.mockResolvedValue(["key", new EncString("encrypted-key")]);
|
||||
|
||||
await expect(
|
||||
sut.purchaseSubscriptionNoPaymentMethod(subscriptionInformation),
|
||||
).rejects.toThrow("Key generation failed");
|
||||
});
|
||||
});
|
||||
|
||||
describe("startFree()", () => {
|
||||
it("given valid free plan information, then it creates a free organization", async () => {
|
||||
const subscriptionInformation = {
|
||||
organization: { name: "test-business", billingEmail: "test@test.com" },
|
||||
plan: { type: PlanType.Free },
|
||||
} as SubscriptionInformation;
|
||||
|
||||
const organizationResponse = {
|
||||
name: subscriptionInformation.organization.name,
|
||||
billingEmail: subscriptionInformation.organization.billingEmail,
|
||||
planType: subscriptionInformation.plan.type,
|
||||
} as OrganizationResponse;
|
||||
|
||||
organizationApiService.create.mockResolvedValue(organizationResponse);
|
||||
keyService.makeOrgKey.mockResolvedValue([new EncString("encrypyted-key"), {} as OrgKey]);
|
||||
keyService.makeKeyPair.mockResolvedValue(["key", new EncString("encrypyted-key")]);
|
||||
encryptService.encryptString.mockResolvedValue(new EncString("collection-encrypyted"));
|
||||
|
||||
//Act
|
||||
const response = await sut.startFree(subscriptionInformation);
|
||||
|
||||
//Assert
|
||||
expect(organizationApiService.create).toHaveBeenCalledTimes(1);
|
||||
expect(response).toEqual(organizationResponse);
|
||||
});
|
||||
|
||||
it("given key generation fails, then it throws an error", async () => {
|
||||
const subscriptionInformation = {
|
||||
organization: { name: "test-business", billingEmail: "test@test.com" },
|
||||
plan: { type: PlanType.Free },
|
||||
} as SubscriptionInformation;
|
||||
|
||||
keyService.makeOrgKey.mockRejectedValue(new Error("Key generation failed"));
|
||||
keyService.makeKeyPair.mockResolvedValue(["key", new EncString("encrypted-key")]);
|
||||
|
||||
await expect(sut.startFree(subscriptionInformation)).rejects.toThrow("Key generation failed");
|
||||
});
|
||||
|
||||
it("given organization creation fails, then it throws an error", async () => {
|
||||
// Arrange
|
||||
const subscriptionInformation = {
|
||||
organization: { name: "test-business", billingEmail: "test@test.com" },
|
||||
plan: { type: PlanType.Free },
|
||||
} as SubscriptionInformation;
|
||||
|
||||
organizationApiService.create.mockRejectedValue(new Error("Failed to create organization"));
|
||||
keyService.makeOrgKey.mockResolvedValue([new EncString("encrypted-key"), {} as OrgKey]);
|
||||
keyService.makeKeyPair.mockResolvedValue(["key", new EncString("encrypted-key")]);
|
||||
encryptService.encryptString.mockResolvedValue(new EncString("collection-encrypyted"));
|
||||
// Act & Assert
|
||||
await expect(sut.startFree(subscriptionInformation)).rejects.toThrow(
|
||||
"Failed to create organization",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Observable, of, switchMap } from "rxjs";
|
||||
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
@@ -27,7 +22,7 @@ import {
|
||||
PlanInformation,
|
||||
SubscriptionInformation,
|
||||
} from "../abstractions";
|
||||
import { PlanType, ProductTierType } from "../enums";
|
||||
import { PlanType } from "../enums";
|
||||
import { OrganizationNoPaymentMethodCreateRequest } from "../models/request/organization-no-payment-method-create-request";
|
||||
import { PaymentSourceResponse } from "../models/response/payment-source.response";
|
||||
|
||||
@@ -47,12 +42,11 @@ export class OrganizationBillingService implements OrganizationBillingServiceAbs
|
||||
private i18nService: I18nService,
|
||||
private organizationApiService: OrganizationApiService,
|
||||
private syncService: SyncService,
|
||||
private configService: ConfigService,
|
||||
) {}
|
||||
|
||||
async getPaymentSource(organizationId: string): Promise<PaymentSourceResponse> {
|
||||
const paymentMethod = await this.billingApiService.getOrganizationPaymentMethod(organizationId);
|
||||
return paymentMethod.paymentSource;
|
||||
return paymentMethod?.paymentSource;
|
||||
}
|
||||
|
||||
async purchaseSubscription(subscription: SubscriptionInformation): Promise<OrganizationResponse> {
|
||||
@@ -229,29 +223,4 @@ export class OrganizationBillingService implements OrganizationBillingServiceAbs
|
||||
this.setPaymentInformation(request, subscription.payment);
|
||||
await this.billingApiService.restartSubscription(organizationId, request);
|
||||
}
|
||||
|
||||
isBreadcrumbingPoliciesEnabled$(organization: Organization): Observable<boolean> {
|
||||
if (organization === null || organization === undefined) {
|
||||
return of(false);
|
||||
}
|
||||
|
||||
return this.configService.getFeatureFlag$(FeatureFlag.PM12276_BreadcrumbEventLogs).pipe(
|
||||
switchMap((featureFlagEnabled) => {
|
||||
if (!featureFlagEnabled) {
|
||||
return of(false);
|
||||
}
|
||||
|
||||
if (organization.isProviderUser || !organization.canEditSubscription) {
|
||||
return of(false);
|
||||
}
|
||||
|
||||
const supportedProducts = [ProductTierType.Teams, ProductTierType.TeamsStarter];
|
||||
const isSupportedProduct = supportedProducts.some(
|
||||
(product) => product === organization.productTierType,
|
||||
);
|
||||
|
||||
return of(isSupportedProduct);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ export enum FeatureFlag {
|
||||
|
||||
/* Billing */
|
||||
TrialPaymentOptional = "PM-8163-trial-payment",
|
||||
PM12276_BreadcrumbEventLogs = "pm-12276-breadcrumbing-for-business-features",
|
||||
PM17772_AdminInitiatedSponsorships = "pm-17772-admin-initiated-sponsorships",
|
||||
UseOrganizationWarningsService = "use-organization-warnings-service",
|
||||
PM21881_ManagePaymentDetailsOutsideCheckout = "pm-21881-manage-payment-details-outside-checkout",
|
||||
@@ -95,7 +94,6 @@ export const DefaultFeatureFlagValue = {
|
||||
|
||||
/* Billing */
|
||||
[FeatureFlag.TrialPaymentOptional]: FALSE,
|
||||
[FeatureFlag.PM12276_BreadcrumbEventLogs]: FALSE,
|
||||
[FeatureFlag.PM17772_AdminInitiatedSponsorships]: FALSE,
|
||||
[FeatureFlag.UseOrganizationWarningsService]: FALSE,
|
||||
[FeatureFlag.PM21881_ManagePaymentDetailsOutsideCheckout]: FALSE,
|
||||
|
||||
Reference in New Issue
Block a user