sentryAppExternalIssueForm.spec.tsx 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. import selectEvent from 'react-select-event';
  2. import {Event as EventFixture} from 'sentry-fixture/event';
  3. import {Group as GroupFixture} from 'sentry-fixture/group';
  4. import {SentryApp} from 'sentry-fixture/sentryApp';
  5. import {SentryAppInstallation} from 'sentry-fixture/sentryAppInstallation';
  6. import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
  7. import SentryAppExternalIssueForm from 'sentry/components/group/sentryAppExternalIssueForm';
  8. import {addQueryParamsToExistingUrl} from 'sentry/utils/queryString';
  9. describe('SentryAppExternalIssueForm', () => {
  10. const group = GroupFixture({
  11. title: 'ApiError: Broken',
  12. shortId: 'SEN123',
  13. permalink: 'https://sentry.io/organizations/sentry/issues/123/?project=1',
  14. });
  15. const component = TestStubs.SentryAppComponent();
  16. const sentryApp = SentryApp();
  17. const sentryAppInstallation = SentryAppInstallation({});
  18. const submitUrl = `/sentry-app-installations/${sentryAppInstallation.uuid}/external-issue-actions/`;
  19. let externalIssueRequest;
  20. beforeEach(() => {
  21. externalIssueRequest = MockApiClient.addMockResponse({
  22. url: submitUrl,
  23. method: 'POST',
  24. body: {},
  25. });
  26. });
  27. afterEach(() => {
  28. MockApiClient.clearMockResponses();
  29. });
  30. describe('create', () => {
  31. it('can create a new issue', async () => {
  32. render(
  33. <SentryAppExternalIssueForm
  34. event={EventFixture()}
  35. onSubmitSuccess={jest.fn()}
  36. group={group}
  37. sentryAppInstallation={sentryAppInstallation}
  38. appName={sentryApp.name}
  39. config={component.schema.create}
  40. action="create"
  41. />
  42. );
  43. // renders each required_fields field
  44. expect(component.schema.create.required_fields).toHaveLength(3);
  45. for (const field of component.schema.create.required_fields) {
  46. expect(screen.getByRole('textbox', {name: field.label})).toBeInTheDocument();
  47. }
  48. // Prevents submission if required fields are not set
  49. await userEvent.clear(screen.getByRole('textbox', {name: 'Title'}));
  50. await userEvent.click(screen.getByRole('button', {name: 'Save Changes'}));
  51. expect(externalIssueRequest).not.toHaveBeenCalled();
  52. selectEvent.openMenu(screen.getByRole('textbox', {name: 'Numbers'}));
  53. await userEvent.type(screen.getByRole('textbox', {name: 'Numbers'}), '1');
  54. await userEvent.click(screen.getByText('one'));
  55. // Sets required fields
  56. await userEvent.type(
  57. screen.getByRole('textbox', {name: 'Title'}),
  58. 'ApiError: Broken'
  59. );
  60. await userEvent.click(screen.getByRole('button', {name: 'Save Changes'}));
  61. expect(externalIssueRequest).toHaveBeenCalledWith(
  62. submitUrl,
  63. expect.objectContaining({
  64. data: {
  65. action: 'create',
  66. description:
  67. 'Sentry Issue: [SEN123](https://sentry.io/organizations/sentry/issues/123/?project=1&referrer=Sample%20App)',
  68. groupId: '1',
  69. numbers: 'number_1',
  70. title: 'ApiError: Broken',
  71. uri: '',
  72. },
  73. method: 'POST',
  74. })
  75. );
  76. });
  77. it('renders prepopulated defaults', () => {
  78. render(
  79. <SentryAppExternalIssueForm
  80. event={EventFixture()}
  81. onSubmitSuccess={jest.fn()}
  82. group={group}
  83. sentryAppInstallation={sentryAppInstallation}
  84. appName={sentryApp.name}
  85. config={component.schema.create}
  86. action="create"
  87. />
  88. );
  89. expect(screen.getByRole('textbox', {name: 'Title'})).toHaveValue(`${group.title}`);
  90. const url = addQueryParamsToExistingUrl(group.permalink, {
  91. referrer: sentryApp.name,
  92. });
  93. expect(screen.getByRole('textbox', {name: 'Description'})).toHaveValue(
  94. `Sentry Issue: [${group.shortId}](${url})`
  95. );
  96. });
  97. });
  98. describe('link', () => {
  99. it('can link an issue', async () => {
  100. render(
  101. <SentryAppExternalIssueForm
  102. event={EventFixture()}
  103. onSubmitSuccess={jest.fn()}
  104. group={group}
  105. sentryAppInstallation={sentryAppInstallation}
  106. appName={sentryApp.name}
  107. config={component.schema.link}
  108. action="link"
  109. />
  110. );
  111. // renders each required_fields field
  112. expect(component.schema.link.required_fields).toHaveLength(1);
  113. for (const field of component.schema.link.required_fields) {
  114. expect(screen.getByRole('textbox', {name: field.label})).toBeInTheDocument();
  115. }
  116. await userEvent.type(screen.getByRole('textbox', {name: 'Issue'}), 'my issue');
  117. await userEvent.click(screen.getByRole('button', {name: 'Save Changes'}));
  118. expect(externalIssueRequest).toHaveBeenCalledWith(
  119. submitUrl,
  120. expect.objectContaining({
  121. data: {
  122. action: 'link',
  123. groupId: '1',
  124. issue: 'my issue',
  125. uri: '',
  126. },
  127. method: 'POST',
  128. })
  129. );
  130. });
  131. });
  132. });
  133. describe('SentryAppExternalIssueForm Async Field', () => {
  134. const component = TestStubs.SentryAppComponentAsync();
  135. const group = GroupFixture({
  136. title: 'ApiError: Broken',
  137. shortId: 'SEN123',
  138. permalink: 'https://sentry.io/organizations/sentry/issues/123/?project=1',
  139. });
  140. const sentryApp = SentryApp();
  141. const sentryAppInstallation = SentryAppInstallation({});
  142. afterEach(() => {
  143. MockApiClient.clearMockResponses();
  144. });
  145. it('renders each required_fields field', async function () {
  146. const mockGetOptions = MockApiClient.addMockResponse({
  147. method: 'GET',
  148. url: '/sentry-app-installations/d950595e-cba2-46f6-8a94-b79e42806f98/external-requests/',
  149. body: {
  150. choices: [
  151. [1, 'Issue 1'],
  152. [2, 'Issue 2'],
  153. ],
  154. },
  155. });
  156. render(
  157. <SentryAppExternalIssueForm
  158. event={EventFixture()}
  159. onSubmitSuccess={jest.fn()}
  160. group={group}
  161. sentryAppInstallation={sentryAppInstallation}
  162. appName={sentryApp.name}
  163. config={component.schema.create}
  164. action="create"
  165. />
  166. );
  167. selectEvent.openMenu(screen.getByText('Numbers'));
  168. await userEvent.type(screen.getByRole('textbox'), 'I');
  169. expect(mockGetOptions).toHaveBeenCalled();
  170. expect(await screen.findByText('Issue 1')).toBeInTheDocument();
  171. expect(await screen.findByText('Issue 2')).toBeInTheDocument();
  172. });
  173. });
  174. describe('SentryAppExternalIssueForm Dependent fields', () => {
  175. const group = GroupFixture({
  176. title: 'ApiError: Broken',
  177. shortId: 'SEN123',
  178. permalink: 'https://sentry.io/organizations/sentry/issues/123/?project=1',
  179. });
  180. const sentryApp = SentryApp();
  181. const sentryAppInstallation = SentryAppInstallation({});
  182. const component = TestStubs.SentryAppComponentDependent();
  183. afterEach(() => {
  184. MockApiClient.clearMockResponses();
  185. });
  186. it('load options for field that has dependencies when the dependent option is selected', async () => {
  187. const url = `/sentry-app-installations/${sentryAppInstallation.uuid}/external-requests/`;
  188. MockApiClient.addMockResponse({
  189. method: 'GET',
  190. url,
  191. body: {
  192. choices: [
  193. ['A', 'project A'],
  194. ['B', 'project B'],
  195. ],
  196. },
  197. match: [MockApiClient.matchQuery({uri: '/integrations/sentry/projects'})],
  198. });
  199. const boardMock = MockApiClient.addMockResponse({
  200. method: 'GET',
  201. url,
  202. body: {
  203. choices: [
  204. ['R', 'board R'],
  205. ['S', 'board S'],
  206. ],
  207. },
  208. match: [
  209. MockApiClient.matchQuery({
  210. uri: '/integrations/sentry/boards',
  211. dependentData: JSON.stringify({project_id: 'A'}),
  212. }),
  213. ],
  214. });
  215. render(
  216. <SentryAppExternalIssueForm
  217. event={EventFixture()}
  218. onSubmitSuccess={jest.fn()}
  219. group={group}
  220. sentryAppInstallation={sentryAppInstallation}
  221. appName={sentryApp.name}
  222. config={component.schema.create}
  223. action="create"
  224. />
  225. );
  226. await userEvent.type(screen.getByRole('textbox', {name: 'Project'}), 'p');
  227. expect(await screen.findByText('project A')).toBeInTheDocument();
  228. expect(screen.getByText('project B')).toBeInTheDocument();
  229. // project select should be disabled and we shouldn't fetch the options yet
  230. expect(screen.getByRole('textbox', {name: 'Board'})).toBeDisabled();
  231. expect(boardMock).not.toHaveBeenCalled();
  232. // when we set the value for project we should get the values for the board
  233. await selectEvent.select(screen.getByRole('textbox', {name: 'Project'}), 'project A');
  234. expect(boardMock).toHaveBeenCalled();
  235. expect(screen.getByRole('textbox', {name: 'Board'})).toBeEnabled();
  236. await userEvent.type(screen.getByRole('textbox', {name: 'Board'}), 'b');
  237. expect(await screen.findByText('board R')).toBeInTheDocument();
  238. expect(screen.getByText('board S')).toBeInTheDocument();
  239. });
  240. });