Skip to content

Commit

Permalink
Merge branch 'main' into km/pm-10413/ssh-keygen-web-client
Browse files Browse the repository at this point in the history
  • Loading branch information
quexten committed Dec 3, 2024
2 parents bf1198f + 9c03cff commit 1b5c3e9
Show file tree
Hide file tree
Showing 24 changed files with 259 additions and 187 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,27 @@
{{ "new" | i18n }}
</button>
<bit-menu #itemOptions>
<a bitMenuItem (click)="newItemNavigate(cipherType.Login)">
<a bitMenuItem [routerLink]="['/add-cipher']" [queryParams]="buildQueryParams(cipherType.Login)">
<i class="bwi bwi-globe" slot="start" aria-hidden="true"></i>
{{ "typeLogin" | i18n }}
</a>
<a bitMenuItem (click)="newItemNavigate(cipherType.Card)">
<a bitMenuItem [routerLink]="['/add-cipher']" [queryParams]="buildQueryParams(cipherType.Card)">
<i class="bwi bwi-credit-card" slot="start" aria-hidden="true"></i>
{{ "typeCard" | i18n }}
</a>
<a bitMenuItem (click)="newItemNavigate(cipherType.Identity)">
<a
bitMenuItem
[routerLink]="['/add-cipher']"
[queryParams]="buildQueryParams(cipherType.Identity)"
>
<i class="bwi bwi-id-card" slot="start" aria-hidden="true"></i>
{{ "typeIdentity" | i18n }}
</a>
<a bitMenuItem (click)="newItemNavigate(cipherType.SecureNote)">
<a
bitMenuItem
[routerLink]="['/add-cipher']"
[queryParams]="buildQueryParams(cipherType.SecureNote)"
>
<i class="bwi bwi-sticky-note" slot="start" aria-hidden="true"></i>
{{ "note" | i18n }}
</a>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,143 +1,168 @@
import { CommonModule } from "@angular/common";
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { Router } from "@angular/router";
import { ActivatedRoute, RouterLink } from "@angular/router";
import { mock } from "jest-mock-extended";

import { JslibModule } from "@bitwarden/angular/jslib.module";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { CipherType } from "@bitwarden/common/vault/enums";
import { ButtonModule, DialogService, MenuModule } from "@bitwarden/components";
import { ButtonModule, DialogService, MenuModule, NoItemsModule } from "@bitwarden/components";

import { BrowserApi } from "../../../../../platform/browser/browser-api";
import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils";
import { AddEditQueryParams } from "../add-edit/add-edit-v2.component";
import { AddEditFolderDialogComponent } from "../add-edit-folder-dialog/add-edit-folder-dialog.component";

import { NewItemDropdownV2Component, NewItemInitialValues } from "./new-item-dropdown-v2.component";

describe("NewItemDropdownV2Component", () => {
let component: NewItemDropdownV2Component;
let fixture: ComponentFixture<NewItemDropdownV2Component>;
const open = jest.fn();
const navigate = jest.fn();
let dialogServiceMock: jest.Mocked<DialogService>;
let browserApiMock: jest.Mocked<typeof BrowserApi>;

jest
.spyOn(BrowserApi, "getTabFromCurrentWindow")
.mockResolvedValue({ url: "https://example.com" } as chrome.tabs.Tab);
const mockTab = { url: "https://example.com" };

beforeAll(() => {
jest.spyOn(BrowserApi, "getTabFromCurrentWindow").mockResolvedValue(mockTab as chrome.tabs.Tab);
jest.spyOn(BrowserPopupUtils, "inPopout").mockReturnValue(false);
jest.spyOn(Utils, "getHostname").mockReturnValue("example.com");
});

beforeEach(async () => {
open.mockClear();
navigate.mockClear();
dialogServiceMock = mock<DialogService>();
dialogServiceMock.open.mockClear();

const activatedRouteMock = {
snapshot: { paramMap: { get: jest.fn() } },
};

const i18nServiceMock = mock<I18nService>();
const folderServiceMock = mock<FolderService>();
const folderApiServiceAbstractionMock = mock<FolderApiServiceAbstraction>();
const accountServiceMock = mock<AccountService>();

await TestBed.configureTestingModule({
imports: [NewItemDropdownV2Component, MenuModule, ButtonModule, JslibModule, CommonModule],
imports: [
JslibModule,
CommonModule,
RouterLink,
ButtonModule,
MenuModule,
NoItemsModule,
NewItemDropdownV2Component,
],
providers: [
{ provide: I18nService, useValue: { t: (key: string) => key } },
{ provide: Router, useValue: { navigate } },
{ provide: ConfigService, useValue: { getFeatureFlag: () => Promise.resolve(false) } },
{ provide: DialogService, useValue: dialogServiceMock },
{ provide: I18nService, useValue: i18nServiceMock },
{ provide: ActivatedRoute, useValue: activatedRouteMock },
{ provide: BrowserApi, useValue: browserApiMock },
{ provide: FolderService, useValue: folderServiceMock },
{ provide: FolderApiServiceAbstraction, useValue: folderApiServiceAbstractionMock },
{ provide: AccountService, useValue: accountServiceMock },
],
})
.overrideProvider(DialogService, { useValue: { open } })
.compileComponents();
}).compileComponents();
});

