1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 07:43:35 +00:00

Address review feedback on UnassignedBannerService (#8680)

* Introduce `UnassignedItemsBannerApiService`

* Delete `WebUnassignedItemsBannerService`
This commit is contained in:
Addison Beck
2024-04-10 16:13:41 -05:00
committed by GitHub
parent 98ed744ae8
commit ab83a367dd
9 changed files with 28 additions and 117 deletions

View File

@@ -1,8 +1,8 @@
<bit-banner
class="-tw-m-6 tw-flex tw-flex-col tw-pb-6"
(onClose)="webUnassignedItemsBannerService.hideBanner()"
(onClose)="unassignedItemsBannerService.hideBanner()"
*ngIf="
(unassignedItemsBannerEnabled$ | async) && (webUnassignedItemsBannerService.showBanner$ | async)
(unassignedItemsBannerEnabled$ | async) && (unassignedItemsBannerService.showBanner$ | async)
"
>
{{ "unassignedItemsBanner" | i18n }}

View File

@@ -2,6 +2,7 @@ import { Component, Input } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { combineLatest, map, Observable } from "rxjs";
import { UnassignedItemsBannerService } from "@bitwarden/angular/services/unassigned-items-banner.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum";
@@ -11,8 +12,6 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { AccountProfile } from "@bitwarden/common/platform/models/domain/account";
import { WebUnassignedItemsBannerService } from "./web-unassigned-items-banner.service";
@Component({
selector: "app-header",
templateUrl: "./web-header.component.html",
@@ -43,7 +42,7 @@ export class WebHeaderComponent {
private platformUtilsService: PlatformUtilsService,
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
private messagingService: MessagingService,
protected webUnassignedItemsBannerService: WebUnassignedItemsBannerService,
protected unassignedItemsBannerService: UnassignedItemsBannerService,
private configService: ConfigService,
) {
this.routeData$ = this.route.data.pipe(

View File

@@ -1,56 +0,0 @@
import { MockProxy, mock } from "jest-mock-extended";
import { firstValueFrom, skip } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { FakeStateProvider, mockAccountServiceWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import {
SHOW_BANNER_KEY,
WebUnassignedItemsBannerService,
} from "./web-unassigned-items-banner.service";
describe("WebUnassignedItemsBanner", () => {
let stateProvider: FakeStateProvider;
let apiService: MockProxy<ApiService>;
const sutFactory = () => new WebUnassignedItemsBannerService(stateProvider, apiService);
beforeEach(() => {
const fakeAccountService = mockAccountServiceWith("userId" as UserId);
stateProvider = new FakeStateProvider(fakeAccountService);
apiService = mock();
});
it("shows the banner if showBanner local state is true", async () => {
const showBanner = stateProvider.activeUser.getFake(SHOW_BANNER_KEY);
showBanner.nextState(true);
const sut = sutFactory();
expect(await firstValueFrom(sut.showBanner$)).toBe(true);
expect(apiService.getShowUnassignedCiphersBanner).not.toHaveBeenCalled();
});
it("does not show the banner if showBanner local state is false", async () => {
const showBanner = stateProvider.activeUser.getFake(SHOW_BANNER_KEY);
showBanner.nextState(false);
const sut = sutFactory();
expect(await firstValueFrom(sut.showBanner$)).toBe(false);
expect(apiService.getShowUnassignedCiphersBanner).not.toHaveBeenCalled();
});
it("fetches from server if local state has not been set yet", async () => {
apiService.getShowUnassignedCiphersBanner.mockResolvedValue(true);
const showBanner = stateProvider.activeUser.getFake(SHOW_BANNER_KEY);
showBanner.nextState(undefined);
const sut = sutFactory();
// skip first value so we get the recomputed value after the server call
expect(await firstValueFrom(sut.showBanner$.pipe(skip(1)))).toBe(true);
// Expect to have updated local state
expect(await firstValueFrom(showBanner.state$)).toBe(true);
expect(apiService.getShowUnassignedCiphersBanner).toHaveBeenCalledTimes(1);
});
});

View File

@@ -1,46 +0,0 @@
import { Injectable } from "@angular/core";
import { EMPTY, concatMap } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import {
StateProvider,
UNASSIGNED_ITEMS_BANNER_DISK,
UserKeyDefinition,
} from "@bitwarden/common/platform/state";
export const SHOW_BANNER_KEY = new UserKeyDefinition<boolean>(
UNASSIGNED_ITEMS_BANNER_DISK,
"showBanner",
{
deserializer: (b) => b,
clearOn: [],
},
);
/** Displays a banner that tells users how to move their unassigned items into a collection. */
@Injectable({ providedIn: "root" })
export class WebUnassignedItemsBannerService {
private _showBanner = this.stateProvider.getActive(SHOW_BANNER_KEY);
showBanner$ = this._showBanner.state$.pipe(
concatMap(async (showBanner) => {
// null indicates that the user has not seen or dismissed the banner yet - get the flag from server
if (showBanner == null) {
const showBannerResponse = await this.apiService.getShowUnassignedCiphersBanner();
await this._showBanner.update(() => showBannerResponse);
return EMPTY; // complete the inner observable without emitting any value; the update on the previous line will trigger another run
}
return showBanner;
}),
);
constructor(
private stateProvider: StateProvider,
private apiService: ApiService,
) {}
async hideBanner() {
await this._showBanner.update(() => false);
}
}