mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 14:23:32 +00:00
[PS-1078] Refactor FolderService to use Observables (#3022)
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
||||
@@ -51,7 +52,7 @@ export class AddEditComponent implements OnInit {
|
||||
|
||||
editMode = false;
|
||||
cipher: CipherView;
|
||||
folders: FolderView[];
|
||||
folders$: Observable<FolderView[]>;
|
||||
collections: CollectionView[] = [];
|
||||
title: string;
|
||||
formPromise: Promise<any>;
|
||||
@@ -243,7 +244,7 @@ export class AddEditComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
this.folders = await this.folderService.getAllDecrypted();
|
||||
this.folders$ = this.folderService.folderViews$;
|
||||
|
||||
if (this.editMode && this.previousCipherId !== this.cipherId) {
|
||||
this.eventService.collect(EventType.Cipher_ClientViewed, this.cipherId);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||
import { firstValueFrom, Observable } from "rxjs";
|
||||
|
||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
import { ITreeNodeObject } from "@bitwarden/common/models/domain/treeNode";
|
||||
@@ -28,7 +29,7 @@ export class VaultFilterComponent implements OnInit {
|
||||
activePersonalOwnershipPolicy: boolean;
|
||||
activeSingleOrganizationPolicy: boolean;
|
||||
collections: DynamicTreeNode<CollectionView>;
|
||||
folders: DynamicTreeNode<FolderView>;
|
||||
folders$: Observable<DynamicTreeNode<FolderView>>;
|
||||
|
||||
constructor(protected vaultFilterService: VaultFilterService) {}
|
||||
|
||||
@@ -45,7 +46,7 @@ export class VaultFilterComponent implements OnInit {
|
||||
this.activeSingleOrganizationPolicy =
|
||||
await this.vaultFilterService.checkForSingleOrganizationPolicy();
|
||||
}
|
||||
this.folders = await this.vaultFilterService.buildFolders();
|
||||
this.folders$ = await this.vaultFilterService.buildNestedFolders();
|
||||
this.collections = await this.initCollections();
|
||||
this.isLoaded = true;
|
||||
}
|
||||
@@ -67,13 +68,13 @@ export class VaultFilterComponent implements OnInit {
|
||||
async applyFilter(filter: VaultFilter) {
|
||||
if (filter.refreshCollectionsAndFolders) {
|
||||
await this.reloadCollectionsAndFolders(filter);
|
||||
filter = this.pruneInvalidatedFilterSelections(filter);
|
||||
filter = await this.pruneInvalidatedFilterSelections(filter);
|
||||
}
|
||||
this.onFilterChange.emit(filter);
|
||||
}
|
||||
|
||||
async reloadCollectionsAndFolders(filter: VaultFilter) {
|
||||
this.folders = await this.vaultFilterService.buildFolders(filter.selectedOrganizationId);
|
||||
this.folders$ = await this.vaultFilterService.buildNestedFolders(filter.selectedOrganizationId);
|
||||
this.collections = filter.myVaultOnly
|
||||
? null
|
||||
: await this.vaultFilterService.buildCollections(filter.selectedOrganizationId);
|
||||
@@ -95,14 +96,17 @@ export class VaultFilterComponent implements OnInit {
|
||||
this.onEditFolder.emit(folder);
|
||||
}
|
||||
|
||||
protected pruneInvalidatedFilterSelections(filter: VaultFilter): VaultFilter {
|
||||
filter = this.pruneInvalidFolderSelection(filter);
|
||||
protected async pruneInvalidatedFilterSelections(filter: VaultFilter): Promise<VaultFilter> {
|
||||
filter = await this.pruneInvalidFolderSelection(filter);
|
||||
filter = this.pruneInvalidCollectionSelection(filter);
|
||||
return filter;
|
||||
}
|
||||
|
||||
protected pruneInvalidFolderSelection(filter: VaultFilter): VaultFilter {
|
||||
if (filter.selectedFolder && !this.folders?.hasId(filter.selectedFolderId)) {
|
||||
protected async pruneInvalidFolderSelection(filter: VaultFilter): Promise<VaultFilter> {
|
||||
if (
|
||||
filter.selectedFolder &&
|
||||
!(await firstValueFrom(this.folders$))?.hasId(filter.selectedFolderId)
|
||||
) {
|
||||
filter.selectedFolder = false;
|
||||
filter.selectedFolderId = null;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { from, mergeMap, Observable } from "rxjs";
|
||||
|
||||
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
||||
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
|
||||
@@ -7,12 +8,16 @@ import { OrganizationService } from "@bitwarden/common/abstractions/organization
|
||||
import { PolicyService } from "@bitwarden/common/abstractions/policy.service";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { PolicyType } from "@bitwarden/common/enums/policyType";
|
||||
import { ServiceUtils } from "@bitwarden/common/misc/serviceUtils";
|
||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
import { TreeNode } from "@bitwarden/common/models/domain/treeNode";
|
||||
import { CollectionView } from "@bitwarden/common/models/view/collectionView";
|
||||
import { FolderView } from "@bitwarden/common/models/view/folderView";
|
||||
|
||||
import { DynamicTreeNode } from "./models/dynamic-tree-node.model";
|
||||
|
||||
const NestingDelimiter = "/";
|
||||
|
||||
@Injectable()
|
||||
export class VaultFilterService {
|
||||
constructor(
|
||||
@@ -36,25 +41,30 @@ export class VaultFilterService {
|
||||
return await this.organizationService.getAll();
|
||||
}
|
||||
|
||||
async buildFolders(organizationId?: string): Promise<DynamicTreeNode<FolderView>> {
|
||||
const storedFolders = await this.folderService.getAllDecrypted();
|
||||
let folders: FolderView[];
|
||||
if (organizationId != null) {
|
||||
const ciphers = await this.cipherService.getAllDecrypted();
|
||||
const orgCiphers = ciphers.filter((c) => c.organizationId == organizationId);
|
||||
folders = storedFolders.filter(
|
||||
(f) =>
|
||||
orgCiphers.filter((oc) => oc.folderId == f.id).length > 0 ||
|
||||
ciphers.filter((c) => c.folderId == f.id).length < 1
|
||||
);
|
||||
} else {
|
||||
folders = storedFolders;
|
||||
}
|
||||
const nestedFolders = await this.folderService.getAllNested(folders);
|
||||
return new DynamicTreeNode<FolderView>({
|
||||
fullList: folders,
|
||||
nestedList: nestedFolders,
|
||||
});
|
||||
buildNestedFolders(organizationId?: string): Observable<DynamicTreeNode<FolderView>> {
|
||||
const transformation = async (storedFolders: FolderView[]) => {
|
||||
let folders: FolderView[];
|
||||
if (organizationId != null) {
|
||||
const ciphers = await this.cipherService.getAllDecrypted();
|
||||
const orgCiphers = ciphers.filter((c) => c.organizationId == organizationId);
|
||||
folders = storedFolders.filter(
|
||||
(f) =>
|
||||
orgCiphers.filter((oc) => oc.folderId == f.id).length > 0 ||
|
||||
ciphers.filter((c) => c.folderId == f.id).length < 1
|
||||
);
|
||||
} else {
|
||||
folders = storedFolders;
|
||||
}
|
||||
const nestedFolders = await this.getAllFoldersNested(folders);
|
||||
return new DynamicTreeNode<FolderView>({
|
||||
fullList: folders,
|
||||
nestedList: nestedFolders,
|
||||
});
|
||||
};
|
||||
|
||||
return this.folderService.folderViews$.pipe(
|
||||
mergeMap((folders) => from(transformation(folders)))
|
||||
);
|
||||
}
|
||||
|
||||
async buildCollections(organizationId?: string): Promise<DynamicTreeNode<CollectionView>> {
|
||||
@@ -79,4 +89,21 @@ export class VaultFilterService {
|
||||
async checkForPersonalOwnershipPolicy(): Promise<boolean> {
|
||||
return await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership);
|
||||
}
|
||||
|
||||
protected async getAllFoldersNested(folders?: FolderView[]): Promise<TreeNode<FolderView>[]> {
|
||||
const nodes: TreeNode<FolderView>[] = [];
|
||||
folders.forEach((f) => {
|
||||
const folderCopy = new FolderView();
|
||||
folderCopy.id = f.id;
|
||||
folderCopy.revisionDate = f.revisionDate;
|
||||
const parts = f.name != null ? f.name.replace(/^\/+|\/+$/g, "").split(NestingDelimiter) : [];
|
||||
ServiceUtils.nestedTraverse(nodes, 0, parts, folderCopy, null, NestingDelimiter);
|
||||
});
|
||||
return nodes;
|
||||
}
|
||||
|
||||
async getFolderNested(id: string): Promise<TreeNode<FolderView>> {
|
||||
const folders = await this.getAllFoldersNested();
|
||||
return ServiceUtils.getTreeNodeObject(folders, id) as TreeNode<FolderView>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,6 +223,7 @@ export const LOG_MAC_FAILURES = new InjectionToken<string>("LOG_MAC_FAILURES");
|
||||
I18nServiceAbstraction,
|
||||
CipherServiceAbstraction,
|
||||
StateServiceAbstraction,
|
||||
BroadcasterServiceAbstraction,
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user