diff --git a/static/gsAdmin/components/changeDatesAction.spec.tsx b/static/gsAdmin/components/changeDatesAction.spec.tsx new file mode 100644 index 00000000000000..ca5287882621c3 --- /dev/null +++ b/static/gsAdmin/components/changeDatesAction.spec.tsx @@ -0,0 +1,80 @@ +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 triggerChangeDatesModal from 'admin/components/changeDatesAction'; + +describe('ChangeDatesAction', () => { + const organization = OrganizationFixture(); + const subscription = SubscriptionFixture({ + organization, + onDemandPeriodStart: '2024-01-01', + onDemandPeriodEnd: '2024-02-01', + contractPeriodStart: '2024-03-01', + contractPeriodEnd: '2024-04-01', + }); + + const onSuccess = jest.fn(); + + const modalProps = { + orgId: organization.slug, + onSuccess, + subscription, + }; + + beforeEach(() => { + MockApiClient.clearMockResponses(); + ModalStore.reset(); + jest.clearAllMocks(); + }); + + async function updateDateField(label: string, value: string) { + const input = await screen.findByLabelText(label); + await userEvent.click(input); + await userEvent.keyboard('{Control>}a{/Control}'); + await userEvent.keyboard(value); + } + + it('submits updated contract and on-demand dates', async () => { + const updateMock = MockApiClient.addMockResponse({ + url: `/customers/${organization.slug}/`, + method: 'PUT', + body: {}, + }); + + triggerChangeDatesModal(modalProps); + const {waitForModalToHide} = renderGlobalModal(); + + await updateDateField('Contract Period End Date', '2024-05-15'); + await updateDateField('On-Demand Period End Date', '2024-02-10'); + + await userEvent.click(screen.getByRole('button', {name: 'Submit'})); + + await waitForModalToHide(); + + await waitFor(() => { + expect(updateMock).toHaveBeenCalledWith( + `/customers/${organization.slug}/`, + expect.objectContaining({ + method: 'PUT', + data: { + onDemandPeriodStart: '2024-01-01', + onDemandPeriodEnd: '2024-02-10', + contractPeriodStart: '2024-03-01', + contractPeriodEnd: '2024-05-15', + }, + }) + ); + }); + + expect(onSuccess).toHaveBeenCalled(); + }); +}); diff --git a/static/gsAdmin/components/changeDatesAction.tsx b/static/gsAdmin/components/changeDatesAction.tsx index fcc36b4cc6530e..6b5492eb3ce4a7 100644 --- a/static/gsAdmin/components/changeDatesAction.tsx +++ b/static/gsAdmin/components/changeDatesAction.tsx @@ -1,31 +1,24 @@ import {Fragment} from 'react'; +import styled from '@emotion/styled'; + +import {Heading} from '@sentry/scraps/text'; import {addSuccessMessage} from 'sentry/actionCreators/indicator'; import {openModal, type ModalRenderProps} from 'sentry/actionCreators/modal'; import {Alert} from 'sentry/components/core/alert'; -import {DateTimeField} from 'sentry/components/deprecatedforms/dateTimeField'; -import Form from 'sentry/components/deprecatedforms/form'; -import withFormContext from 'sentry/components/deprecatedforms/withFormContext'; -import useApi from 'sentry/utils/useApi'; +import InputField from 'sentry/components/forms/fields/inputField'; +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 ChangeDatesModalProps extends ModalRenderProps { onSuccess: () => void; orgId: string; subscription: Subscription; -}; - -type ModalProps = Props & ModalRenderProps; - -class DateFieldNoContext extends DateTimeField { - getType() { - return 'date'; - } } -const DateField = withFormContext(DateFieldNoContext); - function ChangeDatesModal({ orgId, subscription, @@ -33,12 +26,23 @@ function ChangeDatesModal({ closeModal, Header, Body, -}: ModalProps) { - const api = useApi(); +}: ChangeDatesModalProps) { + const {mutateAsync: updateSubscriptionDates, isPending: isUpdating} = useMutation< + Record, + unknown, + Record + >({ + mutationFn: (payload: Record) => + fetchMutation({ + url: `/customers/${orgId}/`, + method: 'PUT', + data: payload, + }), + }); - async function onSubmit(formData: any, _onSubmitSuccess: unknown, onSubmitError: any) { + const onSubmit: OnSubmitCallback = async (formData, onSubmitSuccess, onSubmitError) => { try { - const postData = { + const postData: Record = { onDemandPeriodStart: subscription.onDemandPeriodStart, onDemandPeriodEnd: subscription.onDemandPeriodEnd, contractPeriodStart: subscription.contractPeriodStart, @@ -47,68 +51,73 @@ function ChangeDatesModal({ for (const k in formData) { if (formData[k] !== '' && formData[k]) { - // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message postData[k] = formData[k]; } } - await api.requestPromise(`/customers/${orgId}/`, { - method: 'PUT', - data: postData, - success: () => { - addSuccessMessage('Contract and on-demand period dates updated'); - onSuccess(); - }, - }); + const response = await updateSubscriptionDates(postData); + addSuccessMessage('Contract and on-demand period dates updated'); + onSubmitSuccess(response); + onSuccess(); closeModal(); } catch (err: any) { onSubmitError({ responseJSON: err.responseJSON, }); } - } + }; return ( -
Change Contract and Current On-Demand Period Dates
+
+ Change Contract and Current On-Demand Period Dates +
- + This overrides the current contract and on-demand period dates so the subscription may fall into a weird state. -

To end the contract period immediately, use the End Period Now action.

+

+ To end the contract period immediately, use the "End Billing Period + Immediately" action. +

@@ -116,9 +125,13 @@ function ChangeDatesModal({ ); } -type Options = Pick; +type Options = Omit; const triggerChangeDatesModal = (opts: Options) => openModal(deps => ); export default triggerChangeDatesModal; + +const DateField = styled(InputField)` + padding-left: 0px; +`;