From 00e1c936fbd4211bab09287df0eb6287d2523316 Mon Sep 17 00:00:00 2001
From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com>
Date: Fri, 1 Nov 2024 11:05:02 -0500
Subject: [PATCH] [PM-13928]use the user's email address in owner dropdown
rather than "You" (#11798)
* use the user's email address in owner dropdown rather than "You"
* show ownership value in individual vault when disabled
* import account service in storybook
---
.../src/cipher-form/cipher-form.stories.ts | 7 ++++
.../item-details-section.component.html | 4 +--
.../item-details-section.component.spec.ts | 36 +++++++++++++++++++
.../item-details-section.component.ts | 16 +++++++++
4 files changed, 61 insertions(+), 2 deletions(-)
diff --git a/libs/vault/src/cipher-form/cipher-form.stories.ts b/libs/vault/src/cipher-form/cipher-form.stories.ts
index 13f233f53d..e48cf384c2 100644
--- a/libs/vault/src/cipher-form/cipher-form.stories.ts
+++ b/libs/vault/src/cipher-form/cipher-form.stories.ts
@@ -13,6 +13,7 @@ import { CollectionView } from "@bitwarden/admin-console/common";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
+import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
import { ClientType } from "@bitwarden/common/enums";
@@ -183,6 +184,12 @@ export default {
getClientType: () => ClientType.Browser,
},
},
+ {
+ provide: AccountService,
+ useValue: {
+ activeAccount$: new BehaviorSubject({ email: "test@example.com" }),
+ },
+ },
],
}),
componentWrapperDecorator(
diff --git a/libs/vault/src/cipher-form/components/item-details/item-details-section.component.html b/libs/vault/src/cipher-form/components/item-details/item-details-section.component.html
index 6c6bd8a801..648539932d 100644
--- a/libs/vault/src/cipher-form/components/item-details/item-details-section.component.html
+++ b/libs/vault/src/cipher-form/components/item-details/item-details-section.component.html
@@ -27,9 +27,9 @@
{{ "owner" | i18n }}
{
let cipherFormProvider: MockProxy;
let i18nService: MockProxy;
+ const activeAccount$ = new BehaviorSubject<{ email: string }>({ email: "test@example.com" });
+
beforeEach(async () => {
cipherFormProvider = mock();
i18nService = mock();
@@ -29,6 +35,7 @@ describe("ItemDetailsSectionComponent", () => {
providers: [
{ provide: CipherFormContainer, useValue: cipherFormProvider },
{ provide: I18nService, useValue: i18nService },
+ { provide: AccountService, useValue: { activeAccount$ } },
],
}).compileComponents();
@@ -207,6 +214,35 @@ describe("ItemDetailsSectionComponent", () => {
});
});
+ describe("showPersonalOwnerOption", () => {
+ it("should show personal ownership when the configuration allows", () => {
+ component.config.mode = "edit";
+ component.config.allowPersonalOwnership = true;
+ component.config.organizations = [{ id: "134-433-22" } as Organization];
+ fixture.detectChanges();
+
+ const select = fixture.debugElement.query(By.directive(SelectComponent));
+ const { value, label } = select.componentInstance.items[0];
+
+ expect(value).toBeNull();
+ expect(label).toBe("test@example.com");
+ });
+
+ it("should show personal ownership when the control is disabled", async () => {
+ component.config.mode = "edit";
+ component.config.allowPersonalOwnership = false;
+ component.config.organizations = [{ id: "134-433-22" } as Organization];
+ await component.ngOnInit();
+ fixture.detectChanges();
+
+ const select = fixture.debugElement.query(By.directive(SelectComponent));
+
+ const { value, label } = select.componentInstance.items[0];
+ expect(value).toBeNull();
+ expect(label).toBe("test@example.com");
+ });
+ });
+
describe("showOwnership", () => {
it("should return true if ownership change is allowed or in edit mode with at least one organization", () => {
jest.spyOn(component, "allowOwnershipChange", "get").mockReturnValue(true);
diff --git a/libs/vault/src/cipher-form/components/item-details/item-details-section.component.ts b/libs/vault/src/cipher-form/components/item-details/item-details-section.component.ts
index 06ce363a27..fb193dd3dd 100644
--- a/libs/vault/src/cipher-form/components/item-details/item-details-section.component.ts
+++ b/libs/vault/src/cipher-form/components/item-details/item-details-section.component.ts
@@ -7,6 +7,7 @@ import { concatMap, map } from "rxjs";
import { CollectionView } from "@bitwarden/admin-console/common";
import { JslibModule } from "@bitwarden/angular/jslib.module";
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";
import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
@@ -68,6 +69,9 @@ export class ItemDetailsSectionComponent implements OnInit {
protected showCollectionsControl: boolean;
+ /** The email address associated with the active account */
+ protected userEmail$ = this.accountService.activeAccount$.pipe(map((account) => account.email));
+
@Input({ required: true })
config: CipherFormConfig;
@@ -96,11 +100,23 @@ export class ItemDetailsSectionComponent implements OnInit {
return this.config.initialValues;
}
+ /**
+ * Show the personal ownership option in the Owner dropdown when:
+ * - Personal ownership is allowed
+ * - The `organizationId` control is disabled. This avoids the scenario
+ * where a the dropdown is empty because the user personally owns the cipher
+ * but cannot edit the ownership.
+ */
+ get showPersonalOwnerOption() {
+ return this.allowPersonalOwnership || !this.itemDetailsForm.controls.organizationId.enabled;
+ }
+
constructor(
private cipherFormContainer: CipherFormContainer,
private formBuilder: FormBuilder,
private i18nService: I18nService,
private destroyRef: DestroyRef,
+ private accountService: AccountService,
) {
this.cipherFormContainer.registerChildForm("itemDetails", this.itemDetailsForm);
this.itemDetailsForm.valueChanges