1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-14 15:23:33 +00:00

[PM-26628] Refresh inactive subscription warning after restarting subscription (#16791)

* Refresh inactive subscription warning after restarting subscription

* Fix tests
This commit is contained in:
Alex Morask
2025-10-08 13:14:29 -05:00
committed by Alex Morask
parent ff3245aaaa
commit b396b6bafb
3 changed files with 75 additions and 73 deletions

View File

@@ -50,6 +50,7 @@ import {
SubscriberBillingClient, SubscriberBillingClient,
TaxClient, TaxClient,
} from "@bitwarden/web-vault/app/billing/clients"; } from "@bitwarden/web-vault/app/billing/clients";
import { OrganizationWarningsService } from "@bitwarden/web-vault/app/billing/organizations/warnings/services";
import { import {
EnterBillingAddressComponent, EnterBillingAddressComponent,
EnterPaymentMethodComponent, EnterPaymentMethodComponent,
@@ -221,6 +222,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
private billingNotificationService: BillingNotificationService, private billingNotificationService: BillingNotificationService,
private subscriberBillingClient: SubscriberBillingClient, private subscriberBillingClient: SubscriberBillingClient,
private taxClient: TaxClient, private taxClient: TaxClient,
private organizationWarningsService: OrganizationWarningsService,
) {} ) {}
async ngOnInit(): Promise<void> { async ngOnInit(): Promise<void> {
@@ -808,6 +810,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
paymentMethod, paymentMethod,
billingAddress, billingAddress,
); );
this.organizationWarningsService.refreshInactiveSubscriptionWarning();
} }
private async updateOrganization() { private async updateOrganization() {

View File

@@ -421,11 +421,9 @@ describe("OrganizationWarningsService", () => {
it("should not show dialog when no inactive subscription warning exists", (done) => { it("should not show dialog when no inactive subscription warning exists", (done) => {
organizationBillingClient.getWarnings.mockResolvedValue({} as OrganizationWarningsResponse); organizationBillingClient.getWarnings.mockResolvedValue({} as OrganizationWarningsResponse);
service.showInactiveSubscriptionDialog$(organization).subscribe({ service.showInactiveSubscriptionDialog$(organization).subscribe(() => {
complete: () => { expect(dialogService.openSimpleDialog).not.toHaveBeenCalled();
expect(dialogService.openSimpleDialog).not.toHaveBeenCalled(); done();
done();
},
}); });
}); });
@@ -437,20 +435,18 @@ describe("OrganizationWarningsService", () => {
dialogService.openSimpleDialog.mockResolvedValue(true); dialogService.openSimpleDialog.mockResolvedValue(true);
service.showInactiveSubscriptionDialog$(organization).subscribe({ service.showInactiveSubscriptionDialog$(organization).subscribe(() => {
complete: () => { expect(dialogService.openSimpleDialog).toHaveBeenCalledWith({
expect(dialogService.openSimpleDialog).toHaveBeenCalledWith({ title: "Test Organization subscription suspended",
title: "Test Organization subscription suspended", content: {
content: { key: "suspendedManagedOrgMessage",
key: "suspendedManagedOrgMessage", placeholders: ["Test Reseller Inc"],
placeholders: ["Test Reseller Inc"], },
}, type: "danger",
type: "danger", acceptButtonText: "Close",
acceptButtonText: "Close", cancelButtonText: null,
cancelButtonText: null, });
}); done();
done();
},
}); });
}); });
@@ -463,21 +459,19 @@ describe("OrganizationWarningsService", () => {
dialogService.openSimpleDialog.mockResolvedValue(true); dialogService.openSimpleDialog.mockResolvedValue(true);
router.navigate.mockResolvedValue(true); router.navigate.mockResolvedValue(true);
service.showInactiveSubscriptionDialog$(organization).subscribe({ service.showInactiveSubscriptionDialog$(organization).subscribe(() => {
complete: () => { expect(dialogService.openSimpleDialog).toHaveBeenCalledWith({
expect(dialogService.openSimpleDialog).toHaveBeenCalledWith({ title: "Test Organization subscription suspended",
title: "Test Organization subscription suspended", content: { key: "suspendedOwnerOrgMessage" },
content: { key: "suspendedOwnerOrgMessage" }, type: "danger",
type: "danger", acceptButtonText: "Continue",
acceptButtonText: "Continue", cancelButtonText: "Close",
cancelButtonText: "Close", });
}); expect(router.navigate).toHaveBeenCalledWith(
expect(router.navigate).toHaveBeenCalledWith( ["organizations", "org-id-123", "billing", "payment-details"],
["organizations", "org-id-123", "billing", "payment-details"], { state: { launchPaymentModalAutomatically: true } },
{ state: { launchPaymentModalAutomatically: true } }, );
); done();
done();
},
}); });
}); });
@@ -490,14 +484,12 @@ describe("OrganizationWarningsService", () => {
dialogService.openSimpleDialog.mockResolvedValue(true); dialogService.openSimpleDialog.mockResolvedValue(true);
router.navigate.mockResolvedValue(true); router.navigate.mockResolvedValue(true);
service.showInactiveSubscriptionDialog$(organization).subscribe({ service.showInactiveSubscriptionDialog$(organization).subscribe(() => {
complete: () => { expect(router.navigate).toHaveBeenCalledWith(
expect(router.navigate).toHaveBeenCalledWith( ["organizations", "org-id-123", "billing", "payment-details"],
["organizations", "org-id-123", "billing", "payment-details"], { state: { launchPaymentModalAutomatically: true } },
{ state: { launchPaymentModalAutomatically: true } }, );
); done();
done();
},
}); });
}); });
@@ -509,12 +501,10 @@ describe("OrganizationWarningsService", () => {
dialogService.openSimpleDialog.mockResolvedValue(false); dialogService.openSimpleDialog.mockResolvedValue(false);
service.showInactiveSubscriptionDialog$(organization).subscribe({ service.showInactiveSubscriptionDialog$(organization).subscribe(() => {
complete: () => { expect(dialogService.openSimpleDialog).toHaveBeenCalled();
expect(dialogService.openSimpleDialog).toHaveBeenCalled(); expect(router.navigate).not.toHaveBeenCalled();
expect(router.navigate).not.toHaveBeenCalled(); done();
done();
},
}); });
}); });
@@ -534,18 +524,16 @@ describe("OrganizationWarningsService", () => {
(openChangePlanDialog as jest.Mock).mockReturnValue(mockDialogRef); (openChangePlanDialog as jest.Mock).mockReturnValue(mockDialogRef);
service.showInactiveSubscriptionDialog$(organization).subscribe({ service.showInactiveSubscriptionDialog$(organization).subscribe(() => {
complete: () => { expect(organizationApiService.getSubscription).toHaveBeenCalledWith(organization.id);
expect(organizationApiService.getSubscription).toHaveBeenCalledWith(organization.id); expect(openChangePlanDialog).toHaveBeenCalledWith(dialogService, {
expect(openChangePlanDialog).toHaveBeenCalledWith(dialogService, { data: {
data: { organizationId: organization.id,
organizationId: organization.id, subscription: subscription,
subscription: subscription, productTierType: organization.productTierType,
productTierType: organization.productTierType, },
}, });
}); done();
done();
},
}); });
}); });
@@ -557,17 +545,15 @@ describe("OrganizationWarningsService", () => {
dialogService.openSimpleDialog.mockResolvedValue(true); dialogService.openSimpleDialog.mockResolvedValue(true);
service.showInactiveSubscriptionDialog$(organization).subscribe({ service.showInactiveSubscriptionDialog$(organization).subscribe(() => {
complete: () => { expect(dialogService.openSimpleDialog).toHaveBeenCalledWith({
expect(dialogService.openSimpleDialog).toHaveBeenCalledWith({ title: "Test Organization subscription suspended",
title: "Test Organization subscription suspended", content: { key: "suspendedUserOrgMessage" },
content: { key: "suspendedUserOrgMessage" }, type: "danger",
type: "danger", acceptButtonText: "Close",
acceptButtonText: "Close", cancelButtonText: null,
cancelButtonText: null, });
}); done();
done();
},
}); });
}); });
}); });

