mirror of
https://github.com/bitwarden/browser
synced 2025-12-30 23:23:52 +00:00
[PM-27675] Browser item transfer integration (#17918)
* [PM-27675] Integrate dialogs into VaultItemTransferService * [PM-27675] Update tests for new dialogs * [PM-27675] Center dialogs and prevent closing with escape or pointer events * [PM-27675] Add transferInProgress$ observable to VaultItemsTransferService * [PM-27675] Hook vault item transfer service into browser vault component * [PM-27675] Move defaultUserCollection$ to collection service * [PM-27675] Cleanup dialog styles * [PM-27675] Introduce readySubject to popup vault component to keep prevent flashing content while item transfer is in progress * [PM-27675] Fix vault-v2 tests
This commit is contained in:
@@ -9,6 +9,14 @@ import { CollectionData, Collection, CollectionView } from "../models";
|
||||
export abstract class CollectionService {
|
||||
abstract encryptedCollections$(userId: UserId): Observable<Collection[] | null>;
|
||||
abstract decryptedCollections$(userId: UserId): Observable<CollectionView[]>;
|
||||
|
||||
/**
|
||||
* Gets the default collection for a user in a given organization, if it exists.
|
||||
*/
|
||||
abstract defaultUserCollection$(
|
||||
userId: UserId,
|
||||
orgId: OrganizationId,
|
||||
): Observable<CollectionView | undefined>;
|
||||
abstract upsert(collection: CollectionData, userId: UserId): Promise<any>;
|
||||
abstract replace(collections: { [id: string]: CollectionData }, userId: UserId): Promise<any>;
|
||||
/**
|
||||
|
||||
@@ -15,9 +15,10 @@ import {
|
||||
} from "@bitwarden/common/spec";
|
||||
import { CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid";
|
||||
import { OrgKey } from "@bitwarden/common/types/key";
|
||||
import { newGuid } from "@bitwarden/guid";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
|
||||
import { CollectionData, CollectionView } from "../models";
|
||||
import { CollectionData, CollectionTypes, CollectionView } from "../models";
|
||||
|
||||
import { DECRYPTED_COLLECTION_DATA_KEY, ENCRYPTED_COLLECTION_DATA_KEY } from "./collection.state";
|
||||
import { DefaultCollectionService } from "./default-collection.service";
|
||||
@@ -389,6 +390,83 @@ describe("DefaultCollectionService", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("defaultUserCollection$", () => {
|
||||
it("returns the default collection when one exists matching the org", async () => {
|
||||
const orgId = newGuid() as OrganizationId;
|
||||
const defaultCollection = collectionViewDataFactory(orgId);
|
||||
defaultCollection.type = CollectionTypes.DefaultUserCollection;
|
||||
|
||||
const regularCollection = collectionViewDataFactory(orgId);
|
||||
regularCollection.type = CollectionTypes.SharedCollection;
|
||||
|
||||
await setDecryptedState([defaultCollection, regularCollection]);
|
||||
|
||||
const result = await firstValueFrom(collectionService.defaultUserCollection$(userId, orgId));
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.id).toBe(defaultCollection.id);
|
||||
expect(result?.isDefaultCollection).toBe(true);
|
||||
});
|
||||
|
||||
it("returns undefined when no default collection exists", async () => {
|
||||
const orgId = newGuid() as OrganizationId;
|
||||
const collection1 = collectionViewDataFactory(orgId);
|
||||
collection1.type = CollectionTypes.SharedCollection;
|
||||
|
||||
const collection2 = collectionViewDataFactory(orgId);
|
||||
collection2.type = CollectionTypes.SharedCollection;
|
||||
|
||||
await setDecryptedState([collection1, collection2]);
|
||||
|
||||
const result = await firstValueFrom(collectionService.defaultUserCollection$(userId, orgId));
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns undefined when default collection exists but for different org", async () => {
|
||||
const orgA = newGuid() as OrganizationId;
|
||||
const orgB = newGuid() as OrganizationId;
|
||||
|
||||
const defaultCollectionForOrgA = collectionViewDataFactory(orgA);
|
||||
defaultCollectionForOrgA.type = CollectionTypes.DefaultUserCollection;
|
||||
|
||||
await setDecryptedState([defaultCollectionForOrgA]);
|
||||
|
||||
const result = await firstValueFrom(collectionService.defaultUserCollection$(userId, orgB));
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns undefined when collections array is empty", async () => {
|
||||
const orgId = newGuid() as OrganizationId;
|
||||
|
||||
await setDecryptedState([]);
|
||||
|
||||
const result = await firstValueFrom(collectionService.defaultUserCollection$(userId, orgId));
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns correct collection when multiple orgs have default collections", async () => {
|
||||
const orgA = newGuid() as OrganizationId;
|
||||
const orgB = newGuid() as OrganizationId;
|
||||
|
||||
const defaultCollectionForOrgA = collectionViewDataFactory(orgA);
|
||||
defaultCollectionForOrgA.type = CollectionTypes.DefaultUserCollection;
|
||||
|
||||
const defaultCollectionForOrgB = collectionViewDataFactory(orgB);
|
||||
defaultCollectionForOrgB.type = CollectionTypes.DefaultUserCollection;
|
||||
|
||||
await setDecryptedState([defaultCollectionForOrgA, defaultCollectionForOrgB]);
|
||||
|
||||
const result = await firstValueFrom(collectionService.defaultUserCollection$(userId, orgB));
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.id).toBe(defaultCollectionForOrgB.id);
|
||||
expect(result?.organizationId).toBe(orgB);
|
||||
});
|
||||
});
|
||||
|
||||
const setEncryptedState = (collectionData: CollectionData[] | null) =>
|
||||
stateProvider.setUserState(
|
||||
ENCRYPTED_COLLECTION_DATA_KEY,
|
||||
|
||||
@@ -87,6 +87,17 @@ export class DefaultCollectionService implements CollectionService {
|
||||
return result$;
|
||||
}
|
||||
|
||||
defaultUserCollection$(
|
||||
userId: UserId,
|
||||
orgId: OrganizationId,
|
||||
): Observable<CollectionView | undefined> {
|
||||
return this.decryptedCollections$(userId).pipe(
|
||||
map((collections) => {
|
||||
return collections.find((c) => c.isDefaultCollection && c.organizationId === orgId);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private initializeDecryptedState(userId: UserId): Observable<CollectionView[]> {
|
||||
return combineLatest([
|
||||
this.encryptedCollections$(userId),
|
||||
|
||||
Reference in New Issue
Block a user