1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 00:03:56 +00:00

[AC-1119] [PM-1923] [AC-701] Import into a specified folder or collection (#5683)

* Migrate callouts to the CL ones

* Add folder/collection selection

* Use bitTypography as page header/title

* Migrate submit button to CL

* Migrate fileSelector and fileContents

* Add ability to import into an existing folder/collection

Extended import.service and abstraction to receive importTarget on import()
Pass selectedImportTarget to importService.import()
Wrote unit tests

* Added vault selector, folders/collections selection logic and component library to the import

* Revert changes to the already migrated CL fileSelector, fileContents and header/title

* Fix fileContents input and spacing to submit button

* Use id's instead of name for tghe targetSelector

* Remove unneeded empty line

* Fix import into existing folder/collection

Map ciphers with no folder/no collection to the new rootFolder when selected by the user
Modified and added unit tests

* Added CL to fileSelector and fileInput on vault import

* Added reactive forms and new selector logic to import vault

* Added new texts on Import Vault

* Corrected logic on enable targetSelector

* Removing target selector from being required

* Fixed imports after messing up a merge conflict

* Set No-Folder as default

* Show icons (folder/collection) on targetSelector

* Add icons to vaultSelector

* Set `My Vault` as default of the vaultSelector

* Updates labels based on feedback from design

* Set `My Vault` as default of the vaultSelector pt2

* Improvements to reactive forms on import.component

* Only disabling individual vault import on PersonalOwnership policy

* Use import destination instead of import location

* Add hint to folder/collection dropdown

* Removed required attribute as provided by formGroup

* Display no collection option same as no folder

* Show error on org import with unassigned items

Only admins can have unassigned items (items with no collection)
If these are present in a export/backup file, they should still be imported, to not break existing behaviour. This is limited to admins.
When a member of an org does not set a root collection (no collection option) and any items are unassigned an error message is shown and the import is aborted.

* Removed for-attribute from bit-labels

* Removed bitInput from bit-selects

* Updates to messages.json after PR feedback

* Removed name-attribute from bit-selects

* Removed unneeded variables

* Removed unneeded line break

* Migrate form to use bitSubmit

Rename old submit() to performImport()
Create submit arrow function calling performImport() (which can be overridden/called by org-import.component)
Remove #form and ngNativeValidate
Add bitSubmit and bitFormButton directives
Remove now unneeded loading variable

* Added await to super.performImport()

* Move form check into submit

* AC-1558 - Enable org import with remove individual vault policy

Hide the `My Vault` entry when policy is active
Always check if the policy applies and disable the formGroup if no vault-target is selectable

* [AC-1549] Import page design updates (#5933)

* Display select folder/collection in targetSelector
Filter the no-folder entry from the folderViews-observable
Add labels for the targetSelector placeholders

* Update importTargetHint and remove importTargetOrgHint

* Update language on importUnassignedItemsError

* Add help icon with link to the import documentation

---------

Co-authored-by: Andre Rosado <arosado@bitwarden.com>
This commit is contained in:
Daniel James Smith
2023-08-05 00:05:14 +02:00
committed by GitHub
parent b89f31101f
commit e98cbed437
8 changed files with 455 additions and 86 deletions

View File

@@ -13,6 +13,8 @@ import { CipherRequest } from "@bitwarden/common/vault/models/request/cipher.req
import { CollectionWithIdRequest } from "@bitwarden/common/vault/models/request/collection-with-id.request";
import { FolderWithIdRequest } from "@bitwarden/common/vault/models/request/folder-with-id.request";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import {
AscendoCsvImporter,
@@ -106,7 +108,9 @@ export class ImportService implements ImportServiceAbstraction {
async import(
importer: Importer,
fileContents: string,
organizationId: string = null
organizationId: string = null,
selectedImportTarget: string = null,
isUserAdmin: boolean
): Promise<ImportResult> {
let importResult: ImportResult;
try {
@@ -142,7 +146,17 @@ export class ImportService implements ImportServiceAbstraction {
}
}
if (organizationId && Utils.isNullOrWhitespace(selectedImportTarget) && !isUserAdmin) {
const hasUnassignedCollections = importResult.ciphers.some(
(c) => !Array.isArray(c.collectionIds) || c.collectionIds.length == 0
);
if (hasUnassignedCollections) {
throw new Error(this.i18nService.t("importUnassignedItemsError"));
}
}
try {
await this.setImportTarget(importResult, organizationId, selectedImportTarget);
if (organizationId != null) {
await this.handleOrganizationalImport(importResult, organizationId);
} else {
@@ -403,4 +417,69 @@ export class ImportService implements ImportServiceAbstraction {
return new Error(errorMessage);
}
private async setImportTarget(
importResult: ImportResult,
organizationId: string,
importTarget: string
) {
if (Utils.isNullOrWhitespace(importTarget)) {
return;
}
if (organizationId) {
const collectionViews: CollectionView[] = await this.collectionService.getAllDecrypted();
const targetCollection = collectionViews.find((c) => c.id === importTarget);
const noCollectionRelationShips: [number, number][] = [];
importResult.ciphers.forEach((c, index) => {
if (!Array.isArray(c.collectionIds) || c.collectionIds.length == 0) {
c.collectionIds = [targetCollection.id];
noCollectionRelationShips.push([index, 0]);
}
});
const collections: CollectionView[] = [...importResult.collections];
importResult.collections = [targetCollection];
collections.map((x) => {
const f = new CollectionView();
f.name = `${targetCollection.name}/${x.name}`;
importResult.collections.push(f);
});
const relationships: [number, number][] = [...importResult.collectionRelationships];
importResult.collectionRelationships = [...noCollectionRelationShips];
relationships.map((x) => {
importResult.collectionRelationships.push([x[0], x[1] + 1]);
});
return;
}
const folderViews = await this.folderService.getAllDecryptedFromState();
const targetFolder = folderViews.find((f) => f.id === importTarget);
const noFolderRelationShips: [number, number][] = [];
importResult.ciphers.forEach((c, index) => {
if (Utils.isNullOrEmpty(c.folderId)) {
c.folderId = targetFolder.id;
noFolderRelationShips.push([index, 0]);
}
});
const folders: FolderView[] = [...importResult.folders];
importResult.folders = [targetFolder];
folders.map((x) => {
const newFolderName = `${targetFolder.name}/${x.name}`;
const f = new FolderView();
f.name = newFolderName;
importResult.folders.push(f);
});
const relationships: [number, number][] = [...importResult.folderRelationships];
importResult.folderRelationships = [...noFolderRelationShips];
relationships.map((x) => {
importResult.folderRelationships.push([x[0], x[1] + 1]);
});
}
}