1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-12-19 15:57:42 +01:00

[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
This commit is contained in:
Nick Krantz 2024-11-01 11:05:02 -05:00 committed by GitHub
parent a049b553a6
commit 00e1c936fb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 61 additions and 2 deletions

View File

@ -13,6 +13,7 @@ import { CollectionView } from "@bitwarden/admin-console/common";
import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; 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 { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
import { ClientType } from "@bitwarden/common/enums"; import { ClientType } from "@bitwarden/common/enums";
@ -183,6 +184,12 @@ export default {
getClientType: () => ClientType.Browser, getClientType: () => ClientType.Browser,
}, },
}, },
{
provide: AccountService,
useValue: {
activeAccount$: new BehaviorSubject({ email: "test@example.com" }),
},
},
], ],
}), }),
componentWrapperDecorator( componentWrapperDecorator(

View File

@ -27,9 +27,9 @@
<bit-label>{{ "owner" | i18n }}</bit-label> <bit-label>{{ "owner" | i18n }}</bit-label>
<bit-select formControlName="organizationId"> <bit-select formControlName="organizationId">
<bit-option <bit-option
*ngIf="allowPersonalOwnership" *ngIf="showPersonalOwnerOption"
[value]="null" [value]="null"
[label]="'selfOwnershipLabel' | i18n" [label]="userEmail$ | async"
></bit-option> ></bit-option>
<bit-option <bit-option
*ngFor="let org of config.organizations" *ngFor="let org of config.organizations"

View File

@ -1,13 +1,17 @@
import { CommonModule } from "@angular/common"; import { CommonModule } from "@angular/common";
import { ComponentFixture, fakeAsync, TestBed, tick } from "@angular/core/testing"; import { ComponentFixture, fakeAsync, TestBed, tick } from "@angular/core/testing";
import { ReactiveFormsModule } from "@angular/forms"; import { ReactiveFormsModule } from "@angular/forms";
import { By } from "@angular/platform-browser";
import { mock, MockProxy } from "jest-mock-extended"; import { mock, MockProxy } from "jest-mock-extended";
import { BehaviorSubject } from "rxjs";
import { CollectionView } from "@bitwarden/admin-console/common"; import { CollectionView } from "@bitwarden/admin-console/common";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; 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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { SelectComponent } from "@bitwarden/components";
import { CipherFormConfig } from "../../abstractions/cipher-form-config.service"; import { CipherFormConfig } from "../../abstractions/cipher-form-config.service";
import { CipherFormContainer } from "../../cipher-form-container"; import { CipherFormContainer } from "../../cipher-form-container";
@ -20,6 +24,8 @@ describe("ItemDetailsSectionComponent", () => {
let cipherFormProvider: MockProxy<CipherFormContainer>; let cipherFormProvider: MockProxy<CipherFormContainer>;
let i18nService: MockProxy<I18nService>; let i18nService: MockProxy<I18nService>;
const activeAccount$ = new BehaviorSubject<{ email: string }>({ email: "test@example.com" });
beforeEach(async () => { beforeEach(async () => {
cipherFormProvider = mock<CipherFormContainer>(); cipherFormProvider = mock<CipherFormContainer>();
i18nService = mock<I18nService>(); i18nService = mock<I18nService>();
@ -29,6 +35,7 @@ describe("ItemDetailsSectionComponent", () => {
providers: [ providers: [
{ provide: CipherFormContainer, useValue: cipherFormProvider }, { provide: CipherFormContainer, useValue: cipherFormProvider },
{ provide: I18nService, useValue: i18nService }, { provide: I18nService, useValue: i18nService },
{ provide: AccountService, useValue: { activeAccount$ } },
], ],
}).compileComponents(); }).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", () => { describe("showOwnership", () => {
it("should return true if ownership change is allowed or in edit mode with at least one organization", () => { 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); jest.spyOn(component, "allowOwnershipChange", "get").mockReturnValue(true);

View File

@ -7,6 +7,7 @@ import { concatMap, map } from "rxjs";
import { CollectionView } from "@bitwarden/admin-console/common"; import { CollectionView } from "@bitwarden/admin-console/common";
import { JslibModule } from "@bitwarden/angular/jslib.module"; import { JslibModule } from "@bitwarden/angular/jslib.module";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; 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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
@ -68,6 +69,9 @@ export class ItemDetailsSectionComponent implements OnInit {
protected showCollectionsControl: boolean; protected showCollectionsControl: boolean;
/** The email address associated with the active account */
protected userEmail$ = this.accountService.activeAccount$.pipe(map((account) => account.email));
@Input({ required: true }) @Input({ required: true })
config: CipherFormConfig; config: CipherFormConfig;
@ -96,11 +100,23 @@ export class ItemDetailsSectionComponent implements OnInit {
return this.config.initialValues; 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( constructor(
private cipherFormContainer: CipherFormContainer, private cipherFormContainer: CipherFormContainer,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private i18nService: I18nService, private i18nService: I18nService,
private destroyRef: DestroyRef, private destroyRef: DestroyRef,
private accountService: AccountService,
) { ) {
this.cipherFormContainer.registerChildForm("itemDetails", this.itemDetailsForm); this.cipherFormContainer.registerChildForm("itemDetails", this.itemDetailsForm);
this.itemDetailsForm.valueChanges this.itemDetailsForm.valueChanges