From 2b69ccda40c74beb6edb06d12f62d222cbc45781 Mon Sep 17 00:00:00 2001 From: Alec Rippberger <127791530+alec-livefront@users.noreply.github.com> Date: Thu, 8 Aug 2024 23:45:47 -0500 Subject: [PATCH] [PM-8655] Update web app new item button (#10354) * Add additional extension refresh menu behind feature flag. * Open new cipher dialog with proper cipher type selected. * Adjust onboarding copy and default to login cipher. * Update "New item" button styles. * Add test to ensure onboarding component always calls onAddCipher.emit with the login cipher type. * Hide onboarding and new item changes behind feature flag * Fix missing mock in test. * Remove extensionRefreshEnabled$ and conditional styles from the "add new" button. * Remove rounding class from menu "new" button. --- .../vault-header/vault-header.component.html | 102 +++++++++++++----- .../vault-header/vault-header.component.ts | 12 ++- .../vault-onboarding.component.html | 7 +- .../vault-onboarding.component.spec.ts | 15 +++ .../vault-onboarding.component.ts | 12 ++- .../individual-vault/vault.component.html | 10 +- .../vault/individual-vault/vault.component.ts | 6 +- apps/web/src/locales/en/messages.json | 7 ++ 8 files changed, 135 insertions(+), 36 deletions(-) diff --git a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.html b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.html index b0ae308ea8..3f46cb803c 100644 --- a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.html +++ b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.html @@ -69,30 +69,84 @@
- - - - - - + + + + + + + + + + + + + + + + + + + + + +
diff --git a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts index 7803b1c32f..ad07d2847e 100644 --- a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts @@ -14,6 +14,7 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { CipherType } from "@bitwarden/common/vault/enums"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view"; import { BreadcrumbsModule, MenuModule } from "@bitwarden/components"; @@ -47,6 +48,8 @@ export class VaultHeaderComponent implements OnInit { protected Unassigned = Unassigned; protected All = All; protected CollectionDialogTabType = CollectionDialogTabType; + protected CipherType = CipherType; + protected extensionRefreshEnabled = false; /** * Boolean to determine the loading state of the header. @@ -67,7 +70,7 @@ export class VaultHeaderComponent implements OnInit { @Input() canCreateCollections: boolean; /** Emits an event when the new item button is clicked in the header */ - @Output() onAddCipher = new EventEmitter(); + @Output() onAddCipher = new EventEmitter(); /** Emits an event when the new collection button is clicked in the 'New' dropdown menu */ @Output() onAddCollection = new EventEmitter(); @@ -92,6 +95,9 @@ export class VaultHeaderComponent implements OnInit { this.flexibleCollectionsV1Enabled = await firstValueFrom( this.configService.getFeatureFlag$(FeatureFlag.FlexibleCollectionsV1), ); + this.extensionRefreshEnabled = await firstValueFrom( + this.configService.getFeatureFlag$(FeatureFlag.ExtensionRefresh), + ); } /** @@ -199,8 +205,8 @@ export class VaultHeaderComponent implements OnInit { this.onDeleteCollection.emit(); } - protected addCipher() { - this.onAddCipher.emit(); + protected addCipher(cipherType?: CipherType) { + this.onAddCipher.emit(cipherType); } async addFolder(): Promise { diff --git a/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.html b/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.html index 9f6f589df6..b9647e3237 100644 --- a/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.html +++ b/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.html @@ -22,7 +22,12 @@

{{ "onboardingImportDataDetailsPartOne" | i18n }} {{ "onboardingImportDataDetailsPartTwoNoOrgs" | i18n }} diff --git a/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.spec.ts b/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.spec.ts index 490c07d753..778132676f 100644 --- a/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.spec.ts +++ b/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.spec.ts @@ -5,9 +5,11 @@ import { Subject, of } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateProvider } from "@bitwarden/common/platform/state"; +import { CipherType } from "@bitwarden/common/vault/enums/cipher-type"; import { VaultOnboardingMessages } from "@bitwarden/common/vault/enums/vault-onboarding.enum"; import { VaultOnboardingService as VaultOnboardingServiceAbstraction } from "./services/abstraction/vault-onboarding.service"; @@ -24,6 +26,7 @@ describe("VaultOnboardingComponent", () => { let mockStateProvider: Partial; let setInstallExtLinkSpy: any; let individualVaultPolicyCheckSpy: any; + let mockConfigService: MockProxy; beforeEach(() => { mockPolicyService = mock(); @@ -42,6 +45,7 @@ describe("VaultOnboardingComponent", () => { }), ), }; + mockConfigService = mock(); // eslint-disable-next-line @typescript-eslint/no-floating-promises TestBed.configureTestingModule({ @@ -54,6 +58,7 @@ describe("VaultOnboardingComponent", () => { { provide: I18nService, useValue: mockI18nService }, { provide: ApiService, useValue: mockApiService }, { provide: StateProvider, useValue: mockStateProvider }, + { provide: ConfigService, useValue: mockConfigService }, ], }).compileComponents(); fixture = TestBed.createComponent(VaultOnboardingComponent); @@ -178,4 +183,14 @@ describe("VaultOnboardingComponent", () => { expect(saveCompletedTasksSpy).toHaveBeenCalled(); }); }); + + describe("emitToAddCipher", () => { + it("always emits the `CipherType.Login` type when called", () => { + const emitSpy = jest.spyOn(component.onAddCipher, "emit"); + + component.emitToAddCipher(); + + expect(emitSpy).toHaveBeenCalledWith(CipherType.Login); + }); + }); }); diff --git a/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.ts b/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.ts index a7331c7315..94ae1a4df4 100644 --- a/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.ts @@ -16,7 +16,10 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { CipherType } from "@bitwarden/common/vault/enums/cipher-type"; import { VaultOnboardingMessages } from "@bitwarden/common/vault/enums/vault-onboarding.enum"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { LinkModule } from "@bitwarden/components"; @@ -41,7 +44,7 @@ import { VaultOnboardingService, VaultOnboardingTasks } from "./services/vault-o export class VaultOnboardingComponent implements OnInit, OnChanges, OnDestroy { @Input() ciphers: CipherView[]; @Input() orgs: Organization[]; - @Output() onAddCipher = new EventEmitter(); + @Output() onAddCipher = new EventEmitter(); extensionUrl: string; isIndividualPolicyVault: boolean; @@ -53,12 +56,14 @@ export class VaultOnboardingComponent implements OnInit, OnChanges, OnDestroy { protected onboardingTasks$: Observable; protected showOnboarding = false; + protected extensionRefreshEnabled = false; constructor( protected platformUtilsService: PlatformUtilsService, protected policyService: PolicyService, private apiService: ApiService, private vaultOnboardingService: VaultOnboardingServiceAbstraction, + private configService: ConfigService, ) {} async ngOnInit() { @@ -67,6 +72,9 @@ export class VaultOnboardingComponent implements OnInit, OnChanges, OnDestroy { this.setInstallExtLink(); this.individualVaultPolicyCheck(); this.checkForBrowserExtension(); + this.extensionRefreshEnabled = await this.configService.getFeatureFlag( + FeatureFlag.ExtensionRefresh, + ); } async ngOnChanges(changes: SimpleChanges) { @@ -162,7 +170,7 @@ export class VaultOnboardingComponent implements OnInit, OnChanges, OnDestroy { } emitToAddCipher() { - this.onAddCipher.emit(); + this.onAddCipher.emit(CipherType.Login); } setInstallExtLink() { diff --git a/apps/web/src/app/vault/individual-vault/vault.component.html b/apps/web/src/app/vault/individual-vault/vault.component.html index f0be76018f..183c4f65af 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.html +++ b/apps/web/src/app/vault/individual-vault/vault.component.html @@ -6,14 +6,18 @@ [organizations]="allOrganizations" [canCreateCollections]="canCreateCollections" [collection]="selectedCollection" - (onAddCipher)="addCipher()" + (onAddCipher)="addCipher($event)" (onAddCollection)="addCollection()" (onAddFolder)="addFolder()" (onEditCollection)="editCollection(selectedCollection.node, $event.tab)" (onDeleteCollection)="deleteCollection(selectedCollection.node)" > - +

@@ -80,7 +84,7 @@ (click)="addCipher()" *ngIf="filter.type !== 'trash'" > - + {{ "newItem" | i18n }}
diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts index 6aca5662e5..1b9d0e1b62 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -50,6 +50,7 @@ import { OrganizationId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; +import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; import { CollectionData } from "@bitwarden/common/vault/models/data/collection.data"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; @@ -163,7 +164,6 @@ export class VaultComponent implements OnInit, OnDestroy { protected vaultBulkManagementActionEnabled$ = this.configService.getFeatureFlag$( FeatureFlag.VaultBulkManagementAction, ); - private searchText$ = new Subject(); private refresh$ = new BehaviorSubject(null); private destroy$ = new Subject(); @@ -586,9 +586,9 @@ export class VaultComponent implements OnInit, OnDestroy { } } - async addCipher() { + async addCipher(cipherType?: CipherType) { const component = await this.editCipher(null); - component.type = this.activeFilter.cipherType; + component.type = cipherType || this.activeFilter.cipherType; if (this.activeFilter.organizationId !== "MyVault") { component.organizationId = this.activeFilter.organizationId; component.collections = ( diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 5f35c1c3e5..fd2924badb 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -36,6 +36,9 @@ "notes": { "message": "Notes" }, + "note": { + "message": "Note" + }, "customFields": { "message": "Custom fields" }, @@ -1505,6 +1508,10 @@ "message": "new item", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, + "onboardingImportDataDetailsLoginLink": { + "message": "new login", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new login instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" + }, "onboardingImportDataDetailsPartTwoNoOrgs": { "message": " instead.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead."