monitorForm.spec.tsx 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. import {MemberFixture} from 'sentry-fixture/member';
  2. import {MonitorFixture} from 'sentry-fixture/monitor';
  3. import {OrganizationFixture} from 'sentry-fixture/organization';
  4. import {TeamFixture} from 'sentry-fixture/team';
  5. import {UserFixture} from 'sentry-fixture/user';
  6. import {initializeOrg} from 'sentry-test/initializeOrg';
  7. import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
  8. import selectEvent from 'sentry-test/selectEvent';
  9. import {useMembers} from 'sentry/utils/useMembers';
  10. import useProjects from 'sentry/utils/useProjects';
  11. import {useTeams} from 'sentry/utils/useTeams';
  12. import MonitorForm from 'sentry/views/monitors/components/monitorForm';
  13. import {ScheduleType} from 'sentry/views/monitors/types';
  14. jest.mock('sentry/utils/useProjects');
  15. jest.mock('sentry/utils/useTeams');
  16. jest.mock('sentry/utils/useMembers');
  17. describe('MonitorForm', function () {
  18. const organization = OrganizationFixture();
  19. const member = MemberFixture({user: UserFixture({name: 'John Smith'})});
  20. const team = TeamFixture({slug: 'test-team'});
  21. const {project, router} = initializeOrg({organization});
  22. beforeEach(() => {
  23. jest.mocked(useProjects).mockReturnValue({
  24. fetchError: null,
  25. fetching: false,
  26. hasMore: false,
  27. initiallyLoaded: false,
  28. onSearch: jest.fn(),
  29. reloadProjects: jest.fn(),
  30. placeholders: [],
  31. projects: [project],
  32. });
  33. jest.mocked(useTeams).mockReturnValue({
  34. fetchError: null,
  35. fetching: false,
  36. hasMore: false,
  37. initiallyLoaded: false,
  38. loadMore: jest.fn(),
  39. onSearch: jest.fn(),
  40. teams: [team],
  41. });
  42. jest.mocked(useMembers).mockReturnValue({
  43. fetchError: null,
  44. fetching: false,
  45. hasMore: false,
  46. initiallyLoaded: false,
  47. loadMore: jest.fn(),
  48. onSearch: jest.fn(),
  49. members: [member.user!],
  50. });
  51. });
  52. it('displays human readable schedule', async function () {
  53. render(
  54. <MonitorForm
  55. apiMethod="POST"
  56. apiEndpoint={`/organizations/${organization.slug}/monitors/`}
  57. onSubmitSuccess={jest.fn()}
  58. />,
  59. {router, organization}
  60. );
  61. const schedule = screen.getByRole('textbox', {name: 'Crontab Schedule'});
  62. await userEvent.clear(schedule);
  63. await userEvent.type(schedule, '5 * * * *');
  64. expect(screen.getByText('"At 5 minutes past the hour"')).toBeInTheDocument();
  65. });
  66. it('submits a new monitor', async function () {
  67. const mockHandleSubmitSuccess = jest.fn();
  68. const apiEndpont = `/organizations/${organization.slug}/monitors/`;
  69. render(
  70. <MonitorForm
  71. apiMethod="POST"
  72. apiEndpoint={apiEndpont}
  73. onSubmitSuccess={mockHandleSubmitSuccess}
  74. submitLabel="Add Monitor"
  75. />,
  76. {router, organization}
  77. );
  78. await userEvent.type(screen.getByRole('textbox', {name: 'Name'}), 'My Monitor');
  79. await selectEvent.select(
  80. screen.getByRole('textbox', {name: 'Project'}),
  81. project.slug
  82. );
  83. const schedule = screen.getByRole('textbox', {name: 'Crontab Schedule'});
  84. await userEvent.clear(schedule);
  85. await userEvent.type(schedule, '5 * * * *');
  86. await selectEvent.select(
  87. screen.getByRole('textbox', {name: 'Timezone'}),
  88. 'Los Angeles'
  89. );
  90. await userEvent.type(screen.getByRole('spinbutton', {name: 'Grace Period'}), '5');
  91. await userEvent.type(screen.getByRole('spinbutton', {name: 'Max Runtime'}), '20');
  92. await userEvent.type(
  93. screen.getByRole('spinbutton', {name: 'Failure Tolerance'}),
  94. '4'
  95. );
  96. await userEvent.type(
  97. screen.getByRole('spinbutton', {name: 'Recovery Tolerance'}),
  98. '2'
  99. );
  100. const ownerSelect = screen.getByRole('textbox', {name: 'Owner'});
  101. await selectEvent.select(ownerSelect, 'John Smith');
  102. const notifySelect = screen.getByRole('textbox', {name: 'Notify'});
  103. await selectEvent.openMenu(notifySelect);
  104. expect(
  105. screen.getByRole('menuitemcheckbox', {name: 'John Smith'})
  106. ).toBeInTheDocument();
  107. expect(
  108. screen.getByRole('menuitemcheckbox', {name: '#test-team'})
  109. ).toBeInTheDocument();
  110. await selectEvent.select(notifySelect, 'John Smith');
  111. const submitMock = MockApiClient.addMockResponse({
  112. url: apiEndpont,
  113. method: 'POST',
  114. });
  115. await userEvent.click(screen.getByRole('button', {name: 'Add Monitor'}));
  116. const config = {
  117. checkinMargin: '5',
  118. maxRuntime: '20',
  119. failureIssueThreshold: '4',
  120. recoveryThreshold: '2',
  121. schedule: '5 * * * *',
  122. scheduleType: 'crontab',
  123. timezone: 'America/Los_Angeles',
  124. };
  125. const alertRule = {
  126. environment: undefined,
  127. targets: [{targetIdentifier: 1, targetType: 'Member'}],
  128. };
  129. expect(submitMock).toHaveBeenCalledWith(
  130. expect.anything(),
  131. expect.objectContaining({
  132. data: {
  133. name: 'My Monitor',
  134. project: 'project-slug',
  135. owner: `user:${member.user?.id}`,
  136. type: 'cron_job',
  137. config,
  138. alertRule,
  139. },
  140. })
  141. );
  142. expect(mockHandleSubmitSuccess).toHaveBeenCalled();
  143. });
  144. it('prefills with an existing monitor', async function () {
  145. const monitor = MonitorFixture({project});
  146. const apiEndpont = `/projects/${organization.slug}/${monitor.project.slug}/monitors/${monitor.slug}/`;
  147. if (monitor.config.schedule_type !== ScheduleType.CRONTAB) {
  148. throw new Error('Fixture is not crontab');
  149. }
  150. render(
  151. <MonitorForm
  152. monitor={monitor}
  153. apiMethod="POST"
  154. apiEndpoint={apiEndpont}
  155. onSubmitSuccess={jest.fn()}
  156. submitLabel="Edit Monitor"
  157. />,
  158. {router, organization}
  159. );
  160. // Name and slug
  161. expect(screen.getByRole('textbox', {name: 'Name'})).toHaveValue(monitor.name);
  162. expect(screen.getByRole('textbox', {name: 'Slug'})).toHaveValue(monitor.slug);
  163. // Project
  164. expect(screen.getByRole('textbox', {name: 'Project'})).toBeDisabled();
  165. expect(screen.getByText(project.slug)).toBeInTheDocument();
  166. // Schedule type
  167. await selectEvent.openMenu(screen.getByRole('textbox', {name: 'Schedule Type'}));
  168. const crontabOption = screen.getByRole('menuitemradio', {name: 'Crontab'});
  169. expect(crontabOption).toBeChecked();
  170. await userEvent.click(crontabOption);
  171. // Schedule value
  172. expect(screen.getByRole('textbox', {name: 'Crontab Schedule'})).toHaveValue(
  173. monitor.config.schedule
  174. );
  175. // Schedule timezone
  176. await selectEvent.openMenu(screen.getByRole('textbox', {name: 'Timezone'}));
  177. const losAngelesOption = screen.getByRole('menuitemradio', {name: 'Los Angeles'});
  178. expect(losAngelesOption).toBeChecked();
  179. await userEvent.click(losAngelesOption);
  180. // Margins
  181. expect(screen.getByRole('spinbutton', {name: 'Grace Period'})).toHaveValue(5);
  182. expect(screen.getByRole('spinbutton', {name: 'Max Runtime'})).toHaveValue(10);
  183. // Tolerances
  184. expect(screen.getByRole('spinbutton', {name: 'Failure Tolerance'})).toHaveValue(2);
  185. expect(screen.getByRole('spinbutton', {name: 'Recovery Tolerance'})).toHaveValue(2);
  186. // Ownership
  187. await selectEvent.openMenu(screen.getByRole('textbox', {name: 'Owner'}));
  188. const ownerOption = screen.getByRole('menuitemradio', {name: member.user?.name});
  189. expect(ownerOption).toBeChecked();
  190. await userEvent.keyboard('{Escape}');
  191. // Alert rule configuration
  192. await selectEvent.openMenu(screen.getByRole('textbox', {name: 'Notify'}));
  193. const memberOption = screen.getByRole('menuitemcheckbox', {name: member.user?.name});
  194. expect(memberOption).toBeChecked();
  195. await userEvent.keyboard('{Escape}');
  196. const submitMock = MockApiClient.addMockResponse({
  197. url: apiEndpont,
  198. method: 'POST',
  199. });
  200. // Monitor form is not submitable until something is changed
  201. const submitButton = screen.getByRole('button', {name: 'Edit Monitor'});
  202. expect(submitButton).toBeDisabled();
  203. // Change Failure Tolerance
  204. await userEvent.clear(screen.getByRole('spinbutton', {name: 'Failure Tolerance'}));
  205. await userEvent.type(
  206. screen.getByRole('spinbutton', {name: 'Failure Tolerance'}),
  207. '10'
  208. );
  209. await userEvent.click(submitButton);
  210. // XXX(epurkhiser): When the values are loaded directly from the
  211. // monitor they come in as numbers, when changed via the toggles they
  212. // are translated to strings :(
  213. const config = {
  214. maxRuntime: monitor.config.max_runtime,
  215. checkinMargin: monitor.config.checkin_margin,
  216. recoveryThreshold: monitor.config.recovery_threshold,
  217. schedule: monitor.config.schedule,
  218. scheduleType: monitor.config.schedule_type,
  219. timezone: monitor.config.timezone,
  220. failureIssueThreshold: '10',
  221. };
  222. const alertRule = {
  223. environment: undefined,
  224. targets: [{targetIdentifier: 1, targetType: 'Member'}],
  225. };
  226. expect(submitMock).toHaveBeenCalledWith(
  227. expect.anything(),
  228. expect.objectContaining({
  229. data: {
  230. name: monitor.name,
  231. slug: monitor.slug,
  232. project: monitor.project.slug,
  233. owner: `user:${member.user?.id}`,
  234. type: 'cron_job',
  235. config,
  236. alertRule,
  237. },
  238. })
  239. );
  240. });
  241. });