1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-11 05:43:41 +00:00

[PM-22375] - [Vault] [Clients] Sort My Items collection to the top of Vault collection filters (#15332)

* WIP - default collection sorting

* apply filtering to popup list filters service.

* add tests. add feature flag checks

* finalize my items collection filters

* fix type error

* re-add service

* re-add comment

* remove unused code

* fix sorting logic

* shorten variable name to fit one line

* fix error

* fix more errors

* abstract logic to vault filter service

* fix test

* export sort as function instead of adding to class

* fix more tests

* add collator arg

* remove ts-ignore. fix type errors

* remove optional param

* fix vault filter service
This commit is contained in:
Jordan Aasen
2025-07-09 08:37:38 -07:00
committed by GitHub
parent e7d5cde105
commit 9f1531a1b2
10 changed files with 244 additions and 61 deletions

View File

@@ -1,17 +1,22 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Injectable } from "@angular/core";
import { firstValueFrom, from, map, mergeMap, Observable, switchMap, take } from "rxjs";
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import { CollectionService, CollectionView } from "@bitwarden/admin-console/common";
import {
CollectionService,
CollectionTypes,
CollectionView,
} 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 { 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 { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { SingleUserState, StateProvider } from "@bitwarden/common/platform/state";
import { UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
@@ -40,6 +45,8 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti
protected policyService: PolicyService,
protected stateProvider: StateProvider,
protected accountService: AccountService,
protected configService: ConfigService,
protected i18nService: I18nService,
) {}
async storeCollapsedFilterNodes(
@@ -103,12 +110,20 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti
async buildCollections(organizationId?: string): Promise<DynamicTreeNode<CollectionView>> {
const storedCollections = await this.collectionService.getAllDecrypted();
let collections: CollectionView[];
if (organizationId != null) {
collections = storedCollections.filter((c) => c.organizationId === organizationId);
} else {
collections = storedCollections;
const orgs = await this.buildOrganizations();
const defaulCollectionsFlagEnabled = await this.configService.getFeatureFlag(
FeatureFlag.CreateDefaultLocation,
);
let collections =
organizationId == null
? storedCollections
: storedCollections.filter((c) => c.organizationId === organizationId);
if (defaulCollectionsFlagEnabled) {
collections = sortDefaultCollections(collections, orgs, this.i18nService.collator);
}
const nestedCollections = await this.collectionService.getAllNested(collections);
return new DynamicTreeNode<CollectionView>({
fullList: collections,
@@ -145,7 +160,7 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti
folderCopy.id = f.id;
folderCopy.revisionDate = f.revisionDate;
const parts = f.name != null ? f.name.replace(/^\/+|\/+$/g, "").split(NestingDelimiter) : [];
ServiceUtils.nestedTraverse(nodes, 0, parts, folderCopy, null, NestingDelimiter);
ServiceUtils.nestedTraverse(nodes, 0, parts, folderCopy, undefined, NestingDelimiter);
});
return nodes;
}
@@ -158,3 +173,28 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti
return ServiceUtils.getTreeNodeObjectFromList(folders, id) as TreeNode<FolderView>;
}
}
/**
* Sorts collections with default user collections at the top, sorted by organization name.
* Remaining collections are sorted by name.
* @param collections - The list of collections to sort.
* @param orgs - The list of organizations to use for sorting default user collections.
* @returns Sorted list of collections.
*/
export function sortDefaultCollections(
collections: CollectionView[],
orgs: Organization[] = [],
collator: Intl.Collator,
): CollectionView[] {
const sortedDefaultCollectionTypes = collections
.filter((c) => c.type === CollectionTypes.DefaultUserCollection)
.sort((a, b) => {
const aName = orgs.find((o) => o.id === a.organizationId)?.name ?? a.organizationId;
const bName = orgs.find((o) => o.id === b.organizationId)?.name ?? b.organizationId;
return collator.compare(aName, bName);
});
return [
...sortedDefaultCollectionTypes,
...collections.filter((c) => c.type !== CollectionTypes.DefaultUserCollection),
];
}