mirror of
https://github.com/bitwarden/browser
synced 2025-12-22 19:23:52 +00:00
[PM-25982] Assign to Collections - My Items (#16591)
* update cipher form to exclude my items collections * handle default collections for assign to collections and bulk * account for every returning true for empty arrays
This commit is contained in:
@@ -5,7 +5,11 @@ import { of } 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 { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
@@ -34,7 +38,6 @@ describe("AssignCollectionsComponent", () => {
|
||||
organizationId: "org-id" as OrganizationId,
|
||||
name: "Editable Collection",
|
||||
});
|
||||
|
||||
editCollection.readOnly = false;
|
||||
editCollection.manage = true;
|
||||
|
||||
@@ -52,6 +55,24 @@ describe("AssignCollectionsComponent", () => {
|
||||
});
|
||||
readOnlyCollection2.readOnly = true;
|
||||
|
||||
const sharedCollection = new CollectionView({
|
||||
id: "shared-collection-id" as CollectionId,
|
||||
organizationId: "org-id" as OrganizationId,
|
||||
name: "Shared Collection",
|
||||
});
|
||||
sharedCollection.readOnly = false;
|
||||
sharedCollection.assigned = true;
|
||||
sharedCollection.type = CollectionTypes.SharedCollection;
|
||||
|
||||
const defaultCollection = new CollectionView({
|
||||
id: "default-collection-id" as CollectionId,
|
||||
organizationId: "org-id" as OrganizationId,
|
||||
name: "Default Collection",
|
||||
});
|
||||
defaultCollection.readOnly = false;
|
||||
defaultCollection.manage = true;
|
||||
defaultCollection.type = CollectionTypes.DefaultUserCollection;
|
||||
|
||||
const params = {
|
||||
organizationId: "org-id" as OrganizationId,
|
||||
ciphers: [
|
||||
@@ -116,4 +137,75 @@ describe("AssignCollectionsComponent", () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("default collections", () => {
|
||||
const cipher1 = new CipherView();
|
||||
cipher1.id = "cipher-id-1";
|
||||
cipher1.collectionIds = [editCollection.id, sharedCollection.id];
|
||||
cipher1.edit = true;
|
||||
|
||||
const cipher2 = new CipherView();
|
||||
cipher2.id = "cipher-id-2";
|
||||
cipher2.collectionIds = [defaultCollection.id];
|
||||
cipher2.edit = true;
|
||||
|
||||
const cipher3 = new CipherView();
|
||||
cipher3.id = "cipher-id-3";
|
||||
cipher3.collectionIds = [defaultCollection.id];
|
||||
cipher3.edit = true;
|
||||
|
||||
const cipher4 = new CipherView();
|
||||
cipher4.id = "cipher-id-4";
|
||||
cipher4.collectionIds = [];
|
||||
cipher4.edit = true;
|
||||
|
||||
it('does not show the "Default Collection" if any cipher is in a shared collection', async () => {
|
||||
component.params = {
|
||||
...component.params,
|
||||
ciphers: [cipher1, cipher2],
|
||||
availableCollections: [editCollection, sharedCollection, defaultCollection],
|
||||
};
|
||||
|
||||
await component.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component["availableCollections"].map((c) => c.id)).toEqual([
|
||||
editCollection.id,
|
||||
sharedCollection.id,
|
||||
]);
|
||||
});
|
||||
|
||||
it('shows the "Default Collection" if no ciphers are in a shared collection', async () => {
|
||||
component.params = {
|
||||
...component.params,
|
||||
ciphers: [cipher2, cipher3],
|
||||
availableCollections: [editCollection, sharedCollection, defaultCollection],
|
||||
};
|
||||
|
||||
await component.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component["availableCollections"].map((c) => c.id)).toEqual([
|
||||
editCollection.id,
|
||||
sharedCollection.id,
|
||||
defaultCollection.id,
|
||||
]);
|
||||
});
|
||||
|
||||
it('shows the "Default Collection" for singular cipher', async () => {
|
||||
component.params = {
|
||||
...component.params,
|
||||
ciphers: [cipher4],
|
||||
availableCollections: [readOnlyCollection1, sharedCollection, defaultCollection],
|
||||
};
|
||||
|
||||
await component.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component["availableCollections"].map((c) => c.id)).toEqual([
|
||||
sharedCollection.id,
|
||||
defaultCollection.id,
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,7 +26,11 @@ import {
|
||||
|
||||
// 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 { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import {
|
||||
getOrganizationById,
|
||||
@@ -311,9 +315,19 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI
|
||||
|
||||
await this.setReadOnlyCollectionNames();
|
||||
|
||||
const canAccessDefaultCollection = this.canAccessDefaultCollection(
|
||||
this.params.availableCollections,
|
||||
);
|
||||
|
||||
this.availableCollections = this.params.availableCollections
|
||||
.filter((collection) => {
|
||||
return collection.canEditItems(org);
|
||||
if (canAccessDefaultCollection) {
|
||||
return collection.canEditItems(org);
|
||||
}
|
||||
|
||||
return (
|
||||
collection.canEditItems(org) && collection.type !== CollectionTypes.DefaultUserCollection
|
||||
);
|
||||
})
|
||||
.map((c) => ({
|
||||
icon: "bwi-collection-shared",
|
||||
@@ -447,8 +461,16 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI
|
||||
const org = organizations.find((o) => o.id === orgId);
|
||||
this.orgName = org.name;
|
||||
|
||||
return collections.filter((c) => {
|
||||
return c.organizationId === orgId && !c.readOnly;
|
||||
const orgCollections = collections.filter((c) => c.organizationId === orgId);
|
||||
|
||||
const canAccessDefaultCollection = this.canAccessDefaultCollection(collections);
|
||||
|
||||
return orgCollections.filter((c) => {
|
||||
if (canAccessDefaultCollection) {
|
||||
return !c.readOnly;
|
||||
}
|
||||
|
||||
return !c.readOnly && c.type !== CollectionTypes.DefaultUserCollection;
|
||||
});
|
||||
}),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
@@ -536,4 +558,26 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI
|
||||
})
|
||||
.map((c) => c.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the ciphers to be assigned can be assigned to the Default Collection.
|
||||
* When false, the Default Collections should be excluded from the list of available collections.
|
||||
*/
|
||||
private canAccessDefaultCollection(collections: CollectionView[]): boolean {
|
||||
const collectionsObject = Object.fromEntries(collections.map((c) => [c.id, c]));
|
||||
|
||||
const allCiphersUnassignedOrInDefault = this.params.ciphers.every(
|
||||
(cipher) =>
|
||||
!cipher.collectionIds.length ||
|
||||
cipher.collectionIds.some(
|
||||
(cId) => collectionsObject[cId]?.type === CollectionTypes.DefaultUserCollection,
|
||||
),
|
||||
);
|
||||
|
||||
// When all ciphers are either:
|
||||
// - unassigned
|
||||
// - already in a Default Collection
|
||||
// then the Default Collection can be shown.
|
||||
return allCiphersUnassignedOrInDefault;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user