View File

@@ -46,6 +46,7 @@ export class OrganizationWarningsService {
private refreshFreeTrialWarningTrigger = new Subject<void>(); private refreshFreeTrialWarningTrigger = new Subject<void>();
private refreshTaxIdWarningTrigger = new Subject<void>(); private refreshTaxIdWarningTrigger = new Subject<void>();
private refreshInactiveSubscriptionWarningTrigger = new Subject<void>();
private taxIdWarningRefreshedSubject = new BehaviorSubject<TaxIdWarningType | null>(null); private taxIdWarningRefreshedSubject = new BehaviorSubject<TaxIdWarningType | null>(null);
taxIdWarningRefreshed$ = this.taxIdWarningRefreshedSubject.asObservable(); taxIdWarningRefreshed$ = this.taxIdWarningRefreshedSubject.asObservable();
@@ -164,12 +165,24 @@ export class OrganizationWarningsService {
refreshFreeTrialWarning = () => this.refreshFreeTrialWarningTrigger.next(); refreshFreeTrialWarning = () => this.refreshFreeTrialWarningTrigger.next();
refreshInactiveSubscriptionWarning = () => this.refreshInactiveSubscriptionWarningTrigger.next();
refreshTaxIdWarning = () => this.refreshTaxIdWarningTrigger.next(); refreshTaxIdWarning = () => this.refreshTaxIdWarningTrigger.next();
showInactiveSubscriptionDialog$ = (organization: Organization): Observable<void> => showInactiveSubscriptionDialog$ = (organization: Organization): Observable<void> =>
this.getWarning$(organization, (response) => response.inactiveSubscription).pipe( merge(
filter((warning) => warning !== null), this.getWarning$(organization, (response) => response.inactiveSubscription),
this.refreshInactiveSubscriptionWarningTrigger.pipe(
switchMap(() =>
this.getWarning$(organization, (response) => response.inactiveSubscription, true),
),
),
).pipe(
switchMap(async (warning) => { switchMap(async (warning) => {
if (!warning) {
return;
}
switch (warning.resolution) { switch (warning.resolution) {
case "contact_provider": { case "contact_provider": {
await this.dialogService.openSimpleDialog({ await this.dialogService.openSimpleDialog({