1
0
mirror of https://github.com/bitwarden/browser synced 2026-03-01 11:01:17 +00:00

- moved vault-filter services and models, and collection utils from web-vault to shared lib

This commit is contained in:
Leslie Xiong
2025-12-10 17:51:06 -05:00
parent bbf20eaf5a
commit f9635b55ec
30 changed files with 92 additions and 75 deletions

View File

@@ -0,0 +1,121 @@
// eslint-disable-next-line no-restricted-imports
import { CollectionView } from "@bitwarden/admin-console/common";
import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
import { newGuid } from "@bitwarden/guid";
import { getNestedCollectionTree, getFlatCollectionTree } from "./collection-utils";
describe("CollectionUtils Service", () => {
describe("getNestedCollectionTree", () => {
it("should return collections properly sorted if provided out of order", () => {
// Arrange
const collections: CollectionView[] = [];
const parentCollection = new CollectionView({
name: "Parent",
organizationId: "orgId" as OrganizationId,
id: newGuid() as CollectionId,
});
const childCollection = new CollectionView({
name: "Parent/Child",
organizationId: "orgId" as OrganizationId,
id: newGuid() as CollectionId,
});
collections.push(childCollection);
collections.push(parentCollection);
// Act
const result = getNestedCollectionTree(collections);
// Assert
expect(result[0].node.name).toBe("Parent");
expect(result[0].children[0].node.name).toBe("Child");
});
it("should return an empty array if no collections are provided", () => {
// Arrange
const collections: CollectionView[] = [];
// Act
const result = getNestedCollectionTree(collections);
// Assert
expect(result).toEqual([]);
});
});
describe("getFlatCollectionTree", () => {
it("should flatten a tree node with no children", () => {
// Arrange
const collection = new CollectionView({
name: "Test Collection",
id: "test-id" as CollectionId,
organizationId: "orgId" as OrganizationId,
});
const treeNodes: TreeNode<CollectionView>[] = [
new TreeNode<CollectionView>(collection, {} as TreeNode<CollectionView>),
];
// Act
const result = getFlatCollectionTree(treeNodes);
// Assert
expect(result.length).toBe(1);
expect(result[0]).toBe(collection);
});
it("should flatten a tree node with children", () => {
// Arrange
const parentCollection = new CollectionView({
name: "Parent",
id: "parent-id" as CollectionId,
organizationId: "orgId" as OrganizationId,
});
const child1Collection = new CollectionView({
name: "Child 1",
id: "child1-id" as CollectionId,
organizationId: "orgId" as OrganizationId,
});
const child2Collection = new CollectionView({
name: "Child 2",
id: "child2-id" as CollectionId,
organizationId: "orgId" as OrganizationId,
});
const grandchildCollection = new CollectionView({
name: "Grandchild",
id: "grandchild-id" as CollectionId,
organizationId: "orgId" as OrganizationId,
});
const parentNode = new TreeNode<CollectionView>(
parentCollection,
{} as TreeNode<CollectionView>,
);
const child1Node = new TreeNode<CollectionView>(child1Collection, parentNode);
const child2Node = new TreeNode<CollectionView>(child2Collection, parentNode);
const grandchildNode = new TreeNode<CollectionView>(grandchildCollection, child1Node);
parentNode.children = [child1Node, child2Node];
child1Node.children = [grandchildNode];
const treeNodes: TreeNode<CollectionView>[] = [parentNode];
// Act
const result = getFlatCollectionTree(treeNodes);
// Assert
expect(result.length).toBe(4);
expect(result[0]).toBe(parentCollection);
expect(result).toContain(child1Collection);
expect(result).toContain(child2Collection);
expect(result).toContain(grandchildCollection);
});
});
});

View File

@@ -0,0 +1,88 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
// eslint-disable-next-line no-restricted-imports
import {
CollectionAdminView,
CollectionView,
NestingDelimiter,
} from "@bitwarden/admin-console/common";
import { OrganizationId } from "@bitwarden/common/types/guid";
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
import { ServiceUtils } from "@bitwarden/common/vault/service-utils";
export function getNestedCollectionTree(
collections: CollectionAdminView[],
): TreeNode<CollectionAdminView>[];
export function getNestedCollectionTree(collections: CollectionView[]): TreeNode<CollectionView>[];
export function getNestedCollectionTree(
collections: (CollectionView | CollectionAdminView)[],
): TreeNode<CollectionView | CollectionAdminView>[] {
if (!collections) {
return [];
}
// Collections need to be cloned because ServiceUtils.nestedTraverse actively
// modifies the names of collections.
// These changes risk affecting collections store in StateService.
const clonedCollections: CollectionView[] | CollectionAdminView[] = collections
.sort((a, b) => a.name.localeCompare(b.name))
.map(cloneCollection);
const all: TreeNode<CollectionView | CollectionAdminView>[] = [];
const groupedByOrg = new Map<OrganizationId, (CollectionView | CollectionAdminView)[]>();
clonedCollections.map((c) => {
const key = c.organizationId;
(groupedByOrg.get(key) ?? groupedByOrg.set(key, []).get(key)!).push(c);
});
for (const group of groupedByOrg.values()) {
const nodes: TreeNode<CollectionView | CollectionAdminView>[] = [];
for (const c of group) {
const parts = c.name ? c.name.replace(/^\/+|\/+$/g, "").split(NestingDelimiter) : [];
ServiceUtils.nestedTraverse(nodes, 0, parts, c, undefined, NestingDelimiter);
}
all.push(...nodes);
}
return all;
}
export function cloneCollection(collection: CollectionView): CollectionView;
export function cloneCollection(collection: CollectionAdminView): CollectionAdminView;
export function cloneCollection(
collection: CollectionView | CollectionAdminView,
): CollectionView | CollectionAdminView {
let cloned;
if (collection instanceof CollectionAdminView) {
cloned = Object.assign(
new CollectionAdminView({ ...collection, name: collection.name }),
collection,
);
} else {
cloned = Object.assign(
new CollectionView({ ...collection, name: collection.name }),
collection,
);
}
return cloned;
}
export function getFlatCollectionTree(
nodes: TreeNode<CollectionAdminView>[],
): CollectionAdminView[];
export function getFlatCollectionTree(nodes: TreeNode<CollectionView>[]): CollectionView[];
export function getFlatCollectionTree(
nodes: TreeNode<CollectionView | CollectionAdminView>[],
): (CollectionView | CollectionAdminView)[] {
if (!nodes || nodes.length === 0) {
return [];
}
return nodes.flatMap((node) => {
if (!node.children || node.children.length === 0) {
return [node.node];
}
const children = getFlatCollectionTree(node.children);
return [node.node, ...children];
});
}

View File

@@ -0,0 +1 @@
export * from "./collection-utils";