From c4c275604bcb51bbd4296ec14ea212421b689e10 Mon Sep 17 00:00:00 2001 From: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com> Date: Thu, 21 Mar 2024 15:04:29 -0400 Subject: [PATCH 1/3] Remove FF 'AC-1607_present-user-offboarding-survey' and old cancel functionality (#8322) --- .../user-subscription.component.html | 2 +- .../individual/user-subscription.component.ts | 80 ++++--------------- ...nization-subscription-cloud.component.html | 23 +----- ...ganization-subscription-cloud.component.ts | 42 +--------- libs/common/src/abstractions/api.service.ts | 1 - .../organization-api.service.abstraction.ts | 1 - .../organization/organization-api.service.ts | 4 - .../billing/services/billing-api.service.ts | 4 +- libs/common/src/enums/feature-flag.enum.ts | 1 - libs/common/src/services/api.service.ts | 4 - 10 files changed, 22 insertions(+), 140 deletions(-) diff --git a/apps/web/src/app/billing/individual/user-subscription.component.html b/apps/web/src/app/billing/individual/user-subscription.component.html index 2fd831c71c2..874983df840 100644 --- a/apps/web/src/app/billing/individual/user-subscription.component.html +++ b/apps/web/src/app/billing/individual/user-subscription.component.html @@ -145,7 +145,7 @@ type="button" buttonType="danger" class="btn-submit tw-ml-auto" - (click)="cancel()" + (click)="cancelSubscription()" [appApiAction]="cancelPromise" [disabled]="$any(cancelBtn).loading" *ngIf="subscription && !subscription.cancelled && !subscriptionMarkedForCancel" diff --git a/apps/web/src/app/billing/individual/user-subscription.component.ts b/apps/web/src/app/billing/individual/user-subscription.component.ts index 76e542c3385..7d8c3a0f185 100644 --- a/apps/web/src/app/billing/individual/user-subscription.component.ts +++ b/apps/web/src/app/billing/individual/user-subscription.component.ts @@ -1,12 +1,10 @@ import { Component, OnInit } from "@angular/core"; import { Router } from "@angular/router"; -import { firstValueFrom, lastValueFrom, Observable } from "rxjs"; +import { firstValueFrom, lastValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { SubscriptionResponse } from "@bitwarden/common/billing/models/response/subscription.response"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigServiceAbstraction as ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -34,7 +32,6 @@ export class UserSubscriptionComponent implements OnInit { cancelPromise: Promise; reinstatePromise: Promise; - presentUserWithOffboardingSurvey$: Observable; constructor( private apiService: ApiService, @@ -45,7 +42,6 @@ export class UserSubscriptionComponent implements OnInit { private fileDownloadService: FileDownloadService, private dialogService: DialogService, private environmentService: EnvironmentService, - private configService: ConfigService, private billingAccountProfileStateService: BillingAccountProfileStateService, ) { this.selfHosted = platformUtilsService.isSelfHost(); @@ -53,9 +49,6 @@ export class UserSubscriptionComponent implements OnInit { async ngOnInit() { this.cloudWebVaultUrl = await firstValueFrom(this.environmentService.cloudWebVaultUrl$); - this.presentUserWithOffboardingSurvey$ = this.configService.getFeatureFlag$( - FeatureFlag.AC1607_PresentUserOffboardingSurvey, - ); await this.load(); this.firstLoaded = true; } @@ -105,16 +98,22 @@ export class UserSubscriptionComponent implements OnInit { } } - cancel = async () => { - const presentUserWithOffboardingSurvey = await this.configService.getFeatureFlag( - FeatureFlag.AC1607_PresentUserOffboardingSurvey, - ); + cancelSubscription = async () => { + const reference = openOffboardingSurvey(this.dialogService, { + data: { + type: "User", + }, + }); - if (presentUserWithOffboardingSurvey) { - await this.cancelWithOffboardingSurvey(); - } else { - await this.cancelWithWarning(); + this.cancelPromise = lastValueFrom(reference.closed); + + const result = await this.cancelPromise; + + if (result === OffboardingSurveyDialogResultType.Closed) { + return; } + + await this.load(); }; downloadLicense() { @@ -159,55 +158,6 @@ export class UserSubscriptionComponent implements OnInit { } } - private cancelWithOffboardingSurvey = async () => { - const reference = openOffboardingSurvey(this.dialogService, { - data: { - type: "User", - }, - }); - - this.cancelPromise = lastValueFrom(reference.closed); - - const result = await this.cancelPromise; - - if (result === OffboardingSurveyDialogResultType.Closed) { - return; - } - - await this.load(); - }; - - private async cancelWithWarning() { - if (this.loading) { - return; - } - - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "cancelSubscription" }, - content: { key: "cancelConfirmation" }, - type: "warning", - }); - - if (!confirmed) { - return; - } - - try { - this.cancelPromise = this.apiService.postCancelPremium(); - await this.cancelPromise; - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("canceledSubscription"), - ); - // 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 - this.load(); - } catch (e) { - this.logService.error(e); - } - } - get subscriptionMarkedForCancel() { return ( this.subscription != null && !this.subscription.cancelled && this.subscription.cancelAtEndDate diff --git a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html index 484be34ce62..7f53fba1c03 100644 --- a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html +++ b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html @@ -232,28 +232,9 @@ - diff --git a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts index 24374ee8969..2256a927566 100644 --- a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts +++ b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts @@ -1,6 +1,6 @@ import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; -import { concatMap, firstValueFrom, lastValueFrom, Observable, Subject, takeUntil } from "rxjs"; +import { concatMap, firstValueFrom, lastValueFrom, Subject, takeUntil } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; @@ -11,8 +11,6 @@ import { PlanType } from "@bitwarden/common/billing/enums"; import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response"; import { BillingSubscriptionItemResponse } from "@bitwarden/common/billing/models/response/subscription.response"; import { ProductType } from "@bitwarden/common/enums"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigServiceAbstraction as ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -43,7 +41,6 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy showSecretsManagerSubscribe = false; firstLoaded = false; loading: boolean; - presentUserWithOffboardingSurvey$: Observable; protected readonly teamsStarter = ProductType.TeamsStarter; @@ -58,7 +55,6 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy private organizationApiService: OrganizationApiServiceAbstraction, private route: ActivatedRoute, private dialogService: DialogService, - private configService: ConfigService, ) {} async ngOnInit() { @@ -78,10 +74,6 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy takeUntil(this.destroy$), ) .subscribe(); - - this.presentUserWithOffboardingSurvey$ = this.configService.getFeatureFlag$( - FeatureFlag.AC1607_PresentUserOffboardingSurvey, - ); } ngOnDestroy() { @@ -278,7 +270,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy ); } - cancelWithOffboardingSurvey = async () => { + cancelSubscription = async () => { const reference = openOffboardingSurvey(this.dialogService, { data: { type: "Organization", @@ -295,36 +287,6 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy await this.load(); }; - cancelWithWarning = async () => { - if (this.loading) { - return; - } - - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "cancelSubscription" }, - content: { key: "cancelConfirmation" }, - type: "warning", - }); - - if (!confirmed) { - return; - } - - try { - await this.organizationApiService.cancel(this.organizationId); - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("canceledSubscription"), - ); - // 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 - this.load(); - } catch (e) { - this.logService.error(e); - } - }; - reinstate = async () => { if (this.loading) { return; diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts index 2ef6b327d3e..20ed3216a54 100644 --- a/libs/common/src/abstractions/api.service.ts +++ b/libs/common/src/abstractions/api.service.ts @@ -170,7 +170,6 @@ export abstract class ApiService { postRegister: (request: RegisterRequest) => Promise; postPremium: (data: FormData) => Promise; postReinstatePremium: () => Promise; - postCancelPremium: () => Promise; postAccountStorage: (request: StorageRequest) => Promise; postAccountPayment: (request: PaymentRequest) => Promise; postAccountLicense: (data: FormData) => Promise; diff --git a/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts b/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts index ddf2fc5c019..7f1a40d1404 100644 --- a/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts +++ b/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts @@ -51,7 +51,6 @@ export class OrganizationApiServiceAbstraction { updateSeats: (id: string, request: SeatRequest) => Promise; updateStorage: (id: string, request: StorageRequest) => Promise; verifyBank: (id: string, request: VerifyBankRequest) => Promise; - cancel: (id: string) => Promise; reinstate: (id: string) => Promise; leave: (id: string) => Promise; delete: (id: string, request: SecretVerificationRequest) => Promise; diff --git a/libs/common/src/admin-console/services/organization/organization-api.service.ts b/libs/common/src/admin-console/services/organization/organization-api.service.ts index 4e482112a8d..883bf352604 100644 --- a/libs/common/src/admin-console/services/organization/organization-api.service.ts +++ b/libs/common/src/admin-console/services/organization/organization-api.service.ts @@ -184,10 +184,6 @@ export class OrganizationApiService implements OrganizationApiServiceAbstraction ); } - async cancel(id: string): Promise { - return this.apiService.send("POST", "/organizations/" + id + "/cancel", null, true, false); - } - async reinstate(id: string): Promise { return this.apiService.send("POST", "/organizations/" + id + "/reinstate", null, true, false); } diff --git a/libs/common/src/billing/services/billing-api.service.ts b/libs/common/src/billing/services/billing-api.service.ts index 677e5006197..3d0ff550ea6 100644 --- a/libs/common/src/billing/services/billing-api.service.ts +++ b/libs/common/src/billing/services/billing-api.service.ts @@ -12,7 +12,7 @@ export class BillingApiService implements BillingApiServiceAbstraction { ): Promise { return this.apiService.send( "POST", - "/organizations/" + organizationId + "/churn", + "/organizations/" + organizationId + "/cancel", request, true, false, @@ -20,7 +20,7 @@ export class BillingApiService implements BillingApiServiceAbstraction { } cancelPremiumUserSubscription(request: SubscriptionCancellationRequest): Promise { - return this.apiService.send("POST", "/accounts/churn-premium", request, true, false); + return this.apiService.send("POST", "/accounts/cancel", request, true, false); } async getBillingStatus(id: string): Promise { diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 8ca80759b2b..8a5075e96f3 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -7,7 +7,6 @@ export enum FeatureFlag { GeneratorToolsModernization = "generator-tools-modernization", KeyRotationImprovements = "key-rotation-improvements", FlexibleCollectionsMigration = "flexible-collections-migration", - AC1607_PresentUserOffboardingSurvey = "AC-1607_present-user-offboarding-survey", ShowPaymentMethodWarningBanners = "show-payment-method-warning-banners", } diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index 2ed80cc342e..b6c2ab5c223 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -394,10 +394,6 @@ export class ApiService implements ApiServiceAbstraction { return this.send("POST", "/accounts/reinstate-premium", null, true, false); } - postCancelPremium(): Promise { - return this.send("POST", "/accounts/cancel-premium", null, true, false); - } - async postAccountStorage(request: StorageRequest): Promise { const r = await this.send("POST", "/accounts/storage", request, true, true); return new PaymentResponse(r); From d6fa7a4e467a96fa1296c5077ee818aa4f94fd58 Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Thu, 21 Mar 2024 20:55:46 +0100 Subject: [PATCH 2/3] [PM-6904] Update supported languages for Microsoft Store (Appx) (#8372) * Update locales used on browser, desktop and web * Update supported languages for the Microsoft Store --------- Co-authored-by: Daniel James Smith --- apps/desktop/electron-builder.json | 64 +++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/apps/desktop/electron-builder.json b/apps/desktop/electron-builder.json index afc51ef9fec..c726bada3c2 100644 --- a/apps/desktop/electron-builder.json +++ b/apps/desktop/electron-builder.json @@ -150,7 +150,69 @@ "identityName": "8bitSolutionsLLC.bitwardendesktop", "publisher": "CN=14D52771-DE3C-4886-B8BF-825BA7690418", "publisherDisplayName": "8bit Solutions LLC", - "languages": ["en-US"] + "languages": [ + "en-US", + "af", + "ar", + "az-latn", + "be", + "bg", + "bn", + "bs", + "ca", + "cs", + "cy", + "da", + "de", + "el", + "en-gb", + "en-in", + "es", + "et", + "eu", + "fa", + "fi", + "fil", + "fr", + "gl", + "he", + "hi", + "hr", + "hu", + "id", + "it", + "ja", + "ka", + "km", + "kn", + "ko", + "lt", + "lv", + "ml", + "mr", + "nb", + "ne", + "nl", + "nn", + "or", + "pl", + "pt-br", + "pt-pt", + "ro", + "ru", + "si", + "sk", + "sl", + "sr", + "sv", + "te", + "th", + "tr", + "uk", + "vi", + "zh-cn", + "zh-tw" + ] }, "deb": { "artifactName": "${productName}-${version}-${arch}.${ext}", From 6dc6aec88e3bcd607197cfaf517704b0b82df34d Mon Sep 17 00:00:00 2001 From: SmithThe4th Date: Thu, 21 Mar 2024 16:42:34 -0400 Subject: [PATCH 3/3] [PM-6199] VaultFilterService keeps decrypted collections in memory after logout (#8120) * updated vault filter service to make use of collection service * removed fix me * reverted to use reload collections on org vault as collection admin service does not support state management yet * fixed statement --- .../abstractions/vault-filter.service.ts | 4 ++- .../services/vault-filter.service.spec.ts | 27 +++++++++---------- .../services/vault-filter.service.ts | 27 +++++++------------ .../vault/individual-vault/vault.component.ts | 4 --- .../vault-filter/vault-filter.service.ts | 5 ++-- .../vault/abstractions/collection.service.ts | 2 ++ 6 files changed, 30 insertions(+), 39 deletions(-) diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/services/abstractions/vault-filter.service.ts b/apps/web/src/app/vault/individual-vault/vault-filter/services/abstractions/vault-filter.service.ts index 72be67f7a0a..3f8a0fb99d5 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/services/abstractions/vault-filter.service.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/services/abstractions/vault-filter.service.ts @@ -5,6 +5,7 @@ import { CollectionView } from "@bitwarden/common/src/vault/models/view/collecti import { FolderView } from "@bitwarden/common/src/vault/models/view/folder.view"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; +import { CollectionAdminView } from "../../../../core/views/collection-admin.view"; import { CipherTypeFilter, CollectionFilter, @@ -20,7 +21,6 @@ export abstract class VaultFilterService { folderTree$: Observable>; collectionTree$: Observable>; cipherTypeTree$: Observable>; - reloadCollections: (collections: CollectionView[]) => void; getCollectionNodeFromTree: (id: string) => Promise>; setCollapsedFilterNodes: (collapsedFilterNodes: Set) => Promise; expandOrgFilter: () => Promise; @@ -30,4 +30,6 @@ export abstract class VaultFilterService { head: CipherTypeFilter, array: CipherTypeFilter[], ) => Observable>; + // TODO: Remove this from org vault when collection admin service adopts state management + reloadCollections?: (collections: CollectionAdminView[]) => void; } diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.spec.ts b/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.spec.ts index 511da100b51..e5938b51979 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.spec.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.spec.ts @@ -15,6 +15,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { Utils } from "@bitwarden/common/platform/misc/utils"; import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view"; @@ -31,8 +32,10 @@ describe("vault filter service", () => { let cipherService: MockProxy; let policyService: MockProxy; let i18nService: MockProxy; + let collectionService: MockProxy; let organizations: ReplaySubject; let folderViews: ReplaySubject; + let collectionViews: ReplaySubject; let stateProvider: FakeStateProvider; const mockUserId = Utils.newGuid() as UserId; @@ -48,12 +51,15 @@ describe("vault filter service", () => { accountService = mockAccountServiceWith(mockUserId); stateProvider = new FakeStateProvider(accountService); i18nService.collator = new Intl.Collator("en-US"); + collectionService = mock(); organizations = new ReplaySubject(1); folderViews = new ReplaySubject(1); + collectionViews = new ReplaySubject(1); organizationService.memberOrganizations$ = organizations; folderService.folderViews$ = folderViews; + collectionService.decryptedCollections$ = collectionViews; vaultFilterService = new VaultFilterService( organizationService, @@ -62,6 +68,7 @@ describe("vault filter service", () => { policyService, i18nService, stateProvider, + collectionService, ); collapsedGroupingsState = stateProvider.activeUser.getFake(COLLAPSED_GROUPINGS); }); @@ -196,9 +203,7 @@ describe("vault filter service", () => { createCollectionView("1", "collection 1", "org test id"), createCollectionView("2", "collection 2", "non matching org id"), ]; - // 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 - vaultFilterService.reloadCollections(storedCollections); + collectionViews.next(storedCollections); await expect(firstValueFrom(vaultFilterService.filteredCollections$)).resolves.toEqual([ createCollectionView("1", "collection 1", "org test id"), @@ -213,9 +218,7 @@ describe("vault filter service", () => { createCollectionView("id-2", "Collection 1/Collection 2", "org test id"), createCollectionView("id-3", "Collection 1/Collection 3", "org test id"), ]; - // 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 - vaultFilterService.reloadCollections(storedCollections); + collectionViews.next(storedCollections); const result = await firstValueFrom(vaultFilterService.collectionTree$); @@ -228,9 +231,7 @@ describe("vault filter service", () => { createCollectionView("id-1", "Collection 1", "org test id"), createCollectionView("id-3", "Collection 1/Collection 2/Collection 3", "org test id"), ]; - // 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 - vaultFilterService.reloadCollections(storedCollections); + collectionViews.next(storedCollections); const result = await firstValueFrom(vaultFilterService.collectionTree$); @@ -246,9 +247,7 @@ describe("vault filter service", () => { createCollectionView("id-3", "Collection 1/Collection 2/Collection 3", "org test id"), createCollectionView("id-4", "Collection 1/Collection 4", "org test id"), ]; - // 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 - vaultFilterService.reloadCollections(storedCollections); + collectionViews.next(storedCollections); const result = await firstValueFrom(vaultFilterService.collectionTree$); @@ -266,9 +265,7 @@ describe("vault filter service", () => { createCollectionView("id-1", "Collection 1", "org test id"), createCollectionView("id-3", "Collection 1/Collection 2/Collection 3", "org test id"), ]; - // 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 - vaultFilterService.reloadCollections(storedCollections); + collectionViews.next(storedCollections); const result = await firstValueFrom(vaultFilterService.collectionTree$); diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts b/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts index 9dfc07b5db6..6bedac5bb67 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts @@ -1,13 +1,11 @@ import { Injectable } from "@angular/core"; import { BehaviorSubject, - combineLatest, combineLatestWith, firstValueFrom, map, Observable, of, - ReplaySubject, switchMap, } from "rxjs"; @@ -18,6 +16,7 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ActiveUserState, StateProvider } from "@bitwarden/common/platform/state"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; @@ -57,17 +56,14 @@ export class VaultFilterService implements VaultFilterServiceAbstraction { map((folders) => this.buildFolderTree(folders)), ); - // TODO: Remove once collections is refactored with observables - // replace with collection service observable - private collectionViews$ = new ReplaySubject(1); - filteredCollections$: Observable = combineLatest([ - this.collectionViews$, - this._organizationFilter, - ]).pipe( - switchMap(([collections, org]) => { - return this.filterCollections(collections, org); - }), - ); + filteredCollections$: Observable = + this.collectionService.decryptedCollections$.pipe( + combineLatestWith(this._organizationFilter), + switchMap(([collections, org]) => { + return this.filterCollections(collections, org); + }), + ); + collectionTree$: Observable> = this.filteredCollections$.pipe( map((collections) => this.buildCollectionTree(collections)), ); @@ -87,12 +83,9 @@ export class VaultFilterService implements VaultFilterServiceAbstraction { protected policyService: PolicyService, protected i18nService: I18nService, protected stateProvider: StateProvider, + protected collectionService: CollectionService, ) {} - async reloadCollections(collections: CollectionView[]) { - this.collectionViews$.next(collections); - } - async getCollectionNodeFromTree(id: string) { const collections = await firstValueFrom(this.collectionTree$); return ServiceUtils.getTreeNodeObject(collections, id) as TreeNode; 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 d7d9ab8074a..6fee59d65bf 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -406,10 +406,6 @@ export class VaultComponent implements OnInit, OnDestroy { (filter.organizationId === undefined || filter.organizationId === Unassigned); this.isEmpty = collections?.length === 0 && ciphers?.length === 0; - // This is a temporary fix to avoid double fetching collections. - // TODO: Remove when implementing new VVR menu - this.vaultFilterService.reloadCollections(allCollections); - this.performingInitialLoad = false; this.refreshing = false; }, diff --git a/apps/web/src/app/vault/org-vault/vault-filter/vault-filter.service.ts b/apps/web/src/app/vault/org-vault/vault-filter/vault-filter.service.ts index f82ba945e41..c6d4ee590b8 100644 --- a/apps/web/src/app/vault/org-vault/vault-filter/vault-filter.service.ts +++ b/apps/web/src/app/vault/org-vault/vault-filter/vault-filter.service.ts @@ -6,11 +6,11 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { StateProvider } from "@bitwarden/common/platform/state"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; import { CollectionAdminView } from "../../../vault/core/views/collection-admin.view"; -import { CollectionAdminService } from "../../core/collection-admin.service"; import { VaultFilterService as BaseVaultFilterService } from "../../individual-vault/vault-filter/services/vault-filter.service"; import { CollectionFilter } from "../../individual-vault/vault-filter/shared/models/vault-filter.type"; @@ -32,7 +32,7 @@ export class VaultFilterService extends BaseVaultFilterService implements OnDest policyService: PolicyService, i18nService: I18nService, stateProvider: StateProvider, - protected collectionAdminService: CollectionAdminService, + collectionService: CollectionService, ) { super( organizationService, @@ -41,6 +41,7 @@ export class VaultFilterService extends BaseVaultFilterService implements OnDest policyService, i18nService, stateProvider, + collectionService, ); } diff --git a/libs/common/src/vault/abstractions/collection.service.ts b/libs/common/src/vault/abstractions/collection.service.ts index ab606153292..87ce8edf435 100644 --- a/libs/common/src/vault/abstractions/collection.service.ts +++ b/libs/common/src/vault/abstractions/collection.service.ts @@ -7,6 +7,8 @@ import { TreeNode } from "../models/domain/tree-node"; import { CollectionView } from "../models/view/collection.view"; export abstract class CollectionService { + decryptedCollections$: Observable; + clearActiveUserCache: () => Promise; encrypt: (model: CollectionView) => Promise; decryptedCollectionViews$: (ids: CollectionId[]) => Observable;