Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
e8e8f7b
[EC-775] feat: add compatibility layer from #4154
coroiu Feb 3, 2023
f9888b6
[EC-775] fix: ciphers not reloading on filter change
coroiu Feb 3, 2023
9f0dcfe
[EC-775] feat: add support for cipher types
coroiu Feb 6, 2023
096e7fd
[EC-775] feat: implement organization switching
coroiu Feb 7, 2023
fcc49a2
Merge branch 'master' into EC-775-technical-dependency-refactor-vault…
coroiu Feb 13, 2023
b2c341f
[EC-775] feat: remove invalid folder and collection checks
coroiu Feb 14, 2023
7594233
[EC-775] fix: fix reverse data flow race condition
coroiu Feb 14, 2023
19c5bab
[EC-775] fix: No folder use-case not working
coroiu Feb 14, 2023
4eaeb9b
[EC-775] feat: make navigation behave like master
coroiu Feb 14, 2023
4452ddd
[EC-775] feat: add support for trash
coroiu Feb 14, 2023
1e13859
[EC-775] chore: simplify findNode
coroiu Feb 15, 2023
0b6b075
[EC-775] feat: add support for org vault
coroiu Feb 15, 2023
31944d0
[EC-775] feat: add support for orgId in path
coroiu Feb 15, 2023
9e4b5b3
[EC-775] feat: use proper treenode constructor
coroiu Feb 17, 2023
81295c5
[EC-775] chore: remove unnecessary variable
coroiu Feb 17, 2023
a3b0059
[EC-775] docs: add docs to relevant classes
coroiu Feb 17, 2023
bdd3d80
[EC-775] chore: use existing function for searching tree
coroiu Feb 17, 2023
5dd50c0
[EC-775] fix: hide "new" button in trash view
coroiu Feb 17, 2023
ba558d8
[EC-775] feat: add explicit handling for `AllItems`
coroiu Feb 17, 2023
7726d78
[EC-775] fix: prune folderId when changing organization
coroiu Feb 17, 2023
d7d39a6
[EC-775] fix: properly use `undefined` instead of `null`
coroiu Feb 17, 2023
a8e66fc
[EC-775] chore: simplify setters using ternary operator
coroiu Feb 17, 2023
284d64d
[EC-775] feat: add static typing to `type` filter
coroiu Feb 17, 2023
c274133
[EC-775] feat: use new `All` variable for collections
coroiu Feb 20, 2023
7dbe8dc
[EC-775] feat: return `RouterLink` compatible link from `createRoute`
coroiu Feb 20, 2023
3a996e9
[EC-775] feat: add ordId path support to `createRoute`
coroiu Feb 20, 2023
a69b064
[EC-775] fix: interpret params differently in org vault
coroiu Feb 20, 2023
61395dc
[EC-775] doc: clarify `createRoute`
coroiu Feb 20, 2023
2529205
[EC-775] fix: better `type` typing
coroiu Feb 20, 2023
10a8b32
[EC-775] feat: remove support for path navigation
coroiu Feb 21, 2023
e1803bf
[EC-775] fix: refactor bridge service to improve readability
coroiu Feb 21, 2023
c20cc6f
Merge branch 'master' into EC-775-technical-dependency-refactor-vault…
coroiu Feb 21, 2023
319d469
Merge branch 'master' into EC-775-technical-dependency-refactor-vault…
coroiu Feb 23, 2023
72de73d
Merge branch 'master' into EC-775-technical-dependency-refactor-vault…
coroiu Mar 2, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
import { firstValueFrom, Subject, switchMap, takeUntil } from "rxjs";
import { firstValueFrom, Subject } from "rxjs";

import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/enums/policyType";
import { TreeNode } from "@bitwarden/common/models/domain/tree-node";
import { CollectionView } from "@bitwarden/common/models/view/collection.view";
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";

import { VaultFilterService } from "../services/abstractions/vault-filter.service";
import {
Expand All @@ -34,7 +32,6 @@ import { OrganizationOptionsComponent } from "./organization-options.component";
export class VaultFilterComponent implements OnInit, OnDestroy {
filters?: VaultFilterList;
@Input() activeFilter: VaultFilter = new VaultFilter();
@Output() activeFilterChanged = new EventEmitter<VaultFilter>();
@Output() onSearchTextChanged = new EventEmitter<string>();
@Output() onAddFolder = new EventEmitter<never>();
@Output() onEditFolder = new EventEmitter<FolderFilter>();
Expand Down Expand Up @@ -88,9 +85,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
protected policyService: PolicyService,
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService
) {
this.loadSubscriptions();
}
) {}

async ngOnInit(): Promise<void> {
this.filters = await this.buildAllFilters();
Expand All @@ -104,35 +99,11 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
this.destroy$.complete();
}

protected loadSubscriptions() {
this.vaultFilterService.filteredFolders$
.pipe(
switchMap(async (folders) => {
this.removeInvalidFolderSelection(folders);
}),
takeUntil(this.destroy$)
)
.subscribe();

this.vaultFilterService.filteredCollections$
.pipe(
switchMap(async (collections) => {
this.removeInvalidCollectionSelection(collections);
}),
takeUntil(this.destroy$)
)
.subscribe();
}

