1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00

[PM-21060] Refactor Has Nudges Service (#14653)

* refactor has nudges service to be its own Observable inside the vault nudges service.
This commit is contained in:
Jason Ng
2025-05-07 13:12:46 -04:00
committed by GitHub
parent cbe1c378ec
commit daaf81ec36
5 changed files with 107 additions and 93 deletions

View File

@@ -1,53 +1,70 @@
import { Component } from "@angular/core";
import { combineLatest, map } from "rxjs";
import { Component, OnInit } from "@angular/core";
import { combineLatest, firstValueFrom, map, Observable } from "rxjs";
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 { HasNudgeService } from "@bitwarden/vault";
import { UserId } from "@bitwarden/common/types/guid";
import { VaultNudgesService } from "@bitwarden/vault";
@Component({
selector: "app-tabs-v2",
templateUrl: "./tabs-v2.component.html",
providers: [HasNudgeService],
})
export class TabsV2Component {
export class TabsV2Component implements OnInit {
private activeUserId: UserId | null = null;
protected navButtons$: Observable<
{
label: string;
page: string;
iconKey: string;
iconKeyActive: string;
showBerry?: boolean;
}[]
> = new Observable();
constructor(
private readonly hasNudgeService: HasNudgeService,
private vaultNudgesService: VaultNudgesService,
private accountService: AccountService,
private readonly configService: ConfigService,
) {}
protected navButtons$ = combineLatest([
this.configService.getFeatureFlag$(FeatureFlag.PM8851_BrowserOnboardingNudge),
this.hasNudgeService.nudgeStatus$(),
]).pipe(
map(([onboardingFeatureEnabled, nudgeStatus]) => {
return [
{
label: "vault",
page: "/tabs/vault",
iconKey: "lock",
iconKeyActive: "lock-f",
},
{
label: "generator",
page: "/tabs/generator",
iconKey: "generate",
iconKeyActive: "generate-f",
},
{
label: "send",
page: "/tabs/send",
iconKey: "send",
iconKeyActive: "send-f",
},
{
label: "settings",
page: "/tabs/settings",
iconKey: "cog",
iconKeyActive: "cog-f",
showBerry: onboardingFeatureEnabled && !nudgeStatus.hasSpotlightDismissed,
},
];
}),
);
async ngOnInit() {
this.activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
this.navButtons$ = combineLatest([
this.configService.getFeatureFlag$(FeatureFlag.PM8851_BrowserOnboardingNudge),
this.vaultNudgesService.hasActiveBadges$(this.activeUserId),
]).pipe(
map(([onboardingFeatureEnabled, hasBadges]) => {
return [
{
label: "vault",
page: "/tabs/vault",
iconKey: "lock",
iconKeyActive: "lock-f",
},
{
label: "generator",
page: "/tabs/generator",
iconKey: "generate",
iconKeyActive: "generate-f",
},
{
label: "send",
page: "/tabs/send",
iconKey: "send",
iconKeyActive: "send-f",
},
{
label: "settings",
page: "/tabs/settings",
iconKey: "cog",
iconKeyActive: "cog-f",
showBerry: onboardingFeatureEnabled && hasBadges,
},
];
}),
);
}
}

View File

@@ -1,51 +0,0 @@
import { inject, Injectable } from "@angular/core";
import { combineLatest, distinctUntilChanged, map, Observable, of, switchMap } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { UserId } from "@bitwarden/common/types/guid";
import { DefaultSingleNudgeService } from "../default-single-nudge.service";
import { NudgeStatus, VaultNudgeType } from "../vault-nudges.service";
/**
* Custom Nudge Service used for showing if the user has any existing nudge in the Vault.
*/
@Injectable({
providedIn: "root",
})
export class HasNudgeService extends DefaultSingleNudgeService {
private accountService = inject(AccountService);
private nudgeTypes: VaultNudgeType[] = [
VaultNudgeType.EmptyVaultNudge,
// add additional nudge types here as needed
];
/**
* Returns an observable that emits true if any of the provided nudge types are present
*/
nudgeStatus$(): Observable<NudgeStatus> {
return this.accountService.activeAccount$.pipe(
switchMap((activeAccount) => {
const userId: UserId | undefined = activeAccount?.id;
if (!userId) {
return of({ hasBadgeDismissed: true, hasSpotlightDismissed: true });
}
const nudgeObservables: Observable<NudgeStatus>[] = this.nudgeTypes.map((nudge) =>
super.nudgeStatus$(nudge, userId),
);
return combineLatest(nudgeObservables).pipe(
map((nudgeStates) => {
return {
hasBadgeDismissed: true,
hasSpotlightDismissed: nudgeStates.some((state) => state.hasSpotlightDismissed),
};
}),
distinctUntilChanged(),
);
}),
);
}
}

View File

@@ -1,5 +1,4 @@
export * from "./has-items-nudge.service";
export * from "./download-bitwarden-nudge.service";
export * from "./empty-vault-nudge.service";
export * from "./has-nudge.service";
export * from "./new-item-nudge.service";

View File

@@ -126,4 +126,31 @@ describe("Vault Nudges Service", () => {
expect(result).toBe(false);
});
});
describe("HasActiveBadges", () => {
it("should return true if a nudgeType with hasBadgeDismissed === false", async () => {
TestBed.overrideProvider(EmptyVaultNudgeService, {
useValue: {
nudgeStatus$: () => of({ hasBadgeDismissed: false, hasSpotlightDismissed: false }),
},
});
const service = testBed.inject(VaultNudgesService);
const result = await firstValueFrom(service.hasActiveBadges$("user-id" as UserId));
expect(result).toBe(true);
});
it("should return false if all nudgeTypes have hasBadgeDismissed === true", async () => {
TestBed.overrideProvider(EmptyVaultNudgeService, {
useValue: {
nudgeStatus$: () => of({ hasBadgeDismissed: true, hasSpotlightDismissed: true }),
},
});
const service = testBed.inject(VaultNudgesService);
const result = await firstValueFrom(service.hasActiveBadges$("user-id" as UserId));
expect(result).toBe(false);
});
});
});

View File

@@ -1,5 +1,5 @@
import { inject, Injectable } from "@angular/core";
import { of, switchMap } from "rxjs";
import { combineLatest, map, Observable, of, shareReplay, switchMap } from "rxjs";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
@@ -104,4 +104,26 @@ export class VaultNudgesService {
: { hasBadgeDismissed: true, hasSpotlightDismissed: true };
await this.getNudgeService(nudge).setNudgeStatus(nudge, dismissedStatus, userId);
}
/**
* Check if there are any active badges for the user to show Berry notification in Tabs
* @param userId
*/
hasActiveBadges$(userId: UserId): Observable<boolean> {
// Add more nudge types here if they have the settings badge feature
const nudgeTypes = [VaultNudgeType.EmptyVaultNudge];
const nudgeTypesWithBadge$ = nudgeTypes.map((nudge) => {
return this.getNudgeService(nudge)
.nudgeStatus$(nudge, userId)
.pipe(
map((status) => !status?.hasBadgeDismissed),
shareReplay({ refCount: false, bufferSize: 1 }),
);
});
return combineLatest(nudgeTypesWithBadge$).pipe(
map((results) => results.some((result) => result === true)),
);
}
}