diff --git a/app/components/certificate/action/add/certificate-create-dialog.component.ts b/app/components/certificate/action/add/certificate-create-dialog.component.ts index 2750fac66c..f2e21b38fd 100644 --- a/app/components/certificate/action/add/certificate-create-dialog.component.ts +++ b/app/components/certificate/action/add/certificate-create-dialog.component.ts @@ -17,6 +17,7 @@ import "./certificate-create-dialog.scss"; export class CertificateCreateDialogComponent { public file: File; public form: FormGroup; + public title = "Add certificate"; constructor( private formBuilder: FormBuilder, diff --git a/app/components/layout/pinned-entity-dropdown/pinned-dropdown.component.spec.ts b/app/components/layout/pinned-entity-dropdown/pinned-dropdown.component.spec.ts index eba1fcbbd9..47e911d5c3 100644 --- a/app/components/layout/pinned-entity-dropdown/pinned-dropdown.component.spec.ts +++ b/app/components/layout/pinned-entity-dropdown/pinned-dropdown.component.spec.ts @@ -6,6 +6,7 @@ import { List } from "immutable"; import { BehaviorSubject, Observable } from "rxjs"; import { NavigableRecord, PinnableEntity, PinnedEntityType } from "@batch-flask/core"; +import { ContextMenuService } from "@batch-flask/ui"; import { DropdownModule } from "@batch-flask/ui/dropdown"; import { AccountService, PinnedEntityService } from "app/services"; import * as Fixtures from "test/fixture"; @@ -35,6 +36,12 @@ describe("PinnedDropDownComponent", () => { favorites = new BehaviorSubject(List([])); pinServiceSpy = { favorites: favorites.asObservable(), + unPinFavorite: jasmine.createSpy("unPinFavorite").and.callFake((favorite) => { + // remove from fav list + const favArray = favorites.value.toArray(); + favArray.splice(0, 1); + favorites.next(List(favArray)); + }), }; accountServiceSpy = { @@ -52,6 +59,7 @@ describe("PinnedDropDownComponent", () => { providers: [ { provide: AccountService, useValue: accountServiceSpy }, { provide: PinnedEntityService, useValue: pinServiceSpy }, + { provide: ContextMenuService, useValue: null }, ], schemas: [NO_ERRORS_SCHEMA], }); @@ -69,6 +77,8 @@ describe("PinnedDropDownComponent", () => { expect(component.entityType(createPin(PinnedEntityType.Job))).toBe("Batch job"); expect(component.entityType(createPin(PinnedEntityType.Pool))).toBe("Batch pool"); expect(component.entityType(createPin(PinnedEntityType.StorageContainer))).toBe("Storage container"); + expect(component.entityType(createPin(PinnedEntityType.Certificate))).toBe("Batch certificate"); + expect(component.entityType(createPin(PinnedEntityType.JobSchedule))).toBe("Batch job schedule"); expect(component.entityType(createPin(null))).toBe("unknown"); }); @@ -77,6 +87,8 @@ describe("PinnedDropDownComponent", () => { expect(component.entityIcon(createPin(PinnedEntityType.Job))).toBe("fa-tasks"); expect(component.entityIcon(createPin(PinnedEntityType.Pool))).toBe("fa-database"); expect(component.entityIcon(createPin(PinnedEntityType.StorageContainer))).toBe("fa-cloud-upload"); + expect(component.entityIcon(createPin(PinnedEntityType.Certificate))).toBe("fa-certificate"); + expect(component.entityIcon(createPin(PinnedEntityType.JobSchedule))).toBe("fa-calendar"); expect(component.entityIcon(createPin(null))).toBe("fa-question"); }); }); @@ -117,14 +129,15 @@ describe("PinnedDropDownComponent", () => { const items = fixture.debugElement.queryAll(By.css(".dropdown-item")); expect(items.length).toBe(1); expect(items[0].nativeElement.textContent).toContain("my-job-fred"); - expect(items[0].nativeElement.textContent).toContain("Batch job"); - const icon = items[0].query(By.css(".fa.fa-tasks")); - expect(icon.nativeElement).toBeDefined(); + // type is now in icon title + const icon = items[0].query(By.css(".fa.fa-tasks")).nativeElement; + expect(icon).toBeDefined(); + expect(icon.getAttribute("title")).toBe("Batch job"); }); }); - describe("when there are more than one favorites", () => { + describe("when there are more than one favorite", () => { beforeEach(() => { favorites.next(List([ Fixtures.pinnable.create({ @@ -159,11 +172,34 @@ describe("PinnedDropDownComponent", () => { it("pool should show name over id", () => { const items = debugElement.queryAll(By.css(".dropdown-item")); expect(items[0].nativeElement.textContent).toContain("my-job-matt"); - expect(items[0].nativeElement.textContent).toContain("Batch job"); - expect(items[1].nativeElement.textContent).toContain("my-name-is-bob"); expect(items[1].nativeElement.textContent).not.toContain("my-pool-bob"); - expect(items[1].nativeElement.textContent).toContain("Batch pool"); + }); + }); + + describe("when we remove favorites", () => { + beforeEach(() => { + favorites.next(List([ + Fixtures.pinnable.create({ + id: "my-apple", + routerLink: ["/certificates", "my-apple"], + pinnableType: PinnedEntityType.Certificate, + url: "https://myaccount.westus.batch.com/jobs/my-apple", + }), + ])); + + dropDownButton.nativeElement.click(); + fixture.detectChanges(); + }); + + it("should be one favorite", () => { + expect(favorites.value.count()).toBe(1); + }); + + it("should remove favorite", () => { + component.removeFavorite(favorites.value.toArray()[0] as any); + expect(pinServiceSpy.unPinFavorite).toHaveBeenCalledTimes(1); + expect(favorites.value.count()).toBe(0); }); }); }); diff --git a/app/components/layout/pinned-entity-dropdown/pinned-dropdown.component.ts b/app/components/layout/pinned-entity-dropdown/pinned-dropdown.component.ts index 2071151c20..900ba413dd 100644 --- a/app/components/layout/pinned-entity-dropdown/pinned-dropdown.component.ts +++ b/app/components/layout/pinned-entity-dropdown/pinned-dropdown.component.ts @@ -3,8 +3,8 @@ import { NavigationEnd, Router } from "@angular/router"; import { List } from "immutable"; import { Observable, Subscription } from "rxjs"; -import { PinnableEntity, PinnedEntityType } from "@batch-flask/core"; -import { DropdownComponent } from "@batch-flask/ui"; +import { MouseButton, PinnableEntity, PinnedEntityType } from "@batch-flask/core"; +import { ContextMenu, ContextMenuItem, ContextMenuService, DropdownComponent } from "@batch-flask/ui"; import { AccountService, PinnedEntityService } from "app/services"; import "./pinned-dropdown.scss"; @@ -26,7 +26,8 @@ export class PinnedDropDownComponent implements OnInit, OnDestroy { constructor( private router: Router, private changeDetector: ChangeDetectorRef, - public pinnedEntityService: PinnedEntityService, + private contextMenuService: ContextMenuService, + private pinnedEntityService: PinnedEntityService, private accountService: AccountService) { this.favorites = this.pinnedEntityService.favorites; @@ -59,6 +60,16 @@ export class PinnedDropDownComponent implements OnInit, OnDestroy { this._dropdown.close(); } + public removeFavorite(favorite: PinnableEntity) { + this.pinnedEntityService.unPinFavorite(favorite); + } + + public handleMiddleMouseUp(event: MouseEvent, favorite: PinnableEntity) { + if (event.button === MouseButton.middle) { + this.pinnedEntityService.unPinFavorite(favorite); + } + } + public entityType(favorite: PinnableEntity) { switch (favorite.pinnableType) { case PinnedEntityType.Application: @@ -100,4 +111,10 @@ export class PinnedDropDownComponent implements OnInit, OnDestroy { public trackPinnned(index, entity: PinnableEntity) { return `${entity.pinnableType}/${entity.id}`; } + + public onContextMenu(favorite: PinnableEntity) { + this.contextMenuService.openMenu(new ContextMenu([ + new ContextMenuItem("Remove favorite", () => this.pinnedEntityService.unPinFavorite(favorite)), + ])); + } } diff --git a/app/components/layout/pinned-entity-dropdown/pinned-dropdown.html b/app/components/layout/pinned-entity-dropdown/pinned-dropdown.html index c39a2ad3e4..467861475c 100644 --- a/app/components/layout/pinned-entity-dropdown/pinned-dropdown.html +++ b/app/components/layout/pinned-entity-dropdown/pinned-dropdown.html @@ -3,19 +3,20 @@ {{title}}