1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-12 22:44:11 +00:00

[PM-29262] - improve performance of premium spotlight observable (#18490)

* improve performance of premium spotlight observable

* re-add comment

* fix test. remove unused service
This commit is contained in:
Jordan Aasen
2026-01-29 09:33:08 -08:00
committed by GitHub
parent b8bed69b23
commit 7b0957ab5e
3 changed files with 35 additions and 49 deletions

View File

@@ -9,7 +9,6 @@ import { BehaviorSubject, Observable, Subject, of } from "rxjs";
import { PremiumUpgradeDialogComponent } from "@bitwarden/angular/billing/components";
import { NudgeType, NudgesService } from "@bitwarden/angular/vault";
import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-profile.service";
import {
AutoConfirmExtensionSetupDialogComponent,
AutomaticUserConfirmationService,
@@ -185,7 +184,7 @@ describe("VaultV2Component", () => {
filterVisibilityState$: new BehaviorSubject<any>({}),
} as Partial<VaultPopupListFiltersService>;
const accountActive$ = new BehaviorSubject<FakeAccount | null>({ id: "user-1" });
const activeAccount$ = new BehaviorSubject<FakeAccount | null>({ id: "user-1" });
const cipherSvc = {
failedToDecryptCiphers$: jest.fn().mockReturnValue(of([])),
@@ -222,12 +221,6 @@ describe("VaultV2Component", () => {
hasPremiumFromAnySource$: (_: string) => hasPremiumFromAnySource$,
};
const vaultProfileSvc = {
getProfileCreationDate: jest
.fn()
.mockResolvedValue(new Date(Date.now() - 8 * 24 * 60 * 60 * 1000)), // 8 days ago
};
const configSvc = {
getFeatureFlag$: jest.fn().mockImplementation((_flag: string) => of(false)),
};
@@ -250,16 +243,12 @@ describe("VaultV2Component", () => {
{ provide: VaultPopupScrollPositionService, useValue: scrollSvc },
{
provide: AccountService,
useValue: { activeAccount$: accountActive$ },
useValue: { activeAccount$ },
},
{ provide: CipherService, useValue: cipherSvc },
{ provide: DialogService, useValue: dialogSvc },
{ provide: IntroCarouselService, useValue: introSvc },
{ provide: NudgesService, useValue: nudgesSvc },
{
provide: VaultProfileService,
useValue: vaultProfileSvc,
},
{
provide: VaultPopupCopyButtonsService,
useValue: { showQuickCopyActions$: new BehaviorSubject<boolean>(false) },
@@ -473,7 +462,7 @@ describe("VaultV2Component", () => {
it("dismissVaultNudgeSpotlight forwards to NudgesService with active user id", fakeAsync(() => {
const spy = jest.spyOn(nudgesSvc, "dismissNudge").mockResolvedValue(undefined);
accountActive$.next({ id: "user-xyz" });
activeAccount$.next({ id: "user-xyz" });
void component.ngOnInit();
tick();
@@ -485,6 +474,10 @@ describe("VaultV2Component", () => {
}));
it("accountAgeInDays$ computes integer days since creation", (done) => {
activeAccount$.next({
id: "user-123",
creationDate: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), // 7 days ago
} as any);
getObs<number | null>(component, "accountAgeInDays$").subscribe((days) => {
if (days !== null) {
expect(days).toBeGreaterThanOrEqual(7);
@@ -570,10 +563,6 @@ describe("VaultV2Component", () => {
itemsSvc.cipherCount$.next(10);
hasPremiumFromAnySource$.next(false);
vaultProfileSvc.getProfileCreationDate = jest
.fn()
.mockResolvedValue(new Date(Date.now() - 3 * 24 * 60 * 60 * 1000)); // 3 days ago
(nudgesSvc.showNudgeSpotlight$ as jest.Mock).mockImplementation((type: NudgeType) => {
return of(type === NudgeType.PremiumUpgrade);
});

View File

@@ -5,26 +5,24 @@ import { Component, DestroyRef, effect, inject, OnDestroy, OnInit } from "@angul
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { Router, RouterModule } from "@angular/router";
import {
BehaviorSubject,
combineLatest,
distinctUntilChanged,
filter,
firstValueFrom,
from,
map,
Observable,
shareReplay,
switchMap,
take,
withLatestFrom,
tap,
BehaviorSubject,
withLatestFrom,
} from "rxjs";
import { PremiumUpgradeDialogComponent } from "@bitwarden/angular/billing/components";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { NudgesService, NudgeType } from "@bitwarden/angular/vault";
import { SpotlightComponent } from "@bitwarden/angular/vault/components/spotlight/spotlight.component";
import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-profile.service";
import { DeactivatedOrg, NoResults, VaultOpen } from "@bitwarden/assets/svg";
import {
AutoConfirmExtensionSetupDialogComponent,
@@ -162,10 +160,6 @@ export class VaultV2Component implements OnInit, OnDestroy {
FeatureFlag.BrowserPremiumSpotlight,
);
private showPremiumNudgeSpotlight$ = this.activeUserId$.pipe(
switchMap((userId) => this.nudgesService.showNudgeSpotlight$(NudgeType.PremiumUpgrade, userId)),
);
protected favoriteCiphers$ = this.vaultPopupItemsService.favoriteCiphers$;
protected remainingCiphers$ = this.vaultPopupItemsService.remainingCiphers$;
protected allFilters$ = this.vaultPopupListFiltersService.allFilters$;
@@ -173,38 +167,39 @@ export class VaultV2Component implements OnInit, OnDestroy {
protected hasPremium$ = this.activeUserId$.pipe(
switchMap((userId) => this.billingAccountService.hasPremiumFromAnySource$(userId)),
);
protected accountAgeInDays$ = this.activeUserId$.pipe(
switchMap((userId) => {
const creationDate$ = from(this.vaultProfileService.getProfileCreationDate(userId));
return creationDate$.pipe(
map((creationDate) => {
if (!creationDate) {
return 0;
}
const ageInMilliseconds = Date.now() - creationDate.getTime();
return Math.floor(ageInMilliseconds / (1000 * 60 * 60 * 24));
}),
);
protected accountAgeInDays$ = this.accountService.activeAccount$.pipe(
map((account) => {
if (!account || !account.creationDate) {
return 0;
}
const creationDate = account.creationDate;
const ageInMilliseconds = Date.now() - creationDate.getTime();
return Math.floor(ageInMilliseconds / (1000 * 60 * 60 * 24));
}),
);
protected showPremiumSpotlight$ = combineLatest([
this.premiumSpotlightFeatureFlag$,
this.showPremiumNudgeSpotlight$,
this.activeUserId$.pipe(
switchMap((userId) =>
this.nudgesService.showNudgeSpotlight$(NudgeType.PremiumUpgrade, userId),
),
),
this.showHasItemsVaultSpotlight$,
this.hasPremium$,
this.cipherCount$,
this.accountAgeInDays$,
]).pipe(
map(
([featureFlagEnabled, showPremiumNudge, showHasItemsNudge, hasPremium, count, age]) =>
map(([featureFlagEnabled, showPremiumNudge, showHasItemsNudge, hasPremium, count, age]) => {
return (
featureFlagEnabled &&
showPremiumNudge &&
!showHasItemsNudge &&
!hasPremium &&
count >= 5 &&
age >= 7,
),
age >= 7
);
}),
shareReplay({ bufferSize: 1, refCount: true }),
);
@@ -263,7 +258,6 @@ export class VaultV2Component implements OnInit, OnDestroy {
private router: Router,
private autoConfirmService: AutomaticUserConfirmationService,
private toastService: ToastService,
private vaultProfileService: VaultProfileService,
private billingAccountService: BillingAccountProfileStateService,
private liveAnnouncer: LiveAnnouncer,
private i18nService: I18nService,

View File

@@ -1,8 +1,8 @@
import { inject, Injectable } from "@angular/core";
import { combineLatest, from, Observable, of, switchMap } from "rxjs";
import { catchError } from "rxjs/operators";
import { combineLatest, Observable, of, switchMap } from "rxjs";
import { catchError, map } from "rxjs/operators";
import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-profile.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
@@ -20,11 +20,14 @@ const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;
})
export class HasItemsNudgeService extends DefaultSingleNudgeService {
cipherService = inject(CipherService);
vaultProfileService = inject(VaultProfileService);
accountService = inject(AccountService);
logService = inject(LogService);
nudgeStatus$(nudgeType: NudgeType, userId: UserId): Observable<NudgeStatus> {
const profileDate$ = from(this.vaultProfileService.getProfileCreationDate(userId)).pipe(
const profileDate$ = this.accountService.activeAccount$.pipe(
map((account) => {
return account?.creationDate ?? new Date();
}),
catchError(() => {
this.logService.error("Error getting profile creation date");
// Default to today to ensure we show the nudge