-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[AC-2436] Show unassigned items banner in browser (#8656)
* Boostrap basic banner, show for all admins * Remove UI banner, fix method calls * Invert showBanner -> hideBanner * Add api call * Minor tweaks and wording * Change to active user state * Add tests * Fix mixed up names * Simplify logic * Add feature flag * Do not clear on logout * Show banner in browser as well * Update apps/browser/src/_locales/en/messages.json * Update copy --------- Co-authored-by: Addison Beck <[email protected]> Co-authored-by: Addison Beck <[email protected]> (cherry picked from commit 98ed744)
- Loading branch information
Showing
5 changed files
with
130 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
53 changes: 53 additions & 0 deletions
53
libs/angular/src/services/unassigned-items-banner.service.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
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, UnassignedItemsBannerService } from "./unassigned-items-banner.service"; | ||
|
||
describe("UnassignedItemsBanner", () => { | ||
let stateProvider: FakeStateProvider; | ||
let apiService: MockProxy<ApiService>; | ||
|
||
const sutFactory = () => new UnassignedItemsBannerService(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); | ||
}); | ||
}); |
46 changes: 46 additions & 0 deletions
46
libs/angular/src/services/unassigned-items-banner.service.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
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 UnassignedItemsBannerService { | ||
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); | ||
} | ||
} |