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