Skip to content

Commit fed6c09

Browse files
authored
ref(admin): Migrate ChangeDatesModal away from deprecated form (#103499)
Migrate admin action away from the deprecated forms. before <img width="607" height="714" alt="Screenshot 2025-11-17 at 4 01 19 PM" src="https://github.com/user-attachments/assets/ba76b2ae-5813-4aac-8103-d34578573891" /> after <img width="634" height="622" alt="Screenshot 2025-11-17 at 4 10 54 PM" src="https://github.com/user-attachments/assets/17991671-7467-4452-a214-7a7dfc8e7ba9" />
1 parent c4928be commit fed6c09

File tree

2 files changed

+126
-33
lines changed

2 files changed

+126
-33
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import {OrganizationFixture} from 'sentry-fixture/organization';
2+
3+
import {SubscriptionFixture} from 'getsentry-test/fixtures/subscription';
4+
import {
5+
renderGlobalModal,
6+
screen,
7+
userEvent,
8+
waitFor,
9+
} from 'sentry-test/reactTestingLibrary';
10+
11+
import ModalStore from 'sentry/stores/modalStore';
12+
13+
import triggerChangeDatesModal from 'admin/components/changeDatesAction';
14+
15+
describe('ChangeDatesAction', () => {
16+
const organization = OrganizationFixture();
17+
const subscription = SubscriptionFixture({
18+
organization,
19+
onDemandPeriodStart: '2024-01-01',
20+
onDemandPeriodEnd: '2024-02-01',
21+
contractPeriodStart: '2024-03-01',
22+
contractPeriodEnd: '2024-04-01',
23+
});
24+
25+
const onSuccess = jest.fn();
26+
27+
const modalProps = {
28+
orgId: organization.slug,
29+
onSuccess,
30+
subscription,
31+
};
32+
33+
beforeEach(() => {
34+
MockApiClient.clearMockResponses();
35+
ModalStore.reset();
36+
jest.clearAllMocks();
37+
});
38+
39+
async function updateDateField(label: string, value: string) {
40+
const input = await screen.findByLabelText(label);
41+
await userEvent.click(input);
42+
await userEvent.keyboard('{Control>}a{/Control}');
43+
await userEvent.keyboard(value);
44+
}
45+
46+
it('submits updated contract and on-demand dates', async () => {
47+
const updateMock = MockApiClient.addMockResponse({
48+
url: `/customers/${organization.slug}/`,
49+
method: 'PUT',
50+
body: {},
51+
});
52+
53+
triggerChangeDatesModal(modalProps);
54+
const {waitForModalToHide} = renderGlobalModal();
55+
56+
await updateDateField('Contract Period End Date', '2024-05-15');
57+
await updateDateField('On-Demand Period End Date', '2024-02-10');
58+
59+
await userEvent.click(screen.getByRole('button', {name: 'Submit'}));
60+
61+
await waitForModalToHide();
62+
63+
await waitFor(() => {
64+
expect(updateMock).toHaveBeenCalledWith(
65+
`/customers/${organization.slug}/`,
66+
expect.objectContaining({
67+
method: 'PUT',
68+
data: {
69+
onDemandPeriodStart: '2024-01-01',
70+
onDemandPeriodEnd: '2024-02-10',
71+
contractPeriodStart: '2024-03-01',
72+
contractPeriodEnd: '2024-05-15',
73+
},
74+
})
75+
);
76+
});
77+
78+
expect(onSuccess).toHaveBeenCalled();
79+
});
80+
});
Lines changed: 46 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,48 @@
11
import {Fragment} from 'react';
2+
import styled from '@emotion/styled';
3+
4+
import {Heading} from '@sentry/scraps/text';
25

36
import {addSuccessMessage} from 'sentry/actionCreators/indicator';
47
import {openModal, type ModalRenderProps} from 'sentry/actionCreators/modal';
58
import {Alert} from 'sentry/components/core/alert';
6-
import {DateTimeField} from 'sentry/components/deprecatedforms/dateTimeField';
7-
import Form from 'sentry/components/deprecatedforms/form';
8-
import withFormContext from 'sentry/components/deprecatedforms/withFormContext';
9-
import useApi from 'sentry/utils/useApi';
9+
import InputField from 'sentry/components/forms/fields/inputField';
10+
import Form from 'sentry/components/forms/form';
11+
import type {OnSubmitCallback} from 'sentry/components/forms/types';
12+
import {fetchMutation, useMutation} from 'sentry/utils/queryClient';
1013

1114
import type {Subscription} from 'getsentry/types';
1215

13-
type Props = {
16+
interface ChangeDatesModalProps extends ModalRenderProps {
1417
onSuccess: () => void;
1518
orgId: string;
1619
subscription: Subscription;
17-
};
18-
19-
type ModalProps = Props & ModalRenderProps;
20-
21-
class DateFieldNoContext extends DateTimeField {
22-
getType() {
23-
return 'date';
24-
}
2520
}
2621

27-
const DateField = withFormContext(DateFieldNoContext);
28-
2922
function ChangeDatesModal({
3023
orgId,
3124
subscription,
3225
onSuccess,
3326
closeModal,
3427
Header,
3528
Body,
36-
}: ModalProps) {
37-
const api = useApi();
29+
}: ChangeDatesModalProps) {
30+
const {mutateAsync: updateSubscriptionDates, isPending: isUpdating} = useMutation<
31+
Record<string, any>,
32+
unknown,
33+
Record<string, any>
34+
>({
35+
mutationFn: (payload: Record<string, any>) =>
36+
fetchMutation({
37+
url: `/customers/${orgId}/`,
38+
method: 'PUT',
39+
data: payload,
40+
}),
41+
});
3842

39-
async function onSubmit(formData: any, _onSubmitSuccess: unknown, onSubmitError: any) {
43+
const onSubmit: OnSubmitCallback = async (formData, onSubmitSuccess, onSubmitError) => {
4044
try {
41-
const postData = {
45+
const postData: Record<string, any> = {
4246
onDemandPeriodStart: subscription.onDemandPeriodStart,
4347
onDemandPeriodEnd: subscription.onDemandPeriodEnd,
4448
contractPeriodStart: subscription.contractPeriodStart,
@@ -47,78 +51,87 @@ function ChangeDatesModal({
4751

4852
for (const k in formData) {
4953
if (formData[k] !== '' && formData[k]) {
50-
// @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
5154
postData[k] = formData[k];
5255
}
5356
}
5457

55-
await api.requestPromise(`/customers/${orgId}/`, {
56-
method: 'PUT',
57-
data: postData,
58-
success: () => {
59-
addSuccessMessage('Contract and on-demand period dates updated');
60-
onSuccess();
61-
},
62-
});
58+
const response = await updateSubscriptionDates(postData);
6359

60+
addSuccessMessage('Contract and on-demand period dates updated');
61+
onSubmitSuccess(response);
62+
onSuccess();
6463
closeModal();
6564
} catch (err: any) {
6665
onSubmitError({
6766
responseJSON: err.responseJSON,
6867
});
6968
}
70-
}
69+
};
7170

7271
return (
7372
<Fragment>
74-
<Header>Change Contract and Current On-Demand Period Dates</Header>
73+
<Header closeButton>
74+
<Heading as="h3">Change Contract and Current On-Demand Period Dates</Heading>
75+
</Header>
7576
<Body>
7677
<Form
7778
onSubmit={onSubmit}
7879
onCancel={closeModal}
7980
submitLabel="Submit"
81+
submitDisabled={isUpdating}
8082
cancelLabel="Cancel"
8183
>
8284
<Alert.Container>
83-
<Alert type="warning" showIcon={false}>
85+
<Alert type="info" showIcon={false}>
8486
This overrides the current contract and on-demand period dates so the
8587
subscription may fall into a weird state.
8688
</Alert>
8789
</Alert.Container>
88-
<p>To end the contract period immediately, use the End Period Now action.</p>
90+
<p>
91+
To end the contract period immediately, use the "End Billing Period
92+
Immediately" action.
93+
</p>
8994
<DateField
9095
label="On-Demand Period Start Date"
9196
name="onDemandPeriodStart"
9297
help="The new start date for the on-demand period."
9398
defaultValue={subscription.onDemandPeriodStart}
99+
type="date"
94100
/>
95101
<DateField
96102
label="On-Demand Period End Date"
97103
name="onDemandPeriodEnd"
98104
help="The new end date for the on-demand period."
99105
defaultValue={subscription.onDemandPeriodEnd}
106+
type="date"
100107
/>
101108
<DateField
102109
label="Contract Period Start Date"
103110
name="contractPeriodStart"
104111
help="The new start date for the contract period."
105112
defaultValue={subscription.contractPeriodStart}
113+
type="date"
106114
/>
107115
<DateField
108116
label="Contract Period End Date"
109117
name="contractPeriodEnd"
110118
help="The new end date for the contract period."
111119
defaultValue={subscription.contractPeriodEnd}
120+
type="date"
112121
/>
113122
</Form>
114123
</Body>
115124
</Fragment>
116125
);
117126
}
118127

119-
type Options = Pick<Props, 'orgId' | 'subscription' | 'onSuccess'>;
128+
type Options = Omit<ChangeDatesModalProps, keyof ModalRenderProps>;
120129

121130
const triggerChangeDatesModal = (opts: Options) =>
122131
openModal(deps => <ChangeDatesModal {...deps} {...opts} />);
123132

124133
export default triggerChangeDatesModal;
134+
135+
const DateField = styled(InputField)`
136+
padding-left: 0px;
137+
`;

0 commit comments

Comments
 (0)