1
0
mirror of https://github.com/bitwarden/browser synced 2026-03-01 19:11:22 +00:00

[PM-31675] remove archive from web edit (#18764)

* refactor default cipher archive service, update archive/unarchive in vault-item-dialog, remove archive/unarchive items in edit form
This commit is contained in:
Jason Ng
2026-02-09 16:17:46 -05:00
committed by jaasen-livefront
parent 8e9a9c7e32
commit d7cbfebfab
6 changed files with 66 additions and 60 deletions

View File

@@ -3,7 +3,7 @@
{{ title }}
</span>
@if (isCipherArchived && !params.isAdminConsoleAction) {
@if (isCipherArchived && !params.isAdminConsoleAction && (archiveFlagEnabled$ | async)) {
<span bitBadge bitDialogHeaderEnd> {{ "archived" | i18n }} </span>
}
@@ -86,8 +86,8 @@
@if (showActionButtons) {
<div class="tw-ml-auto">
@if ((userCanArchive$ | async) && !params.isAdminConsoleAction) {
@if (isCipherArchived && !cipher?.isDeleted) {
@if (showArchiveOptions) {
@if (showUnarchiveBtn) {
<button
type="button"
class="tw-mr-1"
@@ -96,7 +96,7 @@
[label]="'unArchive' | i18n"
></button>
}
@if (cipher?.canBeArchived) {
@if (showArchiveBtn) {
<button
type="button"
class="tw-mr-1"

View File

@@ -119,7 +119,7 @@ describe("VaultItemDialogComponent", () => {
provide: CipherArchiveService,
useValue: {
userCanArchive$: jest.fn().mockReturnValue(of(true)),
hasArchiveFlagEnabled$: jest.fn().mockReturnValue(of(true)),
hasArchiveFlagEnabled$: of(true),
archiveWithServer: jest.fn().mockResolvedValue({}),
unarchiveWithServer: jest.fn().mockResolvedValue({}),
},
@@ -258,19 +258,19 @@ describe("VaultItemDialogComponent", () => {
expect(archiveButton).toBeFalsy();
});
it("should show archive button when the user can archive the item and the item can be archived", () => {
it("should show archive button when the user can archive the item, item can be archived, and dialog is in view mode", () => {
component.setTestCipher({ canBeArchived: true });
(component as any).userCanArchive$ = of(true);
component.setTestParams({ mode: "form" });
component.setTestParams({ mode: "view" });
fixture.detectChanges();
const archiveButton = fixture.debugElement.query(By.css("[biticonbutton='bwi-archive']"));
expect(archiveButton).toBeTruthy();
});
it("should not show archive button when the user cannot archive the item", () => {
it("should not show archive button when the user does not have premium", () => {
(component as any).userCanArchive$ = of(false);
component.setTestCipher({});
component.setTestParams({ mode: "form" });
component.setTestParams({ mode: "view" });
fixture.detectChanges();
const archiveButton = fixture.debugElement.query(By.css("[biticonbutton='bwi-archive']"));
expect(archiveButton).toBeFalsy();
@@ -283,18 +283,35 @@ describe("VaultItemDialogComponent", () => {
const archiveButton = fixture.debugElement.query(By.css("[biticonbutton='bwi-archive']"));
expect(archiveButton).toBeFalsy();
});
it("should not show archive button when dialog is not in view mode", () => {
component.setTestCipher({ canBeArchived: true });
(component as any).userCanArchive$ = of(true);
component.setTestParams({ mode: "form" });
fixture.detectChanges();
const archiveButton = fixture.debugElement.query(By.css("[biticonbutton='bwi-archive']"));
expect(archiveButton).toBeFalsy();
});
});
describe("unarchive button", () => {
it("should show the unarchive button when the item is archived", () => {
it("should show the unarchive button when the item is archived, and dialog in view mode", () => {
component.setTestCipher({ isArchived: true });
component.setTestParams({ mode: "form" });
component.setTestParams({ mode: "view" });
fixture.detectChanges();
const unarchiveButton = fixture.debugElement.query(By.css("[biticonbutton='bwi-unarchive']"));
expect(unarchiveButton).toBeTruthy();
});
it("should not show the unarchive button when the item is not archived", () => {
component.setTestCipher({ isArchived: false });
component.setTestParams({ mode: "view" });
fixture.detectChanges();
const unarchiveButton = fixture.debugElement.query(By.css("[biticonbutton='bwi-unarchive']"));
expect(unarchiveButton).toBeFalsy();
});
it("should not show the unarchive button when dialog is not in view mode", () => {
component.setTestCipher({ isArchived: false });
component.setTestParams({ mode: "form" });
fixture.detectChanges();

View File

@@ -28,7 +28,7 @@ import { EventType } from "@bitwarden/common/enums";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { CipherId, CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
import { CipherId, CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid";
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service";
@@ -293,6 +293,20 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy {
return this.cipher?.isArchived;
}
private _userCanArchive = false;
protected get showArchiveOptions(): boolean {
return this._userCanArchive && !this.params.isAdminConsoleAction && this.params.mode === "view";
}
protected get showArchiveBtn(): boolean {
return this.cipher?.canBeArchived;
}
protected get showUnarchiveBtn(): boolean {
return this.isCipherArchived && !this.cipher?.isDeleted;
}
/**
* Flag to initialize/attach the form component.
*/
@@ -341,6 +355,8 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy {
takeUntilDestroyed(),
)
.subscribe();
this.userCanArchive$.pipe(takeUntilDestroyed()).subscribe((v) => (this._userCanArchive = v));
}
async ngOnInit() {
@@ -574,20 +590,14 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy {
await this.changeMode("view");
};
updateCipherFromArchive = (revisionDate: Date, archivedDate: Date | null) => {
this.cipher.archivedDate = archivedDate;
this.cipher.revisionDate = revisionDate;
updateCipherFromResponse = async (cipherResponse: CipherData, userId: UserId) => {
const cipher: Cipher = new Cipher(cipherResponse);
// If we're in View mode, we don't need to update the form.
if (this.params.mode === "view") {
return;
}
cipher.collectionIds = [...this.cipher.collectionIds];
this.cipherFormComponent().patchCipher((current) => {
current.revisionDate = revisionDate;
current.archivedDate = archivedDate;
return current;
});
const cipherView = await this.cipherService.decrypt(cipher, userId);
await this.onCipherSaved(cipherView);
};
archive = async () => {
@@ -597,10 +607,8 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy {
this.cipher.id as CipherId,
activeUserId,
);
this.updateCipherFromArchive(
new Date(cipherResponse.revisionDate),
cipherResponse.archivedDate ? new Date(cipherResponse.archivedDate) : null,
);
await this.updateCipherFromResponse(cipherResponse, activeUserId);
this.toastService.showToast({
variant: "success",
@@ -621,7 +629,9 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy {
this.cipher.id as CipherId,
activeUserId,
);
this.updateCipherFromArchive(new Date(cipherResponse.revisionDate), null);
await this.updateCipherFromResponse(cipherResponse, activeUserId);
this.toastService.showToast({
variant: "success",
message: this.i18nService.t("itemWasUnarchived"),
@@ -631,7 +641,6 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy {
variant: "error",
message: this.i18nService.t("errorOccurred"),
});
return;
}
};