searchTextChanged(t: string) {
this.searchText = t;
this.onSearchTextChanged.emit(t);
}

protected applyVaultFilter(filter: VaultFilter) {
this.activeFilterChanged.emit(filter);
}

applyOrganizationFilter = async (orgNode: TreeNode<OrganizationFilter>): Promise<void> => {
if (!orgNode?.node.enabled) {
this.platformUtilsService.showToast(
Expand All @@ -143,34 +114,31 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
return;
}
const filter = this.activeFilter;
filter.resetOrganization();
if (orgNode?.node.id !== "AllVaults") {
if (orgNode?.node.id === "AllVaults") {
filter.resetOrganization();
} else {
filter.selectedOrganizationNode = orgNode;
}
this.vaultFilterService.setOrganizationFilter(orgNode.node);
await this.vaultFilterService.expandOrgFilter();
this.applyVaultFilter(filter);
};

applyTypeFilter = async (filterNode: TreeNode<CipherTypeFilter>): Promise<void> => {
const filter = this.activeFilter;
filter.resetFilter();
filter.selectedCipherTypeNode = filterNode;
this.applyVaultFilter(filter);
};

applyFolderFilter = async (folderNode: TreeNode<FolderFilter>): Promise<void> => {
const filter = this.activeFilter;
filter.resetFilter();
filter.selectedFolderNode = folderNode;
this.applyVaultFilter(filter);
};

applyCollectionFilter = async (collectionNode: TreeNode<CollectionFilter>): Promise<void> => {
const filter = this.activeFilter;
filter.resetFilter();
filter.selectedCollectionNode = collectionNode;
this.applyVaultFilter(filter);
};

addFolder = async (): Promise<void> => {
Expand All @@ -185,30 +153,6 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
return await firstValueFrom(this.filters?.typeFilter.data$);
}

protected async removeInvalidFolderSelection(folders: FolderView[]) {
if (this.activeFilter.selectedFolderNode) {
if (!folders.some((f) => f.id === this.activeFilter.folderId)) {
const filter = this.activeFilter;
filter.resetFilter();
filter.selectedCipherTypeNode =
(await this.getDefaultFilter()) as TreeNode<CipherTypeFilter>;
this.applyVaultFilter(filter);
}
}
}

protected async removeInvalidCollectionSelection(collections: CollectionView[]) {
if (this.activeFilter.selectedCollectionNode) {
if (!collections.some((f) => f.id === this.activeFilter.collectionId)) {
const filter = this.activeFilter;
filter.resetFilter();
filter.selectedCipherTypeNode =
(await this.getDefaultFilter()) as TreeNode<CipherTypeFilter>;
this.applyVaultFilter(filter);
}
}
}

async buildAllFilters(): Promise<VaultFilterList> {
const builderFilter = {} as VaultFilterList;
builderFilter.organizationFilter = await this.addOrganizationFilter();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export abstract class VaultFilterService {
organizationTree$: Observable<TreeNode<OrganizationFilter>>;
folderTree$: Observable<TreeNode<FolderFilter>>;
collectionTree$: Observable<TreeNode<CollectionFilter>>;
cipherTypeTree$: Observable<TreeNode<CipherTypeFilter>>;
reloadCollections: () => Promise<void>;
getCollectionNodeFromTree: (id: string) => Promise<TreeNode<CollectionFilter>>;
setCollapsedFilterNodes: (collapsedFilterNodes: Set<string>) => Promise<void>;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { Injectable } from "@angular/core";
import { Router } from "@angular/router";
import { combineLatest, map, Observable } from "rxjs";

import { ServiceUtils } from "@bitwarden/common/misc/serviceUtils";
import { TreeNode } from "@bitwarden/common/models/domain/tree-node";

import { RoutedVaultFilterBridge } from "../shared/models/routed-vault-filter-bridge.model";
import {
RoutedVaultFilterModel,
Unassigned,
All,
} from "../shared/models/routed-vault-filter.model";
import { VaultFilter } from "../shared/models/vault-filter.model";
import {
CipherTypeFilter,
CollectionFilter,
FolderFilter,
OrganizationFilter,
} from "../shared/models/vault-filter.type";

import { VaultFilterService } from "./abstractions/vault-filter.service";
import { RoutedVaultFilterService } from "./routed-vault-filter.service";

/**
* This file is part of a layer that is used to temporary bridge between URL filtering and the old state-in-code method.
* This should be removed after we have refactored the {@link VaultItemsComponent} and introduced vertical navigation
* (which will refactor the {@link VaultFilterComponent}).
*
* This class listens to both the new {@link RoutedVaultFilterService} and the old {@link VaultFilterService}.
* When a new filter is emitted the service uses the ids to find the corresponding tree nodes needed for
* the old {@link VaultFilter} model. It then emits a bridge model that contains this information.
*/
@Injectable()
export class RoutedVaultFilterBridgeService {
readonly activeFilter$: Observable<VaultFilter>;

constructor(
private router: Router,
private routedVaultFilterService: RoutedVaultFilterService,
legacyVaultFilterService: VaultFilterService
) {
this.activeFilter$ = combineLatest([
routedVaultFilterService.filter$,
legacyVaultFilterService.collectionTree$,
legacyVaultFilterService.folderTree$,
legacyVaultFilterService.organizationTree$,
legacyVaultFilterService.cipherTypeTree$,
]).pipe(
map(([filter, collectionTree, folderTree, organizationTree, cipherTypeTree]) => {
const legacyFilter = isAdminConsole(filter)
? createLegacyFilterForAdminConsole(filter, collectionTree, cipherTypeTree)
: createLegacyFilterForEndUser(
filter,
collectionTree,
folderTree,
organizationTree,
cipherTypeTree
);

return new RoutedVaultFilterBridge(filter, legacyFilter, this);
})
);
}

navigate(filter: RoutedVaultFilterModel) {
const [commands, extras] = this.routedVaultFilterService.createRoute(filter);
this.router.navigate(commands, extras);
}
}

/**
* Check if the filtering is being done as part of admin console.
* Admin console can be identified by checking if the `organizationId`
* is part of the path.
*
* @param filter Model to check if origin is admin console
* @returns true if filtering being done as part of admin console
*/
function isAdminConsole(filter: RoutedVaultFilterModel) {
return filter.organizationIdParamType === "path";
}

function createLegacyFilterForAdminConsole(
filter: RoutedVaultFilterModel,
collectionTree: TreeNode<CollectionFilter>,
cipherTypeTree: TreeNode<CipherTypeFilter>
): VaultFilter {
const legacyFilter = new VaultFilter();

if (filter.collectionId === undefined && filter.type === undefined) {
legacyFilter.selectedCollectionNode = ServiceUtils.getTreeNodeObject(
collectionTree,
"AllCollections"
);
} else if (filter.collectionId !== undefined && filter.collectionId === Unassigned) {
legacyFilter.selectedCollectionNode = ServiceUtils.getTreeNodeObject(collectionTree, null);
} else if (filter.collectionId !== undefined) {
legacyFilter.selectedCollectionNode = ServiceUtils.getTreeNodeObject(
collectionTree,
filter.collectionId
);
}

if (filter.collectionId === undefined && filter.type === All) {
legacyFilter.selectedCipherTypeNode = ServiceUtils.getTreeNodeObject(
cipherTypeTree,
"AllItems"
);
} else if (filter.type !== undefined && filter.type === "trash") {
legacyFilter.selectedCipherTypeNode = new TreeNode<CipherTypeFilter>(
{ id: "trash", name: "", type: "trash", icon: "" },
null
);
} else if (filter.type !== undefined && filter.type !== "trash") {
legacyFilter.selectedCipherTypeNode = ServiceUtils.getTreeNodeObject(
cipherTypeTree,
filter.type
);
}

return legacyFilter;
}

function createLegacyFilterForEndUser(
filter: RoutedVaultFilterModel,
collectionTree: TreeNode<CollectionFilter>,
folderTree: TreeNode<FolderFilter>,
organizationTree: TreeNode<OrganizationFilter>,
cipherTypeTree: TreeNode<CipherTypeFilter>
): VaultFilter {
const legacyFilter = new VaultFilter();

if (filter.collectionId !== undefined && filter.collectionId === Unassigned) {
legacyFilter.selectedCollectionNode = ServiceUtils.getTreeNodeObject(collectionTree, null);
} else if (filter.collectionId !== undefined && filter.collectionId === All) {
legacyFilter.selectedCollectionNode = ServiceUtils.getTreeNodeObject(
collectionTree,
"AllCollections"
);
} else if (filter.collectionId !== undefined) {
legacyFilter.selectedCollectionNode = ServiceUtils.getTreeNodeObject(
collectionTree,
filter.collectionId
);
}

if (filter.folderId !== undefined && filter.folderId === Unassigned) {
legacyFilter.selectedFolderNode = ServiceUtils.getTreeNodeObject(folderTree, null);
} else if (filter.folderId !== undefined && filter.folderId !== Unassigned) {
legacyFilter.selectedFolderNode = ServiceUtils.getTreeNodeObject(folderTree, filter.folderId);
}

if (filter.organizationId !== undefined) {
legacyFilter.selectedOrganizationNode = ServiceUtils.getTreeNodeObject(
organizationTree,
filter.organizationId
);
}

if (filter.type === undefined) {
legacyFilter.selectedCipherTypeNode = ServiceUtils.getTreeNodeObject(
cipherTypeTree,
"AllItems"
);
} else if (filter.type !== undefined && filter.type === "trash") {
legacyFilter.selectedCipherTypeNode = new TreeNode<CipherTypeFilter>(
{ id: "trash", name: "", type: "trash", icon: "" },
null
);
} else if (filter.type !== undefined && filter.type !== "trash") {
legacyFilter.selectedCipherTypeNode = ServiceUtils.getTreeNodeObject(
cipherTypeTree,
filter.type
);
}

return legacyFilter;
}
Loading