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:
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user