From be8c5f28b5807c9682abd652294a4b3746cf5892 Mon Sep 17 00:00:00 2001 From: Jonas Hendrickx Date: Wed, 26 Mar 2025 17:59:27 +0100 Subject: [PATCH 1/2] [PM-18170] Remove 'PM-15814-alert-owners-of-reseller-managed-orgs' feature flag (#13757) --- .../organizations/collections/vault.component.ts | 8 +------- .../src/app/billing/services/trial-flow.service.ts | 13 ++----------- libs/common/src/enums/feature-flag.enum.ts | 2 -- 3 files changed, 3 insertions(+), 20 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/collections/vault.component.ts b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts index ec92597dc7b..8dfebea5229 100644 --- a/apps/web/src/app/admin-console/organizations/collections/vault.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts @@ -45,7 +45,6 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions"; 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"; @@ -196,7 +195,6 @@ export class VaultComponent implements OnInit, OnDestroy { private refresh$ = new BehaviorSubject(null); private destroy$ = new Subject(); protected addAccessStatus$ = new BehaviorSubject(0); - private resellerManagedOrgAlert: boolean; private vaultItemDialogRef?: DialogRef | undefined; private readonly unpaidSubscriptionDialog$ = this.accountService.activeAccount$.pipe( @@ -264,10 +262,6 @@ export class VaultComponent implements OnInit, OnDestroy { async ngOnInit() { this.userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); - this.resellerManagedOrgAlert = await this.configService.getFeatureFlag( - FeatureFlag.ResellerManagedOrgAlert, - ); - this.trashCleanupWarning = this.i18nService.t( this.platformUtilsService.isSelfHost() ? "trashCleanupWarningSelfHosted" @@ -654,7 +648,7 @@ export class VaultComponent implements OnInit, OnDestroy { ); this.resellerWarning$ = organization$.pipe( - filter((org) => org.isOwner && this.resellerManagedOrgAlert), + filter((org) => org.isOwner), switchMap((org) => from(this.billingApiService.getOrganizationBillingMetadata(org.id)).pipe( map((metadata) => ({ org, metadata })), diff --git a/apps/web/src/app/billing/services/trial-flow.service.ts b/apps/web/src/app/billing/services/trial-flow.service.ts index eb08e5bd7ad..979fc29aed7 100644 --- a/apps/web/src/app/billing/services/trial-flow.service.ts +++ b/apps/web/src/app/billing/services/trial-flow.service.ts @@ -11,8 +11,6 @@ import { BillingSourceResponse } from "@bitwarden/common/billing/models/response import { OrganizationBillingMetadataResponse } from "@bitwarden/common/billing/models/response/organization-billing-metadata.response"; import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response"; import { PaymentSourceResponse } from "@bitwarden/common/billing/models/response/payment-source.response"; -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 { DialogService } from "@bitwarden/components"; @@ -24,15 +22,12 @@ import { FreeTrial } from "../types/free-trial"; @Injectable({ providedIn: "root" }) export class TrialFlowService { - private resellerManagedOrgAlert: boolean; - constructor( private i18nService: I18nService, protected dialogService: DialogService, private router: Router, protected billingApiService: BillingApiServiceAbstraction, private organizationApiService: OrganizationApiServiceAbstraction, - private configService: ConfigService, ) {} checkForOrgsWithUpcomingPaymentIssues( organization: Organization, @@ -98,10 +93,6 @@ export class TrialFlowService { isCanceled: boolean, isUnpaid: boolean, ): Promise { - this.resellerManagedOrgAlert = await this.configService.getFeatureFlag( - FeatureFlag.ResellerManagedOrgAlert, - ); - if (!org?.isOwner && !org.providerId) { await this.dialogService.openSimpleDialog({ title: this.i18nService.t("suspendedOrganizationTitle", org?.name), @@ -113,7 +104,7 @@ export class TrialFlowService { return false; } - if (org.providerId && this.resellerManagedOrgAlert) { + if (org.providerId) { await this.dialogService.openSimpleDialog({ title: this.i18nService.t("suspendedOrganizationTitle", org.name), content: { key: "suspendedManagedOrgMessage", placeholders: [org.providerName] }, @@ -134,7 +125,7 @@ export class TrialFlowService { }); } - if (org.isOwner && isCanceled && this.resellerManagedOrgAlert) { + if (org.isOwner && isCanceled) { await this.changePlan(org); } } diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 5e52eb31840..e3c15a1d40b 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -46,7 +46,6 @@ export enum FeatureFlag { TrialPaymentOptional = "PM-8163-trial-payment", MacOsNativeCredentialSync = "macos-native-credential-sync", PrivateKeyRegeneration = "pm-12241-private-key-regeneration", - ResellerManagedOrgAlert = "PM-15814-alert-owners-of-reseller-managed-orgs", AccountDeprovisioningBanner = "pm-17120-account-deprovisioning-admin-console-banner", PM15179_AddExistingOrgsFromProviderPortal = "pm-15179-add-existing-orgs-from-provider-portal", PM12276_BreadcrumbEventLogs = "pm-12276-breadcrumbing-for-business-features", @@ -107,7 +106,6 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.TrialPaymentOptional]: FALSE, [FeatureFlag.MacOsNativeCredentialSync]: FALSE, [FeatureFlag.PrivateKeyRegeneration]: FALSE, - [FeatureFlag.ResellerManagedOrgAlert]: FALSE, [FeatureFlag.AccountDeprovisioningBanner]: FALSE, [FeatureFlag.PM15179_AddExistingOrgsFromProviderPortal]: FALSE, [FeatureFlag.PM12276_BreadcrumbEventLogs]: FALSE, From a3e01ad672575532c8eac4187562d66663db2ad1 Mon Sep 17 00:00:00 2001 From: Jason Ng Date: Wed, 26 Mar 2025 13:16:40 -0400 Subject: [PATCH 2/2] [PM-10610] push notification to end user notification service (#13876) * use NotificationsService.notifictions$ for tracking inside default end user notification --- .../src/services/jslib-services.module.ts | 2 +- .../src/enums/notification-type.enum.ts | 2 ++ .../internal/noop-notifications.service.ts | 7 ++++- .../notifications/notifications.service.ts | 6 +++- .../end-user-notification.service.ts | 7 ----- ...ault-end-user-notification.service.spec.ts | 9 +++++- .../default-end-user-notification.service.ts | 28 +++++++++++++++---- 7 files changed, 45 insertions(+), 16 deletions(-) diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 3a28f28caaf..37220b5195d 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -1480,7 +1480,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: EndUserNotificationService, useClass: DefaultEndUserNotificationService, - deps: [StateProvider, ApiServiceAbstraction], + deps: [StateProvider, ApiServiceAbstraction, NotificationsService], }), safeProvider({ provide: DeviceTrustToastServiceAbstraction, diff --git a/libs/common/src/enums/notification-type.enum.ts b/libs/common/src/enums/notification-type.enum.ts index db59fcafa69..c366af1eb61 100644 --- a/libs/common/src/enums/notification-type.enum.ts +++ b/libs/common/src/enums/notification-type.enum.ts @@ -24,4 +24,6 @@ export enum NotificationType { SyncOrganizations = 17, SyncOrganizationStatusChanged = 18, SyncOrganizationCollectionSettingChanged = 19, + Notification = 20, + NotificationStatus = 21, } diff --git a/libs/common/src/platform/notifications/internal/noop-notifications.service.ts b/libs/common/src/platform/notifications/internal/noop-notifications.service.ts index f79cabfca8a..9c2435fb1a7 100644 --- a/libs/common/src/platform/notifications/internal/noop-notifications.service.ts +++ b/libs/common/src/platform/notifications/internal/noop-notifications.service.ts @@ -1,9 +1,14 @@ -import { Subscription } from "rxjs"; +import { Observable, Subject, Subscription } from "rxjs"; + +import { NotificationResponse } from "@bitwarden/common/models/response/notification.response"; +import { UserId } from "@bitwarden/common/types/guid"; import { LogService } from "../../abstractions/log.service"; import { NotificationsService } from "../notifications.service"; export class NoopNotificationsService implements NotificationsService { + notifications$: Observable = new Subject(); + constructor(private logService: LogService) {} startListening(): Subscription { diff --git a/libs/common/src/platform/notifications/notifications.service.ts b/libs/common/src/platform/notifications/notifications.service.ts index aa4ff2a57a6..2adc66e361f 100644 --- a/libs/common/src/platform/notifications/notifications.service.ts +++ b/libs/common/src/platform/notifications/notifications.service.ts @@ -1,9 +1,13 @@ -import { Subscription } from "rxjs"; +import { Observable, Subscription } from "rxjs"; + +import { NotificationResponse } from "@bitwarden/common/models/response/notification.response"; +import { UserId } from "@bitwarden/common/types/guid"; /** * A service offering abilities to interact with push notifications from the server. */ export abstract class NotificationsService { + abstract notifications$: Observable; /** * Starts automatic listening and processing of notifications, should only be called once per application, * or you will risk notifications being processed multiple times. diff --git a/libs/vault/src/notifications/abstractions/end-user-notification.service.ts b/libs/vault/src/notifications/abstractions/end-user-notification.service.ts index 2ed7e1de631..fe2852994f7 100644 --- a/libs/vault/src/notifications/abstractions/end-user-notification.service.ts +++ b/libs/vault/src/notifications/abstractions/end-user-notification.service.ts @@ -34,13 +34,6 @@ export abstract class EndUserNotificationService { */ abstract markAsDeleted(notificationId: any, userId: UserId): Promise; - /** - * Create/update a notification in the state for the user specified within the notification. - * @remarks This method should only be called when a notification payload is received from the web socket. - * @param notification - */ - abstract upsert(notification: Notification): Promise; - /** * Clear all notifications from state for the given user. * @param userId diff --git a/libs/vault/src/notifications/services/default-end-user-notification.service.spec.ts b/libs/vault/src/notifications/services/default-end-user-notification.service.spec.ts index ac4304998bc..1d7b2e5aa19 100644 --- a/libs/vault/src/notifications/services/default-end-user-notification.service.spec.ts +++ b/libs/vault/src/notifications/services/default-end-user-notification.service.spec.ts @@ -1,7 +1,8 @@ import { TestBed } from "@angular/core/testing"; -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, of } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { NotificationsService } from "@bitwarden/common/platform/notifications"; import { StateProvider } from "@bitwarden/common/platform/state"; import { NotificationId, UserId } from "@bitwarden/common/types/guid"; import { DefaultEndUserNotificationService } from "@bitwarden/vault"; @@ -36,6 +37,12 @@ describe("End User Notification Center Service", () => { send: mockApiSend, }, }, + { + provide: NotificationsService, + useValue: { + notifications$: of(null), + }, + }, ], }); }); diff --git a/libs/vault/src/notifications/services/default-end-user-notification.service.ts b/libs/vault/src/notifications/services/default-end-user-notification.service.ts index 517a968f8af..404cb7c75c7 100644 --- a/libs/vault/src/notifications/services/default-end-user-notification.service.ts +++ b/libs/vault/src/notifications/services/default-end-user-notification.service.ts @@ -1,8 +1,10 @@ import { Injectable } from "@angular/core"; -import { map, Observable, switchMap } from "rxjs"; +import { concatMap, filter, map, Observable, switchMap } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { NotificationType } from "@bitwarden/common/enums"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; +import { NotificationsService } from "@bitwarden/common/platform/notifications"; import { StateProvider } from "@bitwarden/common/platform/state"; import { UserId } from "@bitwarden/common/types/guid"; @@ -14,12 +16,30 @@ import { NOTIFICATIONS } from "../state/end-user-notification.state"; /** * A service for retrieving and managing notifications for end users. */ -@Injectable() +@Injectable({ + providedIn: "root", +}) export class DefaultEndUserNotificationService implements EndUserNotificationService { constructor( private stateProvider: StateProvider, private apiService: ApiService, - ) {} + private defaultNotifications: NotificationsService, + ) { + this.defaultNotifications.notifications$ + .pipe( + filter( + ([notification]) => + notification.type === NotificationType.Notification || + notification.type === NotificationType.NotificationStatus, + ), + concatMap(([notification, userId]) => + this.updateNotificationState(userId, [ + new NotificationViewData(notification.payload as NotificationViewResponse), + ]), + ), + ) + .subscribe(); + } notifications$ = perUserCache$((userId: UserId): Observable => { return this.notificationState(userId).state$.pipe( @@ -58,8 +78,6 @@ export class DefaultEndUserNotificationService implements EndUserNotificationSer await this.getNotifications(userId); } - upsert(notification: Notification): any {} - async clearState(userId: UserId): Promise { await this.updateNotificationState(userId, []); }