From 7cfc4d1bf08c8c9546290af0e783a8023d712b04 Mon Sep 17 00:00:00 2001 From: Scott Cooper Date: Mon, 17 Nov 2025 16:34:05 -0800 Subject: [PATCH 1/2] ref(admin): Migrate end immediate action --- .../nextBillingPeriodAction.spec.tsx | 58 ++++++++++++++++ .../components/nextBillingPeriodAction.tsx | 68 ++++++++++++------- 2 files changed, 102 insertions(+), 24 deletions(-) create mode 100644 static/gsAdmin/components/nextBillingPeriodAction.spec.tsx diff --git a/static/gsAdmin/components/nextBillingPeriodAction.spec.tsx b/static/gsAdmin/components/nextBillingPeriodAction.spec.tsx new file mode 100644 index 00000000000000..b48f6f4fb94a99 --- /dev/null +++ b/static/gsAdmin/components/nextBillingPeriodAction.spec.tsx @@ -0,0 +1,58 @@ +import {OrganizationFixture} from 'sentry-fixture/organization'; + +import {SubscriptionFixture} from 'getsentry-test/fixtures/subscription'; +import { + renderGlobalModal, + screen, + userEvent, + waitFor, +} from 'sentry-test/reactTestingLibrary'; + +import ModalStore from 'sentry/stores/modalStore'; + +import triggerEndPeriodEarlyModal from 'admin/components/nextBillingPeriodAction'; + +describe('NextBillingPeriodAction', () => { + const organization = OrganizationFixture(); + const subscription = SubscriptionFixture({organization}); + const onSuccess = jest.fn(); + + const modalProps = { + orgId: organization.slug, + onSuccess, + subscription, + }; + + beforeEach(() => { + MockApiClient.clearMockResponses(); + ModalStore.reset(); + jest.clearAllMocks(); + }); + + it('ends the current period immediately', async () => { + const updateMock = MockApiClient.addMockResponse({ + url: `/customers/${organization.slug}/`, + method: 'PUT', + body: {}, + }); + + triggerEndPeriodEarlyModal(modalProps); + const {waitForModalToHide} = renderGlobalModal(); + + await userEvent.click(screen.getByRole('button', {name: 'Submit'})); + + await waitForModalToHide(); + + await waitFor(() => { + expect(updateMock).toHaveBeenCalledWith( + `/customers/${organization.slug}/`, + expect.objectContaining({ + method: 'PUT', + data: {endPeriodEarly: true}, + }) + ); + }); + + expect(onSuccess).toHaveBeenCalled(); + }); +}); diff --git a/static/gsAdmin/components/nextBillingPeriodAction.tsx b/static/gsAdmin/components/nextBillingPeriodAction.tsx index 7c38105a805ba0..5a044610f0157a 100644 --- a/static/gsAdmin/components/nextBillingPeriodAction.tsx +++ b/static/gsAdmin/components/nextBillingPeriodAction.tsx @@ -1,56 +1,76 @@ import {Fragment} from 'react'; +import {Heading} from '@sentry/scraps/text'; + import {addSuccessMessage} from 'sentry/actionCreators/indicator'; import {openModal, type ModalRenderProps} from 'sentry/actionCreators/modal'; -import Form from 'sentry/components/deprecatedforms/form'; -import useApi from 'sentry/utils/useApi'; +import {Alert} from 'sentry/components/core/alert'; +import Form from 'sentry/components/forms/form'; +import type {OnSubmitCallback} from 'sentry/components/forms/types'; +import {fetchMutation, useMutation} from 'sentry/utils/queryClient'; import type {Subscription} from 'getsentry/types'; -type Props = { +interface EndPeriodEarlyModalProps extends ModalRenderProps { onSuccess: () => void; orgId: string; subscription: Subscription; -}; - -type ModalProps = Props & ModalRenderProps; +} -function EndPeriodEarlyModal({orgId, onSuccess, closeModal, Header, Body}: ModalProps) { - const api = useApi(); +function EndPeriodEarlyModal({ + orgId, + onSuccess, + closeModal, + Header, + Body, +}: EndPeriodEarlyModalProps) { + const {mutateAsync: endPeriodEarly, isPending} = useMutation({ + mutationFn: () => + fetchMutation({ + url: `/customers/${orgId}/`, + method: 'PUT', + data: {endPeriodEarly: true}, + }), + }); - async function onSubmit(_: any, _onSubmitSuccess: any, onSubmitError: any) { + const onSubmit: OnSubmitCallback = async ( + _formData, + onSubmitSuccess, + onSubmitError + ) => { try { - const postData = { - endPeriodEarly: true, - }; - - await api.requestPromise(`/customers/${orgId}/`, { - method: 'PUT', - data: postData, - success: () => { - addSuccessMessage('Currrent period ended successfully'); - onSuccess(); - }, - }); + const response = await endPeriodEarly(); + addSuccessMessage('Current period ended successfully'); + onSubmitSuccess(response); + onSuccess(); closeModal(); } catch (err: any) { onSubmitError({ responseJSON: err.responseJSON, }); } - } + }; return ( -
End Current Period Immediately
+
+ End Current Period Immediately +
+ + + Ending the current billing period will immediately start the next billing + cycle and may impact invoicing and usage proration. + +

End the current billing period immediately and start a new one.

@@ -58,7 +78,7 @@ function EndPeriodEarlyModal({orgId, onSuccess, closeModal, Header, Body}: Modal ); } -type Options = Pick; +type Options = Omit; const triggerEndPeriodEarlyModal = (opts: Options) => openModal(deps => ); From ac4974e322c4438b5130c398aae5879471273f0c Mon Sep 17 00:00:00 2001 From: Scott Cooper Date: Mon, 17 Nov 2025 16:34:14 -0800 Subject: [PATCH 2/2] whoops --- static/gsAdmin/components/nextBillingPeriodAction.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/gsAdmin/components/nextBillingPeriodAction.tsx b/static/gsAdmin/components/nextBillingPeriodAction.tsx index 5a044610f0157a..d8b32eaaee37c3 100644 --- a/static/gsAdmin/components/nextBillingPeriodAction.tsx +++ b/static/gsAdmin/components/nextBillingPeriodAction.tsx @@ -24,7 +24,7 @@ function EndPeriodEarlyModal({ Header, Body, }: EndPeriodEarlyModalProps) { - const {mutateAsync: endPeriodEarly, isPending} = useMutation({ + const {mutateAsync: endPeriodEarly, isPending} = useMutation({ mutationFn: () => fetchMutation({ url: `/customers/${orgId}/`,