mirror of
https://github.com/bitwarden/browser
synced 2026-02-25 09:03:28 +00:00
Desktop/pm 18769/migrate vault filters (#17919)
Migrated vault filters to new v3 vault's navigation * Decoupled existing vault filtering from vault component by using routed params with routed-vault-filter-bridge * Converted vault filters to standalone components * Removed extending filter Base Components from deprecated /libs/angular library and handled logic directly * Moved shared 'models' and 'services' directories from web-vault into /libs/vault
This commit is contained in:
committed by
jaasen-livefront
parent
7088447046
commit
acad754540
@@ -7,12 +7,12 @@ import { combineLatest, of, Subject, switchMap, takeUntil } from "rxjs";
|
||||
import {
|
||||
CollectionAdminService,
|
||||
OrganizationUserApiService,
|
||||
CollectionView,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import {
|
||||
getOrganizationById,
|
||||
OrganizationService,
|
||||
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { CollectionView } from "@bitwarden/common/admin-console/models/collections";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// @ts-strict-ignore
|
||||
import { Component, Input } from "@angular/core";
|
||||
|
||||
import { CollectionView } from "@bitwarden/admin-console/common";
|
||||
import { CollectionView } from "@bitwarden/common/admin-console/models/collections";
|
||||
import { uuidAsString } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
|
||||
import { CollectionId } from "@bitwarden/sdk-internal";
|
||||
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
export * from "./utils";
|
||||
export * from "./collection-badge";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Pipe, PipeTransform } from "@angular/core";
|
||||
|
||||
import { CollectionView } from "@bitwarden/admin-console/common";
|
||||
import { CollectionView } from "@bitwarden/common/admin-console/models/collections";
|
||||
|
||||
@Pipe({
|
||||
name: "collectionNameFromId",
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,87 +0,0 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
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];
|
||||
});
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./collection-utils";
|
||||
@@ -15,15 +15,15 @@ import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstraction
|
||||
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
||||
import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
|
||||
import { VaultFilterComponent as BaseVaultFilterComponent } from "../../../../vault/individual-vault/vault-filter/components/vault-filter.component";
|
||||
import { VaultFilterService } from "../../../../vault/individual-vault/vault-filter/services/abstractions/vault-filter.service";
|
||||
import {
|
||||
VaultFilterService,
|
||||
VaultFilterList,
|
||||
VaultFilterSection,
|
||||
VaultFilterType,
|
||||
} from "../../../../vault/individual-vault/vault-filter/shared/models/vault-filter-section.type";
|
||||
import { CollectionFilter } from "../../../../vault/individual-vault/vault-filter/shared/models/vault-filter.type";
|
||||
CollectionFilter,
|
||||
} from "@bitwarden/vault";
|
||||
|
||||
import { VaultFilterComponent as BaseVaultFilterComponent } from "../../../../vault/individual-vault/vault-filter/components/vault-filter.component";
|
||||
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { SearchModule } from "@bitwarden/components";
|
||||
import { VaultFilterServiceAbstraction } from "@bitwarden/vault";
|
||||
|
||||
import { VaultFilterService as VaultFilterServiceAbstraction } from "../../../../vault/individual-vault/vault-filter/services/abstractions/vault-filter.service";
|
||||
import { VaultFilterSharedModule } from "../../../../vault/individual-vault/vault-filter/shared/vault-filter-shared.module";
|
||||
|
||||
import { VaultFilterComponent } from "./vault-filter.component";
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import { Injectable, OnDestroy } from "@angular/core";
|
||||
import { map, Observable, ReplaySubject, Subject } from "rxjs";
|
||||
|
||||
import { CollectionAdminView, CollectionService } from "@bitwarden/admin-console/common";
|
||||
import { CollectionService } from "@bitwarden/admin-console/common";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { CollectionAdminView } from "@bitwarden/common/admin-console/models/collections";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { StateProvider } from "@bitwarden/common/platform/state";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
||||
|
||||
import { VaultFilterService as BaseVaultFilterService } from "../../../../vault/individual-vault/vault-filter/services/vault-filter.service";
|
||||
import { CollectionFilter } from "../../../../vault/individual-vault/vault-filter/shared/models/vault-filter.type";
|
||||
import { VaultFilterService as BaseVaultFilterService, CollectionFilter } from "@bitwarden/vault";
|
||||
|
||||
@Injectable()
|
||||
export class VaultFilterService extends BaseVaultFilterService implements OnDestroy {
|
||||
|
||||
@@ -7,12 +7,12 @@ import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
import { firstValueFrom, switchMap } from "rxjs";
|
||||
|
||||
import { CollectionAdminService } from "@bitwarden/admin-console/common";
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import {
|
||||
CollectionAdminService,
|
||||
CollectionAdminView,
|
||||
Unassigned,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
} from "@bitwarden/common/admin-console/models/collections";
|
||||
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";
|
||||
@@ -27,14 +27,10 @@ import {
|
||||
SearchModule,
|
||||
SimpleDialogOptions,
|
||||
} from "@bitwarden/components";
|
||||
import { NewCipherMenuComponent } from "@bitwarden/vault";
|
||||
import { NewCipherMenuComponent, All, RoutedVaultFilterModel } from "@bitwarden/vault";
|
||||
|
||||
import { HeaderModule } from "../../../../layouts/header/header.module";
|
||||
import { SharedModule } from "../../../../shared";
|
||||
import {
|
||||
All,
|
||||
RoutedVaultFilterModel,
|
||||
} from "../../../../vault/individual-vault/vault-filter/shared/models/routed-vault-filter.model";
|
||||
import { CollectionDialogTabType } from "../../shared/components/collection-dialog";
|
||||
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
|
||||
|
||||
@@ -27,19 +27,22 @@ import {
|
||||
takeUntil,
|
||||
} from "rxjs/operators";
|
||||
|
||||
import {
|
||||
CollectionAdminService,
|
||||
CollectionAdminView,
|
||||
CollectionService,
|
||||
CollectionView,
|
||||
Unassigned,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { CollectionAdminService, CollectionService } from "@bitwarden/admin-console/common";
|
||||
import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe";
|
||||
import { NoResults } from "@bitwarden/assets/svg";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import {
|
||||
CollectionView,
|
||||
CollectionAdminView,
|
||||
Unassigned,
|
||||
} from "@bitwarden/common/admin-console/models/collections";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import {
|
||||
getFlatCollectionTree,
|
||||
getNestedCollectionTree,
|
||||
} from "@bitwarden/common/admin-console/utils";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
|
||||
@@ -81,6 +84,13 @@ import {
|
||||
CollectionAssignmentResult,
|
||||
DecryptionFailureDialogComponent,
|
||||
PasswordRepromptService,
|
||||
VaultFilterServiceAbstraction as VaultFilterService,
|
||||
RoutedVaultFilterBridgeService,
|
||||
RoutedVaultFilterService,
|
||||
createFilterFunction,
|
||||
All,
|
||||
RoutedVaultFilterModel,
|
||||
VaultFilter,
|
||||
} from "@bitwarden/vault";
|
||||
import {
|
||||
OrganizationFreeTrialWarningComponent,
|
||||
@@ -102,15 +112,6 @@ import {
|
||||
BulkDeleteDialogResult,
|
||||
openBulkDeleteDialog,
|
||||
} from "../../../vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component";
|
||||
import { VaultFilterService } from "../../../vault/individual-vault/vault-filter/services/abstractions/vault-filter.service";
|
||||
import { RoutedVaultFilterBridgeService } from "../../../vault/individual-vault/vault-filter/services/routed-vault-filter-bridge.service";
|
||||
import { RoutedVaultFilterService } from "../../../vault/individual-vault/vault-filter/services/routed-vault-filter.service";
|
||||
import { createFilterFunction } from "../../../vault/individual-vault/vault-filter/shared/models/filter-function";
|
||||
import {
|
||||
All,
|
||||
RoutedVaultFilterModel,
|
||||
} from "../../../vault/individual-vault/vault-filter/shared/models/routed-vault-filter.model";
|
||||
import { VaultFilter } from "../../../vault/individual-vault/vault-filter/shared/models/vault-filter.model";
|
||||
import { AdminConsoleCipherFormConfigService } from "../../../vault/org-vault/services/admin-console-cipher-form-config.service";
|
||||
import { GroupApiService, GroupView } from "../core";
|
||||
import { openEntityEventsDialog } from "../manage/entity-events.component";
|
||||
@@ -126,7 +127,6 @@ import {
|
||||
BulkCollectionsDialogResult,
|
||||
} from "./bulk-collections-dialog";
|
||||
import { CollectionAccessRestrictedComponent } from "./collection-access-restricted.component";
|
||||
import { getFlatCollectionTree, getNestedCollectionTree } from "./utils";
|
||||
import { VaultFilterModule } from "./vault-filter/vault-filter.module";
|
||||
import { VaultHeaderComponent } from "./vault-header/vault-header.component";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CollectionAccessSelectionView } from "@bitwarden/admin-console/common";
|
||||
import { CollectionAccessSelectionView } from "@bitwarden/common/admin-console/models/collections";
|
||||
|
||||
export interface AddEditGroupDetail {
|
||||
id: string;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { CollectionAccessSelectionView } from "@bitwarden/admin-console/common";
|
||||
import { CollectionAccessSelectionView } from "@bitwarden/common/admin-console/models/collections";
|
||||
import { View } from "@bitwarden/common/models/view/view";
|
||||
|
||||
import { GroupDetailsResponse } from "../services/group/responses/group.response";
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import {
|
||||
CollectionAccessSelectionView,
|
||||
OrganizationUserDetailsResponse,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { OrganizationUserDetailsResponse } from "@bitwarden/admin-console/common";
|
||||
import {
|
||||
OrganizationUserStatusType,
|
||||
OrganizationUserType,
|
||||
} from "@bitwarden/common/admin-console/enums";
|
||||
import { PermissionsApi } from "@bitwarden/common/admin-console/models/api/permissions.api";
|
||||
import { CollectionAccessSelectionView } from "@bitwarden/common/admin-console/models/collections";
|
||||
|
||||
export class OrganizationUserAdminView {
|
||||
id: string;
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import {
|
||||
OrganizationUserUserDetailsResponse,
|
||||
CollectionAccessSelectionView,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { OrganizationUserUserDetailsResponse } from "@bitwarden/admin-console/common";
|
||||
import {
|
||||
OrganizationUserStatusType,
|
||||
OrganizationUserType,
|
||||
} from "@bitwarden/common/admin-console/enums";
|
||||
import { PermissionsApi } from "@bitwarden/common/admin-console/models/api/permissions.api";
|
||||
import { CollectionAccessSelectionView } from "@bitwarden/common/admin-console/models/collections";
|
||||
|
||||
export class OrganizationUserView {
|
||||
id: string;
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
|
||||
import {
|
||||
CollectionAdminService,
|
||||
CollectionAdminView,
|
||||
OrganizationUserApiService,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
@@ -26,6 +25,7 @@ import {
|
||||
getOrganizationById,
|
||||
OrganizationService,
|
||||
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { CollectionAdminView } from "@bitwarden/common/admin-console/models/collections";
|
||||
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";
|
||||
|
||||
@@ -17,15 +17,15 @@ import {
|
||||
} from "rxjs";
|
||||
import { debounceTime, first } from "rxjs/operators";
|
||||
|
||||
import { CollectionService } from "@bitwarden/admin-console/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import {
|
||||
CollectionService,
|
||||
CollectionData,
|
||||
Collection,
|
||||
CollectionView,
|
||||
CollectionDetailsResponse,
|
||||
CollectionResponse,
|
||||
CollectionView,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
Collection,
|
||||
CollectionData,
|
||||
} from "@bitwarden/common/admin-console/models/collections";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||
|
||||
@@ -15,11 +15,8 @@ import {
|
||||
} from "rxjs";
|
||||
|
||||
import {
|
||||
CollectionAccessSelectionView,
|
||||
CollectionAdminService,
|
||||
CollectionAdminView,
|
||||
OrganizationUserApiService,
|
||||
CollectionView,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import {
|
||||
getOrganizationById,
|
||||
@@ -30,6 +27,11 @@ import {
|
||||
OrganizationUserType,
|
||||
} from "@bitwarden/common/admin-console/enums";
|
||||
import { PermissionsApi } from "@bitwarden/common/admin-console/models/api/permissions.api";
|
||||
import {
|
||||
CollectionAccessSelectionView,
|
||||
CollectionAdminView,
|
||||
CollectionView,
|
||||
} from "@bitwarden/common/admin-console/models/collections";
|
||||
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";
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { combineLatest, firstValueFrom, from, map, switchMap } from "rxjs";
|
||||
|
||||
import { CollectionService, OrganizationUserApiService } from "@bitwarden/admin-console/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import {
|
||||
CollectionDetailsResponse,
|
||||
Collection,
|
||||
CollectionData,
|
||||
CollectionDetailsResponse,
|
||||
CollectionService,
|
||||
OrganizationUserApiService,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
} from "@bitwarden/common/admin-console/models/collections";
|
||||
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";
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import {
|
||||
CollectionAccessSelectionView,
|
||||
OrganizationUserUserDetailsResponse,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { OrganizationUserUserDetailsResponse } from "@bitwarden/admin-console/common";
|
||||
import {
|
||||
OrganizationUserStatusType,
|
||||
OrganizationUserType,
|
||||
} from "@bitwarden/common/admin-console/enums";
|
||||
import { CollectionAccessSelectionView } from "@bitwarden/common/admin-console/models/collections";
|
||||
import { SelectItemView } from "@bitwarden/components";
|
||||
|
||||
import { GroupView } from "../../../core";
|
||||
|
||||
@@ -18,19 +18,21 @@ import {
|
||||
import { first } from "rxjs/operators";
|
||||
|
||||
import {
|
||||
CollectionAccessSelectionView,
|
||||
CollectionAdminService,
|
||||
CollectionAdminView,
|
||||
OrganizationUserApiService,
|
||||
OrganizationUserUserMiniResponse,
|
||||
CollectionResponse,
|
||||
CollectionView,
|
||||
CollectionService,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import {
|
||||
getOrganizationById,
|
||||
OrganizationService,
|
||||
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import {
|
||||
CollectionAccessSelectionView,
|
||||
CollectionAdminView,
|
||||
CollectionView,
|
||||
CollectionResponse,
|
||||
} from "@bitwarden/common/admin-console/models/collections";
|
||||
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";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { AbstractControl, AsyncValidatorFn, FormControl, ValidationErrors } from "@angular/forms";
|
||||
import { combineLatest, map, Observable, of } from "rxjs";
|
||||
|
||||
import { Collection } from "@bitwarden/admin-console/common";
|
||||
import { Collection } from "@bitwarden/common/admin-console/models/collections";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { getById } from "@bitwarden/common/platform/misc";
|
||||
|
||||
@@ -17,14 +17,17 @@ import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.serv
|
||||
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { PasswordRepromptService, CipherFormConfigService } from "@bitwarden/vault";
|
||||
import {
|
||||
PasswordRepromptService,
|
||||
CipherFormConfigService,
|
||||
RoutedVaultFilterBridgeService,
|
||||
RoutedVaultFilterService,
|
||||
} from "@bitwarden/vault";
|
||||
|
||||
import { HeaderModule } from "../../../../layouts/header/header.module";
|
||||
import { SharedModule } from "../../../../shared";
|
||||
import { OrganizationBadgeModule } from "../../../../vault/individual-vault/organization-badge/organization-badge.module";
|
||||
import { PipesModule } from "../../../../vault/individual-vault/pipes/pipes.module";
|
||||
import { RoutedVaultFilterBridgeService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter-bridge.service";
|
||||
import { RoutedVaultFilterService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter.service";
|
||||
import { AdminConsoleCipherFormConfigService } from "../../../../vault/org-vault/services/admin-console-cipher-form-config.service";
|
||||
import { ExposedPasswordsReportComponent as BaseExposedPasswordsReportComponent } from "../exposed-passwords-report.component";
|
||||
|
||||
|
||||
@@ -12,14 +12,17 @@ import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.serv
|
||||
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { CipherFormConfigService, PasswordRepromptService } from "@bitwarden/vault";
|
||||
import {
|
||||
CipherFormConfigService,
|
||||
PasswordRepromptService,
|
||||
RoutedVaultFilterBridgeService,
|
||||
RoutedVaultFilterService,
|
||||
} from "@bitwarden/vault";
|
||||
|
||||
import { HeaderModule } from "../../../../layouts/header/header.module";
|
||||
import { SharedModule } from "../../../../shared";
|
||||
import { OrganizationBadgeModule } from "../../../../vault/individual-vault/organization-badge/organization-badge.module";
|
||||
import { PipesModule } from "../../../../vault/individual-vault/pipes/pipes.module";
|
||||
import { RoutedVaultFilterBridgeService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter-bridge.service";
|
||||
import { RoutedVaultFilterService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter.service";
|
||||
import { AdminConsoleCipherFormConfigService } from "../../../../vault/org-vault/services/admin-console-cipher-form-config.service";
|
||||
import { InactiveTwoFactorReportComponent as BaseInactiveTwoFactorReportComponent } from "../inactive-two-factor-report.component";
|
||||
|
||||
|
||||
@@ -16,14 +16,17 @@ import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.serv
|
||||
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { CipherFormConfigService, PasswordRepromptService } from "@bitwarden/vault";
|
||||
import {
|
||||
CipherFormConfigService,
|
||||
PasswordRepromptService,
|
||||
RoutedVaultFilterBridgeService,
|
||||
RoutedVaultFilterService,
|
||||
} from "@bitwarden/vault";
|
||||
|
||||
import { HeaderModule } from "../../../../layouts/header/header.module";
|
||||
import { SharedModule } from "../../../../shared";
|
||||
import { OrganizationBadgeModule } from "../../../../vault/individual-vault/organization-badge/organization-badge.module";
|
||||
import { PipesModule } from "../../../../vault/individual-vault/pipes/pipes.module";
|
||||
import { RoutedVaultFilterBridgeService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter-bridge.service";
|
||||
import { RoutedVaultFilterService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter.service";
|
||||
import { AdminConsoleCipherFormConfigService } from "../../../../vault/org-vault/services/admin-console-cipher-form-config.service";
|
||||
import { ReusedPasswordsReportComponent as BaseReusedPasswordsReportComponent } from "../reused-passwords-report.component";
|
||||
|
||||
|
||||
@@ -16,14 +16,17 @@ import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.serv
|
||||
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { CipherFormConfigService, PasswordRepromptService } from "@bitwarden/vault";
|
||||
import {
|
||||
CipherFormConfigService,
|
||||
PasswordRepromptService,
|
||||
RoutedVaultFilterBridgeService,
|
||||
RoutedVaultFilterService,
|
||||
} from "@bitwarden/vault";
|
||||
|
||||
import { HeaderModule } from "../../../../layouts/header/header.module";
|
||||
import { SharedModule } from "../../../../shared";
|
||||
import { OrganizationBadgeModule } from "../../../../vault/individual-vault/organization-badge/organization-badge.module";
|
||||
import { PipesModule } from "../../../../vault/individual-vault/pipes/pipes.module";
|
||||
import { RoutedVaultFilterBridgeService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter-bridge.service";
|
||||
import { RoutedVaultFilterService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter.service";
|
||||
import { AdminConsoleCipherFormConfigService } from "../../../../vault/org-vault/services/admin-console-cipher-form-config.service";
|
||||
import { UnsecuredWebsitesReportComponent as BaseUnsecuredWebsitesReportComponent } from "../unsecured-websites-report.component";
|
||||
|
||||
|
||||
@@ -17,14 +17,17 @@ import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.serv
|
||||
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { CipherFormConfigService, PasswordRepromptService } from "@bitwarden/vault";
|
||||
import {
|
||||
CipherFormConfigService,
|
||||
PasswordRepromptService,
|
||||
RoutedVaultFilterBridgeService,
|
||||
RoutedVaultFilterService,
|
||||
} from "@bitwarden/vault";
|
||||
|
||||
import { HeaderModule } from "../../../../layouts/header/header.module";
|
||||
import { SharedModule } from "../../../../shared";
|
||||
import { OrganizationBadgeModule } from "../../../../vault/individual-vault/organization-badge/organization-badge.module";
|
||||
import { PipesModule } from "../../../../vault/individual-vault/pipes/pipes.module";
|
||||
import { RoutedVaultFilterBridgeService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter-bridge.service";
|
||||
import { RoutedVaultFilterService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter.service";
|
||||
import { AdminConsoleCipherFormConfigService } from "../../../../vault/org-vault/services/admin-console-cipher-form-config.service";
|
||||
import { WeakPasswordsReportComponent as BaseWeakPasswordsReportComponent } from "../weak-passwords-report.component";
|
||||
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { CipherFormConfigService, DefaultCipherFormConfigService } from "@bitwarden/vault";
|
||||
import {
|
||||
CipherFormConfigService,
|
||||
DefaultCipherFormConfigService,
|
||||
RoutedVaultFilterBridgeService,
|
||||
RoutedVaultFilterService,
|
||||
} from "@bitwarden/vault";
|
||||
|
||||
import { HeaderModule } from "../../layouts/header/header.module";
|
||||
import { SharedModule } from "../../shared";
|
||||
import { OrganizationBadgeModule } from "../../vault/individual-vault/organization-badge/organization-badge.module";
|
||||
import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module";
|
||||
import { RoutedVaultFilterBridgeService } from "../../vault/individual-vault/vault-filter/services/routed-vault-filter-bridge.service";
|
||||
import { RoutedVaultFilterService } from "../../vault/individual-vault/vault-filter/services/routed-vault-filter.service";
|
||||
import { AdminConsoleCipherFormConfigService } from "../../vault/org-vault/services/admin-console-cipher-form-config.service";
|
||||
|
||||
import { BreachReportComponent } from "./pages/breach-report.component";
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { CollectionAdminService, CollectionAdminView } from "@bitwarden/admin-console/common";
|
||||
import { CollectionAdminService } from "@bitwarden/admin-console/common";
|
||||
import { CollectionAdminView } from "@bitwarden/common/admin-console/models/collections";
|
||||
import { ImportCollectionServiceAbstraction } from "@bitwarden/importer-core";
|
||||
import { UserId } from "@bitwarden/user-core";
|
||||
|
||||
|
||||
@@ -28,8 +28,7 @@ import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service";
|
||||
import { TaskService } from "@bitwarden/common/vault/tasks";
|
||||
import { DialogRef, DIALOG_DATA, DialogService, ToastService } from "@bitwarden/components";
|
||||
|
||||
import { RoutedVaultFilterService } from "../../individual-vault/vault-filter/services/routed-vault-filter.service";
|
||||
import { RoutedVaultFilterService } from "@bitwarden/vault";
|
||||
|
||||
import { VaultItemDialogComponent } from "./vault-item-dialog.component";
|
||||
|
||||
|
||||
@@ -15,11 +15,11 @@ import { Router } from "@angular/router";
|
||||
import { firstValueFrom, Observable, Subject, switchMap } from "rxjs";
|
||||
import { map } from "rxjs/operators";
|
||||
|
||||
import { CollectionView } from "@bitwarden/admin-console/common";
|
||||
import { PremiumBadgeComponent } from "@bitwarden/angular/billing/components/premium-badge";
|
||||
import { VaultViewPasswordHistoryService } from "@bitwarden/angular/services/view-password-history.service";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { CollectionView } from "@bitwarden/common/admin-console/models/collections";
|
||||
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";
|
||||
@@ -63,11 +63,11 @@ import {
|
||||
CipherViewComponent,
|
||||
DecryptionFailureDialogComponent,
|
||||
DefaultChangeLoginPasswordService,
|
||||
RoutedVaultFilterService,
|
||||
RoutedVaultFilterModel,
|
||||
} from "@bitwarden/vault";
|
||||
|
||||
import { SharedModule } from "../../../shared/shared.module";
|
||||
import { RoutedVaultFilterService } from "../../individual-vault/vault-filter/services/routed-vault-filter.service";
|
||||
import { RoutedVaultFilterModel } from "../../individual-vault/vault-filter/shared/models/routed-vault-filter.model";
|
||||
import { WebCipherFormGenerationService } from "../../services/web-cipher-form-generation.service";
|
||||
import { WebVaultPremiumUpgradePromptService } from "../../services/web-premium-upgrade-prompt.service";
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
input,
|
||||
} from "@angular/core";
|
||||
|
||||
import { CollectionView } from "@bitwarden/admin-console/common";
|
||||
import { CollectionView } from "@bitwarden/common/admin-console/models/collections";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
Unassigned,
|
||||
CollectionView,
|
||||
CollectionTypes,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
} from "@bitwarden/common/admin-console/models/collections";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { CipherViewLike } from "@bitwarden/common/vault/utils/cipher-view-like-utils";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CollectionView } from "@bitwarden/admin-console/common";
|
||||
import { CollectionView } from "@bitwarden/common/admin-console/models/collections";
|
||||
import { CipherViewLike } from "@bitwarden/common/vault/utils/cipher-view-like-utils";
|
||||
import { CollectionPermission } from "@bitwarden/web-vault/app/admin-console/organizations/shared/components/access-selector";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CollectionView } from "@bitwarden/admin-console/common";
|
||||
import { CollectionView } from "@bitwarden/common/admin-console/models/collections";
|
||||
import { CipherViewLike } from "@bitwarden/common/vault/utils/cipher-view-like-utils";
|
||||
|
||||
export interface VaultItem<C extends CipherViewLike> {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ScrollingModule } from "@angular/cdk/scrolling";
|
||||
import { TestBed } from "@angular/core/testing";
|
||||
import { of, Subject } from "rxjs";
|
||||
|
||||
import { CollectionView } from "@bitwarden/admin-console/common";
|
||||
import { CollectionView } from "@bitwarden/common/admin-console/models/collections";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
@@ -11,8 +11,7 @@ import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/res
|
||||
import { CipherViewLike } from "@bitwarden/common/vault/utils/cipher-view-like-utils";
|
||||
import { MenuModule, TableModule } from "@bitwarden/components";
|
||||
import { I18nPipe } from "@bitwarden/ui-common";
|
||||
import { RoutedVaultFilterService } from "@bitwarden/web-vault/app/vault/individual-vault/vault-filter/services/routed-vault-filter.service";
|
||||
import { RoutedVaultFilterModel } from "@bitwarden/web-vault/app/vault/individual-vault/vault-filter/shared/models/routed-vault-filter.model";
|
||||
import { RoutedVaultFilterService, RoutedVaultFilterModel } from "@bitwarden/vault";
|
||||
|
||||
import { VaultItem } from "./vault-item";
|
||||
import { VaultItemsComponent } from "./vault-items.component";
|
||||
|
||||
@@ -13,7 +13,11 @@ import {
|
||||
switchMap,
|
||||
} from "rxjs";
|
||||
|
||||
import { CollectionView, Unassigned, CollectionAdminView } from "@bitwarden/admin-console/common";
|
||||
import {
|
||||
CollectionAdminView,
|
||||
Unassigned,
|
||||
CollectionView,
|
||||
} from "@bitwarden/common/admin-console/models/collections";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
|
||||
import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service";
|
||||
@@ -27,7 +31,7 @@ import {
|
||||
} from "@bitwarden/common/vault/utils/cipher-view-like-utils";
|
||||
import { SortDirection, TableDataSource } from "@bitwarden/components";
|
||||
import { OrganizationId } from "@bitwarden/sdk-internal";
|
||||
import { RoutedVaultFilterService } from "@bitwarden/web-vault/app/vault/individual-vault/vault-filter/services/routed-vault-filter.service";
|
||||
import { RoutedVaultFilterService } from "@bitwarden/vault";
|
||||
|
||||
import { GroupView } from "../../../admin-console/organizations/core";
|
||||
|
||||
|
||||
@@ -11,13 +11,13 @@ import {
|
||||
} from "@storybook/angular";
|
||||
import { BehaviorSubject, of } from "rxjs";
|
||||
|
||||
import { OrganizationUserType } from "@bitwarden/common/admin-console/enums";
|
||||
import { PermissionsApi } from "@bitwarden/common/admin-console/models/api/permissions.api";
|
||||
import {
|
||||
CollectionAccessSelectionView,
|
||||
CollectionAdminView,
|
||||
Unassigned,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { OrganizationUserType } from "@bitwarden/common/admin-console/enums";
|
||||
import { PermissionsApi } from "@bitwarden/common/admin-console/models/api/permissions.api";
|
||||
} from "@bitwarden/common/admin-console/models/collections";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service";
|
||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||
@@ -41,7 +41,7 @@ import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/res
|
||||
import { CipherViewLike } from "@bitwarden/common/vault/utils/cipher-view-like-utils";
|
||||
import { LayoutComponent, StorybookGlobalStateProvider } from "@bitwarden/components";
|
||||
import { GlobalStateProvider } from "@bitwarden/state";
|
||||
import { RoutedVaultFilterService } from "@bitwarden/web-vault/app/vault/individual-vault/vault-filter/services/routed-vault-filter.service";
|
||||
import { RoutedVaultFilterService } from "@bitwarden/vault";
|
||||
|
||||
import { GroupView } from "../../../admin-console/organizations/core";
|
||||
import { PreloadedEnglishI18nModule } from "../../../core/tests";
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
import { Component, Inject } from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { CollectionService, CollectionView } from "@bitwarden/admin-console/common";
|
||||
import { CollectionService } from "@bitwarden/admin-console/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { CollectionView } from "@bitwarden/common/admin-console/models/collections";
|
||||
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";
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { Component, Input, OnChanges } from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { Unassigned } from "@bitwarden/admin-console/common";
|
||||
import { Unassigned } from "@bitwarden/common/admin-console/models/collections";
|
||||
import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service";
|
||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
|
||||
@@ -32,12 +32,12 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
||||
import { SyncService } from "@bitwarden/common/platform/sync";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
import { OrganizationFilter } from "@bitwarden/vault";
|
||||
|
||||
import { OrganizationUserResetPasswordService } from "../../../../admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service";
|
||||
import { EnrollMasterPasswordReset } from "../../../../admin-console/organizations/users/enroll-master-password-reset.component";
|
||||
import { LinkSsoService } from "../../../../auth/core/services";
|
||||
import { OptionsInput } from "../shared/components/vault-filter-section.component";
|
||||
import { OrganizationFilter } from "../shared/models/vault-filter.type";
|
||||
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
|
||||
|
||||
@@ -27,22 +27,19 @@ import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
||||
import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
import { OrganizationWarningsService } from "@bitwarden/web-vault/app/billing/organizations/warnings/services";
|
||||
|
||||
import { VaultFilterService } from "../services/abstractions/vault-filter.service";
|
||||
import {
|
||||
VaultFilterServiceAbstraction as VaultFilterService,
|
||||
VaultFilterList,
|
||||
VaultFilterSection,
|
||||
VaultFilterType,
|
||||
} from "../shared/models/vault-filter-section.type";
|
||||
import { VaultFilter } from "../shared/models/vault-filter.model";
|
||||
import {
|
||||
VaultFilter,
|
||||
CipherStatus,
|
||||
CipherTypeFilter,
|
||||
CollectionFilter,
|
||||
FolderFilter,
|
||||
OrganizationFilter,
|
||||
} from "../shared/models/vault-filter.type";
|
||||
} from "@bitwarden/vault";
|
||||
import { OrganizationWarningsService } from "@bitwarden/web-vault/app/billing/organizations/warnings/services";
|
||||
|
||||
import { OrganizationOptionsComponent } from "./organization-options.component";
|
||||
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { CollectionAdminView, CollectionView } from "@bitwarden/admin-console/common";
|
||||
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 { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||
|
||||
import {
|
||||
CipherTypeFilter,
|
||||
CollectionFilter,
|
||||
FolderFilter,
|
||||
OrganizationFilter,
|
||||
} from "../../shared/models/vault-filter.type";
|
||||
|
||||
export abstract class VaultFilterService {
|
||||
collapsedFilterNodes$: Observable<Set<string>>;
|
||||
filteredFolders$: Observable<FolderView[]>;
|
||||
filteredCollections$: Observable<CollectionView[]>;
|
||||
organizationTree$: Observable<TreeNode<OrganizationFilter>>;
|
||||
folderTree$: Observable<TreeNode<FolderFilter>>;
|
||||
collectionTree$: Observable<TreeNode<CollectionFilter>>;
|
||||
cipherTypeTree$: Observable<TreeNode<CipherTypeFilter>>;
|
||||
abstract getCollectionNodeFromTree: (id: string) => Promise<TreeNode<CollectionFilter>>;
|
||||
abstract setCollapsedFilterNodes: (
|
||||
collapsedFilterNodes: Set<string>,
|
||||
userId: UserId,
|
||||
) => Promise<void>;
|
||||
abstract expandOrgFilter: (userId: UserId) => Promise<void>;
|
||||
abstract getOrganizationFilter: () => Observable<Organization>;
|
||||
abstract setOrganizationFilter: (organization: Organization) => void;
|
||||
abstract buildTypeTree: (
|
||||
head: CipherTypeFilter,
|
||||
array: CipherTypeFilter[],
|
||||
) => Observable<TreeNode<CipherTypeFilter>>;
|
||||
// TODO: Remove this from org vault when collection admin service adopts state management
|
||||
abstract reloadCollections?: (collections: CollectionAdminView[]) => void;
|
||||
abstract clearOrganizationFilter: () => void;
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
import { combineLatest, map, Observable } from "rxjs";
|
||||
|
||||
import { Unassigned } from "@bitwarden/admin-console/common";
|
||||
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
||||
import { ServiceUtils } from "@bitwarden/common/vault/service-utils";
|
||||
|
||||
import { RoutedVaultFilterBridge } from "../shared/models/routed-vault-filter-bridge.model";
|
||||
import { RoutedVaultFilterModel, 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);
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
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 && filter.organizationId === Unassigned) {
|
||||
legacyFilter.selectedOrganizationNode = ServiceUtils.getTreeNodeObject(
|
||||
organizationTree,
|
||||
"MyVault",
|
||||
);
|
||||
} else if (filter.organizationId !== undefined && filter.organizationId !== Unassigned) {
|
||||
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 === "archive") {
|
||||
legacyFilter.selectedCipherTypeNode = new TreeNode<CipherTypeFilter>(
|
||||
{ id: "archive", name: "", type: "archive", icon: "" },
|
||||
null,
|
||||
);
|
||||
} else if (filter.type !== undefined && filter.type !== "trash") {
|
||||
legacyFilter.selectedCipherTypeNode = ServiceUtils.getTreeNodeObject(
|
||||
cipherTypeTree,
|
||||
filter.type,
|
||||
);
|
||||
}
|
||||
|
||||
return legacyFilter;
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
import { Injectable, OnDestroy } from "@angular/core";
|
||||
import { ActivatedRoute, NavigationExtras } from "@angular/router";
|
||||
import { combineLatest, map, Observable, Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import {
|
||||
isRoutedVaultFilterItemType,
|
||||
RoutedVaultFilterModel,
|
||||
} from "../shared/models/routed-vault-filter.model";
|
||||
|
||||
/**
|
||||
* This service is an abstraction layer on top of ActivatedRoute that
|
||||
* encapsulates the logic of how filters are stored in the URL.
|
||||
*
|
||||
* The service builds and emits filter models based on URL params and
|
||||
* also contains a method for generating routes to corresponding to those params.
|
||||
*/
|
||||
@Injectable()
|
||||
export class RoutedVaultFilterService implements OnDestroy {
|
||||
private onDestroy = new Subject<void>();
|
||||
|
||||
/**
|
||||
* Filter values extracted from the URL.
|
||||
* To change the values use {@link RoutedVaultFilterService.createRoute}.
|
||||
*/
|
||||
filter$: Observable<RoutedVaultFilterModel>;
|
||||
|
||||
constructor(activatedRoute: ActivatedRoute) {
|
||||
this.filter$ = combineLatest([activatedRoute.paramMap, activatedRoute.queryParamMap]).pipe(
|
||||
map(([params, queryParams]) => {
|
||||
const unsafeType = queryParams.get("type");
|
||||
const type = isRoutedVaultFilterItemType(unsafeType) ? unsafeType : undefined;
|
||||
|
||||
return {
|
||||
collectionId: (queryParams.get("collectionId") as CollectionId) ?? undefined,
|
||||
folderId: queryParams.get("folderId") ?? undefined,
|
||||
organizationId:
|
||||
(params.get("organizationId") as OrganizationId) ??
|
||||
(queryParams.get("organizationId") as OrganizationId) ??
|
||||
undefined,
|
||||
organizationIdParamType:
|
||||
params.get("organizationId") != undefined ? ("path" as const) : ("query" as const),
|
||||
type,
|
||||
};
|
||||
}),
|
||||
takeUntil(this.onDestroy),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a route that can be used to modify filters with Router or RouterLink.
|
||||
* This method is specifically built to leave other query parameters untouched,
|
||||
* meaning that navigation will only affect filters and not e.g. `cipherId`.
|
||||
* To subscribe to changes use {@link RoutedVaultFilterService.filter$}.
|
||||
*
|
||||
* Note:
|
||||
* This method currently only supports changing filters that are stored
|
||||
* in query parameters. This means that {@link RoutedVaultFilterModel.organizationId}
|
||||
* will be ignored if {@link RoutedVaultFilterModel.organizationIdParamType}
|
||||
* is set to `path`.
|
||||
*
|
||||
* @param filter Filter values that should be applied to the URL.
|
||||
* @returns route that can be used with Router or RouterLink
|
||||
*/
|
||||
createRoute(filter: RoutedVaultFilterModel): [commands: any[], extras?: NavigationExtras] {
|
||||
const commands: string[] = [];
|
||||
const extras: NavigationExtras = {
|
||||
queryParams: {
|
||||
collectionId: filter.collectionId ?? null,
|
||||
folderId: filter.folderId ?? null,
|
||||
organizationId:
|
||||
filter.organizationIdParamType === "path" ? null : (filter.organizationId ?? null),
|
||||
type: filter.type ?? null,
|
||||
},
|
||||
queryParamsHandling: "merge",
|
||||
state: {
|
||||
focusMainAfterNav: false,
|
||||
},
|
||||
};
|
||||
return [commands, extras];
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.onDestroy.next();
|
||||
this.onDestroy.complete();
|
||||
}
|
||||
}
|
||||
@@ -1,398 +0,0 @@
|
||||
import {
|
||||
FakeAccountService,
|
||||
mockAccountServiceWith,
|
||||
} from "@bitwarden/common/../spec/fake-account-service";
|
||||
import { FakeSingleUserState } from "@bitwarden/common/../spec/fake-state";
|
||||
import { FakeStateProvider } from "@bitwarden/common/../spec/fake-state-provider";
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
import { firstValueFrom, of, ReplaySubject } from "rxjs";
|
||||
|
||||
import {
|
||||
CollectionService,
|
||||
CollectionType,
|
||||
CollectionTypes,
|
||||
CollectionView,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import * as vaultFilterSvc from "@bitwarden/angular/vault/vault-filter/services/vault-filter.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||
import { COLLAPSED_GROUPINGS } from "@bitwarden/common/vault/services/key-state/collapsed-groupings.state";
|
||||
|
||||
import { VaultFilterService } from "./vault-filter.service";
|
||||
|
||||
jest.mock("@bitwarden/angular/vault/vault-filter/services/vault-filter.service", () => ({
|
||||
sortDefaultCollections: jest.fn(() => []),
|
||||
}));
|
||||
|
||||
describe("vault filter service", () => {
|
||||
let vaultFilterService: VaultFilterService;
|
||||
|
||||
let organizationService: MockProxy<OrganizationService>;
|
||||
let folderService: MockProxy<FolderService>;
|
||||
let cipherService: MockProxy<CipherService>;
|
||||
let policyService: MockProxy<PolicyService>;
|
||||
let i18nService: MockProxy<I18nService>;
|
||||
let collectionService: MockProxy<CollectionService>;
|
||||
let organizations: ReplaySubject<Organization[]>;
|
||||
let folderViews: ReplaySubject<FolderView[]>;
|
||||
let collectionViews: ReplaySubject<CollectionView[]>;
|
||||
let cipherViews: ReplaySubject<CipherView[]>;
|
||||
let organizationDataOwnershipPolicy: ReplaySubject<boolean>;
|
||||
let singleOrgPolicy: ReplaySubject<boolean>;
|
||||
let stateProvider: FakeStateProvider;
|
||||
let configService: MockProxy<ConfigService>;
|
||||
|
||||
const mockUserId = Utils.newGuid() as UserId;
|
||||
let accountService: FakeAccountService;
|
||||
let collapsedGroupingsState: FakeSingleUserState<string[]>;
|
||||
|
||||
beforeEach(() => {
|
||||
organizationService = mock<OrganizationService>();
|
||||
folderService = mock<FolderService>();
|
||||
cipherService = mock<CipherService>();
|
||||
policyService = mock<PolicyService>();
|
||||
i18nService = mock<I18nService>();
|
||||
accountService = mockAccountServiceWith(mockUserId);
|
||||
stateProvider = new FakeStateProvider(accountService);
|
||||
i18nService.collator = new Intl.Collator("en-US");
|
||||
collectionService = mock<CollectionService>();
|
||||
configService = mock<ConfigService>();
|
||||
|
||||
organizations = new ReplaySubject<Organization[]>(1);
|
||||
folderViews = new ReplaySubject<FolderView[]>(1);
|
||||
collectionViews = new ReplaySubject<CollectionView[]>(1);
|
||||
cipherViews = new ReplaySubject<CipherView[]>(1);
|
||||
organizationDataOwnershipPolicy = new ReplaySubject<boolean>(1);
|
||||
singleOrgPolicy = new ReplaySubject<boolean>(1);
|
||||
|
||||
configService.getFeatureFlag$.mockReturnValue(of(true));
|
||||
organizationService.memberOrganizations$.mockReturnValue(organizations);
|
||||
folderService.folderViews$.mockReturnValue(folderViews);
|
||||
collectionService.decryptedCollections$.mockReturnValue(collectionViews);
|
||||
policyService.policyAppliesToUser$
|
||||
.calledWith(PolicyType.OrganizationDataOwnership, mockUserId)
|
||||
.mockReturnValue(organizationDataOwnershipPolicy);
|
||||
policyService.policyAppliesToUser$
|
||||
.calledWith(PolicyType.SingleOrg, mockUserId)
|
||||
.mockReturnValue(singleOrgPolicy);
|
||||
cipherService.cipherListViews$.mockReturnValue(cipherViews);
|
||||
|
||||
vaultFilterService = new VaultFilterService(
|
||||
organizationService,
|
||||
folderService,
|
||||
cipherService,
|
||||
policyService,
|
||||
i18nService,
|
||||
stateProvider,
|
||||
collectionService,
|
||||
accountService,
|
||||
configService,
|
||||
);
|
||||
collapsedGroupingsState = stateProvider.singleUser.getFake(mockUserId, COLLAPSED_GROUPINGS);
|
||||
organizations.next([]);
|
||||
});
|
||||
|
||||
describe("collapsed filter nodes", () => {
|
||||
const nodes = new Set(["1", "2"]);
|
||||
|
||||
it("should update the collapsedFilterNodes$", async () => {
|
||||
await vaultFilterService.setCollapsedFilterNodes(nodes, mockUserId);
|
||||
|
||||
const collapsedGroupingsState = stateProvider.singleUser.getFake(
|
||||
mockUserId,
|
||||
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 () => {
|
||||
collapsedGroupingsState.nextState(["1", "2"]);
|
||||
|
||||
await expect(firstValueFrom(vaultFilterService.collapsedFilterNodes$)).resolves.toEqual(
|
||||
nodes,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("organizations", () => {
|
||||
beforeEach(() => {
|
||||
const storedOrgs = [createOrganization("1", "org1"), createOrganization("2", "org2")];
|
||||
organizations.next(storedOrgs);
|
||||
organizationDataOwnershipPolicy.next(false);
|
||||
singleOrgPolicy.next(false);
|
||||
});
|
||||
|
||||
it("returns a nested tree", async () => {
|
||||
const tree = await firstValueFrom(vaultFilterService.organizationTree$);
|
||||
|
||||
expect(tree.children.length).toBe(3);
|
||||
expect(tree.children.find((o) => o.node.name === "org1"));
|
||||
expect(tree.children.find((o) => o.node.name === "org2"));
|
||||
});
|
||||
|
||||
it("hides My Vault if organization data ownership policy is enabled", async () => {
|
||||
organizationDataOwnershipPolicy.next(true);
|
||||
|
||||
const tree = await firstValueFrom(vaultFilterService.organizationTree$);
|
||||
|
||||
expect(tree.children.length).toBe(2);
|
||||
expect(!tree.children.find((o) => o.node.id === "MyVault"));
|
||||
});
|
||||
|
||||
it("returns 1 organization and My Vault if single organization policy is enabled", async () => {
|
||||
singleOrgPolicy.next(true);
|
||||
|
||||
const tree = await firstValueFrom(vaultFilterService.organizationTree$);
|
||||
|
||||
expect(tree.children.length).toBe(2);
|
||||
expect(tree.children.find((o) => o.node.name === "org1"));
|
||||
expect(tree.children.find((o) => o.node.id === "MyVault"));
|
||||
});
|
||||
|
||||
it("returns 1 organization if both single organization and organization data ownership policies are enabled", async () => {
|
||||
singleOrgPolicy.next(true);
|
||||
organizationDataOwnershipPolicy.next(true);
|
||||
|
||||
const tree = await firstValueFrom(vaultFilterService.organizationTree$);
|
||||
|
||||
expect(tree.children.length).toBe(1);
|
||||
expect(tree.children.find((o) => o.node.name === "org1"));
|
||||
});
|
||||
});
|
||||
|
||||
describe("folders", () => {
|
||||
describe("filtered folders with organization", () => {
|
||||
beforeEach(() => {
|
||||
// Org must be updated before folderService else the subscription uses the null org default value
|
||||
vaultFilterService.setOrganizationFilter(createOrganization("org test id", "Test Org"));
|
||||
});
|
||||
it("returns folders filtered by current organization", async () => {
|
||||
const storedCiphers = [
|
||||
createCipherView("1", "org test id", "folder test id"),
|
||||
createCipherView("2", "non matching org id", "non matching folder id"),
|
||||
];
|
||||
cipherViews.next(storedCiphers);
|
||||
|
||||
const storedFolders = [
|
||||
createFolderView("folder test id", "test"),
|
||||
createFolderView("non matching folder id", "test2"),
|
||||
];
|
||||
folderViews.next(storedFolders);
|
||||
|
||||
await expect(firstValueFrom(vaultFilterService.filteredFolders$)).resolves.toMatchObject([
|
||||
{ id: "folder test id", name: "test" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("returns current organization", () => {
|
||||
vaultFilterService.getOrganizationFilter().subscribe((org) => {
|
||||
expect(org.id).toEqual("org test id");
|
||||
expect(org.identifier).toEqual("Test Org");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("folder tree", () => {
|
||||
it("returns a nested tree", async () => {
|
||||
const storedFolders = [
|
||||
createFolderView("Folder 1 Id", "Folder 1"),
|
||||
createFolderView("Folder 2 Id", "Folder 1/Folder 2"),
|
||||
createFolderView("Folder 3 Id", "Folder 1/Folder 3"),
|
||||
];
|
||||
folderViews.next(storedFolders);
|
||||
cipherViews.next([]);
|
||||
|
||||
const result = await firstValueFrom(vaultFilterService.folderTree$);
|
||||
|
||||
expect(result.children[0].node.id === "Folder 1 Id");
|
||||
expect(result.children[0].children.find((c) => c.node.id === "Folder 2 Id"));
|
||||
expect(result.children[0].children.find((c) => c.node.id === "Folder 3 Id"));
|
||||
}, 10000);
|
||||
});
|
||||
});
|
||||
|
||||
describe("collections", () => {
|
||||
describe("filtered collections", () => {
|
||||
it("returns collections filtered by current organization", async () => {
|
||||
vaultFilterService.setOrganizationFilter(createOrganization("org test id", "Test Org"));
|
||||
|
||||
const storedCollections = [
|
||||
createCollectionView("1", "collection 1", "org test id"),
|
||||
createCollectionView("2", "collection 2", "non matching org id"),
|
||||
];
|
||||
collectionViews.next(storedCollections);
|
||||
|
||||
await expect(firstValueFrom(vaultFilterService.filteredCollections$)).resolves.toEqual([
|
||||
createCollectionView("1", "collection 1", "org test id"),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("collection tree", () => {
|
||||
it("returns tree with children", async () => {
|
||||
const storedCollections = [
|
||||
createCollectionView("id-1", "Collection 1", "org test id"),
|
||||
createCollectionView("id-2", "Collection 1/Collection 2", "org test id"),
|
||||
createCollectionView("id-3", "Collection 1/Collection 3", "org test id"),
|
||||
];
|
||||
collectionViews.next(storedCollections);
|
||||
collectionService.groupByOrganization.mockReturnValue(
|
||||
new Map([["org test id" as OrganizationId, storedCollections]]),
|
||||
);
|
||||
|
||||
const result = await firstValueFrom(vaultFilterService.collectionTree$);
|
||||
|
||||
expect(result.children.map((c) => c.node.id)).toEqual(["id-1"]);
|
||||
expect(result.children[0].children.map((c) => c.node.id)).toEqual(["id-2", "id-3"]);
|
||||
});
|
||||
|
||||
it("returns tree where non-existing collections are excluded from children", async () => {
|
||||
const storedCollections = [
|
||||
createCollectionView("id-1", "Collection 1", "org test id"),
|
||||
createCollectionView("id-3", "Collection 1/Collection 2/Collection 3", "org test id"),
|
||||
];
|
||||
collectionViews.next(storedCollections);
|
||||
collectionService.groupByOrganization.mockReturnValue(
|
||||
new Map([["org test id" as OrganizationId, storedCollections]]),
|
||||
);
|
||||
|
||||
const result = await firstValueFrom(vaultFilterService.collectionTree$);
|
||||
|
||||
expect(result.children.map((c) => c.node.id)).toEqual(["id-1"]);
|
||||
expect(result.children[0].children.map((c) => c.node.id)).toEqual(["id-3"]);
|
||||
expect(result.children[0].children[0].node.name).toBe("Collection 2/Collection 3");
|
||||
});
|
||||
|
||||
it("returns tree with parents", async () => {
|
||||
const storedCollections = [
|
||||
createCollectionView("id-1", "Collection 1", "org test id"),
|
||||
createCollectionView("id-2", "Collection 1/Collection 2", "org test id"),
|
||||
createCollectionView("id-3", "Collection 1/Collection 2/Collection 3", "org test id"),
|
||||
createCollectionView("id-4", "Collection 1/Collection 4", "org test id"),
|
||||
];
|
||||
collectionViews.next(storedCollections);
|
||||
collectionService.groupByOrganization.mockReturnValue(
|
||||
new Map([["org test id" as OrganizationId, storedCollections]]),
|
||||
);
|
||||
|
||||
const result = await firstValueFrom(vaultFilterService.collectionTree$);
|
||||
|
||||
const c1 = result.children[0];
|
||||
const c2 = c1.children[0];
|
||||
const c3 = c2.children[0];
|
||||
const c4 = c1.children[1];
|
||||
expect(c2.parent.node.id).toEqual("id-1");
|
||||
expect(c3.parent.node.id).toEqual("id-2");
|
||||
expect(c4.parent.node.id).toEqual("id-1");
|
||||
});
|
||||
|
||||
it("returns tree where non-existing collections are excluded from parents", async () => {
|
||||
const storedCollections = [
|
||||
createCollectionView("id-1", "Collection 1", "org test id"),
|
||||
createCollectionView("id-3", "Collection 1/Collection 2/Collection 3", "org test id"),
|
||||
];
|
||||
collectionViews.next(storedCollections);
|
||||
collectionService.groupByOrganization.mockReturnValue(
|
||||
new Map([["org test id" as OrganizationId, storedCollections]]),
|
||||
);
|
||||
|
||||
const result = await firstValueFrom(vaultFilterService.collectionTree$);
|
||||
|
||||
const c1 = result.children[0];
|
||||
const c3 = c1.children[0];
|
||||
expect(c3.parent.node.id).toEqual("id-1");
|
||||
});
|
||||
|
||||
it("calls sortDefaultCollections with the correct args", async () => {
|
||||
const storedOrgs = [
|
||||
createOrganization("id-defaultOrg1", "org1"),
|
||||
createOrganization("id-defaultOrg2", "org2"),
|
||||
];
|
||||
organizations.next(storedOrgs);
|
||||
|
||||
const storedCollections = [
|
||||
createCollectionView("id-2", "Collection 2", "org test id"),
|
||||
createCollectionView("id-1", "Collection 1", "org test id"),
|
||||
createCollectionView(
|
||||
"id-3",
|
||||
"Default User Collection - Org 2",
|
||||
"id-defaultOrg2",
|
||||
CollectionTypes.DefaultUserCollection,
|
||||
),
|
||||
createCollectionView(
|
||||
"id-4",
|
||||
"Default User Collection - Org 1",
|
||||
"id-defaultOrg1",
|
||||
CollectionTypes.DefaultUserCollection,
|
||||
),
|
||||
];
|
||||
collectionViews.next(storedCollections);
|
||||
collectionService.groupByOrganization.mockReturnValue(
|
||||
new Map([["org test id" as OrganizationId, storedCollections]]),
|
||||
);
|
||||
|
||||
await firstValueFrom(vaultFilterService.collectionTree$);
|
||||
|
||||
expect(vaultFilterSvc.sortDefaultCollections).toHaveBeenCalledWith(
|
||||
storedCollections,
|
||||
storedOrgs,
|
||||
i18nService.collator,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function createOrganization(id: string, name: string) {
|
||||
const org = new Organization();
|
||||
org.id = id;
|
||||
org.name = name;
|
||||
org.identifier = name;
|
||||
org.isMember = true;
|
||||
return org;
|
||||
}
|
||||
|
||||
function createCipherView(id: string, orgId: string, folderId: string) {
|
||||
const cipher = new CipherView();
|
||||
cipher.id = id;
|
||||
cipher.organizationId = orgId;
|
||||
cipher.folderId = folderId;
|
||||
return cipher;
|
||||
}
|
||||
|
||||
function createFolderView(id: string, name: string): FolderView {
|
||||
const folder = new FolderView();
|
||||
folder.id = id;
|
||||
folder.name = name;
|
||||
return folder;
|
||||
}
|
||||
|
||||
function createCollectionView(
|
||||
id: string,
|
||||
name: string,
|
||||
orgId: string,
|
||||
type?: CollectionType,
|
||||
): CollectionView {
|
||||
const collection = new CollectionView({
|
||||
id: id as CollectionId,
|
||||
name,
|
||||
organizationId: orgId as OrganizationId,
|
||||
});
|
||||
|
||||
if (type) {
|
||||
collection.type = type;
|
||||
}
|
||||
|
||||
return collection;
|
||||
}
|
||||
});
|
||||
@@ -1,364 +0,0 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Injectable } from "@angular/core";
|
||||
import {
|
||||
BehaviorSubject,
|
||||
combineLatest,
|
||||
filter,
|
||||
firstValueFrom,
|
||||
map,
|
||||
Observable,
|
||||
of,
|
||||
switchMap,
|
||||
} from "rxjs";
|
||||
|
||||
import {
|
||||
CollectionService,
|
||||
CollectionTypes,
|
||||
CollectionView,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { sortDefaultCollections } from "@bitwarden/angular/vault/vault-filter/services/vault-filter.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { SingleUserState, StateProvider } from "@bitwarden/common/platform/state";
|
||||
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||
import { ServiceUtils } from "@bitwarden/common/vault/service-utils";
|
||||
import { COLLAPSED_GROUPINGS } from "@bitwarden/common/vault/services/key-state/collapsed-groupings.state";
|
||||
import { CipherListView } from "@bitwarden/sdk-internal";
|
||||
import { cloneCollection } from "@bitwarden/web-vault/app/admin-console/organizations/collections";
|
||||
|
||||
import {
|
||||
CipherTypeFilter,
|
||||
CollectionFilter,
|
||||
FolderFilter,
|
||||
OrganizationFilter,
|
||||
} from "../shared/models/vault-filter.type";
|
||||
|
||||
import { VaultFilterService as VaultFilterServiceAbstraction } from "./abstractions/vault-filter.service";
|
||||
|
||||
const NestingDelimiter = "/";
|
||||
|
||||
@Injectable()
|
||||
export class VaultFilterService implements VaultFilterServiceAbstraction {
|
||||
protected activeUserId$ = this.accountService.activeAccount$.pipe(getUserId);
|
||||
|
||||
memberOrganizations$ = this.activeUserId$.pipe(
|
||||
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([
|
||||
this.memberOrganizations$,
|
||||
this.activeUserId$.pipe(
|
||||
switchMap((userId) => this.policyService.policyAppliesToUser$(PolicyType.SingleOrg, userId)),
|
||||
),
|
||||
this.activeUserId$.pipe(
|
||||
switchMap((userId) =>
|
||||
this.policyService.policyAppliesToUser$(PolicyType.OrganizationDataOwnership, userId),
|
||||
),
|
||||
),
|
||||
]).pipe(
|
||||
switchMap(([orgs, singleOrgPolicy, organizationDataOwnershipPolicy]) =>
|
||||
this.buildOrganizationTree(orgs, singleOrgPolicy, organizationDataOwnershipPolicy),
|
||||
),
|
||||
);
|
||||
|
||||
protected _organizationFilter = new BehaviorSubject<Organization>(null);
|
||||
|
||||
filteredFolders$: Observable<FolderView[]> = this.activeUserId$.pipe(
|
||||
switchMap((userId) =>
|
||||
combineLatest([
|
||||
this.folderService.folderViews$(userId),
|
||||
this.cipherService.cipherListViews$(userId),
|
||||
this._organizationFilter,
|
||||
]),
|
||||
),
|
||||
filter(([folders, ciphers, org]) => !!ciphers), // ciphers may be null, meaning decryption is in progress. Ignore this emission
|
||||
switchMap(([folders, ciphers, org]) => {
|
||||
return this.filterFolders(folders, ciphers, org);
|
||||
}),
|
||||
);
|
||||
|
||||
folderTree$: Observable<TreeNode<FolderFilter>> = this.filteredFolders$.pipe(
|
||||
map((folders) => this.buildFolderTree(folders)),
|
||||
);
|
||||
|
||||
filteredCollections$: Observable<CollectionView[]> = combineLatest([
|
||||
this.accountService.activeAccount$.pipe(
|
||||
getUserId,
|
||||
switchMap((userId) => this.collectionService.decryptedCollections$(userId)),
|
||||
),
|
||||
this._organizationFilter,
|
||||
]).pipe(switchMap(([collections, org]) => this.filterCollections(collections, org)));
|
||||
|
||||
collectionTree$: Observable<TreeNode<CollectionFilter>> = combineLatest([
|
||||
this.filteredCollections$,
|
||||
this.memberOrganizations$,
|
||||
]).pipe(
|
||||
map(([collections, organizations]) => this.buildCollectionTree(collections, organizations)),
|
||||
);
|
||||
|
||||
cipherTypeTree$: Observable<TreeNode<CipherTypeFilter>> = this.buildCipherTypeTree();
|
||||
|
||||
private collapsedGroupingsState(userId: UserId): SingleUserState<string[]> {
|
||||
return this.stateProvider.getUser(userId, COLLAPSED_GROUPINGS);
|
||||
}
|
||||
|
||||
constructor(
|
||||
protected organizationService: OrganizationService,
|
||||
protected folderService: FolderService,
|
||||
protected cipherService: CipherService,
|
||||
protected policyService: PolicyService,
|
||||
protected i18nService: I18nService,
|
||||
protected stateProvider: StateProvider,
|
||||
protected collectionService: CollectionService,
|
||||
protected accountService: AccountService,
|
||||
) {}
|
||||
|
||||
async getCollectionNodeFromTree(id: string) {
|
||||
const collections = await firstValueFrom(this.collectionTree$);
|
||||
return ServiceUtils.getTreeNodeObject(collections, id) as TreeNode<CollectionFilter>;
|
||||
}
|
||||
|
||||
async setCollapsedFilterNodes(collapsedFilterNodes: Set<string>, userId: UserId): Promise<void> {
|
||||
await this.collapsedGroupingsState(userId).update(() => Array.from(collapsedFilterNodes));
|
||||
}
|
||||
|
||||
protected async getCollapsedFilterNodes(): Promise<Set<string>> {
|
||||
return await firstValueFrom(this.collapsedFilterNodes$);
|
||||
}
|
||||
|
||||
getOrganizationFilter() {
|
||||
return this._organizationFilter;
|
||||
}
|
||||
|
||||
clearOrganizationFilter() {
|
||||
this._organizationFilter.next(null);
|
||||
}
|
||||
|
||||
setOrganizationFilter(organization: Organization) {
|
||||
if (organization?.id != "AllVaults") {
|
||||
this._organizationFilter.next(organization);
|
||||
} else {
|
||||
this._organizationFilter.next(null);
|
||||
}
|
||||
}
|
||||
|
||||
async expandOrgFilter(userId: UserId) {
|
||||
const collapsedFilterNodes = await firstValueFrom(this.collapsedFilterNodes$);
|
||||
if (!collapsedFilterNodes.has("AllVaults")) {
|
||||
return;
|
||||
}
|
||||
collapsedFilterNodes.delete("AllVaults");
|
||||
await this.setCollapsedFilterNodes(collapsedFilterNodes, userId);
|
||||
}
|
||||
|
||||
protected async buildOrganizationTree(
|
||||
orgs: Organization[],
|
||||
singleOrgPolicy: boolean,
|
||||
organizationDataOwnershipPolicy: boolean,
|
||||
): Promise<TreeNode<OrganizationFilter>> {
|
||||
const headNode = this.getOrganizationFilterHead();
|
||||
if (!organizationDataOwnershipPolicy) {
|
||||
const myVaultNode = this.getOrganizationFilterMyVault();
|
||||
headNode.children.push(myVaultNode);
|
||||
}
|
||||
if (singleOrgPolicy) {
|
||||
orgs = orgs.slice(0, 1);
|
||||
}
|
||||
if (orgs) {
|
||||
const orgNodes: TreeNode<OrganizationFilter>[] = [];
|
||||
orgs.forEach((org) => {
|
||||
const orgCopy = org as OrganizationFilter;
|
||||
orgCopy.icon = "bwi-business";
|
||||
const node = new TreeNode<OrganizationFilter>(orgCopy, headNode, orgCopy.name);
|
||||
orgNodes.push(node);
|
||||
});
|
||||
// Sort organization nodes, then add them to the list after 'My Vault' and 'All Vaults' if present
|
||||
orgNodes.sort((a, b) => a.node.name.localeCompare(b.node.name));
|
||||
headNode.children.push(...orgNodes);
|
||||
}
|
||||
return headNode;
|
||||
}
|
||||
|
||||
protected getOrganizationFilterHead(): TreeNode<OrganizationFilter> {
|
||||
const head = new Organization() as OrganizationFilter;
|
||||
head.enabled = true;
|
||||
return new TreeNode<OrganizationFilter>(head, null, "allVaults", "AllVaults");
|
||||
}
|
||||
|
||||
protected getOrganizationFilterMyVault(): TreeNode<OrganizationFilter> {
|
||||
const myVault = new Organization() as OrganizationFilter;
|
||||
myVault.id = "MyVault" as OrganizationId;
|
||||
myVault.icon = "bwi-user";
|
||||
myVault.enabled = true;
|
||||
myVault.hideOptions = true;
|
||||
return new TreeNode<OrganizationFilter>(myVault, null, this.i18nService.t("myVault"));
|
||||
}
|
||||
|
||||
buildTypeTree(
|
||||
head: CipherTypeFilter,
|
||||
array?: CipherTypeFilter[],
|
||||
): Observable<TreeNode<CipherTypeFilter>> {
|
||||
const headNode = new TreeNode<CipherTypeFilter>(head, null);
|
||||
array?.forEach((filter) => {
|
||||
const node = new TreeNode<CipherTypeFilter>(filter, headNode, filter.name);
|
||||
headNode.children.push(node);
|
||||
});
|
||||
return of(headNode);
|
||||
}
|
||||
|
||||
protected async filterCollections(
|
||||
storedCollections: CollectionView[],
|
||||
org?: Organization,
|
||||
): Promise<CollectionView[]> {
|
||||
return org?.id != null
|
||||
? storedCollections.filter((c) => c.organizationId === org.id)
|
||||
: storedCollections;
|
||||
}
|
||||
|
||||
protected buildCollectionTree(
|
||||
collections?: CollectionView[],
|
||||
orgs?: Organization[],
|
||||
): TreeNode<CollectionFilter> {
|
||||
const headNode = this.getCollectionFilterHead();
|
||||
if (!collections) {
|
||||
return headNode;
|
||||
}
|
||||
const all: TreeNode<CollectionFilter>[] = [];
|
||||
collections = sortDefaultCollections(collections, orgs, this.i18nService.collator);
|
||||
const groupedByOrg = this.collectionService.groupByOrganization(collections);
|
||||
|
||||
for (const group of groupedByOrg.values()) {
|
||||
const nodes: TreeNode<CollectionFilter>[] = [];
|
||||
for (const c of group) {
|
||||
const collectionCopy = cloneCollection(
|
||||
new CollectionView({ ...c, name: c.name }),
|
||||
) as CollectionFilter;
|
||||
collectionCopy.icon =
|
||||
c.type === CollectionTypes.DefaultUserCollection ? "bwi-user" : "bwi-collection-shared";
|
||||
const parts = c.name ? c.name.replace(/^\/+|\/+$/g, "").split(NestingDelimiter) : [];
|
||||
ServiceUtils.nestedTraverse(nodes, 0, parts, collectionCopy, undefined, NestingDelimiter);
|
||||
}
|
||||
all.push(...nodes);
|
||||
}
|
||||
|
||||
all.forEach((n) => {
|
||||
n.parent = headNode;
|
||||
headNode.children.push(n);
|
||||
});
|
||||
|
||||
return headNode;
|
||||
}
|
||||
|
||||
protected getCollectionFilterHead(): TreeNode<CollectionFilter> {
|
||||
const head = CollectionView.vaultFilterHead() as CollectionFilter;
|
||||
return new TreeNode<CollectionFilter>(head, null, "collections", "AllCollections");
|
||||
}
|
||||
|
||||
protected async filterFolders(
|
||||
storedFolders: FolderView[],
|
||||
ciphers: CipherView[] | CipherListView[],
|
||||
org?: Organization,
|
||||
): Promise<FolderView[]> {
|
||||
// If no org or "My Vault" is selected, show all folders
|
||||
if (org?.id == null || org?.id == "MyVault") {
|
||||
return storedFolders;
|
||||
}
|
||||
|
||||
// Otherwise, show only folders that have ciphers from the selected org and the "no folder" folder
|
||||
const orgCiphers = ciphers.filter((c) => c.organizationId == org?.id);
|
||||
return storedFolders.filter(
|
||||
(f) => orgCiphers.some((oc) => oc.folderId == f.id) || f.id == null,
|
||||
);
|
||||
}
|
||||
|
||||
protected buildFolderTree(folders?: FolderView[]): TreeNode<FolderFilter> {
|
||||
const headNode = this.getFolderFilterHead();
|
||||
if (!folders) {
|
||||
return headNode;
|
||||
}
|
||||
const nodes: TreeNode<FolderFilter>[] = [];
|
||||
folders.forEach((f) => {
|
||||
const folderCopy = new FolderView() as FolderFilter;
|
||||
folderCopy.id = f.id;
|
||||
folderCopy.revisionDate = f.revisionDate;
|
||||
folderCopy.icon = "bwi-folder";
|
||||
folderCopy.fullName = f.name; // save full folder name before separating it into parts
|
||||
const parts = f.name != null ? f.name.replace(/^\/+|\/+$/g, "").split(NestingDelimiter) : [];
|
||||
ServiceUtils.nestedTraverse(nodes, 0, parts, folderCopy, null, NestingDelimiter);
|
||||
});
|
||||
|
||||
nodes.forEach((n) => {
|
||||
n.parent = headNode;
|
||||
headNode.children.push(n);
|
||||
});
|
||||
return headNode;
|
||||
}
|
||||
|
||||
protected getFolderFilterHead(): TreeNode<FolderFilter> {
|
||||
const head = new FolderView() as FolderFilter;
|
||||
return new TreeNode<FolderFilter>(head, null, "folders", "AllFolders");
|
||||
}
|
||||
|
||||
protected buildCipherTypeTree(): Observable<TreeNode<CipherTypeFilter>> {
|
||||
const allTypeFilters: CipherTypeFilter[] = [
|
||||
{
|
||||
id: "favorites",
|
||||
name: this.i18nService.t("favorites"),
|
||||
type: "favorites",
|
||||
icon: "bwi-star",
|
||||
},
|
||||
{
|
||||
id: "login",
|
||||
name: this.i18nService.t("typeLogin"),
|
||||
type: CipherType.Login,
|
||||
icon: "bwi-globe",
|
||||
},
|
||||
{
|
||||
id: "card",
|
||||
name: this.i18nService.t("typeCard"),
|
||||
type: CipherType.Card,
|
||||
icon: "bwi-credit-card",
|
||||
},
|
||||
{
|
||||
id: "identity",
|
||||
name: this.i18nService.t("typeIdentity"),
|
||||
type: CipherType.Identity,
|
||||
icon: "bwi-id-card",
|
||||
},
|
||||
{
|
||||
id: "note",
|
||||
name: this.i18nService.t("typeSecureNote"),
|
||||
type: CipherType.SecureNote,
|
||||
icon: "bwi-sticky-note",
|
||||
},
|
||||
{
|
||||
id: "sshKey",
|
||||
name: this.i18nService.t("typeSshKey"),
|
||||
type: CipherType.SshKey,
|
||||
icon: "bwi-key",
|
||||
},
|
||||
];
|
||||
|
||||
return this.buildTypeTree(
|
||||
{ id: "AllItems", name: "allItems", type: "all", icon: "" },
|
||||
allTypeFilters,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,12 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga
|
||||
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 { VaultFilterService } from "../../services/abstractions/vault-filter.service";
|
||||
import { VaultFilterSection, VaultFilterType } from "../models/vault-filter-section.type";
|
||||
import { VaultFilter } from "../models/vault-filter.model";
|
||||
import {
|
||||
VaultFilterServiceAbstraction as VaultFilterService,
|
||||
VaultFilterSection,
|
||||
VaultFilterType,
|
||||
VaultFilter,
|
||||
} from "@bitwarden/vault";
|
||||
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
|
||||
|
||||
@@ -1,230 +0,0 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Unassigned } from "@bitwarden/admin-console/common";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
|
||||
import { createFilterFunction } from "./filter-function";
|
||||
import { All } from "./routed-vault-filter.model";
|
||||
|
||||
describe("createFilter", () => {
|
||||
describe("given a generic cipher", () => {
|
||||
it("should return true when no filter is applied", () => {
|
||||
const cipher = createCipher();
|
||||
const filterFunction = createFilterFunction({});
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("given a favorite cipher", () => {
|
||||
const cipher = createCipher({ favorite: true });
|
||||
|
||||
it("should return true when filtering for favorites", () => {
|
||||
const filterFunction = createFilterFunction({ type: "favorites" });
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false when filtering for trash", () => {
|
||||
const filterFunction = createFilterFunction({ type: "trash" });
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("given a deleted cipher", () => {
|
||||
const cipher = createCipher({ deletedDate: new Date() });
|
||||
|
||||
it("should return true when filtering for trash", () => {
|
||||
const filterFunction = createFilterFunction({ type: "trash" });
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false when filtering for favorites", () => {
|
||||
const filterFunction = createFilterFunction({ type: "favorites" });
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false when type is not specified in filter", () => {
|
||||
const filterFunction = createFilterFunction({});
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("given a cipher with type", () => {
|
||||
it("should return true when filter matches cipher type", () => {
|
||||
const cipher = createCipher({ type: CipherType.Identity });
|
||||
const filterFunction = createFilterFunction({ type: "identity" });
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false when filter does not match cipher type", () => {
|
||||
const cipher = createCipher({ type: CipherType.Card });
|
||||
const filterFunction = createFilterFunction({ type: "favorites" });
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("given a cipher with folder id", () => {
|
||||
it("should return true when filter matches folder id", () => {
|
||||
const cipher = createCipher({ folderId: "folderId" });
|
||||
const filterFunction = createFilterFunction({ folderId: "folderId" });
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false when filter does not match folder id", () => {
|
||||
const cipher = createCipher({ folderId: "folderId" });
|
||||
const filterFunction = createFilterFunction({ folderId: "differentFolderId" });
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("given a cipher without folder", () => {
|
||||
const cipher = createCipher({ folderId: null });
|
||||
|
||||
it("should return true when filtering on unassigned folder", () => {
|
||||
const filterFunction = createFilterFunction({ folderId: Unassigned });
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("given an organizational cipher (with organization and collections)", () => {
|
||||
const cipher = createCipher({
|
||||
organizationId: "organizationId",
|
||||
collectionIds: ["collectionId", "anotherId"],
|
||||
});
|
||||
|
||||
it("should return true when filter matches collection id", () => {
|
||||
const filterFunction = createFilterFunction({
|
||||
collectionId: "collectionId",
|
||||
organizationId: "organizationId",
|
||||
});
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false when filter does not match collection id", () => {
|
||||
const filterFunction = createFilterFunction({
|
||||
collectionId: "nonMatchingCollectionId",
|
||||
organizationId: "organizationId",
|
||||
});
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false when filter does not match organization id", () => {
|
||||
const filterFunction = createFilterFunction({
|
||||
organizationId: "nonMatchingOrganizationId",
|
||||
});
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false when filtering for my vault only", () => {
|
||||
const filterFunction = createFilterFunction({ organizationId: Unassigned });
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false when filtering by All Collections", () => {
|
||||
const filterFunction = createFilterFunction({ collectionId: All });
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("given an unassigned organizational cipher (with organization, without collection)", () => {
|
||||
const cipher = createCipher({ organizationId: "organizationId", collectionIds: [] });
|
||||
|
||||
it("should return true when filtering for unassigned collection", () => {
|
||||
const filterFunction = createFilterFunction({ collectionId: Unassigned });
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("should return true when filter matches organization id", () => {
|
||||
const filterFunction = createFilterFunction({ organizationId: "organizationId" });
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("given an individual cipher (without organization or collection)", () => {
|
||||
const cipher = createCipher({ organizationId: null, collectionIds: [] });
|
||||
|
||||
it("should return false when filtering for unassigned collection", () => {
|
||||
const filterFunction = createFilterFunction({ collectionId: Unassigned });
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("should return true when filtering for my vault only", () => {
|
||||
const cipher = createCipher({ organizationId: null });
|
||||
const filterFunction = createFilterFunction({ organizationId: Unassigned });
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function createCipher(options: Partial<CipherView> = {}) {
|
||||
const cipher = new CipherView();
|
||||
|
||||
cipher.favorite = options.favorite ?? false;
|
||||
cipher.deletedDate = options.deletedDate;
|
||||
cipher.type = options.type ?? CipherType.Login;
|
||||
cipher.folderId = options.folderId;
|
||||
cipher.collectionIds = options.collectionIds;
|
||||
cipher.organizationId = options.organizationId;
|
||||
|
||||
return cipher;
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
import { Unassigned } from "@bitwarden/admin-console/common";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import {
|
||||
CipherViewLike,
|
||||
CipherViewLikeUtils,
|
||||
} from "@bitwarden/common/vault/utils/cipher-view-like-utils";
|
||||
|
||||
import { All, RoutedVaultFilterModel } from "./routed-vault-filter.model";
|
||||
|
||||
export type FilterFunction = (cipher: CipherViewLike) => boolean;
|
||||
|
||||
export function createFilterFunction(
|
||||
filter: RoutedVaultFilterModel,
|
||||
archiveEnabled?: boolean,
|
||||
): FilterFunction {
|
||||
return (cipher) => {
|
||||
const type = CipherViewLikeUtils.getType(cipher);
|
||||
const isDeleted = CipherViewLikeUtils.isDeleted(cipher);
|
||||
|
||||
if (filter.type === "favorites" && !cipher.favorite) {
|
||||
return false;
|
||||
}
|
||||
if (filter.type === "card" && type !== CipherType.Card) {
|
||||
return false;
|
||||
}
|
||||
if (filter.type === "identity" && type !== CipherType.Identity) {
|
||||
return false;
|
||||
}
|
||||
if (filter.type === "login" && type !== CipherType.Login) {
|
||||
return false;
|
||||
}
|
||||
if (filter.type === "note" && type !== CipherType.SecureNote) {
|
||||
return false;
|
||||
}
|
||||
if (filter.type === "sshKey" && type !== CipherType.SshKey) {
|
||||
return false;
|
||||
}
|
||||
if (filter.type === "trash" && !isDeleted) {
|
||||
return false;
|
||||
}
|
||||
// Hide trash unless explicitly selected
|
||||
if (filter.type !== "trash" && isDeleted) {
|
||||
return false;
|
||||
}
|
||||
// Archive filter logic is only applied if the feature flag is enabled
|
||||
if (archiveEnabled) {
|
||||
if (filter.type === "archive" && !CipherViewLikeUtils.isArchived(cipher)) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
filter.type !== "archive" &&
|
||||
filter.type !== "trash" &&
|
||||
CipherViewLikeUtils.isArchived(cipher)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// No folder
|
||||
if (filter.folderId === Unassigned && cipher.folderId != null) {
|
||||
return false;
|
||||
}
|
||||
// Folder
|
||||
if (
|
||||
filter.folderId !== undefined &&
|
||||
filter.folderId !== All &&
|
||||
filter.folderId !== Unassigned &&
|
||||
cipher.folderId !== filter.folderId
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
// All collections (top level)
|
||||
if (filter.collectionId === All) {
|
||||
return false;
|
||||
}
|
||||
// Unassigned
|
||||
if (
|
||||
filter.collectionId === Unassigned &&
|
||||
(cipher.organizationId == null ||
|
||||
(cipher.collectionIds != null && cipher.collectionIds.length > 0))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
// Collection
|
||||
if (
|
||||
filter.collectionId !== undefined &&
|
||||
filter.collectionId !== All &&
|
||||
filter.collectionId !== Unassigned &&
|
||||
(cipher.collectionIds == null || !cipher.collectionIds.includes(filter.collectionId as any))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
// My Vault
|
||||
if (filter.organizationId === Unassigned && cipher.organizationId != null) {
|
||||
return false;
|
||||
}
|
||||
// Organization
|
||||
else if (
|
||||
filter.organizationId !== undefined &&
|
||||
filter.organizationId !== Unassigned &&
|
||||
cipher.organizationId !== filter.organizationId
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
import { Unassigned } from "@bitwarden/admin-console/common";
|
||||
import { CollectionId } from "@bitwarden/common/types/guid";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
||||
|
||||
import { RoutedVaultFilterBridgeService } from "../../services/routed-vault-filter-bridge.service";
|
||||
|
||||
import {
|
||||
All,
|
||||
isRoutedVaultFilterItemType,
|
||||
RoutedVaultFilterItemType,
|
||||
RoutedVaultFilterModel,
|
||||
} from "./routed-vault-filter.model";
|
||||
import { VaultFilter, VaultFilterFunction } from "./vault-filter.model";
|
||||
import {
|
||||
OrganizationFilter,
|
||||
CipherTypeFilter,
|
||||
FolderFilter,
|
||||
CollectionFilter,
|
||||
CipherStatus,
|
||||
} from "./vault-filter.type";
|
||||
|
||||
/**
|
||||
* 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 model supplies legacy code with the old state-in-code models saved as tree nodes.
|
||||
* It can also receive requests to select a new tree node by using setters.
|
||||
* However instead of just replacing the tree node models, it requests a URL navigation,
|
||||
* thereby bridging between legacy and URL filtering.
|
||||
*/
|
||||
export class RoutedVaultFilterBridge implements VaultFilter {
|
||||
constructor(
|
||||
private routedFilter: RoutedVaultFilterModel,
|
||||
private legacyFilter: VaultFilter,
|
||||
private bridgeService: RoutedVaultFilterBridgeService,
|
||||
) {}
|
||||
get collectionBreadcrumbs(): TreeNode<CollectionFilter>[] {
|
||||
return this.legacyFilter.collectionBreadcrumbs;
|
||||
}
|
||||
get isCollectionSelected(): boolean {
|
||||
return this.legacyFilter.isCollectionSelected;
|
||||
}
|
||||
get isUnassignedCollectionSelected(): boolean {
|
||||
return this.legacyFilter.isUnassignedCollectionSelected;
|
||||
}
|
||||
get isMyVaultSelected(): boolean {
|
||||
return this.legacyFilter.isMyVaultSelected;
|
||||
}
|
||||
get selectedOrganizationNode(): TreeNode<OrganizationFilter> {
|
||||
return this.legacyFilter.selectedOrganizationNode;
|
||||
}
|
||||
set selectedOrganizationNode(value: TreeNode<OrganizationFilter>) {
|
||||
this.bridgeService.navigate({
|
||||
...this.routedFilter,
|
||||
organizationId: value?.node.id === "MyVault" ? Unassigned : value?.node.id,
|
||||
folderId: undefined,
|
||||
collectionId: undefined,
|
||||
});
|
||||
}
|
||||
get selectedCipherTypeNode(): TreeNode<CipherTypeFilter> {
|
||||
return this.legacyFilter.selectedCipherTypeNode;
|
||||
}
|
||||
set selectedCipherTypeNode(value: TreeNode<CipherTypeFilter>) {
|
||||
let type: RoutedVaultFilterItemType | undefined;
|
||||
|
||||
if (value?.node.id === "AllItems" && this.routedFilter.organizationIdParamType === "path") {
|
||||
type = All;
|
||||
} else if (
|
||||
value?.node.id === "AllItems" &&
|
||||
this.routedFilter.organizationIdParamType === "query"
|
||||
) {
|
||||
type = undefined;
|
||||
} else if (isRoutedVaultFilterItemType(value?.node.id)) {
|
||||
type = value?.node.id;
|
||||
}
|
||||
|
||||
this.bridgeService.navigate({
|
||||
...this.routedFilter,
|
||||
type,
|
||||
folderId: undefined,
|
||||
collectionId: undefined,
|
||||
});
|
||||
}
|
||||
get selectedFolderNode(): TreeNode<FolderFilter> {
|
||||
return this.legacyFilter.selectedFolderNode;
|
||||
}
|
||||
set selectedFolderNode(value: TreeNode<FolderFilter>) {
|
||||
const folderId = value != null && value.node.id === null ? Unassigned : value?.node.id;
|
||||
this.bridgeService.navigate({
|
||||
...this.routedFilter,
|
||||
folderId,
|
||||
type: undefined,
|
||||
collectionId: undefined,
|
||||
});
|
||||
}
|
||||
get selectedCollectionNode(): TreeNode<CollectionFilter> {
|
||||
return this.legacyFilter.selectedCollectionNode;
|
||||
}
|
||||
set selectedCollectionNode(value: TreeNode<CollectionFilter>) {
|
||||
let collectionId: CollectionId | All | Unassigned | undefined;
|
||||
|
||||
if (value != null && value.node.id === null) {
|
||||
collectionId = Unassigned;
|
||||
} else if (
|
||||
value?.node.id === "AllCollections" &&
|
||||
this.routedFilter.organizationIdParamType === "path"
|
||||
) {
|
||||
collectionId = undefined;
|
||||
} else if (
|
||||
value?.node.id === "AllCollections" &&
|
||||
this.routedFilter.organizationIdParamType === "query"
|
||||
) {
|
||||
collectionId = All;
|
||||
} else {
|
||||
collectionId = value?.node.id;
|
||||
}
|
||||
|
||||
this.bridgeService.navigate({
|
||||
...this.routedFilter,
|
||||
collectionId,
|
||||
type: undefined,
|
||||
folderId: undefined,
|
||||
});
|
||||
}
|
||||
get isFavorites(): boolean {
|
||||
return this.legacyFilter.isFavorites;
|
||||
}
|
||||
get isDeleted(): boolean {
|
||||
return this.legacyFilter.isDeleted;
|
||||
}
|
||||
get isArchived(): boolean {
|
||||
return this.legacyFilter.isArchived;
|
||||
}
|
||||
get organizationId(): string {
|
||||
return this.legacyFilter.organizationId;
|
||||
}
|
||||
get cipherType(): CipherType {
|
||||
return this.legacyFilter.cipherType;
|
||||
}
|
||||
get cipherStatus(): CipherStatus {
|
||||
return this.legacyFilter.cipherStatus;
|
||||
}
|
||||
get cipherTypeId(): string {
|
||||
return this.legacyFilter.cipherTypeId;
|
||||
}
|
||||
get folderId(): string {
|
||||
return this.legacyFilter.folderId;
|
||||
}
|
||||
get collectionId(): string {
|
||||
return this.legacyFilter.collectionId;
|
||||
}
|
||||
resetFilter(): void {
|
||||
this.bridgeService.navigate({
|
||||
...this.routedFilter,
|
||||
collectionId: undefined,
|
||||
folderId: undefined,
|
||||
organizationId:
|
||||
this.routedFilter.organizationIdParamType === "path"
|
||||
? this.routedFilter.organizationId
|
||||
: undefined,
|
||||
type: undefined,
|
||||
});
|
||||
}
|
||||
resetOrganization(): void {
|
||||
this.bridgeService.navigate({ ...this.routedFilter, organizationId: undefined });
|
||||
}
|
||||
buildFilter(): VaultFilterFunction {
|
||||
return this.legacyFilter.buildFilter();
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import { Unassigned } from "@bitwarden/admin-console/common";
|
||||
import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
|
||||
|
||||
/**
|
||||
* A constant used to represent viewing "all" of a particular filter.
|
||||
*/
|
||||
export const All = "all";
|
||||
export type All = typeof All;
|
||||
|
||||
// TODO: Remove `All` when moving to vertical navigation.
|
||||
const itemTypes = [
|
||||
"favorites",
|
||||
"login",
|
||||
"card",
|
||||
"identity",
|
||||
"note",
|
||||
"sshKey",
|
||||
"archive",
|
||||
"trash",
|
||||
All,
|
||||
] as const;
|
||||
|
||||
export type RoutedVaultFilterItemType = (typeof itemTypes)[number];
|
||||
|
||||
export function isRoutedVaultFilterItemType(value: unknown): value is RoutedVaultFilterItemType {
|
||||
return itemTypes.includes(value as any);
|
||||
}
|
||||
|
||||
export interface RoutedVaultFilterModel {
|
||||
collectionId?: CollectionId | All | Unassigned;
|
||||
folderId?: string;
|
||||
organizationId?: OrganizationId | Unassigned;
|
||||
type?: RoutedVaultFilterItemType;
|
||||
|
||||
organizationIdParamType?: "path" | "query";
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
||||
import { UnionOfValues } from "@bitwarden/common/vault/types/union-of-values";
|
||||
|
||||
import {
|
||||
CipherTypeFilter,
|
||||
CollectionFilter,
|
||||
FolderFilter,
|
||||
OrganizationFilter,
|
||||
} from "./vault-filter.type";
|
||||
|
||||
export type VaultFilterType =
|
||||
| OrganizationFilter
|
||||
| CipherTypeFilter
|
||||
| FolderFilter
|
||||
| CollectionFilter;
|
||||
|
||||
export const VaultFilterLabel = {
|
||||
OrganizationFilter: "organizationFilter",
|
||||
TypeFilter: "typeFilter",
|
||||
FolderFilter: "folderFilter",
|
||||
CollectionFilter: "collectionFilter",
|
||||
ArchiveFilter: "archiveFilter",
|
||||
TrashFilter: "trashFilter",
|
||||
} as const;
|
||||
|
||||
type VaultFilterLabel = UnionOfValues<typeof VaultFilterLabel>;
|
||||
|
||||
export type VaultFilterSection = {
|
||||
data$: Observable<TreeNode<VaultFilterType>>;
|
||||
header: {
|
||||
showHeader: boolean;
|
||||
isSelectable: boolean;
|
||||
};
|
||||
action: (filterNode: TreeNode<VaultFilterType>) => Promise<void>;
|
||||
edit?: {
|
||||
filterName: string;
|
||||
action: (filter: VaultFilterType) => void;
|
||||
};
|
||||
add?: {
|
||||
text: string;
|
||||
route?: string;
|
||||
action?: () => void;
|
||||
};
|
||||
options?: {
|
||||
component: any;
|
||||
};
|
||||
divider?: boolean;
|
||||
premiumOptions?: {
|
||||
/** When true, the premium badge will show on the filter for non-premium users. */
|
||||
showBadgeForNonPremium?: true;
|
||||
/**
|
||||
* Action to be called instead of applying the filter.
|
||||
* Useful when the user does not have access to a filter (e.g., premium feature)
|
||||
* and custom behavior is needed when invoking the filter.
|
||||
*/
|
||||
blockFilterAction?: () => Promise<void>;
|
||||
};
|
||||
};
|
||||
|
||||
export type VaultFilterList = {
|
||||
[key in VaultFilterLabel]?: VaultFilterSection;
|
||||
};
|
||||
@@ -1,334 +0,0 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { CollectionView } from "@bitwarden/admin-console/common";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { CollectionId } from "@bitwarden/common/types/guid";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||
|
||||
import { VaultFilter } from "./vault-filter.model";
|
||||
import {
|
||||
CipherTypeFilter,
|
||||
CollectionFilter,
|
||||
FolderFilter,
|
||||
OrganizationFilter,
|
||||
} from "./vault-filter.type";
|
||||
|
||||
describe("VaultFilter", () => {
|
||||
describe("filterFunction", () => {
|
||||
const allCiphersFilter = new TreeNode<CipherTypeFilter>(
|
||||
{
|
||||
id: "AllItems",
|
||||
name: "allItems",
|
||||
type: "all",
|
||||
icon: "",
|
||||
},
|
||||
null,
|
||||
);
|
||||
const favoriteCiphersFilter = new TreeNode<CipherTypeFilter>(
|
||||
{
|
||||
id: "favorites",
|
||||
name: "favorites",
|
||||
type: "favorites",
|
||||
icon: "bwi-star",
|
||||
},
|
||||
null,
|
||||
);
|
||||
const identityCiphersFilter = new TreeNode<CipherTypeFilter>(
|
||||
{
|
||||
id: "identity",
|
||||
name: "identity",
|
||||
type: CipherType.Identity,
|
||||
icon: "bwi-id-card",
|
||||
},
|
||||
null,
|
||||
);
|
||||
const trashFilter = new TreeNode<CipherTypeFilter>(
|
||||
{
|
||||
id: "trash",
|
||||
name: "trash",
|
||||
type: "trash",
|
||||
icon: "bwi-trash",
|
||||
},
|
||||
null,
|
||||
);
|
||||
describe("generic cipher", () => {
|
||||
it("should return true when no filter is applied", () => {
|
||||
const cipher = createCipher();
|
||||
const filterFunction = createFilterFunction({});
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("given a favorite cipher", () => {
|
||||
const cipher = createCipher({ favorite: true });
|
||||
|
||||
it("should return true when filtering for favorites", () => {
|
||||
const filterFunction = createFilterFunction({ selectedCipherTypeNode: allCiphersFilter });
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false when filtering for trash", () => {
|
||||
const filterFunction = createFilterFunction({ selectedCipherTypeNode: trashFilter });
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("given a deleted cipher", () => {
|
||||
const cipher = createCipher({ deletedDate: new Date() });
|
||||
|
||||
it("should return true when filtering for trash", () => {
|
||||
const filterFunction = createFilterFunction({ selectedCipherTypeNode: trashFilter });
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false when filtering for favorites", () => {
|
||||
const filterFunction = createFilterFunction({
|
||||
selectedCipherTypeNode: favoriteCiphersFilter,
|
||||
});
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("given a cipher with type", () => {
|
||||
it("should return true when filter matches cipher type", () => {
|
||||
const cipher = createCipher({ type: CipherType.Identity });
|
||||
const filterFunction = createFilterFunction({
|
||||
selectedCipherTypeNode: identityCiphersFilter,
|
||||
});
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false when filter does not match cipher type", () => {
|
||||
const cipher = createCipher({ type: CipherType.Card });
|
||||
const filterFunction = createFilterFunction({
|
||||
selectedCipherTypeNode: identityCiphersFilter,
|
||||
});
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("given a cipher with folder id", () => {
|
||||
it("should return true when filter matches folder id", () => {
|
||||
const cipher = createCipher({ folderId: "folderId" });
|
||||
const filterFunction = createFilterFunction({
|
||||
selectedFolderNode: createFolderFilterNode({ id: "folderId" }),
|
||||
});
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false when filter does not match folder id", () => {
|
||||
const cipher = createCipher({ folderId: "folderId" });
|
||||
const filterFunction = createFilterFunction({
|
||||
selectedFolderNode: createFolderFilterNode({ id: "differentFolderId" }),
|
||||
});
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("given a cipher without folder", () => {
|
||||
const cipher = createCipher({ folderId: null });
|
||||
|
||||
it("should return true when filtering on unassigned folder", () => {
|
||||
const filterFunction = createFilterFunction({
|
||||
selectedFolderNode: createFolderFilterNode({ id: null }),
|
||||
});
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("given an organizational cipher (with organization and collections)", () => {
|
||||
const cipher = createCipher({
|
||||
organizationId: "organizationId",
|
||||
collectionIds: ["collectionId", "anotherId"],
|
||||
});
|
||||
|
||||
it("should return true when filter matches collection id", () => {
|
||||
const filterFunction = createFilterFunction({
|
||||
selectedCollectionNode: createCollectionFilterNode({
|
||||
id: "collectionId",
|
||||
organizationId: "organizationId",
|
||||
}),
|
||||
});
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false when filter does not match collection id", () => {
|
||||
const filterFunction = createFilterFunction({
|
||||
selectedCollectionNode: createCollectionFilterNode({
|
||||
id: "nonMatchingCollectionId",
|
||||
organizationId: "organizationId",
|
||||
}),
|
||||
});
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false when filter does not match organization id", () => {
|
||||
const filterFunction = createFilterFunction({
|
||||
selectedOrganizationNode: createOrganizationFilterNode({
|
||||
id: "nonMatchingOrganizationId",
|
||||
}),
|
||||
});
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false when filtering for my vault only", () => {
|
||||
const filterFunction = createFilterFunction({
|
||||
selectedOrganizationNode: createOrganizationFilterNode({ id: "MyVault" }),
|
||||
});
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false when filtering by All Collections", () => {
|
||||
const filterFunction = createFilterFunction({
|
||||
selectedCollectionNode: createCollectionFilterNode({
|
||||
id: "AllCollections" as CollectionId,
|
||||
}),
|
||||
});
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("given an unassigned organizational cipher (with organization, without collection)", () => {
|
||||
const cipher = createCipher({ organizationId: "organizationId", collectionIds: [] });
|
||||
|
||||
it("should return true when filtering for unassigned collection", () => {
|
||||
const filterFunction = createFilterFunction({
|
||||
selectedCollectionNode: createCollectionFilterNode({ id: null }),
|
||||
});
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("should return true when filter matches organization id", () => {
|
||||
const filterFunction = createFilterFunction({
|
||||
selectedOrganizationNode: createOrganizationFilterNode({ id: "organizationId" }),
|
||||
});
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("given an individual cipher (without organization or collection)", () => {
|
||||
const cipher = createCipher({ organizationId: null, collectionIds: [] });
|
||||
|
||||
it("should return false when filtering for unassigned collection", () => {
|
||||
const filterFunction = createFilterFunction({
|
||||
selectedCollectionNode: createCollectionFilterNode({ id: null }),
|
||||
});
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("should return true when filtering for my vault only", () => {
|
||||
const cipher = createCipher({ organizationId: null });
|
||||
const filterFunction = createFilterFunction({
|
||||
selectedOrganizationNode: createOrganizationFilterNode({ id: "MyVault" }),
|
||||
});
|
||||
|
||||
const result = filterFunction(cipher);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function createFilterFunction(options: Partial<VaultFilter> = {}) {
|
||||
return new VaultFilter(options).buildFilter();
|
||||
}
|
||||
|
||||
function createOrganizationFilterNode(
|
||||
options: Partial<OrganizationFilter>,
|
||||
): TreeNode<OrganizationFilter> {
|
||||
const org = new Organization() as OrganizationFilter;
|
||||
org.id = options.id;
|
||||
org.icon = options.icon ?? "";
|
||||
return new TreeNode<OrganizationFilter>(org, null);
|
||||
}
|
||||
|
||||
function createFolderFilterNode(options: Partial<FolderFilter>): TreeNode<FolderFilter> {
|
||||
const folder = new FolderView() as FolderFilter;
|
||||
folder.id = options.id;
|
||||
folder.name = options.name;
|
||||
folder.icon = options.icon ?? "";
|
||||
folder.revisionDate = options.revisionDate ?? new Date();
|
||||
return new TreeNode<FolderFilter>(folder, null);
|
||||
}
|
||||
|
||||
function createCollectionFilterNode(
|
||||
options: Partial<CollectionFilter>,
|
||||
): TreeNode<CollectionFilter> {
|
||||
const collection = new CollectionView({
|
||||
name: options.name ?? "Test Name",
|
||||
id: options.id ?? null,
|
||||
organizationId: options.organizationId ?? "Org Id",
|
||||
}) as CollectionFilter;
|
||||
return new TreeNode<CollectionFilter>(collection, {} as TreeNode<CollectionFilter>);
|
||||
}
|
||||
|
||||
function createCipher(options: Partial<CipherView> = {}) {
|
||||
const cipher = new CipherView();
|
||||
|
||||
cipher.favorite = options.favorite ?? false;
|
||||
cipher.deletedDate = options.deletedDate;
|
||||
cipher.type = options.type;
|
||||
cipher.folderId = options.folderId;
|
||||
cipher.collectionIds = options.collectionIds;
|
||||
cipher.organizationId = options.organizationId;
|
||||
|
||||
return cipher;
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { CipherType, isCipherType } from "@bitwarden/common/vault/enums";
|
||||
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
|
||||
import {
|
||||
CipherStatus,
|
||||
CipherTypeFilter,
|
||||
CollectionFilter,
|
||||
FolderFilter,
|
||||
OrganizationFilter,
|
||||
} from "./vault-filter.type";
|
||||
|
||||
export type VaultFilterFunction = (cipher: CipherView) => boolean;
|
||||
|
||||
// TODO: Replace shared VaultFilter Model with this one and
|
||||
// refactor browser and desktop code to use this model.
|
||||
export class VaultFilter {
|
||||
selectedOrganizationNode: TreeNode<OrganizationFilter>;
|
||||
selectedCipherTypeNode: TreeNode<CipherTypeFilter>;
|
||||
selectedFolderNode: TreeNode<FolderFilter>;
|
||||
selectedCollectionNode: TreeNode<CollectionFilter>;
|
||||
|
||||
/**
|
||||
* A list of collection filters that form a chain from the organization root to currently selected collection.
|
||||
* To be used when rendering a breadcrumb UI for navigating the collection hierarchy.
|
||||
* Begins from the organization root and excludes the currently selected collection.
|
||||
*/
|
||||
get collectionBreadcrumbs(): TreeNode<CollectionFilter>[] {
|
||||
if (!this.isCollectionSelected) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const collections = [this.selectedCollectionNode];
|
||||
while (collections[collections.length - 1].parent != undefined) {
|
||||
collections.push(collections[collections.length - 1].parent);
|
||||
}
|
||||
|
||||
return collections.slice(1).reverse();
|
||||
}
|
||||
|
||||
/**
|
||||
* The vault is filtered by a specific collection
|
||||
*/
|
||||
get isCollectionSelected(): boolean {
|
||||
return (
|
||||
this.selectedCollectionNode != null &&
|
||||
this.selectedCollectionNode.node.id !== "AllCollections"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The vault is filtered by the "Unassigned" collection
|
||||
*/
|
||||
get isUnassignedCollectionSelected(): boolean {
|
||||
return this.selectedCollectionNode != null && this.selectedCollectionNode.node.id === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The vault is filtered by the users individual vault
|
||||
*/
|
||||
get isMyVaultSelected(): boolean {
|
||||
return this.selectedOrganizationNode?.node.id === "MyVault";
|
||||
}
|
||||
|
||||
get isFavorites(): boolean {
|
||||
return this.selectedCipherTypeNode?.node.type === "favorites";
|
||||
}
|
||||
|
||||
get isDeleted(): boolean {
|
||||
return this.selectedCipherTypeNode?.node.type === "trash" ? true : null;
|
||||
}
|
||||
|
||||
get isArchived(): boolean {
|
||||
return this.selectedCipherTypeNode?.node.type === "archive";
|
||||
}
|
||||
|
||||
get organizationId(): string {
|
||||
return this.selectedOrganizationNode?.node.id;
|
||||
}
|
||||
|
||||
get cipherType(): CipherType {
|
||||
return isCipherType(this.selectedCipherTypeNode?.node.type)
|
||||
? this.selectedCipherTypeNode?.node.type
|
||||
: null;
|
||||
}
|
||||
|
||||
get cipherStatus(): CipherStatus {
|
||||
return this.selectedCipherTypeNode?.node.type;
|
||||
}
|
||||
|
||||
get cipherTypeId(): string {
|
||||
return this.selectedCipherTypeNode?.node.id;
|
||||
}
|
||||
|
||||
get folderId(): string {
|
||||
return this.selectedFolderNode?.node.id;
|
||||
}
|
||||
|
||||
get collectionId(): string {
|
||||
return this.selectedCollectionNode?.node.id;
|
||||
}
|
||||
|
||||
constructor(init?: Partial<VaultFilter>) {
|
||||
Object.assign(this, init);
|
||||
}
|
||||
|
||||
resetFilter() {
|
||||
this.selectedCipherTypeNode = null;
|
||||
this.selectedFolderNode = null;
|
||||
this.selectedCollectionNode = null;
|
||||
}
|
||||
|
||||
resetOrganization() {
|
||||
this.selectedOrganizationNode = null;
|
||||
}
|
||||
|
||||
buildFilter(): VaultFilterFunction {
|
||||
return (cipher) => {
|
||||
let cipherPassesFilter = true;
|
||||
if (this.isFavorites && cipherPassesFilter) {
|
||||
cipherPassesFilter = cipher.favorite;
|
||||
}
|
||||
if (this.isDeleted && cipherPassesFilter) {
|
||||
cipherPassesFilter = cipher.isDeleted;
|
||||
}
|
||||
if (this.isArchived && cipherPassesFilter) {
|
||||
cipherPassesFilter = cipher.isArchived;
|
||||
}
|
||||
if (this.cipherType && cipherPassesFilter) {
|
||||
cipherPassesFilter = cipher.type === this.cipherType;
|
||||
}
|
||||
if (this.selectedFolderNode) {
|
||||
// No folder
|
||||
if (this.folderId === null && cipherPassesFilter) {
|
||||
cipherPassesFilter = cipher.folderId === null;
|
||||
}
|
||||
// Folder
|
||||
if (this.folderId !== null && cipherPassesFilter) {
|
||||
cipherPassesFilter = cipher.folderId === this.folderId;
|
||||
}
|
||||
}
|
||||
if (this.selectedCollectionNode) {
|
||||
// All Collections
|
||||
if (this.collectionId === "AllCollections" && cipherPassesFilter) {
|
||||
cipherPassesFilter = false;
|
||||
}
|
||||
// Unassigned
|
||||
if (this.collectionId === null && cipherPassesFilter) {
|
||||
cipherPassesFilter =
|
||||
cipher.organizationId != null &&
|
||||
(cipher.collectionIds == null || cipher.collectionIds.length === 0);
|
||||
}
|
||||
// Collection
|
||||
if (
|
||||
this.collectionId !== null &&
|
||||
this.collectionId !== "AllCollections" &&
|
||||
cipherPassesFilter
|
||||
) {
|
||||
cipherPassesFilter =
|
||||
cipher.collectionIds != null && cipher.collectionIds.includes(this.collectionId);
|
||||
}
|
||||
}
|
||||
if (this.selectedOrganizationNode) {
|
||||
// My Vault
|
||||
if (this.organizationId === "MyVault" && cipherPassesFilter) {
|
||||
cipherPassesFilter = cipher.organizationId === null;
|
||||
}
|
||||
// Organization
|
||||
else if (this.organizationId !== null && cipherPassesFilter) {
|
||||
cipherPassesFilter = cipher.organizationId === this.organizationId;
|
||||
}
|
||||
}
|
||||
return cipherPassesFilter;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { CollectionAdminView } from "@bitwarden/admin-console/common";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { ITreeNodeObject } from "@bitwarden/common/vault/models/domain/tree-node";
|
||||
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||
|
||||
export type CipherStatus = "all" | "favorites" | "archive" | "trash" | CipherType;
|
||||
|
||||
export type CipherTypeFilter = ITreeNodeObject & { type: CipherStatus; icon: string };
|
||||
export type CollectionFilter = CollectionAdminView & {
|
||||
icon: string;
|
||||
};
|
||||
export type FolderFilter = FolderView & {
|
||||
icon: string;
|
||||
/**
|
||||
* Full folder name.
|
||||
*
|
||||
* Used for when the folder `name` property is be separated into parts.
|
||||
*/
|
||||
fullName?: string;
|
||||
};
|
||||
export type OrganizationFilter = Organization & { icon: string; hideOptions?: boolean };
|
||||
@@ -1,14 +1,13 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { SearchModule } from "@bitwarden/components";
|
||||
import { VaultFilterServiceAbstraction, VaultFilterService } from "@bitwarden/vault";
|
||||
import { OrganizationWarningsModule } from "@bitwarden/web-vault/app/billing/organizations/warnings/organization-warnings.module";
|
||||
|
||||
import { VaultFilterSharedModule } from "../../individual-vault/vault-filter/shared/vault-filter-shared.module";
|
||||
|
||||
import { OrganizationOptionsComponent } from "./components/organization-options.component";
|
||||
import { VaultFilterComponent } from "./components/vault-filter.component";
|
||||
import { VaultFilterService as VaultFilterServiceAbstraction } from "./services/abstractions/vault-filter.service";
|
||||
import { VaultFilterService } from "./services/vault-filter.service";
|
||||
|
||||
@NgModule({
|
||||
imports: [VaultFilterSharedModule, SearchModule, OrganizationWarningsModule],
|
||||
|
||||
@@ -3,13 +3,13 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from
|
||||
import { Router } from "@angular/router";
|
||||
import { firstValueFrom, switchMap } from "rxjs";
|
||||
|
||||
import { CollectionAdminService } from "@bitwarden/admin-console/common";
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import {
|
||||
Unassigned,
|
||||
CollectionView,
|
||||
CollectionAdminService,
|
||||
CollectionTypes,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
} from "@bitwarden/common/admin-console/models/collections";
|
||||
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";
|
||||
@@ -24,16 +24,12 @@ import {
|
||||
MenuModule,
|
||||
SimpleDialogOptions,
|
||||
} from "@bitwarden/components";
|
||||
import { NewCipherMenuComponent } from "@bitwarden/vault";
|
||||
import { NewCipherMenuComponent, All, RoutedVaultFilterModel } from "@bitwarden/vault";
|
||||
|
||||
import { CollectionDialogTabType } from "../../../admin-console/organizations/shared/components/collection-dialog";
|
||||
import { HeaderModule } from "../../../layouts/header/header.module";
|
||||
import { SharedModule } from "../../../shared";
|
||||
import { PipesModule } from "../pipes/pipes.module";
|
||||
import {
|
||||
All,
|
||||
RoutedVaultFilterModel,
|
||||
} from "../vault-filter/shared/models/routed-vault-filter.model";
|
||||
|
||||
@Component({
|
||||
selector: "app-vault-header",
|
||||
|
||||
@@ -25,13 +25,7 @@ import {
|
||||
tap,
|
||||
} from "rxjs/operators";
|
||||
|
||||
import {
|
||||
CollectionData,
|
||||
CollectionDetailsResponse,
|
||||
CollectionService,
|
||||
CollectionView,
|
||||
Unassigned,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { CollectionService } from "@bitwarden/admin-console/common";
|
||||
import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe";
|
||||
import {
|
||||
NoResults,
|
||||
@@ -50,7 +44,17 @@ import {
|
||||
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import {
|
||||
CollectionDetailsResponse,
|
||||
CollectionView,
|
||||
Unassigned,
|
||||
CollectionData,
|
||||
} from "@bitwarden/common/admin-console/models/collections";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import {
|
||||
getNestedCollectionTree,
|
||||
getFlatCollectionTree,
|
||||
} from "@bitwarden/common/admin-console/utils";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
@@ -97,6 +101,15 @@ import {
|
||||
DecryptionFailureDialogComponent,
|
||||
DefaultCipherFormConfigService,
|
||||
PasswordRepromptService,
|
||||
VaultFilterServiceAbstraction as VaultFilterService,
|
||||
RoutedVaultFilterBridgeService,
|
||||
RoutedVaultFilterService,
|
||||
createFilterFunction,
|
||||
All,
|
||||
RoutedVaultFilterModel,
|
||||
VaultFilter,
|
||||
FolderFilter,
|
||||
OrganizationFilter,
|
||||
VaultItemsTransferService,
|
||||
DefaultVaultItemsTransferService,
|
||||
} from "@bitwarden/vault";
|
||||
@@ -104,10 +117,6 @@ import { UnifiedUpgradePromptService } from "@bitwarden/web-vault/app/billing/in
|
||||
import { OrganizationWarningsModule } from "@bitwarden/web-vault/app/billing/organizations/warnings/organization-warnings.module";
|
||||
import { OrganizationWarningsService } from "@bitwarden/web-vault/app/billing/organizations/warnings/services";
|
||||
|
||||
import {
|
||||
getNestedCollectionTree,
|
||||
getFlatCollectionTree,
|
||||
} from "../../admin-console/organizations/collections";
|
||||
import {
|
||||
AutoConfirmPolicy,
|
||||
AutoConfirmPolicyDialogComponent,
|
||||
@@ -140,16 +149,6 @@ import {
|
||||
} from "./bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component";
|
||||
import { VaultBannersComponent } from "./vault-banners/vault-banners.component";
|
||||
import { VaultFilterComponent } from "./vault-filter/components/vault-filter.component";
|
||||
import { VaultFilterService } from "./vault-filter/services/abstractions/vault-filter.service";
|
||||
import { RoutedVaultFilterBridgeService } from "./vault-filter/services/routed-vault-filter-bridge.service";
|
||||
import { RoutedVaultFilterService } from "./vault-filter/services/routed-vault-filter.service";
|
||||
import { createFilterFunction } from "./vault-filter/shared/models/filter-function";
|
||||
import {
|
||||
All,
|
||||
RoutedVaultFilterModel,
|
||||
} from "./vault-filter/shared/models/routed-vault-filter.model";
|
||||
import { VaultFilter } from "./vault-filter/shared/models/vault-filter.model";
|
||||
import { FolderFilter, OrganizationFilter } from "./vault-filter/shared/models/vault-filter.type";
|
||||
import { VaultFilterModule } from "./vault-filter/vault-filter.module";
|
||||
import { VaultHeaderComponent } from "./vault-header/vault-header.component";
|
||||
import { VaultOnboardingComponent } from "./vault-onboarding/vault-onboarding.component";
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { TestBed } from "@angular/core/testing";
|
||||
import { BehaviorSubject, of } from "rxjs";
|
||||
|
||||
import { CollectionAdminService, CollectionAdminView } from "@bitwarden/admin-console/common";
|
||||
import { CollectionAdminService } from "@bitwarden/admin-console/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums";
|
||||
import { CollectionAdminView } from "@bitwarden/common/admin-console/models/collections";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { mockAccountServiceWith } from "@bitwarden/common/spec";
|
||||
import { CipherId, UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
|
||||
import { RoutedVaultFilterService } from "../../individual-vault/vault-filter/services/routed-vault-filter.service";
|
||||
import { RoutedVaultFilterService } from "@bitwarden/vault";
|
||||
|
||||
import { AdminConsoleCipherFormConfigService } from "./admin-console-cipher-form-config.service";
|
||||
|
||||
|
||||
@@ -16,9 +16,12 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data";
|
||||
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
||||
import { CipherFormConfig, CipherFormConfigService, CipherFormMode } from "@bitwarden/vault";
|
||||
|
||||
import { RoutedVaultFilterService } from "../../individual-vault/vault-filter/services/routed-vault-filter.service";
|
||||
import {
|
||||
CipherFormConfig,
|
||||
CipherFormConfigService,
|
||||
CipherFormMode,
|
||||
RoutedVaultFilterService,
|
||||
} from "@bitwarden/vault";
|
||||
|
||||
/** Admin Console implementation of the `CipherFormConfigService`. */
|
||||
@Injectable()
|
||||
|
||||
Reference in New Issue
Block a user