1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-24 08:33:29 +00:00

Search builder (#13823)

* Work on SearchBuilderComponent

* Get component to not throw errors

Co-authored-by: Matt Gibson <mgibson@bitwarden.com>

* Rename to filter

* Align Buttons Correctly

* Filter Build Updates

* Add VaultFilterMetadataService

* Rename Directory

* Emit filter

---------

Co-authored-by: Matt Gibson <mgibson@bitwarden.com>
This commit is contained in:
Justin Baur
2025-03-13 13:34:41 -04:00
committed by Matt Gibson
parent 5922947474
commit ecb95bb471
6 changed files with 523 additions and 2 deletions

View File

@@ -0,0 +1,118 @@
import { firstValueFrom, of } from "rxjs";
import { CipherType } from "../enums";
import { AttachmentView } from "../models/view/attachment.view";
import { CipherView } from "../models/view/cipher.view";
import { FieldView } from "../models/view/field.view";
import {
VaultFilterMetadata,
VaultFilterMetadataService as VaultFilterMetadataService,
} from "./vault-filter-metadata.service";
type TestCipher = {
organization?: string;
type: CipherType;
folderId?: string;
fields?: string[];
collectionIds?: string[];
attachments?: number;
};
const createCipher = (data: TestCipher) => {
const cipher = new CipherView();
cipher.organizationId = data.organization ?? null;
cipher.type = data.type;
cipher.fields = data.fields?.map((f) => {
const field = new FieldView();
field.name = f;
return field;
});
cipher.collectionIds = data.collectionIds;
if (data.attachments != null) {
const attachments: AttachmentView[] = [];
for (let i = 0; i < data.attachments; i++) {
attachments.push(new AttachmentView());
}
cipher.attachments = attachments;
}
return cipher;
};
describe("VaultFilterMetadataService", () => {
const sut = new VaultFilterMetadataService();
describe("collectMetadata", () => {
const testData: {
name: string;
input: CipherView[];
output: VaultFilterMetadata;
}[] = [
{
name: "single personal vault cipher",
input: [createCipher({ type: CipherType.Card })],
output: {
vaults: new Set([null]),
fieldNames: new Set([]),
itemTypes: new Set([CipherType.Card]),
folders: new Set([]),
collections: new Set([]),
anyHaveAttachment: false,
},
},
{
name: "multiple different org ciphers",
input: [
createCipher({
organization: "org-one",
type: CipherType.Login,
attachments: 2,
collectionIds: ["one"],
fields: ["one", "one"],
}),
createCipher({
organization: "org-one",
type: CipherType.Login,
attachments: 2,
collectionIds: ["one"],
fields: ["one", "one"],
}),
createCipher({
organization: "org-two",
type: CipherType.Login,
attachments: 2,
collectionIds: ["one"],
fields: ["one", "one"],
}),
createCipher({
organization: "org-two",
type: CipherType.Card,
attachments: 2,
collectionIds: ["three"],
fields: ["one", "five"],
}),
],
output: {
vaults: new Set(["org-one", "org-two"]),
fieldNames: new Set(["one", "five"]),
itemTypes: new Set([CipherType.Login, CipherType.Card]),
folders: new Set([]),
collections: new Set(["one", "three"]),
anyHaveAttachment: true,
},
},
];
it.each(testData)("$name", async ({ input, output }) => {
const actualMetadata = await firstValueFrom(of(input).pipe(sut.collectMetadata()));
expect(actualMetadata.vaults).toEqual(output.vaults);
expect(actualMetadata.fieldNames).toEqual(output.fieldNames);
expect(actualMetadata.itemTypes).toEqual(output.itemTypes);
expect(actualMetadata.folders).toEqual(output.folders);
expect(actualMetadata.collections).toEqual(output.collections);
expect(actualMetadata.anyHaveAttachment).toBe(output.anyHaveAttachment);
});
});
});

View File

@@ -0,0 +1,63 @@
import { map } from "rxjs";
import { CipherType } from "../enums";
import { CipherView } from "../models/view/cipher.view";
export type VaultFilterMetadata = {
vaults: Set<string | null>;
fieldNames: Set<string>;
itemTypes: Set<CipherType>;
folders: Set<string>;
collections: Set<string>;
anyHaveAttachment: boolean;
};
export class VaultFilterMetadataService {
collectMetadata() {
return map<CipherView[], VaultFilterMetadata>((ciphers) => {
return ciphers.reduce<VaultFilterMetadata>(
(metadata, cipher) => {
// Track type
metadata.itemTypes.add(cipher.type);
// Track vault
metadata.vaults.add(cipher.organizationId ?? null);
// Track all field names
if (cipher.fields != null) {
for (const field of cipher.fields) {
metadata.fieldNames.add(field.name);
}
}
// Track all folder ids
if (cipher.folderId != null) {
metadata.folders.add(cipher.folderId);
}
// Track all collections
if (cipher.collectionIds != null) {
for (const collectionId of cipher.collectionIds) {
metadata.collections.add(collectionId);
}
}
// Track if any have an attachment
if (cipher.attachments != null && cipher.attachments.length > 0) {
metadata.anyHaveAttachment = true;
}
return metadata;
},
{
vaults: new Set<string | null>(),
fieldNames: new Set<string>(),
itemTypes: new Set<CipherType>(),
folders: new Set<string>(),
collections: new Set<string>(),
anyHaveAttachment: false,
},
);
});
}
}