sentryAppExternalIssueForm.spec.jsx 8.3 KB

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