[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.
This commit is contained in:
parent
304bd662ec
commit
2b69ccda40
|
@ -69,30 +69,84 @@
|
|||
|
||||
<div *ngIf="filter.type !== 'trash'" class="tw-shrink-0">
|
||||
<div appListDropdown>
|
||||
<button
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
type="button"
|
||||
[bitMenuTriggerFor]="addOptions"
|
||||
id="newItemDropdown"
|
||||
appA11yTitle="{{ 'new' | i18n }}"
|
||||
>
|
||||
{{ "new" | i18n }}<i class="bwi bwi-angle-down tw-ml-2" aria-hidden="true"></i>
|
||||
</button>
|
||||
<bit-menu #addOptions aria-labelledby="newItemDropdown">
|
||||
<button type="button" bitMenuItem (click)="addCipher()">
|
||||
<i class="bwi bwi-fw bwi-globe" aria-hidden="true"></i>
|
||||
{{ "item" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="addFolder()">
|
||||
<i class="bwi bwi-fw bwi-folder" aria-hidden="true"></i>
|
||||
{{ "folder" | i18n }}
|
||||
</button>
|
||||
<button *ngIf="canCreateCollections" type="button" bitMenuItem (click)="addCollection()">
|
||||
<i class="bwi bwi-fw bwi-collection" aria-hidden="true"></i>
|
||||
{{ "collection" | i18n }}
|
||||
</button>
|
||||
</bit-menu>
|
||||
<ng-container [ngSwitch]="extensionRefreshEnabled">
|
||||
<ng-container *ngSwitchCase="true">
|
||||
<button
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
type="button"
|
||||
[bitMenuTriggerFor]="addOptions"
|
||||
id="newItemDropdown"
|
||||
appA11yTitle="{{ 'new' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-plus-f" aria-hidden="true"></i>
|
||||
{{ "new" | i18n }}<i class="bwi tw-ml-2" aria-hidden="true"></i>
|
||||
</button>
|
||||
<bit-menu #addOptions aria-labelledby="newItemDropdown">
|
||||
<button type="button" bitMenuItem (click)="addCipher(CipherType.Login)">
|
||||
<i class="bwi bwi-globe" slot="start" aria-hidden="true"></i>
|
||||
{{ "typeLogin" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="addCipher(CipherType.Card)">
|
||||
<i class="bwi bwi-credit-card" slot="start" aria-hidden="true"></i>
|
||||
{{ "typeCard" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="addCipher(CipherType.Identity)">
|
||||
<i class="bwi bwi-id-card" slot="start" aria-hidden="true"></i>
|
||||
{{ "typeIdentity" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="addCipher(CipherType.SecureNote)">
|
||||
<i class="bwi bwi-sticky-note" slot="start" aria-hidden="true"></i>
|
||||
{{ "note" | i18n }}
|
||||
</button>
|
||||
<bit-menu-divider />
|
||||
<button type="button" bitMenuItem (click)="addFolder()">
|
||||
<i class="bwi bwi-fw bwi-folder" aria-hidden="true"></i>
|
||||
{{ "folder" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
*ngIf="canCreateCollections"
|
||||
type="button"
|
||||
bitMenuItem
|
||||
(click)="addCollection()"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-collection" aria-hidden="true"></i>
|
||||
{{ "collection" | i18n }}
|
||||
</button>
|
||||
</bit-menu>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="false">
|
||||
<button
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
type="button"
|
||||
[bitMenuTriggerFor]="addOptions"
|
||||
id="newItemDropdown"
|
||||
appA11yTitle="{{ 'new' | i18n }}"
|
||||
>
|
||||
{{ "new" | i18n }}<i class="bwi bwi-angle-down tw-ml-2" aria-hidden="true"></i>
|
||||
</button>
|
||||
<bit-menu #addOptions aria-labelledby="newItemDropdown">
|
||||
<button type="button" bitMenuItem (click)="addCipher()">
|
||||
<i class="bwi bwi-fw bwi-globe" aria-hidden="true"></i>
|
||||
{{ "item" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="addFolder()">
|
||||
<i class="bwi bwi-fw bwi-folder" aria-hidden="true"></i>
|
||||
{{ "folder" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
*ngIf="canCreateCollections"
|
||||
type="button"
|
||||
bitMenuItem
|
||||
(click)="addCollection()"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-collection" aria-hidden="true"></i>
|
||||
{{ "collection" | i18n }}
|
||||
</button>
|
||||
</bit-menu>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</app-header>
|
||||
|
|
|
@ -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<void>();
|
||||
@Output() onAddCipher = new EventEmitter<CipherType | undefined>();
|
||||
|
||||
/** Emits an event when the new collection button is clicked in the 'New' dropdown menu */
|
||||
@Output() onAddCollection = new EventEmitter<null>();
|
||||
|
@ -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<void> {
|
||||
|
|
|
@ -22,7 +22,12 @@
|
|||
<p class="tw-pl-1">
|
||||
{{ "onboardingImportDataDetailsPartOne" | i18n }}
|
||||
<button type="button" bitLink (click)="emitToAddCipher()">
|
||||
{{ "onboardingImportDataDetailsLink" | i18n }}
|
||||
{{
|
||||
(extensionRefreshEnabled
|
||||
? "onboardingImportDataDetailsLoginLink"
|
||||
: "onboardingImportDataDetailsLink"
|
||||
) | i18n
|
||||
}}
|
||||
</button>
|
||||
<span>
|
||||
{{ "onboardingImportDataDetailsPartTwoNoOrgs" | i18n }}
|
||||
|
|
|
@ -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<StateProvider>;
|
||||
let setInstallExtLinkSpy: any;
|
||||
let individualVaultPolicyCheckSpy: any;
|
||||
let mockConfigService: MockProxy<ConfigService>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockPolicyService = mock<PolicyService>();
|
||||
|
@ -42,6 +45,7 @@ describe("VaultOnboardingComponent", () => {
|
|||
}),
|
||||
),
|
||||
};
|
||||
mockConfigService = mock<ConfigService>();
|
||||
|
||||
// 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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<void>();
|
||||
@Output() onAddCipher = new EventEmitter<CipherType>();
|
||||
|
||||
extensionUrl: string;
|
||||
isIndividualPolicyVault: boolean;
|
||||
|
@ -53,12 +56,14 @@ export class VaultOnboardingComponent implements OnInit, OnChanges, OnDestroy {
|
|||
|
||||
protected onboardingTasks$: Observable<VaultOnboardingTasks>;
|
||||
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() {
|
||||
|
|
|
@ -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)"
|
||||
></app-vault-header>
|
||||
|
||||
<app-vault-onboarding [ciphers]="ciphers" [orgs]="allOrganizations" (onAddCipher)="addCipher()">
|
||||
<app-vault-onboarding
|
||||
[ciphers]="ciphers"
|
||||
[orgs]="allOrganizations"
|
||||
(onAddCipher)="addCipher($event)"
|
||||
>
|
||||
</app-vault-onboarding>
|
||||
|
||||
<div class="tw-flex tw-flex-row -tw-mx-2.5">
|
||||
|
@ -80,7 +84,7 @@
|
|||
(click)="addCipher()"
|
||||
*ngIf="filter.type !== 'trash'"
|
||||
>
|
||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
||||
<i class="bwi bwi-plus-f bwi-fw" aria-hidden="true"></i>
|
||||
{{ "newItem" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -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<string>();
|
||||
private refresh$ = new BehaviorSubject<void>(null);
|
||||
private destroy$ = new Subject<void>();
|
||||
|
@ -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 = (
|
||||
|
|
|
@ -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."
|
||||
|
|
Loading…
Reference in New Issue