mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 06:13:38 +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 { Component, OnInit } from "@angular/core";
|
||||||
import { combineLatest, map } from "rxjs";
|
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 { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
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({
|
@Component({
|
||||||
selector: "app-tabs-v2",
|
selector: "app-tabs-v2",
|
||||||
templateUrl: "./tabs-v2.component.html",
|
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(
|
constructor(
|
||||||
private readonly hasNudgeService: HasNudgeService,
|
private vaultNudgesService: VaultNudgesService,
|
||||||
|
private accountService: AccountService,
|
||||||
private readonly configService: ConfigService,
|
private readonly configService: ConfigService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
protected navButtons$ = combineLatest([
|
async ngOnInit() {
|
||||||
this.configService.getFeatureFlag$(FeatureFlag.PM8851_BrowserOnboardingNudge),
|
this.activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
this.hasNudgeService.nudgeStatus$(),
|
|
||||||
]).pipe(
|
this.navButtons$ = combineLatest([
|
||||||
map(([onboardingFeatureEnabled, nudgeStatus]) => {
|
this.configService.getFeatureFlag$(FeatureFlag.PM8851_BrowserOnboardingNudge),
|
||||||
return [
|
this.vaultNudgesService.hasActiveBadges$(this.activeUserId),
|
||||||
{
|
]).pipe(
|
||||||
label: "vault",
|
map(([onboardingFeatureEnabled, hasBadges]) => {
|
||||||
page: "/tabs/vault",
|
return [
|
||||||
iconKey: "lock",
|
{
|
||||||
iconKeyActive: "lock-f",
|
label: "vault",
|
||||||
},
|
page: "/tabs/vault",
|
||||||
{
|
iconKey: "lock",
|
||||||
label: "generator",
|
iconKeyActive: "lock-f",
|
||||||
page: "/tabs/generator",
|
},
|
||||||
iconKey: "generate",
|
{
|
||||||
iconKeyActive: "generate-f",
|
label: "generator",
|
||||||
},
|
page: "/tabs/generator",
|
||||||
{
|
iconKey: "generate",
|
||||||
label: "send",
|
iconKeyActive: "generate-f",
|
||||||
page: "/tabs/send",
|
},
|
||||||
iconKey: "send",
|
{
|
||||||
iconKeyActive: "send-f",
|
label: "send",
|
||||||
},
|
page: "/tabs/send",
|
||||||
{
|
iconKey: "send",
|
||||||
label: "settings",
|
iconKeyActive: "send-f",
|
||||||
page: "/tabs/settings",
|
},
|
||||||
iconKey: "cog",
|
{
|
||||||
iconKeyActive: "cog-f",
|
label: "settings",
|
||||||
showBerry: onboardingFeatureEnabled && !nudgeStatus.hasSpotlightDismissed,
|
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 "./has-items-nudge.service";
|
||||||
export * from "./download-bitwarden-nudge.service";
|
export * from "./download-bitwarden-nudge.service";
|
||||||
export * from "./empty-vault-nudge.service";
|
export * from "./empty-vault-nudge.service";
|
||||||
export * from "./has-nudge.service";
|
|
||||||
export * from "./new-item-nudge.service";
|
export * from "./new-item-nudge.service";
|
||||||
|
|||||||
@@ -126,4 +126,31 @@ describe("Vault Nudges Service", () => {
|
|||||||
expect(result).toBe(false);
|
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 { 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 { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
@@ -104,4 +104,26 @@ export class VaultNudgesService {
|
|||||||
: { hasBadgeDismissed: true, hasSpotlightDismissed: true };
|
: { hasBadgeDismissed: true, hasSpotlightDismissed: true };
|
||||||
await this.getNudgeService(nudge).setNudgeStatus(nudge, dismissedStatus, userId);
|
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