1
0
mirror of https://github.com/bitwarden/browser synced 2026-01-21 11:53:34 +00:00

[PM-29061] Remove Feature Flag 24996 (#18009)

* refactor(billing): remove FF from vault banner

* refactor(billing): remove from prompt service

* chore(billing): remove feature flag

* fix(billing): remove premium banner

* tests(billing): remove premium banner tests

* chore(vault): clean up premium banner dependencies

* fix(billing): revert formatting

* fix(billing): revert formatting

* fix(billing): remove old FF

* fix(billling): revert formatting
This commit is contained in:
Stephon Brown
2026-01-07 12:19:54 -05:00
committed by GitHub
parent 1f763f470a
commit fa45110420
9 changed files with 17 additions and 363 deletions

View File

@@ -5,8 +5,6 @@ import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-pro
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService, Account } from "@bitwarden/common/auth/abstractions/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { SyncService } from "@bitwarden/common/platform/sync/sync.service";
@@ -26,7 +24,6 @@ import {
describe("UnifiedUpgradePromptService", () => {
let sut: UnifiedUpgradePromptService;
const mockAccountService = mock<AccountService>();
const mockConfigService = mock<ConfigService>();
const mockBillingService = mock<BillingAccountProfileStateService>();
const mockVaultProfileService = mock<VaultProfileService>();
const mockSyncService = mock<SyncService>();
@@ -59,7 +56,6 @@ describe("UnifiedUpgradePromptService", () => {
function setupTestService() {
sut = new UnifiedUpgradePromptService(
mockAccountService,
mockConfigService,
mockBillingService,
mockVaultProfileService,
mockSyncService,
@@ -80,7 +76,6 @@ describe("UnifiedUpgradePromptService", () => {
beforeEach(() => {
mockAccountService.activeAccount$ = accountSubject.asObservable();
mockPlatformUtilsService.isSelfHost.mockReturnValue(false);
mockConfigService.getFeatureFlag$.mockReturnValue(of(true));
mockStateProvider.getUserState$.mockReturnValue(of(false));
setupTestService();
@@ -96,7 +91,6 @@ describe("UnifiedUpgradePromptService", () => {
mockAccountService.activeAccount$ = accountSubject.asObservable();
mockDialogOpen.mockReset();
mockReset(mockDialogService);
mockReset(mockConfigService);
mockReset(mockBillingService);
mockReset(mockVaultProfileService);
mockReset(mockSyncService);
@@ -112,11 +106,10 @@ describe("UnifiedUpgradePromptService", () => {
mockStateProvider.getUserState$.mockReturnValue(of(false));
mockStateProvider.setUserState.mockResolvedValue(undefined);
});
it("should subscribe to account and feature flag observables when checking display conditions", async () => {
it("should subscribe to account observables when checking display conditions", async () => {
// Arrange
mockPlatformUtilsService.isSelfHost.mockReturnValue(false);
mockOrganizationService.memberOrganizations$.mockReturnValue(of([]));
mockConfigService.getFeatureFlag$.mockReturnValue(of(false));
mockBillingService.hasPremiumFromAnySource$.mockReturnValue(of(false));
setupTestService();
@@ -125,34 +118,12 @@ describe("UnifiedUpgradePromptService", () => {
await sut.displayUpgradePromptConditionally();
// Assert
expect(mockConfigService.getFeatureFlag$).toHaveBeenCalledWith(
FeatureFlag.PM24996_ImplementUpgradeFromFreeDialog,
);
expect(mockAccountService.activeAccount$).toBeDefined();
});
it("should not show dialog when feature flag is disabled", async () => {
// Arrange
mockPlatformUtilsService.isSelfHost.mockReturnValue(false);
mockOrganizationService.memberOrganizations$.mockReturnValue(of([]));
mockConfigService.getFeatureFlag$.mockReturnValue(of(false));
mockBillingService.hasPremiumFromAnySource$.mockReturnValue(of(false));
const recentDate = new Date();
recentDate.setMinutes(recentDate.getMinutes() - 3); // 3 minutes old
mockVaultProfileService.getProfileCreationDate.mockResolvedValue(recentDate);
setupTestService();
// Act
const result = await sut.displayUpgradePromptConditionally();
// Assert
expect(result).toBeNull();
expect(mockDialogOpen).not.toHaveBeenCalled();
});
it("should not show dialog when user has premium", async () => {
// Arrange
mockPlatformUtilsService.isSelfHost.mockReturnValue(false);
mockConfigService.getFeatureFlag$.mockReturnValue(of(true));
mockBillingService.hasPremiumFromAnySource$.mockReturnValue(of(true));
mockOrganizationService.memberOrganizations$.mockReturnValue(of([]));
setupTestService();
@@ -167,7 +138,6 @@ describe("UnifiedUpgradePromptService", () => {
it("should not show dialog when user has any organization membership", async () => {
// Arrange
mockConfigService.getFeatureFlag$.mockReturnValue(of(true));
mockBillingService.hasPremiumFromAnySource$.mockReturnValue(of(false));
mockOrganizationService.memberOrganizations$.mockReturnValue(of([{ id: "org1" } as any]));
mockPlatformUtilsService.isSelfHost.mockReturnValue(false);
@@ -183,7 +153,6 @@ describe("UnifiedUpgradePromptService", () => {
it("should not show dialog when profile is older than 5 minutes", async () => {
// Arrange
mockConfigService.getFeatureFlag$.mockReturnValue(of(true));
mockBillingService.hasPremiumFromAnySource$.mockReturnValue(of(false));
mockOrganizationService.memberOrganizations$.mockReturnValue(of([]));
const oldDate = new Date();
@@ -202,7 +171,6 @@ describe("UnifiedUpgradePromptService", () => {
it("should show dialog when all conditions are met", async () => {
//Arrange
mockConfigService.getFeatureFlag$.mockReturnValue(of(true));
mockBillingService.hasPremiumFromAnySource$.mockReturnValue(of(false));
mockOrganizationService.memberOrganizations$.mockReturnValue(of([]));
const recentDate = new Date();
@@ -224,7 +192,6 @@ describe("UnifiedUpgradePromptService", () => {
it("should not show dialog when account is null/undefined", async () => {
// Arrange
mockConfigService.getFeatureFlag$.mockReturnValue(of(true));
accountSubject.next(null); // Set account to null
setupTestService();
@@ -238,7 +205,6 @@ describe("UnifiedUpgradePromptService", () => {
it("should not show dialog when profile creation date is unavailable", async () => {
// Arrange
mockConfigService.getFeatureFlag$.mockReturnValue(of(true));
mockBillingService.hasPremiumFromAnySource$.mockReturnValue(of(false));
mockOrganizationService.memberOrganizations$.mockReturnValue(of([]));
mockVaultProfileService.getProfileCreationDate.mockResolvedValue(null);
@@ -256,7 +222,6 @@ describe("UnifiedUpgradePromptService", () => {
it("should not show dialog when running in self-hosted environment", async () => {
// Arrange
mockConfigService.getFeatureFlag$.mockReturnValue(of(true));
mockOrganizationService.memberOrganizations$.mockReturnValue(of([]));
mockBillingService.hasPremiumFromAnySource$.mockReturnValue(of(false));
const recentDate = new Date();
@@ -275,7 +240,6 @@ describe("UnifiedUpgradePromptService", () => {
it("should not show dialog when user has previously dismissed the modal", async () => {
// Arrange
mockConfigService.getFeatureFlag$.mockReturnValue(of(true));
mockBillingService.hasPremiumFromAnySource$.mockReturnValue(of(false));
mockOrganizationService.memberOrganizations$.mockReturnValue(of([]));
const recentDate = new Date();
@@ -295,7 +259,6 @@ describe("UnifiedUpgradePromptService", () => {
it("should save dismissal state when user closes the dialog", async () => {
// Arrange
mockConfigService.getFeatureFlag$.mockReturnValue(of(true));
mockBillingService.hasPremiumFromAnySource$.mockReturnValue(of(false));
mockOrganizationService.memberOrganizations$.mockReturnValue(of([]));
const recentDate = new Date();
@@ -320,7 +283,6 @@ describe("UnifiedUpgradePromptService", () => {
it("should not save dismissal state when user upgrades to premium", async () => {
// Arrange
mockConfigService.getFeatureFlag$.mockReturnValue(of(true));
mockBillingService.hasPremiumFromAnySource$.mockReturnValue(of(false));
mockOrganizationService.memberOrganizations$.mockReturnValue(of([]));
const recentDate = new Date();

View File

@@ -6,8 +6,6 @@ import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-pro
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { SyncService } from "@bitwarden/common/platform/sync/sync.service";
@@ -38,7 +36,6 @@ export class UnifiedUpgradePromptService {
private unifiedUpgradeDialogRef: DialogRef<UnifiedUpgradeDialogResult> | null = null;
constructor(
private accountService: AccountService,
private configService: ConfigService,
private billingAccountProfileStateService: BillingAccountProfileStateService,
private vaultProfileService: VaultProfileService,
private syncService: SyncService,
@@ -70,26 +67,13 @@ export class UnifiedUpgradePromptService {
isProfileLessThanFiveMinutesOld$,
hasOrganizations$,
this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id),
this.configService.getFeatureFlag$(FeatureFlag.PM24996_ImplementUpgradeFromFreeDialog),
hasDismissedModal$,
]).pipe(
map(
([
isProfileLessThanFiveMinutesOld,
hasOrganizations,
hasPremium,
isFlagEnabled,
hasDismissed,
]) => {
return (
isProfileLessThanFiveMinutesOld &&
!hasOrganizations &&
!hasPremium &&
isFlagEnabled &&
!hasDismissed
);
},
),
map(([isProfileLessThanFiveMinutesOld, hasOrganizations, hasPremium, hasDismissed]) => {
return (
isProfileLessThanFiveMinutesOld && !hasOrganizations && !hasPremium && !hasDismissed
);
}),
);
}),
take(1),

View File

@@ -1,5 +1,5 @@
import { TestBed } from "@angular/core/testing";
import { BehaviorSubject, firstValueFrom, take, timeout } from "rxjs";
import { BehaviorSubject } from "rxjs";
import { AuthRequestServiceAbstraction } from "@bitwarden/auth/common";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
@@ -17,11 +17,7 @@ import {
import { UserId } from "@bitwarden/common/types/guid";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import {
PREMIUM_BANNER_REPROMPT_KEY,
VaultBannersService,
VisibleVaultBanner,
} from "./vault-banners.service";
import { VaultBannersService, VisibleVaultBanner } from "./vault-banners.service";
describe("VaultBannersService", () => {
let service: VaultBannersService;
@@ -79,101 +75,6 @@ describe("VaultBannersService", () => {
jest.useRealTimers();
});
describe("Premium", () => {
it("waits until sync is completed before showing premium banner", async () => {
hasPremiumFromAnySource$.next(false);
isSelfHost.mockReturnValue(false);
lastSync$.next(null);
service = TestBed.inject(VaultBannersService);
const premiumBanner$ = service.shouldShowPremiumBanner$(userId);
// Should not emit when sync is null
await expect(firstValueFrom(premiumBanner$.pipe(take(1), timeout(100)))).rejects.toThrow();
// Should emit when sync is completed
lastSync$.next(new Date("2024-05-14"));
expect(await firstValueFrom(premiumBanner$)).toBe(true);
});
it("does not show a premium banner for self-hosted users", async () => {
hasPremiumFromAnySource$.next(false);
isSelfHost.mockReturnValue(true);
service = TestBed.inject(VaultBannersService);
expect(await firstValueFrom(service.shouldShowPremiumBanner$(userId))).toBe(false);
});
it("does not show a premium banner when they have access to premium", async () => {
hasPremiumFromAnySource$.next(true);
isSelfHost.mockReturnValue(false);
service = TestBed.inject(VaultBannersService);
expect(await firstValueFrom(service.shouldShowPremiumBanner$(userId))).toBe(false);
});
describe("dismissing", () => {
beforeEach(async () => {
jest.useFakeTimers();
const date = new Date("2023-06-08");
date.setHours(0, 0, 0, 0);
jest.setSystemTime(date.getTime());
service = TestBed.inject(VaultBannersService);
await service.dismissBanner(userId, VisibleVaultBanner.Premium);
});
afterEach(() => {
jest.useRealTimers();
});
it("updates state on first dismiss", async () => {
const state = await firstValueFrom(
fakeStateProvider.getUser(userId, PREMIUM_BANNER_REPROMPT_KEY).state$,
);
const oneWeekLater = new Date("2023-06-15");
oneWeekLater.setHours(0, 0, 0, 0);
expect(state).toEqual({
numberOfDismissals: 1,
nextPromptDate: oneWeekLater.getTime(),
});
});
it("updates state on second dismiss", async () => {
const state = await firstValueFrom(
fakeStateProvider.getUser(userId, PREMIUM_BANNER_REPROMPT_KEY).state$,
);
const oneMonthLater = new Date("2023-07-08");
oneMonthLater.setHours(0, 0, 0, 0);
expect(state).toEqual({
numberOfDismissals: 2,
nextPromptDate: oneMonthLater.getTime(),
});
});
it("updates state on third dismiss", async () => {
const state = await firstValueFrom(
fakeStateProvider.getUser(userId, PREMIUM_BANNER_REPROMPT_KEY).state$,
);
const oneYearLater = new Date("2024-06-08");
oneYearLater.setHours(0, 0, 0, 0);
expect(state).toEqual({
numberOfDismissals: 3,
nextPromptDate: oneYearLater.getTime(),
});
});
});
});
describe("OutdatedBrowser", () => {
beforeEach(async () => {
// Hardcode `MSIE` in userAgent string

View File

@@ -1,5 +1,5 @@
import { Injectable } from "@angular/core";
import { Observable, combineLatest, firstValueFrom, map, filter, mergeMap, take } from "rxjs";
import { firstValueFrom, map } from "rxjs";
import { AuthRequestServiceAbstraction } from "@bitwarden/auth/common";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
@@ -7,7 +7,6 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abs
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import {
StateProvider,
PREMIUM_BANNER_DISK_LOCAL,
BANNERS_DISMISSED_DISK,
UserKeyDefinition,
SingleUserState,
@@ -18,30 +17,14 @@ import { UnionOfValues } from "@bitwarden/common/vault/types/union-of-values";
export const VisibleVaultBanner = {
OutdatedBrowser: "outdated-browser",
Premium: "premium",
VerifyEmail: "verify-email",
PendingAuthRequest: "pending-auth-request",
} as const;
export type VisibleVaultBanner = UnionOfValues<typeof VisibleVaultBanner>;
type PremiumBannerReprompt = {
numberOfDismissals: number;
/** Timestamp representing when to show the prompt next */
nextPromptDate: number;
};
/** Banners that will be re-shown on a new session */
type SessionBanners = Omit<VisibleVaultBanner, typeof VisibleVaultBanner.Premium>;
export const PREMIUM_BANNER_REPROMPT_KEY = new UserKeyDefinition<PremiumBannerReprompt>(
PREMIUM_BANNER_DISK_LOCAL,
"bannerReprompt",
{
deserializer: (bannerReprompt) => bannerReprompt,
clearOn: [], // Do not clear user tutorials
},
);
type SessionBanners = VisibleVaultBanner;
export const BANNERS_DISMISSED_DISK_KEY = new UserKeyDefinition<SessionBanners[]>(
BANNERS_DISMISSED_DISK,
@@ -76,33 +59,6 @@ export class VaultBannersService {
return pendingAuthRequests.length > 0 && !alreadyDismissed;
}
shouldShowPremiumBanner$(userId: UserId): Observable<boolean> {
const premiumBannerState = this.premiumBannerState(userId);
const premiumSources$ = combineLatest([
this.billingAccountProfileStateService.hasPremiumFromAnySource$(userId),
premiumBannerState.state$,
]);
return this.syncService.lastSync$(userId).pipe(
filter((lastSync) => lastSync !== null),
take(1), // Wait until the first sync is complete before considering the premium status
mergeMap(() => premiumSources$),
map(([canAccessPremium, dismissedState]) => {
const shouldShowPremiumBanner =
!canAccessPremium && !this.platformUtilsService.isSelfHost();
// Check if nextPromptDate is in the past passed
if (shouldShowPremiumBanner && dismissedState?.nextPromptDate) {
const nextPromptDate = new Date(dismissedState.nextPromptDate);
const now = new Date();
return now >= nextPromptDate;
}
return shouldShowPremiumBanner;
}),
);
}
/** Returns true when the update browser banner should be shown */
async shouldShowUpdateBrowserBanner(userId: UserId): Promise<boolean> {
const outdatedBrowser = window.navigator.userAgent.indexOf("MSIE") !== -1;
@@ -128,23 +84,11 @@ export class VaultBannersService {
/** Dismiss the given banner and perform any respective side effects */
async dismissBanner(userId: UserId, banner: SessionBanners): Promise<void> {
if (banner === VisibleVaultBanner.Premium) {
await this.dismissPremiumBanner(userId);
} else {
await this.sessionBannerState(userId).update((current) => {
const bannersDismissed = current ?? [];
await this.sessionBannerState(userId).update((current) => {
const bannersDismissed = current ?? [];
return [...bannersDismissed, banner];
});
}
}
/**
*
* @returns a SingleUserState for the premium banner reprompt state
*/
private premiumBannerState(userId: UserId): SingleUserState<PremiumBannerReprompt> {
return this.stateProvider.getUser(userId, PREMIUM_BANNER_REPROMPT_KEY);
return [...bannersDismissed, banner];
});
}
/**
@@ -161,42 +105,4 @@ export class VaultBannersService {
// use nullish coalescing to default to an empty array
return (await firstValueFrom(this.sessionBannerState(userId).state$)) ?? [];
}
/** Increment dismissal state of the premium banner */
private async dismissPremiumBanner(userId: UserId): Promise<void> {
await this.premiumBannerState(userId).update((current) => {
const numberOfDismissals = current?.numberOfDismissals ?? 0;
const now = new Date();
// Set midnight of the current day
now.setHours(0, 0, 0, 0);
// First dismissal, re-prompt in 1 week
if (numberOfDismissals === 0) {
now.setDate(now.getDate() + 7);
return {
numberOfDismissals: 1,
nextPromptDate: now.getTime(),
};
}
// Second dismissal, re-prompt in 1 month
if (numberOfDismissals === 1) {
now.setMonth(now.getMonth() + 1);
return {
numberOfDismissals: 2,
nextPromptDate: now.getTime(),
};
}
// 3+ dismissals, re-prompt each year
// Avoid day/month edge cases and only increment year
const nextYear = new Date(now.getFullYear() + 1, now.getMonth(), now.getDate());
nextYear.setHours(0, 0, 0, 0);
return {
numberOfDismissals: numberOfDismissals + 1,
nextPromptDate: nextYear.getTime(),
};
});
}
}

View File

@@ -44,15 +44,3 @@
(onDismiss)="dismissBanner(VisibleVaultBanner.VerifyEmail)"
(onVerified)="dismissBanner(VisibleVaultBanner.VerifyEmail)"
></app-verify-email>
<bit-banner
id="premium-banner"
bannerType="premium"
*ngIf="premiumBannerVisible$ | async"
(onClose)="dismissBanner(VisibleVaultBanner.Premium)"
>
{{ "premiumUpgradeUnlockFeatures" | i18n }}
<a bitLink linkType="secondary" routerLink="/settings/subscription/premium">
{{ "goPremium" | i18n }}
</a>
</bit-banner>

View File

@@ -1,5 +1,4 @@
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { By } from "@angular/platform-browser";
import { RouterTestingModule } from "@angular/router/testing";
import { mock } from "jest-mock-extended";
import { BehaviorSubject, Subject } from "rxjs";
@@ -8,15 +7,13 @@ import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.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 { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { MessageListener } from "@bitwarden/common/platform/messaging";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { BannerComponent, BannerModule } from "@bitwarden/components";
import { BannerModule } from "@bitwarden/components";
import { VerifyEmailComponent } from "../../../auth/settings/verify-email.component";
import { SharedModule } from "../../../shared";
@@ -30,11 +27,9 @@ describe("VaultBannersComponent", () => {
let messageSubject: Subject<{ command: string }>;
const premiumBanner$ = new BehaviorSubject<boolean>(false);
const pendingAuthRequest$ = new BehaviorSubject<boolean>(false);
const PM24996_ImplementUpgradeFromFreeDialogFlag$ = new BehaviorSubject<boolean>(false);
const mockUserId = Utils.newGuid() as UserId;
const bannerService = mock<VaultBannersService>({
shouldShowPremiumBanner$: jest.fn((userId: UserId) => premiumBanner$),
shouldShowUpdateBrowserBanner: jest.fn(),
shouldShowVerifyEmailBanner: jest.fn(),
shouldShowPendingAuthRequestBanner: jest.fn((userId: UserId) =>
@@ -88,17 +83,6 @@ describe("VaultBannersComponent", () => {
allMessages$: messageSubject.asObservable(),
}),
},
{
provide: ConfigService,
useValue: mock<ConfigService>({
getFeatureFlag$: jest.fn((flag: FeatureFlag) => {
if (flag === FeatureFlag.PM24996_ImplementUpgradeFromFreeDialog) {
return PM24996_ImplementUpgradeFromFreeDialogFlag$;
}
return new BehaviorSubject(false);
}),
}),
},
],
})
.overrideProvider(VaultBannersService, { useValue: bannerService })
@@ -112,53 +96,6 @@ describe("VaultBannersComponent", () => {
fixture.detectChanges();
});
describe("premiumBannerVisible$", () => {
beforeEach(() => {
// Reset feature flag to default (false) before each test
PM24996_ImplementUpgradeFromFreeDialogFlag$.next(false);
});
it("shows premium banner when shouldShowPremiumBanner is true and feature flag is off", async () => {
premiumBanner$.next(true);
PM24996_ImplementUpgradeFromFreeDialogFlag$.next(false);
fixture.detectChanges();
const banner = fixture.debugElement.query(By.directive(BannerComponent));
expect(banner.componentInstance.bannerType()).toBe("premium");
});
it("hides premium banner when feature flag is enabled", async () => {
premiumBanner$.next(true);
PM24996_ImplementUpgradeFromFreeDialogFlag$.next(true);
fixture.detectChanges();
const banner = fixture.debugElement.query(By.directive(BannerComponent));
expect(banner).toBeNull();
});
it("dismisses premium banner when shouldShowPremiumBanner is false", async () => {
premiumBanner$.next(false);
PM24996_ImplementUpgradeFromFreeDialogFlag$.next(false);
fixture.detectChanges();
const banner = fixture.debugElement.query(By.directive(BannerComponent));
expect(banner).toBeNull();
});
it("hides premium banner when both shouldShowPremiumBanner is false and feature flag is enabled", async () => {
premiumBanner$.next(false);
PM24996_ImplementUpgradeFromFreeDialogFlag$.next(true);
fixture.detectChanges();
const banner = fixture.debugElement.query(By.directive(BannerComponent));
expect(banner).toBeNull();
});
});
describe("determineVisibleBanner", () => {
[
{

View File

@@ -1,14 +1,11 @@
import { Component, Input, OnInit } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { Router } from "@angular/router";
import { combineLatest, filter, firstValueFrom, map, Observable, switchMap } from "rxjs";
import { filter, firstValueFrom, map } from "rxjs";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { MessageListener } from "@bitwarden/common/platform/messaging";
import { UserId } from "@bitwarden/common/types/guid";
import { BannerModule } from "@bitwarden/components";
import { OrganizationFreeTrialWarningComponent } from "@bitwarden/web-vault/app/billing/organizations/warnings/components";
@@ -32,7 +29,6 @@ import { VaultBannersService, VisibleVaultBanner } from "./services/vault-banner
})
export class VaultBannersComponent implements OnInit {
visibleBanners: VisibleVaultBanner[] = [];
premiumBannerVisible$: Observable<boolean>;
VisibleVaultBanner = VisibleVaultBanner;
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@@ -45,23 +41,7 @@ export class VaultBannersComponent implements OnInit {
private router: Router,
private accountService: AccountService,
private messageListener: MessageListener,
private configService: ConfigService,
) {
this.premiumBannerVisible$ = this.activeUserId$.pipe(
filter((userId): userId is UserId => userId != null),
switchMap((userId) =>
combineLatest([
this.vaultBannerService.shouldShowPremiumBanner$(userId),
this.configService.getFeatureFlag$(FeatureFlag.PM24996_ImplementUpgradeFromFreeDialog),
]).pipe(
map(
([shouldShowBanner, PM24996_ImplementUpgradeFromFreeDialogEnabled]) =>
shouldShowBanner && !PM24996_ImplementUpgradeFromFreeDialogEnabled,
),
),
),
);
// Listen for auth request messages and show banner immediately
this.messageListener.allMessages$
.pipe(

View File

@@ -27,7 +27,6 @@ export enum FeatureFlag {
TrialPaymentOptional = "PM-8163-trial-payment",
PM24032_NewNavigationPremiumUpgradeButton = "pm-24032-new-navigation-premium-upgrade-button",
PM25379_UseNewOrganizationMetadataStructure = "pm-25379-use-new-organization-metadata-structure",
PM24996_ImplementUpgradeFromFreeDialog = "pm-24996-implement-upgrade-from-free-dialog",
PM26793_FetchPremiumPriceFromPricingService = "pm-26793-fetch-premium-price-from-pricing-service",
PM23713_PremiumBadgeOpensNewPremiumUpgradeDialog = "pm-23713-premium-badge-opens-new-premium-upgrade-dialog",
PM26462_Milestone_3 = "pm-26462-milestone-3",
@@ -137,7 +136,6 @@ export const DefaultFeatureFlagValue = {
[FeatureFlag.TrialPaymentOptional]: FALSE,
[FeatureFlag.PM24032_NewNavigationPremiumUpgradeButton]: FALSE,
[FeatureFlag.PM25379_UseNewOrganizationMetadataStructure]: FALSE,
[FeatureFlag.PM24996_ImplementUpgradeFromFreeDialog]: FALSE,
[FeatureFlag.PM26793_FetchPremiumPriceFromPricingService]: FALSE,
[FeatureFlag.PM23713_PremiumBadgeOpensNewPremiumUpgradeDialog]: FALSE,
[FeatureFlag.PM26462_Milestone_3]: FALSE,

View File

@@ -198,9 +198,7 @@ export const CIPHERS_DISK_LOCAL = new StateDefinition("ciphersLocal", "disk", {
export const CIPHERS_MEMORY = new StateDefinition("ciphersMemory", "memory", {
browser: "memory-large-object",
});
export const PREMIUM_BANNER_DISK_LOCAL = new StateDefinition("premiumBannerReprompt", "disk", {
web: "disk-local",
});
export const BANNERS_DISMISSED_DISK = new StateDefinition("bannersDismissed", "disk");
export const VAULT_APPEARANCE = new StateDefinition("vaultAppearance", "disk");
export const SECURITY_TASKS_DISK = new StateDefinition("securityTasks", "disk");