beforeEach(() => {
fixture = TestBed.createComponent(NewItemDropdownV2Component);
component = fixture.componentInstance;
fixture.detectChanges();
});

it("opens new folder dialog", () => {
component.openFolderDialog();
describe("buildQueryParams", () => {
it("should build query params for a Login cipher when not popped out", async () => {
await component.ngOnInit();
component.initialValues = {
folderId: "222-333-444",
organizationId: "444-555-666",
collectionId: "777-888-999",
} as NewItemInitialValues;

expect(open).toHaveBeenCalledWith(AddEditFolderDialogComponent);
});
jest.spyOn(BrowserPopupUtils, "inPopout").mockReturnValue(false);
jest.spyOn(Utils, "getHostname").mockReturnValue("example.com");

describe("new item", () => {
const emptyParams: AddEditQueryParams = {
collectionId: undefined,
organizationId: undefined,
folderId: undefined,
};
const params = await component.buildQueryParams(CipherType.Login);

beforeEach(() => {
jest.spyOn(component, "newItemNavigate");
expect(params).toEqual({
type: CipherType.Login.toString(),
collectionId: "777-888-999",
organizationId: "444-555-666",
folderId: "222-333-444",
uri: "https://example.com",
name: "example.com",
});
});

it("navigates to new login", async () => {
await component.newItemNavigate(CipherType.Login);
it("should build query params for a Login cipher when popped out", async () => {
component.initialValues = {
collectionId: "777-888-999",
} as NewItemInitialValues;

expect(navigate).toHaveBeenCalledWith(["/add-cipher"], {
queryParams: {
type: CipherType.Login.toString(),
name: "example.com",
uri: "https://example.com",
...emptyParams,
},
});
});
jest.spyOn(BrowserPopupUtils, "inPopout").mockReturnValue(true);

it("navigates to new card", async () => {
await component.newItemNavigate(CipherType.Card);
const params = await component.buildQueryParams(CipherType.Login);

expect(navigate).toHaveBeenCalledWith(["/add-cipher"], {
queryParams: { type: CipherType.Card.toString(), ...emptyParams },
expect(params).toEqual({
type: CipherType.Login.toString(),
collectionId: "777-888-999",
});
});

it("navigates to new identity", async () => {
await component.newItemNavigate(CipherType.Identity);
it("should build query params for a secure note", async () => {
component.initialValues = {
collectionId: "777-888-999",
} as NewItemInitialValues;

expect(navigate).toHaveBeenCalledWith(["/add-cipher"], {
queryParams: { type: CipherType.Identity.toString(), ...emptyParams },
const params = await component.buildQueryParams(CipherType.SecureNote);

expect(params).toEqual({
type: CipherType.SecureNote.toString(),
collectionId: "777-888-999",
});
});

it("navigates to new note", async () => {
await component.newItemNavigate(CipherType.SecureNote);
it("should build query params for an Identity", async () => {
component.initialValues = {
collectionId: "777-888-999",
} as NewItemInitialValues;

const params = await component.buildQueryParams(CipherType.Identity);

expect(navigate).toHaveBeenCalledWith(["/add-cipher"], {
queryParams: { type: CipherType.SecureNote.toString(), ...emptyParams },
expect(params).toEqual({
type: CipherType.Identity.toString(),
collectionId: "777-888-999",
});
});

it("includes initial values", async () => {
it("should build query params for a Card", async () => {
component.initialValues = {
folderId: "222-333-444",
organizationId: "444-555-666",
collectionId: "777-888-999",
} as NewItemInitialValues;

await component.newItemNavigate(CipherType.Login);

expect(navigate).toHaveBeenCalledWith(["/add-cipher"], {
queryParams: {
type: CipherType.Login.toString(),
folderId: "222-333-444",
organizationId: "444-555-666",
collectionId: "777-888-999",
uri: "https://example.com",
name: "example.com",
},
const params = await component.buildQueryParams(CipherType.Card);

expect(params).toEqual({
type: CipherType.Card.toString(),
collectionId: "777-888-999",
});
});

it("does not include name or uri when the extension is popped out", async () => {
jest.spyOn(BrowserPopupUtils, "inPopout").mockReturnValue(true);

it("should build query params for a SshKey", async () => {
component.initialValues = {
folderId: "222-333-444",
organizationId: "444-555-666",
collectionId: "777-888-999",
} as NewItemInitialValues;

await component.newItemNavigate(CipherType.Login);
const params = await component.buildQueryParams(CipherType.SshKey);

expect(navigate).toHaveBeenCalledWith(["/add-cipher"], {
queryParams: {
type: CipherType.Login.toString(),
folderId: "222-333-444",
organizationId: "444-555-666",
collectionId: "777-888-999",
},
expect(params).toEqual({
type: CipherType.SshKey.toString(),
collectionId: "777-888-999",
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export interface NewItemInitialValues {
})
export class NewItemDropdownV2Component implements OnInit {
cipherType = CipherType;

private tab?: chrome.tabs.Tab;
/**
* Optional initial values to pass to the add cipher form
*/
Expand All @@ -45,19 +45,19 @@ export class NewItemDropdownV2Component implements OnInit {

async ngOnInit() {
this.sshKeysEnabled = await this.configService.getFeatureFlag(FeatureFlag.SSHKeyVaultItem);
this.tab = await BrowserApi.getTabFromCurrentWindow();
}

private async buildQueryParams(type: CipherType): Promise<AddEditQueryParams> {
const tab = await BrowserApi.getTabFromCurrentWindow();
const poppedOut = BrowserPopupUtils.inPopout(window);

const loginDetails: { uri?: string; name?: string } = {};

// When a Login Cipher is created and the extension is not popped out,
// pass along the uri and name
if (!poppedOut && type === CipherType.Login && tab) {
loginDetails.uri = tab.url;
loginDetails.name = Utils.getHostname(tab.url);
if (!poppedOut && type === CipherType.Login && this.tab) {
loginDetails.uri = this.tab.url;
loginDetails.name = Utils.getHostname(this.tab.url);
}

return {
Expand All @@ -69,10 +69,6 @@ export class NewItemDropdownV2Component implements OnInit {
};
}

async newItemNavigate(type: CipherType) {
await this.router.navigate(["/add-cipher"], { queryParams: await this.buildQueryParams(type) });
}

openFolderDialog() {
this.dialogService.open(AddEditFolderDialogComponent);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ export const GLOBAL_VAULT_UI_ONBOARDING = new KeyDefinition<boolean>(

@Injectable()
export class VaultUiOnboardingService {
// TODO: Update this date to the release date of the new Browser UI
private onboardingUiReleaseDate = new Date("2024-07-25");
private onboardingUiReleaseDate = new Date("2024-12-10");

private vaultUiOnboardingState: GlobalState<boolean> = this.stateProvider.getGlobal(
GLOBAL_VAULT_UI_ONBOARDING,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<button
type="button"
bitLink
linkType="primary"
bit-item-content
aria-haspopup="true"
(click)="openHistoryDialog()"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,7 @@ import {
standalone: true,
selector: "credential-generator",
templateUrl: "credential-generator.component.html",
imports: [
DialogModule,
ButtonModule,
JslibModule,
GeneratorModule,
ItemModule,
ButtonModule,
LinkModule,
],
imports: [DialogModule, ButtonModule, JslibModule, GeneratorModule, ItemModule, LinkModule],
})
export class CredentialGeneratorComponent {
constructor(private dialogService: DialogService) {}
Expand Down
10 changes: 0 additions & 10 deletions apps/desktop/src/vault/app/vault/add-edit.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -512,16 +512,6 @@ <h2 class="box-header">
[ngClass]="{ 'bwi-eye': !showPrivateKey, 'bwi-eye-slash': showPrivateKey }"
></i>
</button>
<button
type="button"
class="row-btn"
appStopClick
appA11yTitle="{{ 'regenerateSshKey' | i18n }}"
(click)="generateSshKey()"
*ngIf="cipher.edit || !editMode"
>
<i class="bwi bwi-lg bwi-generate" aria-hidden="true"></i>
</button>
</div>
</div>
<div class="box-content-row box-content-row-flex" appBoxRow>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</bit-callout>
<ng-container *ngIf="!done">
<bit-callout type="warning" *ngIf="users.length > 0 && !error">
<p bitTypography="body1">{{ "deleteOrganizationUserWarning" | i18n }}</p>
<p bitTypography="body1">{{ "deleteManyOrganizationUsersWarningDesc" | i18n }}</p>
</bit-callout>
<bit-table>
<ng-container header>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<bit-dialog dialogSize="large" [title]="'removeUsers' | i18n">
<bit-dialog dialogSize="large" [title]="'removeMembers' | i18n">
<ng-container bitDialogContent>
<bit-callout type="danger" *ngIf="users.length <= 0">
{{ "noSelectedUsersApplicable" | i18n }}
Expand Down Expand Up @@ -79,7 +79,7 @@
[disabled]="loading"
[bitAction]="submit"
>
{{ "removeUsers" | i18n }}
{{ "removeMembers" | i18n }}
</button>
<button bitButton type="button" buttonType="secondary" bitDialogClose>
{{ "close" | i18n }}
Expand Down
Loading

0 comments on commit 1b5c3e9

Please sign in to comment.