[PM-35258] Add archive confirmation to Desktop and fix right click menu (#20208)

* [PM-35258] add archive confirmation dialog to vault item drawer

* [PM-35258] allow archiving organization items from context menu and more menu

* [PM-35258] fix right-click context menu opening copy menu instead of full options
pull/20199/head
Shane Melton 2 months ago committed by GitHub
parent 76e0ee1b33
commit 696ce48572
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -93,6 +93,7 @@
}
<button
#optionsMenuTrigger="menuTrigger"
[bitMenuTriggerFor]="cipherOptions"
[disabled]="disabled()"
bitIconButton="bwi-ellipsis-v"

@ -1,7 +1,7 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { NgClass } from "@angular/common";
import { Component, HostListener, ViewChild, computed, inject, input, output } from "@angular/core";
import { Component, HostListener, computed, inject, input, output, viewChild } from "@angular/core";
import { PremiumBadgeComponent } from "@bitwarden/angular/billing/components/premium-badge/premium-badge.component";
import { IconComponent } from "@bitwarden/angular/vault/components/icon.component";
@ -13,11 +13,9 @@ import {
CipherViewLikeUtils,
} from "@bitwarden/common/vault/utils/cipher-view-like-utils";
import {
AriaDisableDirective,
BitIconButtonComponent,
MenuModule,
MenuTriggerForDirective,
TooltipDirective,
TableModule,
LinkModule,
} from "@bitwarden/components";
@ -46,9 +44,7 @@ interface CopyFieldConfig {
NgClass,
I18nPipe,
TableModule,
AriaDisableDirective,
OrganizationNameBadgeComponent,
TooltipDirective,
BitIconButtonComponent,
MenuModule,
CopyCipherFieldDirective,
@ -61,9 +57,7 @@ interface CopyFieldConfig {
export class VaultCipherRowComponent<C extends CipherViewLike> {
protected RowHeightClass = `tw-h-[75px]`;
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@ViewChild(MenuTriggerForDirective, { static: false }) menuTrigger: MenuTriggerForDirective;
protected readonly menuTrigger = viewChild<MenuTriggerForDirective>("optionsMenuTrigger");
protected readonly disabled = input<boolean>();
protected readonly cipher = input<C>();
@ -99,7 +93,6 @@ export class VaultCipherRowComponent<C extends CipherViewLike> {
protected readonly showArchiveButton = computed(() => {
return (
!this.cipher().organizationId &&
!CipherViewLikeUtils.isArchived(this.cipher()) &&
!CipherViewLikeUtils.isDeleted(this.cipher())
);
@ -295,8 +288,8 @@ export class VaultCipherRowComponent<C extends CipherViewLike> {
return;
}
if (!this.disabled() && this.menuTrigger) {
this.menuTrigger.toggleMenuOnRightClick(event);
if (!this.disabled() && this.menuTrigger()) {
this.menuTrigger().toggleMenuOnRightClick(event);
}
}
}

@ -521,7 +521,7 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
case "archive":
if (event.items.length === 1) {
const cipher = await this.cipherService.getFullCipherView(event.items[0]);
if (!cipher.organizationId && !cipher.isDeleted && !cipher.isArchived) {
if (!cipher.isDeleted && !cipher.isArchived) {
if (!(await firstValueFrom(this.userCanArchive$))) {
await this.premiumUpgradePromptService.promptForPremium();
return;

@ -249,6 +249,20 @@ describe("VaultItemDialogComponent", () => {
});
describe("archive", () => {
it("shows a confirmation dialog before archiving", async () => {
component.setTestCipher({ id: "cipher-id", collectionIds: [] } as any);
jest.spyOn(component as any, "updateCipherFromResponse").mockResolvedValue(undefined);
await component.archive();
expect(mockDialogService.openSimpleDialog).toHaveBeenCalledWith({
title: { key: "archiveItem" },
content: { key: "archiveItemDialogContent" },
acceptButtonText: { key: "archiveVerb" },
type: "info",
});
});
it("calls archiveService.archiveWithServer with the cipher id and active user id", async () => {
component.setTestCipher({ id: "cipher-id", collectionIds: [] } as any);
jest.spyOn(component as any, "updateCipherFromResponse").mockResolvedValue(undefined);
@ -260,6 +274,15 @@ describe("VaultItemDialogComponent", () => {
"test-user-id",
);
});
it("does not archive when the user cancels the confirmation", async () => {
component.setTestCipher({ id: "cipher-id", collectionIds: [] } as any);
mockDialogService.openSimpleDialog.mockResolvedValueOnce(false);
await component.archive();
expect(mockArchiveService.archiveWithServer).not.toHaveBeenCalled();
});
});
describe("unarchive", () => {

@ -592,6 +592,17 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy {
};
archive = async () => {
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "archiveItem" },
content: { key: "archiveItemDialogContent" },
acceptButtonText: { key: "archiveVerb" },
type: "info",
});
if (!confirmed) {
return;
}
const activeUserId = await firstValueFrom(this.userId$);
try {
const cipherResponse = await this.archiveService.archiveWithServer(

Loading…
Cancel
Save