stacktraceLinkModal.spec.tsx 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. import {Repository} from 'sentry-fixture/repository';
  2. import {RepositoryProjectPathConfig} from 'sentry-fixture/repositoryProjectPathConfig';
  3. import {
  4. act,
  5. renderGlobalModal,
  6. screen,
  7. userEvent,
  8. waitFor,
  9. } from 'sentry-test/reactTestingLibrary';
  10. import {openModal} from 'sentry/actionCreators/modal';
  11. import StacktraceLinkModal from 'sentry/components/events/interfaces/frame/stacktraceLinkModal';
  12. import * as analytics from 'sentry/utils/analytics';
  13. jest.mock('sentry/utils/analytics');
  14. describe('StacktraceLinkModal', () => {
  15. const org = TestStubs.Organization();
  16. const project = TestStubs.Project();
  17. const integration = TestStubs.GitHubIntegration();
  18. const filename = '/sentry/app.py';
  19. const repo = Repository({integrationId: integration.id});
  20. const config = RepositoryProjectPathConfig({project, repo, integration});
  21. const sourceUrl = 'https://github.com/getsentry/sentry/blob/master/src/sentry/app.py';
  22. const configData = {
  23. stackRoot: '',
  24. sourceRoot: 'src/',
  25. integrationId: integration.id,
  26. repositoryId: repo.id,
  27. defaultBranch: 'master',
  28. };
  29. const onSubmit = jest.fn();
  30. const closeModal = jest.fn();
  31. const analyticsSpy = jest.spyOn(analytics, 'trackAnalytics');
  32. beforeEach(() => {
  33. MockApiClient.addMockResponse({
  34. url: `/organizations/${org.slug}/code-mappings/`,
  35. method: 'POST',
  36. });
  37. MockApiClient.addMockResponse({
  38. url: `/projects/${org.slug}/${project.slug}/stacktrace-link/`,
  39. body: {config, sourceUrl, integrations: [integration]},
  40. });
  41. MockApiClient.addMockResponse({
  42. url: `/organizations/${org.slug}/derive-code-mappings/`,
  43. body: [],
  44. });
  45. });
  46. afterEach(() => {
  47. MockApiClient.clearMockResponses();
  48. jest.clearAllMocks();
  49. });
  50. it('links to source code with one GitHub integration', () => {
  51. renderGlobalModal();
  52. act(() =>
  53. openModal(modalProps => (
  54. <StacktraceLinkModal
  55. {...modalProps}
  56. filename={filename}
  57. closeModal={closeModal}
  58. integrations={[integration]}
  59. organization={org}
  60. project={project}
  61. onSubmit={onSubmit}
  62. />
  63. ))
  64. );
  65. expect(screen.getByText('Tell us where your source code is')).toBeInTheDocument();
  66. // Links to GitHub with one integration
  67. expect(screen.getByText('GitHub')).toBeInTheDocument();
  68. expect(screen.getByText('GitHub')).toHaveAttribute(
  69. 'href',
  70. 'https://github.com/test-integration'
  71. );
  72. });
  73. it('closes modal after successful quick setup', async () => {
  74. MockApiClient.addMockResponse({
  75. url: `/projects/${org.slug}/${project.slug}/repo-path-parsing/`,
  76. method: 'POST',
  77. body: {...configData},
  78. });
  79. renderGlobalModal();
  80. act(() =>
  81. openModal(modalProps => (
  82. <StacktraceLinkModal
  83. {...modalProps}
  84. filename={filename}
  85. closeModal={closeModal}
  86. integrations={[integration]}
  87. organization={org}
  88. project={project}
  89. onSubmit={onSubmit}
  90. />
  91. ))
  92. );
  93. await userEvent.type(
  94. screen.getByRole('textbox', {name: 'Repository URL'}),
  95. 'sourceUrl'
  96. );
  97. await userEvent.click(screen.getByRole('button', {name: 'Save'}));
  98. await waitFor(() => {
  99. expect(closeModal).toHaveBeenCalled();
  100. });
  101. expect(onSubmit).toHaveBeenCalledTimes(1);
  102. });
  103. it('keeps modal open on unsuccessful quick setup', async () => {
  104. MockApiClient.addMockResponse({
  105. url: `/projects/${org.slug}/${project.slug}/repo-path-parsing/`,
  106. method: 'POST',
  107. body: {sourceUrl: ['Could not find repo']},
  108. statusCode: 400,
  109. });
  110. renderGlobalModal({context: TestStubs.routerContext()});
  111. act(() =>
  112. openModal(modalProps => (
  113. <StacktraceLinkModal
  114. {...modalProps}
  115. filename={filename}
  116. closeModal={closeModal}
  117. integrations={[integration]}
  118. organization={org}
  119. project={project}
  120. onSubmit={onSubmit}
  121. />
  122. ))
  123. );
  124. await userEvent.type(
  125. screen.getByRole('textbox', {name: 'Repository URL'}),
  126. 'sourceUrl{enter}'
  127. );
  128. await userEvent.click(screen.getByRole('button', {name: 'Save'}));
  129. await waitFor(() => {
  130. expect(closeModal).not.toHaveBeenCalled();
  131. });
  132. expect(
  133. screen.getByText('We don’t have access to that', {exact: false})
  134. ).toBeInTheDocument();
  135. expect(screen.getByRole('link', {name: 'add your repo.'})).toHaveAttribute(
  136. 'href',
  137. '/settings/org-slug/integrations/github/1/'
  138. );
  139. });
  140. it('displays suggestions from code mappings', async () => {
  141. MockApiClient.addMockResponse({
  142. url: `/organizations/${org.slug}/derive-code-mappings/`,
  143. body: [
  144. {
  145. filename: 'stack/root/file/stack/root/file/stack/root/file.py',
  146. repo_name: 'getsentry/codemap',
  147. repo_branch: 'master',
  148. stacktrace_root: '/stack/root',
  149. source_path: '/source/root/',
  150. },
  151. {
  152. filename: 'stack/root/file.py',
  153. repo_name: 'getsentry/codemap',
  154. repo_branch: 'master',
  155. stacktrace_root: '/stack/root',
  156. source_path: '/source/root/',
  157. },
  158. ],
  159. });
  160. MockApiClient.addMockResponse({
  161. url: `/projects/${org.slug}/${project.slug}/repo-path-parsing/`,
  162. method: 'POST',
  163. body: {...configData},
  164. });
  165. renderGlobalModal();
  166. act(() =>
  167. openModal(modalProps => (
  168. <StacktraceLinkModal
  169. {...modalProps}
  170. filename={filename}
  171. closeModal={closeModal}
  172. integrations={[integration]}
  173. organization={org}
  174. project={project}
  175. onSubmit={onSubmit}
  176. />
  177. ))
  178. );
  179. expect(
  180. await screen.findByText(
  181. 'Select from one of these suggestions or paste your URL below'
  182. )
  183. ).toBeInTheDocument();
  184. const suggestion =
  185. 'https://github.com/getsentry/codemap/blob/master/stack/root/file.py';
  186. expect(screen.getByText(suggestion)).toBeInTheDocument();
  187. // Paste and save suggestion
  188. await userEvent.type(
  189. screen.getByRole('textbox', {name: 'Repository URL'}),
  190. suggestion
  191. );
  192. await userEvent.click(screen.getByRole('button', {name: 'Save'}));
  193. await waitFor(() => {
  194. expect(closeModal).toHaveBeenCalled();
  195. });
  196. expect(analyticsSpy).toHaveBeenCalledWith(
  197. 'integrations.stacktrace_complete_setup',
  198. expect.objectContaining({
  199. is_suggestion: true,
  200. })
  201. );
  202. });
  203. });