From 9ddaf96020fcff2ce06ede28c6f608dac1fb44af Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Mon, 10 Feb 2025 14:20:05 +0100 Subject: [PATCH] [PM-13811] Remove conditional code for extension refresh on web (#13145) * Enable UI refresh on web by default Removing all conditional code around the `ExtensionRefresh`-feature-flag on the web-UI * Remove no longer needed extensRefresh helpers --------- Co-authored-by: Daniel James Smith --- .../vault-cipher-row.component.html | 2 +- .../vault-items/vault-cipher-row.component.ts | 19 +- .../individual-vault/add-edit.component.ts | 6 +- .../vault-header/vault-header.component.html | 124 ++++------- .../vault-header/vault-header.component.ts | 15 +- .../vault-onboarding.component.html | 7 +- .../vault-onboarding.component.spec.ts | 4 - .../vault-onboarding.component.ts | 7 - .../vault/individual-vault/vault.component.ts | 200 +++--------------- .../vault-header/vault-header.component.html | 55 +---- .../vault-header/vault-header.component.ts | 12 +- .../app/vault/org-vault/vault.component.ts | 134 +----------- .../utils/extension-refresh-redirect.spec.ts | 62 ------ .../src/utils/extension-refresh-redirect.ts | 28 --- .../src/utils/extension-refresh-swap.ts | 32 --- 15 files changed, 86 insertions(+), 621 deletions(-) delete mode 100644 libs/angular/src/utils/extension-refresh-redirect.spec.ts delete mode 100644 libs/angular/src/utils/extension-refresh-redirect.ts delete mode 100644 libs/angular/src/utils/extension-refresh-swap.ts diff --git a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html index 7e59853851c..befeee43f69 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html +++ b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html @@ -22,7 +22,7 @@ [routerLink]="[]" [queryParams]="{ itemId: cipher.id, action: clickAction }" queryParamsHandling="merge" - [replaceUrl]="extensionRefreshEnabled" + [replaceUrl]="true" title="{{ 'editItemWithName' | i18n: cipher.name }}" type="button" appStopProp diff --git a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts index 4af08e19d74..5f686fcec9c 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts @@ -1,12 +1,9 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; -import { firstValueFrom } from "rxjs"; import { CollectionView } from "@bitwarden/admin-console/common"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -25,11 +22,6 @@ import { RowHeightClass } from "./vault-items.component"; export class VaultCipherRowComponent implements OnInit { protected RowHeightClass = RowHeightClass; - /** - * Flag to determine if the extension refresh feature flag is enabled. - */ - protected extensionRefreshEnabled = false; - @Input() disabled: boolean; @Input() cipher: CipherView; @Input() showOwner: boolean; @@ -61,19 +53,12 @@ export class VaultCipherRowComponent implements OnInit { ]; protected organization?: Organization; - constructor( - private configService: ConfigService, - private i18nService: I18nService, - ) {} + constructor(private i18nService: I18nService) {} /** * Lifecycle hook for component initialization. - * Checks if the extension refresh feature flag is enabled to provide to template. */ async ngOnInit(): Promise { - this.extensionRefreshEnabled = await firstValueFrom( - this.configService.getFeatureFlag$(FeatureFlag.ExtensionRefresh), - ); if (this.cipher.organizationId != null) { this.organization = this.organizations.find((o) => o.id === this.cipher.organizationId); } @@ -83,7 +68,7 @@ export class VaultCipherRowComponent implements OnInit { if (this.cipher.decryptionFailure) { return "showFailedToDecrypt"; } - return this.extensionRefreshEnabled ? "view" : null; + return "view"; } protected get showTotpCopyButton() { diff --git a/apps/web/src/app/vault/individual-vault/add-edit.component.ts b/apps/web/src/app/vault/individual-vault/add-edit.component.ts index 62ce55848f5..02dcbc30161 100644 --- a/apps/web/src/app/vault/individual-vault/add-edit.component.ts +++ b/apps/web/src/app/vault/individual-vault/add-edit.component.ts @@ -143,11 +143,7 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On }, 1000); } - const extensionRefreshEnabled = await firstValueFrom( - this.configService.getFeatureFlag$(FeatureFlag.ExtensionRefresh), - ); - - this.cardIsExpired = extensionRefreshEnabled && isCardExpired(this.cipher.card); + this.cardIsExpired = isCardExpired(this.cipher.card); } ngOnDestroy() { diff --git a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.html b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.html index 8ac6138db7c..8d576098a74 100644 --- a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.html +++ b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.html @@ -69,88 +69,48 @@
- - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + +
diff --git a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts index 75b39a2ca40..63af397e726 100644 --- a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts @@ -9,13 +9,10 @@ import { OnInit, Output, } from "@angular/core"; -import { firstValueFrom } from "rxjs"; import { Unassigned, CollectionView } from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; @@ -50,7 +47,6 @@ export class VaultHeaderComponent implements OnInit { protected All = All; protected CollectionDialogTabType = CollectionDialogTabType; protected CipherType = CipherType; - protected extensionRefreshEnabled: boolean; /** * Boolean to determine the loading state of the header. @@ -85,16 +81,9 @@ export class VaultHeaderComponent implements OnInit { /** Emits an event when the delete collection button is clicked in the header */ @Output() onDeleteCollection = new EventEmitter(); - constructor( - private i18nService: I18nService, - private configService: ConfigService, - ) {} + constructor(private i18nService: I18nService) {} - async ngOnInit() { - this.extensionRefreshEnabled = await firstValueFrom( - this.configService.getFeatureFlag$(FeatureFlag.ExtensionRefresh), - ); - } + async ngOnInit() {} /** * The id of the organization that is currently being filtered on. diff --git a/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.html b/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.html index b9647e3237d..aa56daac071 100644 --- a/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.html +++ b/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.html @@ -22,12 +22,7 @@

{{ "onboardingImportDataDetailsPartOne" | i18n }} {{ "onboardingImportDataDetailsPartTwoNoOrgs" | i18n }} diff --git a/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.spec.ts b/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.spec.ts index e017bc9b35d..327a077dc6a 100644 --- a/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.spec.ts +++ b/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.spec.ts @@ -7,7 +7,6 @@ import { Subject, of } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateProvider } from "@bitwarden/common/platform/state"; @@ -28,7 +27,6 @@ describe("VaultOnboardingComponent", () => { let mockStateProvider: Partial; let setInstallExtLinkSpy: any; let individualVaultPolicyCheckSpy: any; - let mockConfigService: MockProxy; beforeEach(() => { mockPolicyService = mock(); @@ -47,7 +45,6 @@ describe("VaultOnboardingComponent", () => { }), ), }; - mockConfigService = mock(); // eslint-disable-next-line @typescript-eslint/no-floating-promises TestBed.configureTestingModule({ @@ -60,7 +57,6 @@ describe("VaultOnboardingComponent", () => { { provide: I18nService, useValue: mockI18nService }, { provide: ApiService, useValue: mockApiService }, { provide: StateProvider, useValue: mockStateProvider }, - { provide: ConfigService, useValue: mockConfigService }, ], }).compileComponents(); fixture = TestBed.createComponent(VaultOnboardingComponent); diff --git a/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.ts b/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.ts index 3535d31852e..e3bd8fc10bd 100644 --- a/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.ts @@ -18,8 +18,6 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherType } from "@bitwarden/common/vault/enums/cipher-type"; import { VaultOnboardingMessages } from "@bitwarden/common/vault/enums/vault-onboarding.enum"; @@ -58,14 +56,12 @@ export class VaultOnboardingComponent implements OnInit, OnChanges, OnDestroy { protected onboardingTasks$: Observable; protected showOnboarding = false; - protected extensionRefreshEnabled = false; constructor( protected platformUtilsService: PlatformUtilsService, protected policyService: PolicyService, private apiService: ApiService, private vaultOnboardingService: VaultOnboardingServiceAbstraction, - private configService: ConfigService, ) {} async ngOnInit() { @@ -74,9 +70,6 @@ export class VaultOnboardingComponent implements OnInit, OnChanges, OnDestroy { this.setInstallExtLink(); this.individualVaultPolicyCheck(); this.checkForBrowserExtension(); - this.extensionRefreshEnabled = await this.configService.getFeatureFlag( - FeatureFlag.ExtensionRefresh, - ); } async ngOnChanges(changes: SimpleChanges) { diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts index 950c1d77731..ff8a008bcc5 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -1,15 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { DialogRef } from "@angular/cdk/dialog"; -import { - ChangeDetectorRef, - Component, - NgZone, - OnDestroy, - OnInit, - ViewChild, - ViewContainerRef, -} from "@angular/core"; +import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { ActivatedRoute, Params, Router } from "@angular/router"; import { BehaviorSubject, @@ -42,7 +34,6 @@ import { Unassigned, } from "@bitwarden/admin-console/common"; import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe"; -import { ModalService } from "@bitwarden/angular/services/modal.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; @@ -57,9 +48,7 @@ import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; import { EventType } from "@bitwarden/common/enums"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; 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"; @@ -71,7 +60,6 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; -import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { ServiceUtils } from "@bitwarden/common/vault/service-utils"; @@ -105,13 +93,11 @@ import { VaultItemEvent } from "../components/vault-items/vault-item-event"; import { VaultItemsModule } from "../components/vault-items/vault-items.module"; import { getNestedCollectionTree } from "../utils/collection-utils"; -import { AddEditComponent } from "./add-edit.component"; import { AttachmentDialogCloseResult, AttachmentDialogResult, AttachmentsV2Component, } from "./attachments-v2.component"; -import { AttachmentsComponent } from "./attachments.component"; import { BulkDeleteDialogResult, openBulkDeleteDialog, @@ -160,15 +146,6 @@ const SearchTextDebounceInterval = 200; }) export class VaultComponent implements OnInit, OnDestroy { @ViewChild("vaultFilter", { static: true }) filterComponent: VaultFilterComponent; - @ViewChild("attachments", { read: ViewContainerRef, static: true }) - attachmentsModalRef: ViewContainerRef; - @ViewChild("folderAddEdit", { read: ViewContainerRef, static: true }) - folderAddEditModalRef: ViewContainerRef; - @ViewChild("cipherAddEdit", { read: ViewContainerRef, static: true }) - cipherAddEditModalRef: ViewContainerRef; - @ViewChild("share", { read: ViewContainerRef, static: true }) shareModalRef: ViewContainerRef; - @ViewChild("collectionsModal", { read: ViewContainerRef, static: true }) - collectionsModalRef: ViewContainerRef; trashCleanupWarning: string = null; kdfIterations: number; @@ -193,7 +170,6 @@ export class VaultComponent implements OnInit, OnDestroy { private searchText$ = new Subject(); private refresh$ = new BehaviorSubject(null); private destroy$ = new Subject(); - private extensionRefreshEnabled: boolean; private hasSubscription$ = new BehaviorSubject(false); private vaultItemDialogRef?: DialogRef | undefined; @@ -260,7 +236,6 @@ export class VaultComponent implements OnInit, OnDestroy { private router: Router, private changeDetectorRef: ChangeDetectorRef, private i18nService: I18nService, - private modalService: ModalService, private dialogService: DialogService, private messagingService: MessagingService, private platformUtilsService: PlatformUtilsService, @@ -278,7 +253,6 @@ export class VaultComponent implements OnInit, OnDestroy { private eventCollectionService: EventCollectionService, private searchService: SearchService, private searchPipe: SearchPipe, - private configService: ConfigService, private apiService: ApiService, private billingAccountProfileStateService: BillingAccountProfileStateService, private toastService: ToastService, @@ -437,15 +411,15 @@ export class VaultComponent implements OnInit, OnDestroy { firstSetup$ .pipe( switchMap(() => this.route.queryParams), - // Only process the queryParams if the dialog is not open (only when extension refresh is enabled) - filter(() => this.vaultItemDialogRef == undefined || !this.extensionRefreshEnabled), + // Only process the queryParams if the dialog is not open + filter(() => this.vaultItemDialogRef == undefined), switchMap(async (params) => { const cipherId = getCipherIdFromParams(params); if (cipherId) { if (await this.cipherService.get(cipherId)) { let action = params.action; - // Default to "view" if extension refresh is enabled - if (action == null && this.extensionRefreshEnabled) { + // Default to "view" + if (action == null) { action = "view"; } @@ -544,11 +518,6 @@ export class VaultComponent implements OnInit, OnDestroy { this.refreshing = false; }, ); - - // Check if the extension refresh feature flag is enabled - this.extensionRefreshEnabled = await this.configService.getFeatureFlag( - FeatureFlag.ExtensionRefresh, - ); } ngOnDestroy() { @@ -642,8 +611,7 @@ export class VaultComponent implements OnInit, OnDestroy { * Handles opening the attachments dialog for a cipher. * Runs several checks to ensure that the user has the correct permissions * and then opens the attachments dialog. - * Uses the new AttachmentsV2Component if the extensionRefresh feature flag is enabled. - * + * Uses the new AttachmentsV2Component * @param cipher * @returns */ @@ -668,51 +636,20 @@ export class VaultComponent implements OnInit, OnDestroy { } } - const canEditAttachments = await this.canEditAttachments(cipher); + const dialogRef = AttachmentsV2Component.open(this.dialogService, { + cipherId: cipher.id as CipherId, + }); - let madeAttachmentChanges = false; + const result: AttachmentDialogCloseResult = await lastValueFrom(dialogRef.closed); - if (this.extensionRefreshEnabled) { - const dialogRef = AttachmentsV2Component.open(this.dialogService, { - cipherId: cipher.id as CipherId, - }); - - const result: AttachmentDialogCloseResult = await lastValueFrom(dialogRef.closed); - - if ( - result.action === AttachmentDialogResult.Uploaded || - result.action === AttachmentDialogResult.Removed - ) { - this.refresh(); - } - - return; + if ( + result.action === AttachmentDialogResult.Uploaded || + result.action === AttachmentDialogResult.Removed + ) { + this.refresh(); } - const [modal] = await this.modalService.openViewRef( - AttachmentsComponent, - this.attachmentsModalRef, - (comp) => { - comp.cipherId = cipher.id; - comp.viewOnly = !canEditAttachments; - comp.onUploadedAttachment - .pipe(takeUntil(this.destroy$)) - .subscribe(() => (madeAttachmentChanges = true)); - comp.onDeletedAttachment - .pipe(takeUntil(this.destroy$)) - .subscribe(() => (madeAttachmentChanges = true)); - comp.onReuploadedAttachment - .pipe(takeUntil(this.destroy$)) - .subscribe(() => (madeAttachmentChanges = true)); - }, - ); - - modal.onClosed.pipe(takeUntil(this.destroy$)).subscribe(() => { - if (madeAttachmentChanges) { - this.refresh(); - } - madeAttachmentChanges = false; - }); + return; } /** @@ -751,48 +688,13 @@ export class VaultComponent implements OnInit, OnDestroy { await this.go({ cipherId: null, itemId: null, action: null }); } - async addCipher(cipherType?: CipherType) { - const type = cipherType ?? this.activeFilter.cipherType; - - if (this.extensionRefreshEnabled) { - return this.addCipherV2(type); - } - - const component = (await this.editCipher(null)) as AddEditComponent; - component.type = type; - if ( - this.activeFilter.organizationId !== "MyVault" && - this.activeFilter.organizationId != null - ) { - component.organizationId = this.activeFilter.organizationId; - component.collections = ( - await firstValueFrom(this.vaultFilterService.filteredCollections$) - ).filter((c) => !c.readOnly && c.id != null); - } - const selectedColId = this.activeFilter.collectionId; - if (selectedColId !== "AllCollections" && selectedColId != null) { - const selectedCollection = ( - await firstValueFrom(this.vaultFilterService.filteredCollections$) - ).find((c) => c.id === selectedColId); - component.organizationId = selectedCollection?.organizationId; - if (!selectedCollection.readOnly) { - component.collectionIds = [selectedColId]; - } - } - component.folderId = this.activeFilter.folderId; - } - /** * Opens the add cipher dialog. * @param cipherType The type of cipher to add. - * @returns The dialog reference. */ - async addCipherV2(cipherType?: CipherType) { - const cipherFormConfig = await this.cipherFormConfigService.buildConfig( - "add", - null, - cipherType, - ); + async addCipher(cipherType?: CipherType) { + const type = cipherType ?? this.activeFilter.cipherType; + const cipherFormConfig = await this.cipherFormConfigService.buildConfig("add", null, type); const collectionId = this.activeFilter.collectionId !== "AllCollections" && this.activeFilter.collectionId != null ? this.activeFilter.collectionId @@ -823,6 +725,12 @@ export class VaultComponent implements OnInit, OnDestroy { return this.editCipherId(cipher?.id, cloneMode); } + /** + * Edit a cipher using the new VaultItemDialog. + * @param id + * @param cloneMode + * @returns + */ async editCipherId(id: string, cloneMode?: boolean) { const cipher = await this.cipherService.get(id); @@ -836,49 +744,6 @@ export class VaultComponent implements OnInit, OnDestroy { return; } - if (this.extensionRefreshEnabled) { - await this.editCipherIdV2(cipher, cloneMode); - return; - } - - const [modal, childComponent] = await this.modalService.openViewRef( - AddEditComponent, - this.cipherAddEditModalRef, - (comp) => { - comp.cipherId = id; - comp.collectionId = this.selectedCollection?.node.id; - - comp.onSavedCipher.pipe(takeUntil(this.destroy$)).subscribe(() => { - modal.close(); - this.refresh(); - }); - comp.onDeletedCipher.pipe(takeUntil(this.destroy$)).subscribe(() => { - modal.close(); - this.refresh(); - }); - comp.onRestoredCipher.pipe(takeUntil(this.destroy$)).subscribe(() => { - modal.close(); - this.refresh(); - }); - }, - ); - - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - modal.onClosedPromise().then(() => { - void this.go({ cipherId: null, itemId: null, action: null }); - }); - - return childComponent; - } - - /** - * Edit a cipher using the new VaultItemDialog. - * - * @param cipher - * @param cloneMode - */ - private async editCipherIdV2(cipher: Cipher, cloneMode?: boolean) { const cipherFormConfig = await this.cipherFormConfigService.buildConfig( cloneMode ? "clone" : "edit", cipher.id as CipherId, @@ -1076,11 +941,7 @@ export class VaultComponent implements OnInit, OnDestroy { } } - const component = await this.editCipher(cipher, true); - - if (component != null) { - component.cloneMode = true; - } + await this.editCipher(cipher, true); } restore = async (c: CipherView): Promise => { @@ -1331,15 +1192,6 @@ export class VaultComponent implements OnInit, OnDestroy { this.refresh$.next(); } - private async canEditAttachments(cipher: CipherView) { - if (cipher.organizationId == null || cipher.edit) { - return true; - } - - const organization = this.allOrganizations.find((o) => o.id === cipher.organizationId); - return organization.canEditAllCiphers; - } - private async go(queryParams: any = null) { if (queryParams == null) { queryParams = { diff --git a/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.html b/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.html index 8178e3b2935..edba6e4753c 100644 --- a/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.html +++ b/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.html @@ -104,8 +104,8 @@ *ngIf="filter.type !== 'trash' && filter.collectionId !== Unassigned && organization" class="tw-shrink-0" > - - + +

- - - -
- - - - - -
- - - - - - -
diff --git a/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.ts b/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.ts index 7f118a48db3..e3c99231a86 100644 --- a/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.ts +++ b/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.ts @@ -13,7 +13,6 @@ import { import { JslibModule } from "@bitwarden/angular/jslib.module"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { ProductTierType } from "@bitwarden/common/billing/enums"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -90,11 +89,6 @@ export class VaultHeaderComponent implements OnInit { protected CollectionDialogTabType = CollectionDialogTabType; - /** - * Whether the extension refresh feature flag is enabled. - */ - protected extensionRefreshEnabled = false; - /** The cipher type enum. */ protected CipherType = CipherType; @@ -106,11 +100,7 @@ export class VaultHeaderComponent implements OnInit { private configService: ConfigService, ) {} - async ngOnInit() { - this.extensionRefreshEnabled = await this.configService.getFeatureFlag( - FeatureFlag.ExtensionRefresh, - ); - } + async ngOnInit() {} get title() { const headerType = this.i18nService.t("collections").toLowerCase(); diff --git a/apps/web/src/app/vault/org-vault/vault.component.ts b/apps/web/src/app/vault/org-vault/vault.component.ts index fe76f9842e9..b4ba9ff5512 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.ts +++ b/apps/web/src/app/vault/org-vault/vault.component.ts @@ -1,15 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { DialogRef } from "@angular/cdk/dialog"; -import { - ChangeDetectorRef, - Component, - NgZone, - OnDestroy, - OnInit, - ViewChild, - ViewContainerRef, -} from "@angular/core"; +import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute, Params, Router } from "@angular/router"; import { BehaviorSubject, @@ -37,12 +29,10 @@ import { import { CollectionAdminService, CollectionAdminView, - CollectionService, CollectionView, Unassigned, } from "@bitwarden/admin-console/common"; import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe"; -import { ModalService } from "@bitwarden/angular/services/modal.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; @@ -127,7 +117,6 @@ import { import { VaultHeaderComponent } from "../org-vault/vault-header/vault-header.component"; import { getNestedCollectionTree } from "../utils/collection-utils"; -import { AddEditComponent } from "./add-edit.component"; import { BulkCollectionsDialogComponent, BulkCollectionsDialogResult, @@ -166,13 +155,6 @@ enum AddAccessStatusType { export class VaultComponent implements OnInit, OnDestroy { protected Unassigned = Unassigned; - @ViewChild("attachments", { read: ViewContainerRef, static: true }) - attachmentsModalRef: ViewContainerRef; - @ViewChild("cipherAddEdit", { read: ViewContainerRef, static: true }) - cipherAddEditModalRef: ViewContainerRef; - @ViewChild("collectionsModal", { read: ViewContainerRef, static: true }) - collectionsModalRef: ViewContainerRef; - trashCleanupWarning: string = null; activeFilter: VaultFilter = new VaultFilter(); @@ -210,7 +192,6 @@ export class VaultComponent implements OnInit, OnDestroy { private refresh$ = new BehaviorSubject(null); private destroy$ = new Subject(); protected addAccessStatus$ = new BehaviorSubject(0); - private extensionRefreshEnabled: boolean; private resellerManagedOrgAlert: boolean; private vaultItemDialogRef?: DialogRef | undefined; @@ -249,7 +230,6 @@ export class VaultComponent implements OnInit, OnDestroy { private changeDetectorRef: ChangeDetectorRef, private syncService: SyncService, private i18nService: I18nService, - private modalService: ModalService, private dialogService: DialogService, private messagingService: MessagingService, private broadcasterService: BroadcasterService, @@ -265,7 +245,6 @@ export class VaultComponent implements OnInit, OnDestroy { private eventCollectionService: EventCollectionService, private totpService: TotpService, private apiService: ApiService, - private collectionService: CollectionService, private toastService: ToastService, private configService: ConfigService, private cipherFormConfigService: CipherFormConfigService, @@ -278,10 +257,6 @@ export class VaultComponent implements OnInit, OnDestroy { ) {} async ngOnInit() { - this.extensionRefreshEnabled = await this.configService.getFeatureFlag( - FeatureFlag.ExtensionRefresh, - ); - this.resellerManagedOrgAlert = await this.configService.getFeatureFlag( FeatureFlag.ResellerManagedOrgAlert, ); @@ -555,7 +530,7 @@ export class VaultComponent implements OnInit, OnDestroy { firstSetup$ .pipe( switchMap(() => combineLatest([this.route.queryParams, allCipherMap$])), - filter(() => this.vaultItemDialogRef == undefined || !this.extensionRefreshEnabled), + filter(() => this.vaultItemDialogRef == undefined), switchMap(async ([qParams, allCiphersMap]) => { const cipherId = getCipherIdFromParams(qParams); @@ -586,15 +561,15 @@ export class VaultComponent implements OnInit, OnDestroy { return; } - // Default to "view" if extension refresh is enabled - if (action == null && this.extensionRefreshEnabled) { + // Default to "view" + if (action == null) { action = "view"; } if (action === "view") { await this.viewCipherById(cipher); } else { - await this.editCipherId(cipher, false); + await this.editCipher(cipher, false); } } else { this.toastService.showToast({ @@ -836,27 +811,8 @@ export class VaultComponent implements OnInit, OnDestroy { } } + /** Opens the Add/Edit Dialog */ async addCipher(cipherType?: CipherType) { - if (this.extensionRefreshEnabled) { - return this.addCipherV2(cipherType); - } - - let collections: CollectionView[] = []; - - // Admins limited to only adding items to collections they have access to. - collections = await firstValueFrom(this.editableCollections$); - - await this.editCipher(null, false, (comp) => { - comp.type = cipherType || this.activeFilter.cipherType; - comp.collections = collections; - if (this.activeFilter.collectionId) { - comp.collectionIds = [this.activeFilter.collectionId]; - } - }); - } - - /** Opens the Add/Edit Dialog. Only to be used when the BrowserExtension feature flag is active */ - async addCipherV2(cipherType?: CipherType) { const cipherFormConfig = await this.cipherFormConfigService.buildConfig( "add", null, @@ -877,24 +833,8 @@ export class VaultComponent implements OnInit, OnDestroy { * Edit the given cipher or add a new cipher * @param cipherView - When set, the cipher to be edited * @param cloneCipher - `true` when the cipher should be cloned. - * Used in place of the `additionalComponentParameters`, as - * the `editCipherIdV2` method has a differing implementation. - * @param defaultComponentParameters - A method that takes in an instance of - * the `AddEditComponent` to edit methods directly. */ - async editCipher( - cipher: CipherView | null, - cloneCipher: boolean, - additionalComponentParameters?: (comp: AddEditComponent) => void, - ) { - return this.editCipherId(cipher, cloneCipher, additionalComponentParameters); - } - - async editCipherId( - cipher: CipherView | null, - cloneCipher: boolean, - additionalComponentParameters?: (comp: AddEditComponent) => void, - ) { + async editCipher(cipher: CipherView | null, cloneCipher: boolean) { if ( cipher && cipher.reprompt !== 0 && @@ -905,55 +845,6 @@ export class VaultComponent implements OnInit, OnDestroy { return; } - if (this.extensionRefreshEnabled) { - await this.editCipherIdV2(cipher, cloneCipher); - return; - } - - const defaultComponentParameters = (comp: AddEditComponent) => { - comp.organization = this.organization; - comp.organizationId = this.organization.id; - comp.cipherId = cipher?.id; - comp.collectionId = this.activeFilter.collectionId; - comp.onSavedCipher.pipe(takeUntil(this.destroy$)).subscribe(() => { - modal.close(); - this.refresh(); - }); - comp.onDeletedCipher.pipe(takeUntil(this.destroy$)).subscribe(() => { - modal.close(); - this.refresh(); - }); - comp.onRestoredCipher.pipe(takeUntil(this.destroy$)).subscribe(() => { - modal.close(); - this.refresh(); - }); - }; - - const [modal, childComponent] = await this.modalService.openViewRef( - AddEditComponent, - this.cipherAddEditModalRef, - additionalComponentParameters == null - ? defaultComponentParameters - : (comp) => { - defaultComponentParameters(comp); - additionalComponentParameters(comp); - }, - ); - - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - modal.onClosedPromise().then(() => { - this.go({ cipherId: null, itemId: null, action: null }); - }); - - return childComponent; - } - - /** - * Edit a cipher using the new AddEditCipherDialogV2 component. - * Only to be used behind the ExtensionRefresh feature flag. - */ - private async editCipherIdV2(cipher: CipherView | null, cloneCipher: boolean) { const cipherFormConfig = await this.cipherFormConfigService.buildConfig( cloneCipher ? "clone" : "edit", cipher?.id as CipherId | null, @@ -1038,16 +929,7 @@ export class VaultComponent implements OnInit, OnDestroy { } } - let collections: CollectionView[] = []; - - // Admins limited to only adding items to collections they have access to. - collections = await firstValueFrom(this.editableCollections$); - - await this.editCipher(cipher, true, (comp) => { - comp.cloneMode = true; - comp.collections = collections; - comp.collectionIds = cipher.collectionIds; - }); + await this.editCipher(cipher, true); } restore = async (c: CipherView): Promise => { diff --git a/libs/angular/src/utils/extension-refresh-redirect.spec.ts b/libs/angular/src/utils/extension-refresh-redirect.spec.ts deleted file mode 100644 index 3291a4496ff..00000000000 --- a/libs/angular/src/utils/extension-refresh-redirect.spec.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { TestBed } from "@angular/core/testing"; -import { Navigation, Router, UrlTree } from "@angular/router"; -import { mock, MockProxy } from "jest-mock-extended"; - -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; - -import { extensionRefreshRedirect } from "./extension-refresh-redirect"; - -describe("extensionRefreshRedirect", () => { - let configService: MockProxy; - let router: MockProxy; - - beforeEach(() => { - configService = mock(); - router = mock(); - - TestBed.configureTestingModule({ - providers: [ - { provide: ConfigService, useValue: configService }, - { provide: Router, useValue: router }, - ], - }); - }); - - it("returns true when ExtensionRefresh flag is disabled", async () => { - configService.getFeatureFlag.mockResolvedValue(false); - - const result = await TestBed.runInInjectionContext(() => - extensionRefreshRedirect("/redirect")(), - ); - - expect(result).toBe(true); - expect(configService.getFeatureFlag).toHaveBeenCalledWith(FeatureFlag.ExtensionRefresh); - expect(router.parseUrl).not.toHaveBeenCalled(); - }); - - it("returns UrlTree when ExtensionRefresh flag is enabled and preserves query params", async () => { - configService.getFeatureFlag.mockResolvedValue(true); - - const urlTree = new UrlTree(); - urlTree.queryParams = { test: "test" }; - - const navigation: Navigation = { - extras: {}, - id: 0, - initialUrl: new UrlTree(), - extractedUrl: urlTree, - trigger: "imperative", - previousNavigation: undefined, - }; - - router.getCurrentNavigation.mockReturnValue(navigation); - - await TestBed.runInInjectionContext(() => extensionRefreshRedirect("/redirect")()); - - expect(configService.getFeatureFlag).toHaveBeenCalledWith(FeatureFlag.ExtensionRefresh); - expect(router.createUrlTree).toHaveBeenCalledWith(["/redirect"], { - queryParams: urlTree.queryParams, - }); - }); -}); diff --git a/libs/angular/src/utils/extension-refresh-redirect.ts b/libs/angular/src/utils/extension-refresh-redirect.ts deleted file mode 100644 index 2baa3a3ec89..00000000000 --- a/libs/angular/src/utils/extension-refresh-redirect.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { inject } from "@angular/core"; -import { UrlTree, Router } from "@angular/router"; - -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; - -/** - * Helper function to redirect to a new URL based on the ExtensionRefresh feature flag. - * @param redirectUrl - The URL to redirect to if the ExtensionRefresh flag is enabled. - */ -export function extensionRefreshRedirect(redirectUrl: string): () => Promise { - return async () => { - const configService = inject(ConfigService); - const router = inject(Router); - - const shouldRedirect = await configService.getFeatureFlag(FeatureFlag.ExtensionRefresh); - if (shouldRedirect) { - const currentNavigation = router.getCurrentNavigation(); - const queryParams = currentNavigation?.extractedUrl?.queryParams || {}; - - // Preserve query params when redirecting as it is likely that the refreshed component - // will be consuming the same query params. - return router.createUrlTree([redirectUrl], { queryParams }); - } else { - return true; - } - }; -} diff --git a/libs/angular/src/utils/extension-refresh-swap.ts b/libs/angular/src/utils/extension-refresh-swap.ts deleted file mode 100644 index 6512be032d2..00000000000 --- a/libs/angular/src/utils/extension-refresh-swap.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Type, inject } from "@angular/core"; -import { Route, Routes } from "@angular/router"; - -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; - -import { componentRouteSwap } from "./component-route-swap"; - -/** - * Helper function to swap between two components based on the ExtensionRefresh feature flag. - * @param defaultComponent - The current non-refreshed component to render. - * @param refreshedComponent - The new refreshed component to render. - * @param options - The shared route options to apply to the default component, and to the alt component if altOptions is not provided. - * @param altOptions - The alt route options to apply to the alt component. - */ -export function extensionRefreshSwap( - defaultComponent: Type, - refreshedComponent: Type, - options: Route, - altOptions?: Route, -): Routes { - return componentRouteSwap( - defaultComponent, - refreshedComponent, - async () => { - const configService = inject(ConfigService); - return configService.getFeatureFlag(FeatureFlag.ExtensionRefresh); - }, - options, - altOptions, - ); -}