mirror of
https://github.com/bitwarden/browser
synced 2026-02-10 21:50:15 +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:
@@ -7,7 +7,7 @@
|
||||
[backAction]="handleBackButton"
|
||||
showBackButton
|
||||
>
|
||||
@if (config?.originalCipher?.archivedDate) {
|
||||
@if (config?.originalCipher?.archivedDate && (archiveFlagEnabled$ | async)) {
|
||||
<ng-container slot="end">
|
||||
<span bitBadge variant="secondary" [appA11yTitle]="'archived' | i18n">
|
||||
{{ "archived" | i18n }}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<popup-page>
|
||||
<popup-header slot="header" [pageTitle]="headerText" showBackButton>
|
||||
<ng-container slot="end">
|
||||
@if (cipher?.isArchived) {
|
||||
@if (cipher?.isArchived && (archiveFlagEnabled$ | async)) {
|
||||
<span bitBadge variant="secondary" [appA11yTitle]="'archived' | i18n">
|
||||
{{ "archived" | i18n }}
|
||||
</span>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -91,21 +91,11 @@ export class DefaultCipherArchiveService implements CipherArchiveService {
|
||||
const response = new ListResponse(r, CipherResponse);
|
||||
|
||||
const currentCiphers = await firstValueFrom(this.cipherService.ciphers$(userId));
|
||||
// prevent mutating ciphers$ state
|
||||
const localCiphers = structuredClone(currentCiphers);
|
||||
const responseDataArray = response.data.map(
|
||||
(cipher) => new CipherData(cipher, currentCiphers[cipher.id as CipherId]?.collectionIds),
|
||||
);
|
||||
|
||||
for (const cipher of response.data) {
|
||||
const localCipher = localCiphers[cipher.id as CipherId];
|
||||
|
||||
if (localCipher == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
localCipher.archivedDate = cipher.archivedDate;
|
||||
localCipher.revisionDate = cipher.revisionDate;
|
||||
}
|
||||
|
||||
await this.cipherService.upsert(Object.values(localCiphers), userId);
|
||||
await this.cipherService.upsert(responseDataArray, userId);
|
||||
return response.data[0];
|
||||
}
|
||||
|
||||
@@ -115,21 +105,11 @@ export class DefaultCipherArchiveService implements CipherArchiveService {
|
||||
const response = new ListResponse(r, CipherResponse);
|
||||
|
||||
const currentCiphers = await firstValueFrom(this.cipherService.ciphers$(userId));
|
||||
// prevent mutating ciphers$ state
|
||||
const localCiphers = structuredClone(currentCiphers);
|
||||
const responseDataArray = response.data.map(
|
||||
(cipher) => new CipherData(cipher, currentCiphers[cipher.id as CipherId]?.collectionIds),
|
||||
);
|
||||
|
||||
for (const cipher of response.data) {
|
||||
const localCipher = localCiphers[cipher.id as CipherId];
|
||||
|
||||
if (localCipher == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
localCipher.archivedDate = cipher.archivedDate;
|
||||
localCipher.revisionDate = cipher.revisionDate;
|
||||
}
|
||||
|
||||
await this.cipherService.upsert(Object.values(localCiphers), userId);
|
||||
await this.cipherService.upsert(responseDataArray, userId);
|
||||
return response.data[0];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user