mirror of
https://github.com/bitwarden/browser
synced 2025-12-13 23:03:32 +00:00
[PM-12035] Vault filter updates to use SingleUserState (#13641)
* vault filter use SingleUserState * fixing tests * Changes so that userId is passed to service, instead of access in service * passing activeUserId from the components to service * Sugggested changes * updating functions to be abstract on vault-filter.service * updating all functions to be abstract on vault filter service
This commit is contained in:
@@ -89,8 +89,8 @@ export class VaultFilterComponent
|
|||||||
const collapsedNodes = await firstValueFrom(this.vaultFilterService.collapsedFilterNodes$);
|
const collapsedNodes = await firstValueFrom(this.vaultFilterService.collapsedFilterNodes$);
|
||||||
|
|
||||||
collapsedNodes.delete("AllCollections");
|
collapsedNodes.delete("AllCollections");
|
||||||
|
const userId = await firstValueFrom(this.activeUserId$);
|
||||||
await this.vaultFilterService.setCollapsedFilterNodes(collapsedNodes);
|
await this.vaultFilterService.setCollapsedFilterNodes(collapsedNodes, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async addCollectionFilter(): Promise<VaultFilterSection> {
|
protected async addCollectionFilter(): Promise<VaultFilterSection> {
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private trialFlowService = inject(TrialFlowService);
|
private trialFlowService = inject(TrialFlowService);
|
||||||
|
protected activeUserId$ = this.accountService.activeAccount$.pipe(getUserId);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected vaultFilterService: VaultFilterService,
|
protected vaultFilterService: VaultFilterService,
|
||||||
@@ -162,7 +163,8 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
|
|||||||
filter.selectedOrganizationNode = orgNode;
|
filter.selectedOrganizationNode = orgNode;
|
||||||
}
|
}
|
||||||
this.vaultFilterService.setOrganizationFilter(orgNode.node);
|
this.vaultFilterService.setOrganizationFilter(orgNode.node);
|
||||||
await this.vaultFilterService.expandOrgFilter();
|
const userId = await firstValueFrom(this.activeUserId$);
|
||||||
|
await this.vaultFilterService.expandOrgFilter(userId);
|
||||||
};
|
};
|
||||||
|
|
||||||
applyTypeFilter = async (filterNode: TreeNode<CipherTypeFilter>): Promise<void> => {
|
applyTypeFilter = async (filterNode: TreeNode<CipherTypeFilter>): Promise<void> => {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Observable } from "rxjs";
|
|||||||
|
|
||||||
import { CollectionAdminView, CollectionView } from "@bitwarden/admin-console/common";
|
import { CollectionAdminView, CollectionView } from "@bitwarden/admin-console/common";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
||||||
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||||
|
|
||||||
@@ -22,16 +23,19 @@ export abstract class VaultFilterService {
|
|||||||
folderTree$: Observable<TreeNode<FolderFilter>>;
|
folderTree$: Observable<TreeNode<FolderFilter>>;
|
||||||
collectionTree$: Observable<TreeNode<CollectionFilter>>;
|
collectionTree$: Observable<TreeNode<CollectionFilter>>;
|
||||||
cipherTypeTree$: Observable<TreeNode<CipherTypeFilter>>;
|
cipherTypeTree$: Observable<TreeNode<CipherTypeFilter>>;
|
||||||
getCollectionNodeFromTree: (id: string) => Promise<TreeNode<CollectionFilter>>;
|
abstract getCollectionNodeFromTree: (id: string) => Promise<TreeNode<CollectionFilter>>;
|
||||||
setCollapsedFilterNodes: (collapsedFilterNodes: Set<string>) => Promise<void>;
|
abstract setCollapsedFilterNodes: (
|
||||||
expandOrgFilter: () => Promise<void>;
|
collapsedFilterNodes: Set<string>,
|
||||||
getOrganizationFilter: () => Observable<Organization>;
|
userId: UserId,
|
||||||
setOrganizationFilter: (organization: Organization) => void;
|
) => Promise<void>;
|
||||||
buildTypeTree: (
|
abstract expandOrgFilter: (userId: UserId) => Promise<void>;
|
||||||
|
abstract getOrganizationFilter: () => Observable<Organization>;
|
||||||
|
abstract setOrganizationFilter: (organization: Organization) => void;
|
||||||
|
abstract buildTypeTree: (
|
||||||
head: CipherTypeFilter,
|
head: CipherTypeFilter,
|
||||||
array: CipherTypeFilter[],
|
array: CipherTypeFilter[],
|
||||||
) => Observable<TreeNode<CipherTypeFilter>>;
|
) => Observable<TreeNode<CipherTypeFilter>>;
|
||||||
// TODO: Remove this from org vault when collection admin service adopts state management
|
// TODO: Remove this from org vault when collection admin service adopts state management
|
||||||
reloadCollections?: (collections: CollectionAdminView[]) => void;
|
abstract reloadCollections?: (collections: CollectionAdminView[]) => void;
|
||||||
clearOrganizationFilter: () => void;
|
abstract clearOrganizationFilter: () => void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import {
|
|||||||
FakeAccountService,
|
FakeAccountService,
|
||||||
mockAccountServiceWith,
|
mockAccountServiceWith,
|
||||||
} from "@bitwarden/common/../spec/fake-account-service";
|
} from "@bitwarden/common/../spec/fake-account-service";
|
||||||
import { FakeActiveUserState } from "@bitwarden/common/../spec/fake-state";
|
import { FakeSingleUserState } from "@bitwarden/common/../spec/fake-state";
|
||||||
import { FakeStateProvider } from "@bitwarden/common/../spec/fake-state-provider";
|
import { FakeStateProvider } from "@bitwarden/common/../spec/fake-state-provider";
|
||||||
import { mock, MockProxy } from "jest-mock-extended";
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
import { firstValueFrom, ReplaySubject } from "rxjs";
|
import { firstValueFrom, ReplaySubject } from "rxjs";
|
||||||
@@ -42,7 +42,7 @@ describe("vault filter service", () => {
|
|||||||
|
|
||||||
const mockUserId = Utils.newGuid() as UserId;
|
const mockUserId = Utils.newGuid() as UserId;
|
||||||
let accountService: FakeAccountService;
|
let accountService: FakeAccountService;
|
||||||
let collapsedGroupingsState: FakeActiveUserState<string[]>;
|
let collapsedGroupingsState: FakeSingleUserState<string[]>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
organizationService = mock<OrganizationService>();
|
organizationService = mock<OrganizationService>();
|
||||||
@@ -83,21 +83,21 @@ describe("vault filter service", () => {
|
|||||||
collectionService,
|
collectionService,
|
||||||
accountService,
|
accountService,
|
||||||
);
|
);
|
||||||
collapsedGroupingsState = stateProvider.activeUser.getFake(COLLAPSED_GROUPINGS);
|
collapsedGroupingsState = stateProvider.singleUser.getFake(mockUserId, COLLAPSED_GROUPINGS);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("collapsed filter nodes", () => {
|
describe("collapsed filter nodes", () => {
|
||||||
const nodes = new Set(["1", "2"]);
|
const nodes = new Set(["1", "2"]);
|
||||||
|
|
||||||
it("should update the collapsedFilterNodes$", async () => {
|
it("should update the collapsedFilterNodes$", async () => {
|
||||||
await vaultFilterService.setCollapsedFilterNodes(nodes);
|
await vaultFilterService.setCollapsedFilterNodes(nodes, mockUserId);
|
||||||
|
|
||||||
const collapsedGroupingsState = stateProvider.activeUser.getFake(COLLAPSED_GROUPINGS);
|
const collapsedGroupingsState = stateProvider.singleUser.getFake(
|
||||||
expect(await firstValueFrom(collapsedGroupingsState.state$)).toEqual(Array.from(nodes));
|
|
||||||
expect(collapsedGroupingsState.nextMock).toHaveBeenCalledWith([
|
|
||||||
mockUserId,
|
mockUserId,
|
||||||
Array.from(nodes),
|
COLLAPSED_GROUPINGS,
|
||||||
]);
|
);
|
||||||
|
expect(await firstValueFrom(collapsedGroupingsState.state$)).toEqual(Array.from(nodes));
|
||||||
|
expect(collapsedGroupingsState.nextMock).toHaveBeenCalledWith(Array.from(nodes));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("loads from state on initialization", async () => {
|
it("loads from state on initialization", async () => {
|
||||||
|
|||||||
@@ -23,8 +23,10 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
|
|||||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { ActiveUserState, StateProvider } from "@bitwarden/common/platform/state";
|
import { SingleUserState, StateProvider } from "@bitwarden/common/platform/state";
|
||||||
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||||
@@ -47,12 +49,17 @@ const NestingDelimiter = "/";
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class VaultFilterService implements VaultFilterServiceAbstraction {
|
export class VaultFilterService implements VaultFilterServiceAbstraction {
|
||||||
private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id));
|
protected activeUserId$ = this.accountService.activeAccount$.pipe(getUserId);
|
||||||
|
|
||||||
memberOrganizations$ = this.activeUserId$.pipe(
|
memberOrganizations$ = this.activeUserId$.pipe(
|
||||||
switchMap((id) => this.organizationService.memberOrganizations$(id)),
|
switchMap((id) => this.organizationService.memberOrganizations$(id)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
collapsedFilterNodes$ = this.activeUserId$.pipe(
|
||||||
|
switchMap((id) => this.collapsedGroupingsState(id).state$),
|
||||||
|
map((state) => new Set(state)),
|
||||||
|
);
|
||||||
|
|
||||||
organizationTree$: Observable<TreeNode<OrganizationFilter>> = combineLatest([
|
organizationTree$: Observable<TreeNode<OrganizationFilter>> = combineLatest([
|
||||||
this.memberOrganizations$,
|
this.memberOrganizations$,
|
||||||
this.activeUserId$.pipe(
|
this.activeUserId$.pipe(
|
||||||
@@ -103,11 +110,9 @@ export class VaultFilterService implements VaultFilterServiceAbstraction {
|
|||||||
|
|
||||||
cipherTypeTree$: Observable<TreeNode<CipherTypeFilter>> = this.buildCipherTypeTree();
|
cipherTypeTree$: Observable<TreeNode<CipherTypeFilter>> = this.buildCipherTypeTree();
|
||||||
|
|
||||||
private collapsedGroupingsState: ActiveUserState<string[]> =
|
private collapsedGroupingsState(userId: UserId): SingleUserState<string[]> {
|
||||||
this.stateProvider.getActive(COLLAPSED_GROUPINGS);
|
return this.stateProvider.getUser(userId, COLLAPSED_GROUPINGS);
|
||||||
|
}
|
||||||
readonly collapsedFilterNodes$: Observable<Set<string>> =
|
|
||||||
this.collapsedGroupingsState.state$.pipe(map((c) => new Set(c)));
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected organizationService: OrganizationService,
|
protected organizationService: OrganizationService,
|
||||||
@@ -125,8 +130,8 @@ export class VaultFilterService implements VaultFilterServiceAbstraction {
|
|||||||
return ServiceUtils.getTreeNodeObject(collections, id) as TreeNode<CollectionFilter>;
|
return ServiceUtils.getTreeNodeObject(collections, id) as TreeNode<CollectionFilter>;
|
||||||
}
|
}
|
||||||
|
|
||||||
async setCollapsedFilterNodes(collapsedFilterNodes: Set<string>): Promise<void> {
|
async setCollapsedFilterNodes(collapsedFilterNodes: Set<string>, userId: UserId): Promise<void> {
|
||||||
await this.collapsedGroupingsState.update(() => Array.from(collapsedFilterNodes));
|
await this.collapsedGroupingsState(userId).update(() => Array.from(collapsedFilterNodes));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async getCollapsedFilterNodes(): Promise<Set<string>> {
|
protected async getCollapsedFilterNodes(): Promise<Set<string>> {
|
||||||
@@ -149,13 +154,13 @@ export class VaultFilterService implements VaultFilterServiceAbstraction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async expandOrgFilter() {
|
async expandOrgFilter(userId: UserId) {
|
||||||
const collapsedFilterNodes = await firstValueFrom(this.collapsedFilterNodes$);
|
const collapsedFilterNodes = await firstValueFrom(this.collapsedFilterNodes$);
|
||||||
if (!collapsedFilterNodes.has("AllVaults")) {
|
if (!collapsedFilterNodes.has("AllVaults")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
collapsedFilterNodes.delete("AllVaults");
|
collapsedFilterNodes.delete("AllVaults");
|
||||||
await this.setCollapsedFilterNodes(collapsedFilterNodes);
|
await this.setCollapsedFilterNodes(collapsedFilterNodes, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async buildOrganizationTree(
|
protected async buildOrganizationTree(
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { Component, InjectionToken, Injector, Input, OnDestroy, OnInit } from "@angular/core";
|
import { Component, InjectionToken, Injector, Input, OnDestroy, OnInit } from "@angular/core";
|
||||||
import { Observable, Subject, takeUntil } from "rxjs";
|
import { firstValueFrom, Observable, Subject, takeUntil } from "rxjs";
|
||||||
import { map } from "rxjs/operators";
|
import { map } from "rxjs/operators";
|
||||||
|
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { ITreeNodeObject, TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
import { ITreeNodeObject, TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
||||||
|
|
||||||
import { VaultFilterService } from "../../services/abstractions/vault-filter.service";
|
import { VaultFilterService } from "../../services/abstractions/vault-filter.service";
|
||||||
@@ -17,6 +19,7 @@ import { VaultFilter } from "../models/vault-filter.model";
|
|||||||
})
|
})
|
||||||
export class VaultFilterSectionComponent implements OnInit, OnDestroy {
|
export class VaultFilterSectionComponent implements OnInit, OnDestroy {
|
||||||
private destroy$ = new Subject<void>();
|
private destroy$ = new Subject<void>();
|
||||||
|
private activeUserId$ = getUserId(this.accountService.activeAccount$);
|
||||||
|
|
||||||
@Input() activeFilter: VaultFilter;
|
@Input() activeFilter: VaultFilter;
|
||||||
@Input() section: VaultFilterSection;
|
@Input() section: VaultFilterSection;
|
||||||
@@ -29,6 +32,7 @@ export class VaultFilterSectionComponent implements OnInit, OnDestroy {
|
|||||||
constructor(
|
constructor(
|
||||||
private vaultFilterService: VaultFilterService,
|
private vaultFilterService: VaultFilterService,
|
||||||
private injector: Injector,
|
private injector: Injector,
|
||||||
|
private accountService: AccountService,
|
||||||
) {
|
) {
|
||||||
this.vaultFilterService.collapsedFilterNodes$
|
this.vaultFilterService.collapsedFilterNodes$
|
||||||
.pipe(takeUntil(this.destroy$))
|
.pipe(takeUntil(this.destroy$))
|
||||||
@@ -126,7 +130,8 @@ export class VaultFilterSectionComponent implements OnInit, OnDestroy {
|
|||||||
} else {
|
} else {
|
||||||
this.collapsedFilterNodes.add(node.id);
|
this.collapsedFilterNodes.add(node.id);
|
||||||
}
|
}
|
||||||
await this.vaultFilterService.setCollapsedFilterNodes(this.collapsedFilterNodes);
|
const userId = await firstValueFrom(this.activeUserId$);
|
||||||
|
await this.vaultFilterService.setCollapsedFilterNodes(this.collapsedFilterNodes, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// an injector is necessary to pass data into a dynamic component
|
// an injector is necessary to pass data into a dynamic component
|
||||||
|
|||||||
Reference in New Issue
Block a user